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 工具类):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /** * 获取连接池. * * @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 的话, 没那么容易到这个瓶颈
相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 | /** * 获取一个独立的Jedis实例 * @return jedis */ public Jedis getSingleJedis() { Jedis jedis = new Jedis(ip, port, timeout); jedis.connect(); if (StringUtils.isNotBlank(password)) { jedis.auth(password); } return jedis; }<br><br> // 关闭Jedis直接调用 jedis.close() 即可 |