Java中多线程服务中遇到的Redis并发问题?

本文探讨了一个H5游戏中遇到的Redis并发问题,具体表现为ClassCastException及JedisConnectionException异常。通过对代码的深入分析,作者放弃了使用Jedis连接池的方式,转而采用每次创建新的Jedis实例并在使用后立即关闭的方法解决了并发问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:

一个中小型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() 即可

  

 

转载于:https://www.cnblogs.com/imyjy/p/7307635.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值