背景:
一個中小型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() 即可
