前言
在分布式系統中,注冊中心充當著重要角色,是服務發現、客戶端負載均衡中不可缺少的一員,注冊中心除了能夠實作基本的功能外,他的穩定性、可用性和健壯性對整個分布式系統的流暢運行影響重大,dubbo作為國內一款主流的分布式系統,支持的注冊中心有zookeeper、nacos和redis等第三方中間件,同時也支持Simple和Multicast的方式,zk和nacos可能是最常使用的方式,到底誰更勝一籌呢?以下的事故現場便有答案,
在分布式系統中,服務往往由提供方來定義,并給出服務定義的sdk包,消費方通過引入提供方的sdk包,進行服務的發現,但是當一個子系統需要依賴成千上百個子系統的服務,那么需要依賴成千上百個子系統的sdk包,顯然有些不友好,那么有什么方式可以不引依賴呢,dubbo提供了泛化呼叫方式,泛化呼叫雖然解決了依賴參考的問題,但是也存在一些使用不當引發的致命問題,通過如下一個泛化服務定義未快取的demo案例來揭穿,
案例復現
pom.xml檔案中引入2.5.7版本的dubbo,0.11版本的zkClient依賴,采用zk作為注冊中心,
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.7</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
通過如下的代碼來模擬泛化呼叫,helloService()方法中進行泛化服務的定義,并回傳泛化服務,然后在sayHello()方法中進行服務泛化呼叫,sayHello方法總通過一個死回圈一直進行服務獲取,真到發生例外,
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.springframework.stereotype.Service;
@Service
public class HelloGenericService {
private GenericService helloService() {
ReferenceConfig<GenericService> config = new ReferenceConfig<>();
config.setInterface("com.qiao.hao.ting.service.HelloService");
config.setGeneric(true);
config.setProtocol("dubbo");
config.setCheck(false);
//采用zk作為注冊中心
config.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
//config.setRegistry(new RegistryConfig("nacos://127.0.0.1:8848"));
config.setTimeout(1000);
config.setApplication(new ApplicationConfig("general"));
GenericService service = config.get();
return service;
}
public Object sayHello() {
while (true) {
try {
GenericService genericService = helloService();
//rpc呼叫
//genericService.$invoke("syaHello", new String[]{}, new Object[]{});
} catch (Exception e) {
e.printStackTrace();
break;
}
}
return "success";
}
}
觸發sayHello呼叫之后,來看zk節點的資訊,通過zkCli客戶端視窗查看dubbo注冊節點資訊,helloService每被呼叫一次,則會向zk的/dubbo/對應介面/consumers目錄寫入一個消費節點,

程式一直運行下去,消費者節點個數會直接溢位ls命令能夠接受的陣列大小 ,

同時zk的data目錄下的檔案大小在不斷地增加,那么一個最直觀的問題就是磁盤隨著時間推移一定會被打滿,

同時,通過dubbo-admin查看服務注冊資訊,可以看到com.qiao.hao.ting.service.HelloService服務節點個數不止一個,隨著helloService的一直運行,那么節點個數就會一直增加,

現在把注冊中心改為nacos,注冊客戶端采用0.0.1版本的dubbo-registry-nacos,
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>0.0.1</version>
</dependency>
把泛化服務定義的registry設定為nacos,
private GenericService helloService() {
ReferenceConfig<GenericService> config = new ReferenceConfig<>();
config.setInterface("com.qiao.hao.ting.service.HelloService");
config.setGeneric(true);
config.setProtocol("dubbo");
config.setCheck(false);
//config.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
//采用nacos作為注冊中心
config.setRegistry(new RegistryConfig("nacos://127.0.0.1:8848"));
config.setTimeout(1000);
config.setApplication(new ApplicationConfig("general"));
GenericService service = config.get();
return service;
}
觸發sayHello方法,通過nacos的管理界面可知,無論程式怎么跑,com.qiao.hao.ting.service.HelloService消費者注冊資訊只有一條,

為了對比的一致性,都通過dubbo-admin進行對比,dubbo-admin默認是通過zk進行注冊的,這里需要對dubbo-admin進行小改造,通過以下兩步把dubbo-admin切換到nacos,第一,下載對應dubbo-admin版本的原始碼(本案例是2.5.7版本),然后引入0.0.1版本的dubbo-registry-nacos的依賴,

第二,把dubbo.registry.address的地址改為nacos://127.0.0.1:8848,

然后重新構建dubbo-admin,運行,最后查看服務串列,可知,在nacos作為注冊中心下,該com.qiao.hao.ting.service.HelloService服務也只會存在一條注冊資訊,

問題分析
由于沒有把泛化服務進行快取,每次呼叫的時候都會進行一次服務注冊,服務注冊請求發送到zk,zk就會進行一個節點的寫入;nacos中的一致性不是像zk通過節點資料進行維護,并不會出現服務無限重復注冊的情況(兩者具體的原理不在這里進行說明,敬請期待),
GenericService service = config.get();
當然實際代碼中幾乎不可能出現死回圈呼叫注冊的,但是在高并發,或者長時間維持一定量的請求,那么還是會導致zk的磁盤耗盡、io讀寫例外、導致zk不可用,從而導致整個集群的服務注冊發型能力不可用,
能不能在測驗階段發現這種問題,如果測驗人員比較厲害的,還可能關注服務注冊這塊,但是一般不可能,服務注冊一般不在測驗范圍,在功能測驗,就算算上單元、冒煙、整體及回歸測驗,也不可能會出現zk的不可用,壓力測驗,一般比較短暫,短暫時間內的磁盤寫入量,機器應該是能夠抗住的,除非測驗環境也做了監控,但一般也不可能,
解決方案
如果使用zk作為注冊中心了,那么如何預防和解決這樣的問題呢,
1、對服務進行快取,比如改為如下代碼,
@Service
public class HelloGenericService {
private GenericService genericService;
private Object lockObject = new Object();
private GenericService helloService() {
if(genericService != null) {
return genericService;
}
synchronized (lockObject) {
if(genericService != null) {
return genericService;
}
ReferenceConfig<GenericService> config = new ReferenceConfig<>();
config.setInterface("com.qiao.hao.ting.service.HelloService");
config.setGeneric(true);
config.setProtocol("dubbo");
config.setCheck(false);
//config.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
config.setRegistry(new RegistryConfig("nacos://127.0.0.1:8848"));
config.setTimeout(1000);
config.setApplication(new ApplicationConfig("general"));
genericService = config.get();
}
return genericService;
}
}
2、加強代碼審核
3、對zk節點進行監控,比如磁盤、cpu、io等物理監控,注冊服務請求的網路監控,
結論
建議選擇nacos作為注冊中心,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/402697.html
標籤:其他
上一篇:大資料高級開發工程師——大資料相關工具之一 Sqoop
下一篇:bt_mx
