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

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM