dubbo連線zookeeper註冊中心因為斷網導致執行緒無限等待問題
最近維護的系統切換了網路環境,由聯通換成了電信網路,因為某些過濾規則導致系統連不上zookeeper伺服器(應用系統機器在深圳,網路為電信線路,zookeeper伺服器在北京,網路為聯通線路),因為我不是運維人員也不懂運維相關的技術,所以排查了很久也不知道原因,最後無奈之下把深圳這邊的網路切回了聯通,系統恢復正常。
但是因為本次事故體現了一個很嚴重的問題,即當zookeeper註冊中心連不上時dubbo的執行緒會無限等待,因為系統有一些定時任務會比較頻繁地開啟新執行緒連線dubbo,所以導致的結果是tomcat一會兒執行緒池就滿了,其它的不依賴dubbo的功能也被阻塞無法使用。
所以需要解決一個問題,即在斷網的情況下要保證應用程式可以執行(有很多功能不依賴外網),一開始我以為dubbo應該有對ZKClient連線相關的超時時間配置,結果找了很久也沒發現,後來debug了dubbo的原始碼發現根本就沒有設定超時時間,ZKClient預設的超時時間是Integer.MAX_VALUE,幾乎等於無限等待,所以無奈之下只好重寫了dubbo的ZookeeperClient實現,好在dubbo的擴充套件性非常好,基於SPI的擴充套件非常方便,下面是我的擴充套件程式碼:
1、增加一個檔案com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter放置於專案的META-INF/dubbo/internal資料夾下,dubbo便會自動掃描到這個檔案。檔案內容很簡單就一句話:zkclient=com.gwall.zookeeper.ZkclientZookeeperTransporter
即把dubbo原來的預設實現替換為我的實現。
2、ZkclientZookeeperTransporter並不是我想替換的程式碼,我要替換的是ZkclientZookeeperTransporter的成員變數ZookeeperClient,只是dubbo只在ZookeeperTransporter上加了SPI註解,所以只好這樣辦了,ZkclientZookeeperTransporter程式碼照抄過來。
3、在ZkclientZookeeperTransporter裡面使用自己的ZkclientZookeeperClient,程式碼如下:
package com.gwall.zookeeper;
import java.util.List;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.remoting.zookeeper.ChildListener;
import com.alibaba.dubbo.remoting.zookeeper.StateListener;
import com.alibaba.dubbo.remoting.zookeeper.support.AbstractZookeeperClient;
/**
* 修改dubbo提供的ZkclientZookeeperClient
* 主要目的是增加一個連線zookeeper的超時時間,避免ZkClient預設的無限等待
* @author long.zr
*
*/
public class ZkclientZookeeperClient extends AbstractZookeeperClient<IZkChildListener> {
private final ZkClient client;
private volatile KeeperState state = KeeperState.SyncConnected;
public ZkclientZookeeperClient(URL url) {
super(url);
//設定超時時間為5000毫秒
client = new ZkClient(url.getBackupAddress(),5000);
client.subscribeStateChanges(new IZkStateListener() {
public void handleStateChanged(KeeperState state) throws Exception {
ZkclientZookeeperClient.this.state = state;
if (state == KeeperState.Disconnected) {
stateChanged(StateListener.DISCONNECTED);
} else if (state == KeeperState.SyncConnected) {
stateChanged(StateListener.CONNECTED);
}
}
public void handleNewSession() throws Exception {
stateChanged(StateListener.RECONNECTED);
}
});
}
public void createPersistent(String path) {
try {
client.createPersistent(path, true);
} catch (ZkNodeExistsException e) {
}
}
public void createEphemeral(String path) {
try {
client.createEphemeral(path);
} catch (ZkNodeExistsException e) {
}
}
public void delete(String path) {
try {
client.delete(path);
} catch (ZkNoNodeException e) {
}
}
public List<String> getChildren(String path) {
try {
return client.getChildren(path);
} catch (ZkNoNodeException e) {
return null;
}
}
public boolean isConnected() {
return state == KeeperState.SyncConnected;
}
public void doClose() {
client.close();
}
public IZkChildListener createTargetChildListener(String path, final ChildListener listener) {
return new IZkChildListener() {
public void handleChildChange(String parentPath, List<String> currentChilds)
throws Exception {
listener.childChanged(parentPath, currentChilds);
}
};
}
public List<String> addTargetChildListener(String path, final IZkChildListener listener) {
return client.subscribeChildChanges(path, listener);
}
public void removeTargetChildListener(String path, IZkChildListener listener) {
client.unsubscribeChildChanges(path, listener);
}
}
程式碼基本上是照抄dubbo的,唯一的區別是註釋那裡傳遞了一個超時時間為5000毫秒,過了超時時間ZkClient會丟擲異常,執行緒也不會無限等待了。
奇怪的是dubbo為什麼不在這裡提供超時時間的配置,或許是因為ZkClient本身不屬於dubbo的功能吧。