日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯網營銷解決方案
生產故障|Dubbo泛化調用引發(fā)的“血案”

1、背景

平臺部給出的故障原因:泛化調用時候,provider沒啟動,導致每次請求都在zk創(chuàng)建消費節(jié)點,導致在短時間大量訪問zk并創(chuàng)建了240萬+的節(jié)點,導致zk所有節(jié)點陸續(xù)崩潰導致,多個應用因無法連接到zk報錯。

原因是聽說泛化調用時候,provider沒啟動,導致每次請求都在zk創(chuàng)建消費節(jié)點。

由于并不是自己負責的項目,為了弄清楚背后的原因,通過進行實驗來探究該故障的深層次原因。

2、求證

2.1 泛化不使用緩存

測試代碼如下:

public Result getProductGenericCache(ProductDTO dto) {
ReferenceConfig reference = new ReferenceConfig();
ApplicationConfig application = new ApplicationConfig();
application.setName("dubbo-demo-client-consumer-generic");
// 連接注冊中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
// 服務消費者缺省值配置
ConsumerConfig consumer = new ConsumerConfig();
consumer.setTimeout(5000);
consumer.setRetries(0);

reference.setApplication(application);
reference.setRegistry(registry);
reference.setConsumer(consumer);
reference.setInterface(com.demo.dubbo.api.ProductService.class); // 弱類型接口名
// reference.setVersion("");
// reference.setGroup("");
reference.setGeneric(true); // 聲明為泛化接口
GenericService svc = reference.get();
Object target = svc.$invoke("findProduct", new String[]{ProductDTO.class.getName()}, new Object[]{dto});
return Result.success((Map)target);
}

由于沒有緩存reference,因此每次請求這個方法,就會在zk創(chuàng)建個消費節(jié)點(無論provider是否啟動),請求量大的時候,就會導致zk所有節(jié)點陸續(xù)崩潰。

如果泛化不使用緩存,請求量大時會創(chuàng)建大量zk節(jié)點。

2.2 泛化使用緩存

測試代碼如下:

@Override
public Result getProductGenericCache(ProductDTO dto) {
ReferenceConfigCache referenceCache = ReferenceConfigCache.getCache();

ReferenceConfig reference = new ReferenceConfig();//緩存,否則每次請求都會創(chuàng)建一個ReferenceConfig,并在zk注冊節(jié)點,最終可能導致zk節(jié)點過多影響性能
ApplicationConfig application = new ApplicationConfig();
application.setName("pangu-client-consumer-generic");
// 連接注冊中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");

// 服務消費者缺省值配置
ConsumerConfig consumer = new ConsumerConfig();
consumer.setTimeout(5000);
consumer.setRetries(0);

reference.setApplication(application);
reference.setRegistry(registry);
reference.setConsumer(consumer);
reference.setInterface(com.demo.dubbo.api.ProductService.class); // 弱類型接口名
// reference.setVersion("");
// reference.setGroup("");
reference.setGeneric(true); // 聲明為泛化接口
GenericService svc = referenceCache.get(reference);//cache.get方法中會緩存 Reference對象,并且調用ReferenceConfig.get方法啟動ReferenceConfig
Object target = svc.$invoke("findProduct", new String[]{ProductDTO.class.getName()}, new Object[]{dto});
return Result.success((Map)target);
}

經過測試,如果使用緩存,無論provider端無論是否啟動,都只會在zk創(chuàng)建一個消費節(jié)點。

2.3 設置服務檢查為true

設置check=true,測試代碼如下:

@Override
public Result getProductGenericCache(ProductDTO dto) {
ReferenceConfigCache referenceCache = ReferenceConfigCache.getCache();

ReferenceConfig reference = new ReferenceConfig();//緩存,否則每次請求都會創(chuàng)建一個ReferenceConfig,并在zk注冊節(jié)點,最終可能導致zk節(jié)點過多影響性能
ApplicationConfig application = new ApplicationConfig();
application.setName("pangu-client-consumer-generic");
// 連接注冊中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");

// 服務消費者缺省值配置
ConsumerConfig consumer = new ConsumerConfig();
consumer.setTimeout(5000);
consumer.setRetries(0);

reference.setApplication(application);
reference.setRegistry(registry);
reference.setConsumer(consumer);
reference.setCheck(true);//試驗3,設置檢測服務存活
reference.setInterface(org.pangu.api.ProductService.class); // 弱類型接口名
// reference.setVersion("");
// reference.setGroup("");
reference.setGeneric(true); // 聲明為泛化接口
GenericService svc = referenceCache.get(reference);//cache.get方法中會緩存 Reference對象,并且調用ReferenceConfig.get方法啟動ReferenceConfig
Object target = svc.$invoke("findProduct", new String[]{ProductDTO.class.getName()}, new Object[]{dto});//實際網關中,方法名、參數類型、參數是作為參數傳入
return Result.success((Map)target);
}

