Java中多线程服务中遇到的Redis并发问题?
背景:
一个中小型H5游戏
核心错误信息:
(1): java.lang.ClassCastException: [B cannot be cast to java.lang.Long
at redis.clients.jedis.Connection.getIntegerReply(Connection.java:201)
at redis.clients.jedis.Jedis.del(Jedis.java:129)
(2):redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Socket closed
at redis.clients.jedis.Protocol.process(Protocol.java:131)
at redis.clients.jedis.Protocol.read(Protocol.java:187)
at redis.clients.jedis.Connection.getIntegerReply(Connection.java:201)
at redis.clients.jedis.Jedis.del(Jedis.java:129)
贴上核心问题代码(Jedis工具类):
/** * 获取连接池. * * @return 连接池实例 */ private static JedisPool getPool() { String key = ip + ":" + port; JedisPool pool = null; //这里为了提供大多数情况下线程池Map里面已经有对应ip的线程池直接返回,提高效率 if (maps.containsKey(key)) { pool = maps.get(key); return pool; } //这里的同步代码块防止多个线程同时产生多个相同的ip线程池 synchronized (JedisUtil.class) { if (!maps.containsKey(key)) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setTestOnBorrow(true); config.setTestOnReturn(true); config.setMaxWaitMillis(maxWaitMillis); config.setBlockWhenExhausted(blockWhenExhausted); try { if (password != null && !"".equals(password)) { pool = new JedisPool(config, ip, port, timeout, password); } else { pool = new JedisPool(config, ip, port, timeout); } maps.put(key, pool); } catch (Exception e) { e.printStackTrace(); } } else { pool = maps.get(key); } } return pool; } /** * 获取Redis实例. * * @return Redis工具类实例 */ public Jedis getJedis() { Jedis jedis = null; int count = 0; while (jedis == null && count < retryNum) { try { JedisPool pool = getPool(); jedis = pool.getResource(); } catch (Exception e) { logger.error("get redis master failed!", e); } finally { closeJedis(jedis); } count++; } return jedis; } /** * 释放redis实例到连接池. * * @param jedis redis实例 */ public void closeJedis(Jedis jedis) { if (jedis != null) { getPool().returnResource(jedis); } }
问题剖析:
查阅了线上资料,发现是由于多线程使用了同一个Jedis实例导致的并发问题.
结果:
一开始,我发现我调用了getJedis()获取了jedis实例并使用后没有关闭.
于是我把关闭Jedis的操作加上去了
结果是错误的量少了
但还是有报错,说明这是其中一个问题.
最后还是没能使用Jedis连接池搞定这个问题
解决办法:
抛弃使用连接池
每次使用Jedis都生成一个独立的实例
每次用完以后就close()
这样也就不存在并发的问题了
这样做有一个潜在的问题是如果并发量达到很大值,Redis连接数被塞满的话还是会出现问题.
一般情况下不是非常大的并发,用完就close的话,没那么容易到这个瓶颈
相关代码:
/** * 获取一个独立的Jedis实例 * @return jedis */ public Jedis getSingleJedis() { Jedis jedis = new Jedis(ip, port, timeout); jedis.connect(); if (StringUtils.isNotBlank(password)) { jedis.auth(password); } return jedis; }
// 关闭Jedis直接调用 jedis.close() 即可