生产故障|Dubbo泛化调用引发的“血案”(一)

WilliamGates
发布于 2022-6-20 17:50
浏览
0收藏

 

大家好,我是威哥,《RocketMQ技术内幕》作者、RocketMQ社区首席布道师、中通快递基础架构资深架构师,越努力越幸运,唯有坚持不懈,与大家共勉。


1、背景


上个月公司zk集群发生了一次故障,要求所有项目组自检有无使用Dubbo编程式/泛化调用,强制使用@Reference生成Consumer。

 

平台部给出的故障原因:泛化调用时候,provider没启动,导致每次请求都在zk创建消费节点,导致在短时间大量访问zk并创建了240万+的节点,导致zk所有节点陆续崩溃导致,多个应用因无法连接到zk报错。

 

原因是听说泛化调用时候,provider没启动,导致每次请求都在zk创建消费节点。

 

由于并不是自己负责的项目,为了弄清楚背后的原因,通过进行实验来探究该故障的深层次原因。

 

2、求证


2.1 泛化不使用缓存


测试代码如下:

public Result<Map> getProductGenericCache(ProductDTO dto) {
    ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
    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创建个消费节点(无论provider是否启动),请求量大的时候,就会导致zk所有节点陆续崩溃。

 

如果泛化不使用缓存,请求量大时会创建大量zk节点。

 

2.2 泛化使用缓存


测试代码如下:

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

    ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();//缓存,否则每次请求都会创建一个ReferenceConfig,并在zk注册节点,最终可能导致zk节点过多影响性能
    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创建一个消费节点。

 

2.3 设置服务检查为true


设置check=true,测试代码如下:

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

    ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();//缓存,否则每次请求都会创建一个ReferenceConfig,并在zk注册节点,最终可能导致zk节点过多影响性能
    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);
}

 

文章转自公众号:中间件兴趣圈

标签
已于2022-6-20 17:50:10修改
收藏
回复
举报
回复
    相关推荐