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() 即可

  

 

posted @ 2017-08-23 14:52  EEEEET  阅读(5129)  评论(1编辑  收藏  举报