redis集群scan_Redis scan命令的一次坑
Redis作為當前服務架構不可或缺的Cache,其支持豐富多樣的數據結構,Redis在使用中其實也有很多坑,本次博主遇到的坑或許說是Java程序員會遇到的多一點,下面就聽博主詳細道來。
線上服務堵塞
String key = keyOf(appid);
int retryCount = 3;
int socketRetryCount = 3;
Exception ex = null;
while(retryCount > 0 && socketRetryCount > 0) {
try {
return redisDao.getMap(key);
}catch (Exception e) {
}
}
12月2日被告知服務出現異常,查看日志發現其運行到上述代碼getMap方法處后日志就沒有內容了。
問題分析
"pool-13-thread-6" prio=10 tid=0x00007f754800e800 nid=0x71b5 waiting on condition [0x00007f758f0ee000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000779b75f40> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:583)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
at redis.clients.util.Pool.getResource(Pool.java:49)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
at org.reborndb.reborn.RoundRobinJedisPool.getResource(RoundRobinJedisPool.java:300)
at com.le.smartconnect.adapter.spring.RebornConnectionFactory.getConnection(RebornConnectionFactory.java:43)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
at xxx.run(xxx.java:80)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000074f529b08> (a java.util.concurrent.ThreadPoolExecutor$Worker)
從線程日志可以看出服務堵塞在獲取redis連接處.
分析:
代碼配置中redis最大連接為3000
redis配置中session_max_timeout為0,即永不斷開連接
一次修改分析
從以上兩點分析得出,redis連接被耗盡,于是查找代碼得知由于重寫spring-data-redis中的hscan方面導致,代碼如下:
RedisConnection rc = redisTemplate.getConnectionFactory().getConnection();
if (rc instanceof JedisConnection) {
JedisConnection JedisConnection = (JedisConnection) rc;
return new ConvertingCursor, Map.Entry>(
JedisConnection.hScan(rawValue(key), cursor, scanOptions),
new Converter, Map.Entry>() {
@Override
public Entry convert(final Entry source) {
return new Map.Entry() {
@Override
public String getKey() {
return hashKeySerializer.deserialize(source.getKey());
}
@Override
public String getValue() {
return hashValueSerializer.deserialize(source.getValue());
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException(
"Values cannot be set when scanning through entries.");
}
};
}
});
} else {
return hashOps.scan(key, scanOptions);
}
上述代碼返回ConvertingCursor后未釋放連接,導出連接被占滿。
二次修改分析
于是修改代碼為正常釋放連接
try {
...
}finally {
RedisConnectionUtils.releaseConnection(rc, factory);
}
代碼經過上線,再次跑程序查看線上日志發現報了大量的Connection time out.
于是博主就思考是不是由于重寫代碼不對,嘗試使用spring-data-redis的原生代碼,即直接調用hashOps.scan(key, scanOptions)方法,再次上線。
上線后觀察日志:發現這次不是報Connection time out,日志中大量報Unknown reply:錯誤。
分析如下:
由于代碼是在多線程環境下運行,有幾百個線程去調用hscan操作,spring-data-redis原生的代碼執行完一次hscan操作后就會關閉連接并返回一個迭代器Cursor,但是遍歷Cursor時在本次count后會再次根據游標重新使用該連接進行查詢,可是連接卻已經被關閉,這時會使用新的連接是可以正常迭代的,但是一旦復用到其他線程使用的連接則會導致報錯Unknown reply.
三次修改分析
經過思考后得出結論,redis在執行scan操作時一旦連接被釋放,那么scan操作將不會進行下去,則報Connection time out.
查閱官方文檔得出結論,redis的scan操作需要full iteration,即最優方式是一個連接將以此scan任務執行完全后釋放該連接。
redis-scan-doc
修改代碼如下:
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = factory.getConnection();
if (rc instanceof JedisConnection) {
JedisConnection JedisConnection = (JedisConnection) rc;
Cursor> cursorResult = new ConvertingCursor, Map.Entry>(
JedisConnection.hScan(rawValue(key), cursor, scanOptions),
new Converter, Map.Entry>() {
...
});
return new ScanResult>(cursorResult, factory, rc);}
public void releaseConnection() throws IOException{
IOException ex = null;
if(cursor != null) {
try {
cursor.close();
} catch (IOException e) {
ex = e;
}
}
try {
RedisConnectionUtils.releaseConnection(rc, factory);
} catch (Exception e) {
}
if(ex != null) {
throw ex;
}
}
將連接返回給業務代碼,并在業務代碼執行完畢后將連接釋放,問題解決。
總結
連接一旦開啟就必須釋放,否則造成內存泄漏或服務堵塞不可用
重寫代碼時需要謹記仔細查閱官方文檔給出的方案并實施
多線程下使用redis的scan操作需要使用一個連接遍歷完Cursor,而不能復用連接,否則導致報錯Unknown reply.
總結
以上是生活随笔為你收集整理的redis集群scan_Redis scan命令的一次坑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 亚马逊云科技中国北京与宁夏两个区域实现1
- 下一篇: 索尼第二季度营收增长33% 预计全年电子