線上問題排查:記一次 Redis Cluster Pipeline 導致的死鎖問題
一、背景介紹
JedisClusterPipeline使用
JedisClusterPipline jedisClusterPipline = redisService.clusterPipelined();List<Object> response;try { for (String key : keys) { jedisClusterPipline.hmget(key, VALUE1, VALUE2); } // 獲取結果 response = jedisClusterPipline.syncAndReturnAll();} finally { jedisClusterPipline.close();}
二、故障現場記錄
三、故障過程分析
-
執行緒一直等待連線而沒有被中斷。 -
執行緒獲取不到連線。
3.1 執行緒一直等待連線而沒有被中斷原因分析
org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)方法下。
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
...
PooledObject<T> p = null;
// 獲取blockWhenExhausted配置項,該配置預設值為true
boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
long waitTime = System.currentTimeMillis();
while (p == null) {
create = false;
if (blockWhenExhausted) {
// 從佇列獲取空閒的物件,該方法不會阻塞,沒有空閒物件會返回null
p = idleObjects.pollFirst();
// 沒有空閒物件則建立
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
if (p == null) {
// borrowMaxWaitMillis預設值為-1
if (borrowMaxWaitMillis < 0) {
// 執行緒棧快照裡所有的dubbo執行緒都卡在這裡,這是個阻塞方法,如果佇列裡沒有新的連線會一直等待下去
p = idleObjects.takeFirst();
} else {
// 等待borrowMaxWaitMillis配置的時間還沒有拿到連線的話就返回null
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
if (!p.allocate()) {
p = null;
}
}
...
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}
3.2 執行緒獲取不到連線原因分析
獲取不到連線無非兩種情況:
連不上Redis,無法建立連線連線池裡的所有連線都被佔用了,無法獲取到連線
建立連線
private PooledObject<T> create() throws Exception {
int localMaxTotal = getMaxTotal();
long newCreateCount = createCount.incrementAndGet();
if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
newCreateCount > Integer.MAX_VALUE) {
createCount.decrementAndGet();
return null;
}
final PooledObject<T> p;
try {
// 建立redis連線,如果發生超時會丟擲異常
// 預設的connectionTimeout和soTimeout都是2秒
p = factory.makeObject();
} catch (Exception e) {
createCount.decrementAndGet();
// 這裡會把異常繼續往上丟擲
throw e;
}
AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getLogAbandoned()) {
p.setLogAbandoned(true);
}
createdCount.incrementAndGet();
allObjects.put(new IdentityWrapper<T>(p.getObject()), p);
return p;
}
猜想二:是不是業務程式碼沒有歸還Redis連線?
JedisClusterPipeline使用
JedisClusterPipline jedisClusterPipline = redisService.clusterPipelined();List<Object> response;try { for (String key : keys) { // 申請連線,內部會先呼叫JedisClusterPipeline.getClient(String key)方法獲取連線 jedisClusterPipline.hmget(key, VALUE1, VALUE2); // 獲取到了連線,快取到poolToJedisMap } // 獲取結果 response = jedisClusterPipline.syncAndReturnAll();} finally { // 歸還所有連線 jedisClusterPipline.close();}
JedisClusterPipeline部分原始碼
public class JedisClusterPipline extends PipelineBase implements Closeable {
private static final Logger log = LoggerFactory.getLogger(JedisClusterPipline.class);
// 用於記錄redis命令的執行順序
private final Queue<Client> orderedClients = new LinkedList<>();
// redis連線快取
private final Map<JedisPool, Jedis> poolToJedisMap = new HashMap<>();
private final JedisSlotBasedConnectionHandler connectionHandler;
private final JedisClusterInfoCache clusterInfoCache;
public JedisClusterPipline(JedisSlotBasedConnectionHandler connectionHandler, JedisClusterInfoCache clusterInfoCache) {
this.connectionHandler = connectionHandler;
this.clusterInfoCache = clusterInfoCache;
}
@Override
protected Client getClient(String key) {
return getClient(SafeEncoder.encode(key));
}
@Override
protected Client getClient(byte[] key) {
Client client;
// 計算key所在的slot
int slot = JedisClusterCRC16.getSlot(key);
// 獲取solt對應的連線池
JedisPool pool = clusterInfoCache.getSlotPool(slot);
// 從快取獲取連線
Jedis borrowedJedis = poolToJedisMap.get(pool);
// 快取中沒有連線則從連線池獲取並快取
if (null == borrowedJedis) {
borrowedJedis = pool.getResource();
poolToJedisMap.put(pool, borrowedJedis);
}
client = borrowedJedis.getClient();
orderedClients.add(client);
return client;
}
@Override
public void close() {
for (Jedis jedis : poolToJedisMap.values()) {
// 清除連線內的殘留資料,防止連線歸還時有資料漏讀的現象
try {
jedis.getClient().getAll();
} catch (Throwable throwable) {
log.warn("關閉jedis時遍歷異常,遍歷的目的是:清除連線內的殘留資料,防止連線歸還時有資料漏讀的現象");
}
try {
jedis.close();
} catch (Throwable throwable) {
log.warn("關閉jedis異常");
}
}
// 歸還連線
clean();
orderedClients.clear();
poolToJedisMap.clear();
}
/**
* go through all the responses and generate the right response type (warning :
* usually it is a waste of time).
*
* @return A list of all the responses in the order
*/
public List<Object> syncAndReturnAll() {
List<Object> formatted = new ArrayList<>();
List<Throwable> throwableList = new ArrayList<>();
for (Client client : orderedClients) {
try {
Response response = generateResponse(client.getOne());
if(response == null){
continue;
}
formatted.add(response.get());
} catch (Throwable e) {
throwableList.add(e);
}
}
slotCacheRefreshed(throwableList);
return formatted;
}
}
猜想四:是不是發生了死鎖?
舉個例子:
四、死鎖證明
以上只是猜想,為了證明確實發生了死鎖,需要以下條件:
執行緒當前獲取到了哪些連線池的連線執行緒當前在等待哪些連線池的連線每個連線池還剩多少連線
4.1 步驟一:獲取執行緒在等待哪個連線池有空閒連線
引用關係:
4.2 步驟二:獲取執行緒當前持有了哪些連線池的連線
第二步:第一步拿到JedisClusterPipeline持有哪個連線池的連線後,再查詢持有此
4.3 死鎖分析
0x6a578e0c80x6a578e0480x6a578ddc80x6a578e5380x6a578e838
五、總結
來源:本文轉自公眾號vivo網際網路技術
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70013542/viewspace-2998576/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- 線上併發事務死鎖問題排查
- 記一次線上FGC問題排查GC
- 一次詭異的線上資料庫的死鎖問題排查過程資料庫
- SpringBoot Seata 死鎖問題排查Spring Boot
- 一次JVM記憶體問題導致的線上事故JVM記憶體
- 記一次線上websocket返回400問題排查Web
- JAVA死鎖排查-效能測試問題排查思路Java
- 記一次線上崩潰問題的排查過程
- 記一次線上報錯日誌問題排查
- 記一次 MySQL select for update 死鎖問題MySql
- 記錄一次fs配置導致串線的問題
- Oracle死鎖一例(ORA-00060),鎖表導致的業務死鎖問題Oracle
- AdornerDecorator的CacheMode繫結和windows鎖屏導致TableControl鎖死問題Windows
- 由於基本資料型別使用姿勢不對導致的線上"死迴圈"問題排查資料型別
- 線上FullGC問題排查實踐——手把手教你排查線上問題GC
- 一次線上CPU高的問題排查實踐
- 一次線上問題的排查解決過程
- 一次線上問題排查所引發的思考
- 記錄一次問題排查
- 記一次oom問題排查OOM
- MySQL死鎖問題MySql
- 記一次鎖使用不當導致Dubbo執行緒阻塞問題執行緒
- 記一次排查CPU高的問題
- 分散式鎖導致的超賣問題分散式
- 記一次 Laravel MethodNotAllowedHttpException 問題排查LaravelHTTPException
- Arthas常用功能及一次線上問題排查
- 線上問題排查例項分析|關於 Redis 記憶體洩漏Redis記憶體
- 線上問題排查例項分析|關於Redis記憶體洩漏Redis記憶體
- 一次錯誤使用 go-cache 導致出現的線上問題Go
- 記一次儲存問題導致的rac故障案例
- 記一次 Mac 意外重啟導致的 Homestead 問題Mac
- JVM 常見線上問題 → CPU 100%、記憶體洩露 問題排查JVM記憶體洩露
- 死鎖問題排查過程-間隙鎖的復現以及解決
- 記一次線上問題引發的對 Mysql 鎖機制分析MySql
- MySQL 死鎖問題分析MySql
- 故障解析丨一次死鎖問題的解決
- redis connect timeout問題排查Redis