redis高并发导致读写变慢(redis多线程) 您所在的位置:网站首页 zerotier延迟高速度慢 redis高并发导致读写变慢(redis多线程)

redis高并发导致读写变慢(redis多线程)

2023-10-08 05:13| 来源: 网络整理| 查看: 265

文章目录 redis 高并发读写变慢分析原因Java中堆和栈——关键字volatile 解决方案Java中堆和栈——关键字volatileAtomicBoolean 调整后示例

redis 高并发读写变慢

最近在最redis + MQ高并发的一个功能,压测时发现redis读写性能突然降低很多,而redis已经启用一年多,一直没问题,走了点弯路后发现是因为对 JedisPool的高并发处理上存在效率 问题,如下为分析:

压测:此功能线上一小时6万的访问量,压测时同一比数据100各并发扫5分钟就开始报问题 效果:一开始读写毫秒级,1分钟后逐渐变慢,全部扫描完平均读写速度9s 在这里插入图片描述

分析原因 写的redis工具类RedisUtil.java开启JedisPool的逻辑效率有问题,如下段代码每次操作getJedis()时都会通过synchronized控制多线程并发,并且每次操作都会初始化,当高并发时会 持续等待 Java中堆和栈——关键字volatile

每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时 volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

//哨兵模式 private static JedisSentinelPool jedisPool = null; //单机模式 private static JedisPool jedisPoolS = null; public static Jedis getJedis() { Jedis jedis = null; if (jedisPoolS == null) { poolInit(); } } /* * 在多线程环境同步初始化, 这里效率有问题 */ private synchronized static void poolInit() { if (jedisPool == null) { initialPool(); } } /* * 多线程并发时这里会持续初始化,再加上synchronized的所会导致持续等待 */ private static void initialPool(){ try { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(MAX_ACTIVE); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW); String[] redisIPList = redisIP.split(","); Set sentinels=new HashSet(); for(int i=0;i jedisPoolS = new JedisPool(config, redisIP, redisPort, 100000, AUTH); }else { jedisPool = new JedisSentinelPool(master, sentinels, config, TIMEOUT, AUTH); } } catch (Exception e) { e.printStackTrace(); logger.error("First create JedisPool error : "+e); } } 解决方案 Java中堆和栈——关键字volatile

每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时 volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

AtomicBoolean

AtomicBoolean是java.util.concurrent.atomic包下的原子变量,这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程 (或者说只是在硬件级别上阻塞了)。

例如AtomicBoolean,在这个Boolean值的变化的时候不允许在之间插入,保持操作的原子性。方法和举例:compareAndSet(boolean expect, boolean update)。这个方法主要两个作用

比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句把AtomicBoolean的值设成update,比较最要的是这两件事是一气呵成的,这两个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。 原文链接:百度资料 将Jedispool初始化到 堆内存 中通过 volatile 变量使线程从主存中读取变量的值利用AtomicBoolean进行 CAS方法加锁 ,保证单一初始化 //主要代码段 private volatile static JedisSentinelPool jedisPool = null; private volatile static JedisPool jedisPoolS = null; private static AtomicBoolean initFlag = new AtomicBoolean(false); if(initFlag.compareAndSet(false, true)) { ... } 调整后示例 package com.sinosoft.prpall.pubfun.redis; import java.util.HashSet; import java.util.Set; import org.apache.log4j.Logger; import com.sinosoft.sysframework.reference.AppConfig; import com.sinosoft.utility.string.Str; import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisSentinelPool; /** * Redis 工具类 * @author caspar * https://blog.csdn.net/tuposky */ public class RedisUtil { protected static Logger logger = Logger.getLogger(RedisUtil.class); //redis服务器地址 private static String redisIP = AppConfig.get("Redis.SentinelServiceIP"); //redis服务器端口 private static int redisPort = Integer.parseInt(AppConfig.get("Redis.Port")); //主服务器名 private static String master = AppConfig.get("Redis.SentinelMasterName"); //访问密码 private static String AUTH = AppConfig.get("Redis.Auth"); //连接的DB序号 private static int dbSerialNo = Integer.parseInt(Str.chgStrZero(AppConfig.get("Redis.DBSerialNo"))); //可用连接实例的最大数目,默认值为8; //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 private static int MAX_ACTIVE = Integer.parseInt(Str.chgStrZero(AppConfig.get("Redis.MaxActive"))); //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 private static int MAX_IDLE = Integer.parseInt(Str.chgStrZero(AppConfig.get("Redis.MaxIdle"))); //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException; private static int MAX_WAIT = Integer.parseInt(Str.chgStrZero(AppConfig.get("Redis.WaitTime"))); //超时时间 private static int TIMEOUT = Integer.parseInt(Str.chgStrZero(AppConfig.get("Redis.TimeOut"))); //使用redis服务器类型:1-测试(单机模式),2-生产(哨兵模式) private static String redisType = AppConfig.get("Redis.RedisType"); //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; private static boolean TEST_ON_BORROW = true; //将Jedispool初始化到 堆内存 中通过 volatile 变量使线程从主存中读取变量的值 private volatile static JedisSentinelPool jedisPool = null; private volatile static JedisPool jedisPoolS = null; //利用AtomicBoolean进行 CAS方法加锁 ,保证单一初始化 private static AtomicBoolean initFlag = new AtomicBoolean(false); /** * redis过期时间,以秒为单位 */ public final static int EXRP_HOUR = 60*60; //一小时 public final static int EXRP_DAY = 60*60*24; //一天 public final static int EXRP_MONTH = 60*60*24*30; //一个月 /** * 初始化Redis连接池 */ static{ // 利用CAS方法加锁,保证单一初始化 if(initFlag.compareAndSet(false, true)) { try { logger.info(Thread.currentThread().getId() + " init pool"); JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(MAX_ACTIVE); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW); String[] redisIPList = redisIP.split(","); Set sentinels=new HashSet(); for(int i=0;i jedisPoolS = new JedisPool(config, redisIP, redisPort, 100000, AUTH); }else { jedisPool = new JedisSentinelPool(master, sentinels, config, TIMEOUT, AUTH); } } catch (Exception e) { // 初始化失败,放开标志让其它线程尝试初始化池 initFlag.set(false); e.printStackTrace(); logger.error("First create JedisPool error : "+e); } }else { // 等待第一个进入的线程初始化完毕 while(jedisPoolS == null && jedisPool == null) { logger.info(Thread.currentThread().getId() + " 等待其它线程完成连接池初始化 ..."); } } } /** * 同步获取Jedis实例 * @return Jedis */ public static Jedis getJedis() { Jedis jedis = null; try { //使用redis服务器类型:1-测试(单机模式),2-生产(哨兵模式) if("1".equals(redisType)) { if (jedisPoolS != null) { jedis = jedisPoolS.getResource(); jedis.select(dbSerialNo); } }else { if (jedisPool != null) { jedis = jedisPool.getResource(); jedis.select(dbSerialNo); } } } catch (Exception e) { e.printStackTrace(); logger.error("Get jedis error : ", e); throw e; } return jedis; } public void returnJedisResource(Jedis jedis){ if(jedis != null){ jedis.close(); } } }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有