情況一:啟動provider服務,然后啟動消費端泛化,請求此泛化方法,在zk只注冊了一個consumer節(jié)點;停止provider,再請求此泛化方法,發(fā)現zk上此節(jié)點數量不變化。

為什么呢?provider停止后,請求不再創(chuàng)建zk節(jié)點的原因是RegistryConfig的ref已經在啟動時候生成了代理(由于啟動時候provider服務存在,check=true校驗過通過),因此不再創(chuàng)建。

情況二:不啟動provider服務,直接啟動消費端泛化,請求此泛化方法,發(fā)現每請求一次,在zk就會創(chuàng)建一個消費節(jié)點。至此驗證到故障。

那么這種情況,為什么會每次請求都在zk創(chuàng)建消費節(jié)點呢?根本原因是什么?

private T createProxy(Map map) {
//忽略其它代碼

if (isJvmRefer) {
//忽略其它代碼
} else {
if (url != null && url.length() > 0) {
//忽略其它代碼
} else { // assemble URL from register center's configuration
List us = loadRegistries(false);//代碼@1
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));//代碼@2
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
}
}

if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));//代碼@3
} else {
List> invokers = new ArrayList>();
URL registryURL = null;
for (URL url : urls) {//代碼@4
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameterIfAbsent(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}

Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
if (c && !invoker.isAvailable()) {//check=true,provider服務不存在,拋出異常
// make it possible for consumer to retry later if provider is temporarily unavailable
initialized = false;
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
// create service proxy
return (T) proxyFactory.getProxy(invoker);
}
  • 首次請求泛化方法,由于ReferenceConfig的ref為null,因此執(zhí)行createProxy,執(zhí)行代碼@1、@2、@3,在zk創(chuàng)建消費節(jié)點,但是由于check=true,因此拋出IllegalStateException異常,最終ReferenceConfig的ref依然為null。
  • 第二次請求泛化方法,由于ReferenceConfig已經被緩存,這次的ReferenceConfig對象就是首次的ReferenceConfig對象,獲取ReferenceConfig的代理對象ref,由于ReferenceConfig的ref為null,因此執(zhí)行createProxy,執(zhí)行代碼@1、@2、@4,在zk創(chuàng)建消費節(jié)點,但是由于check=true,因此拋出IllegalStateException異常,最終ReferenceConfig的ref依然為null。
  • 第三次,以及后續(xù)的請求,都和第二次請求是一樣效果。

為什么每次在zk都創(chuàng)建消費節(jié)點,只能說明訂閱url不同導致的,如果url相同,在zk是不會創(chuàng)建的。那么訂閱url的組成對一個服務來說有哪些不同呢?

查看ReferenceConfig.init(),發(fā)現訂閱url上有timestamp,是當前時間戳,這也說明了為什么每次都去注冊,因為訂閱url不同,如下圖:

訂閱url上加上這個timestamp是否有些不合理呢?經過查看官方,在2.7.5版本中已經將訂閱的URL中的timestamp去掉了,只會對一個URL訂閱一次。

3、故障結論

由于使用了泛化調用,但啟動者沒有啟動,而且使用了check等于true,每次調用都會嘗試去注冊,但在dubbo2.7.5之前,注冊的URL帶了時間戳,導致每請求一次就在zk上創(chuàng)建一個節(jié)點,導致產生大量節(jié)點,最終導致zk崩掉。


分享標題:生產故障|Dubbo泛化調用引發(fā)的“血案”
網頁鏈接:http://m.5511xx.com/article/cccjidi.html