zoukankan      html  css  js  c++  java
  • dubbo学习笔记(一)超时与重试

    dubbo提供在provider和consumer端,都提供了超时(timeout)和重试(retries)的参数配置。


    配置方式

    provider端在<dubbo:service>中配置,consumer端在<dubbo:reference>中配置。

    默认值

    timeout默认值为1000,单位毫秒,表示超时时间是1秒;

    retries默认值为2,表示重试2次,加上本身调用1次,一共有3次调用;

    org.apache.dubbo.common.Constants类可以找到:

    public static final int DEFAULT_TIMEOUT = 1000;
    public static final int DEFAULT_RETRIES = 2;
    

    优先级

    consumer端配置优先于provider端配置


    通过一个例子来对这2个参数进行详细学习和解读。
    建立如下工程结构:

    其中:
    demo-consumer为consumer端应用(SpringBoot工程)
    demo-provider为provider端父工程,它下面有2个子工程,
        demo-interface为dubbo服务接口定义
        demo-service为provider端应用(SpringBoot工程),实现了demo-interface接口,

    demo-interface里接口定义:

    public interface HelloService {
    
        String hello(String name);
    }
    

    定义了1个HelloService接口,里面一个hello方法,请求参数为1个String,返回String。

    在demo-service里对该接口的实现类:

    @Service("helloService")
    public class HelloServiceImpl implements HelloService {
        @Override
        public String hello(String name) {
            System.out.println("hello begin=>" + name);
            System.out.println("hello end=>" + name);
            return "hello " + name;
        }
    }
    

    demo-service中的dubbo-demo-provider.xml定义:

    <dubbo:service ref="helloService" interface="com.cdfive.demo.service.HelloService"  />
    

    demo-consumer中的dubbo-demo-consumer.xml定义:

    <dubbo:reference id="helloService" interface="com.cdfive.demo.service.HelloService" />
    

    为了通过浏览器输入请求地址访问,在demo-consumer定义了一个Controller:

    @RequestMapping("test")
    @RestController
    public class TestController {
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("hello")
        public String hello(String name) {
            return helloService.hello(name);
        }
    }
    

    这样我们就可以通过浏览器里访问http://localhost:8081/test/hello?name=xxx,来调用dubbo接口:HelloService#hello

    demo-consumer的启动类:

    @Slf4j
    @ImportResource("classpath:config/applicationContext.xml")
    @SpringBootApplication(scanBasePackages = "com.cdfive")
    public class DemoConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoConsumerApplication.class, args);
            log.info("demo consumer started");
        }
    }
    

    demo-service的启动类:

    @Slf4j
    @ImportResource("classpath:config/applicationContext.xml")
    @SpringBootApplication(scanBasePackages = "com.cdfive")
    public class DemoProviderApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoProviderApplication.class, args);
            log.info("demo provider started");
        }
    }
    

    OK,准备工作完毕。
    把provider端和consumer端应用都启起来,浏览器中访问:http://localhost:8081/test/hello?name=cdfive

    可以看到,浏览器中显示:
    hello cdfive

    在demo-service的控制台显示:
    hello begin=>cdfive
    hello end=>cdfive

    前面提到,timeout默认是1000,就是1秒超时。这里我们修改HelloServiceImpl的实现,通过Thread.sleep(2000)模拟业务方法超时,休眠2s:

    @Service("helloService")
    public class HelloServiceImpl implements HelloService {
        @Override
        public String hello(String name) {
            System.out.println("hello begin=>" + name);
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("hello end=>" + name);
            return "hello " + name;
        }
    }
    

    重启demo-service,F5刷新浏览器再次访问:http://localhost:8081/test/hello?name=cdfive

    case 1: 默认情况,provider和consumer端的timeout和retires均不设置,使用默认值

    观察输出结果
    浏览器:
    There was an unexpected error (type=Internal Server Error, status=500).
    Failed to invoke the method hello in the service com.cdfive.demo.service.HelloService. Tried 3 times of the providers....

    provider端:
    hello begin=>cdfive
    hello begin=>cdfive
    hello end=>cdfive
    hello begin=>cdfive
    hello end=>cdfive
    hello end=>cdfive

    consumer端:
    [ WARN ] [2019-01-01 14:52:03,007] [DubboClientHandler-192.168.1.100:20001-thread-2] com.alibaba.dubbo.remoting.exchange.support.DefaultFuture [71] - [DUBBO] The timeout response finally returned at 2019-01-01 14:52:03.007, response Response [id=6, version=null, status=20, event=false, error=null, result=RpcResult [result=hello cdfive, exception=null]], channel: /192.168.1.100:61755 -> /192.168.1.100:20001, dubbo version: 2.6.0, current host: 192.168.1.100
    [ WARN ] [2019-01-01 14:52:04,008] [DubboClientHandler-192.168.1.100:20001-thread-2] com.alibaba.dubbo.remoting.exchange.support.DefaultFuture [71] - [DUBBO] The timeout response finally returned at 2019-01-01 14:52:04.008, response Response [id=7, version=null, status=20, event=false, error=null, result=RpcResult [result=hello cdfive, exception=null]], channel: /192.168.1.100:61755 -> /192.168.1.100:20001, dubbo version: 2.6.0, current host: 192.168.1.100
    [ ERROR] [2019-01-01 14:52:04,011] [http-nio-8081-exec-6] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] [182] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method hello in the service com.cdfive.demo.service.HelloService. Tried 3 times of the providers [192.168.1.100:20001] (1/1) from the registry localhost:2181 on the consumer 192.168.1.100 using the dubbo version 2.6.0. Last error is: Invoke remote method timeout. method: hello, provider: dubbo://192.168.1.100:20001/com.cdfive.demo.service.HelloService?anyhost=true&application=demo_consumer&check=false&default.check=false&dubbo=2.6.0&generic=false&interface=com.cdfive.demo.service.HelloService&methods=hello&pid=7032&register.ip=192.168.1.100&remote.timestamp=1546325119243&side=consumer&timestamp=1546324847011, cause: Waiting server-side response timeout. start time: 2019-01-01 14:52:03.006, end time: 2019-01-01 14:52:04.008, client elapsed: 1 ms, server elapsed: 1001 ms, timeout: 1000 ms, request: Request [id=8, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=hello, parameterTypes=[class java.lang.String], arguments=[cdfive], attachments={path=com.cdfive.demo.service.HelloService, interface=com.cdfive.demo.service.HelloService, version=0.0.0}]], channel: /192.168.1.100:61755 -> /192.168.1.100:20001] with root cause
    com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout. start time: 2019-01-01 14:52:03.006, end time: 2019-01-01 14:52:04.008, client elapsed: 1 ms, server elapsed: 1001 ms, timeout: 1000 ms, request: Request [id=8, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=hello, parameterTypes=[class java.lang.String], arguments=[cdfive], attachments={path=com.cdfive.demo.service.HelloService, interface=com.cdfive.demo.service.HelloService, version=0.0.0}]], channel: /192.168.1.100:61755 -> /192.168.1.100:20001
    at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:134)
    at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:111)
    at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:95)
    at com.alibaba.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:142)
    at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:73)
    at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:74)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:68)
    at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:53)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:68)
    at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:47)
    at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:68)
    at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:52)
    at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77)
    at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:232)
    at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:70)
    at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:51)
    at com.alibaba.dubbo.common.bytecode.proxy0.hello(proxy0.java)
    at com.cdfive.demo.controller.TestController.hello(TestController.java:21)

    浏览器打印了异常信息,其中Tried 3 times of the providers表明尝试了3次调用;
    provider打印了3次请求调用情况,注意到begin和end不是按顺序的,也就是说上1个调用没执行完,因为1秒超时时间到了,又开始了新的一次重试,而前面的调用并未终止;
    consumer打印了3次WARN ...[DUBBO] The timeout response...,和一次ERROR,错误信息跟浏览器看到的一致,只是多了堆栈信息。

    总结:
    在不设置timeout和retries的时候,如果provider端接口一直出现超时,provider端会调用3次,而日志中没有任何警告或错误信息;
    consumer端虽然重试了2次,加本身调用的1次,一共发起3次调用,如果provider3次全部超时,consumer端会打印超时异常信息;
    provider端日志中没有任何警告或错误信息不利于发现问题;前面的调用并未终止,如果是非查询类接口且接口没有实现幂等性时,可能产生重复数据

    case 2: provider端retries=0,consumer端不设置

    重启并访问,浏览器中仍然是错误信息,但是Tried 1 times of the providers,表示只调用了1次 ;
    provider端仅打印了1次调用:
    hello begin=>cdfive
    hello end=>cdfive
    consumer端打印1次WARN、1次ERROR

    总结:
    在provider端设置retries=0已经生效,接口仅调用了1次;

    case 3: provider端retries=0,consumer端retries=1

    重启并访问,浏览器中仍然是错误信息,但是Tried 2 times of the providers,表明调用了2次 ;
    provider端打印了2次调用,consumer打印了2次WARN、1次ERROR

    总结:
    consumer的retries优先级较高,两端都设置的情况下,以consumer端的retries为准

    case 4: provider端timeout=3000, retries=0,consumer端retries=0

    接下来我们把重试都关闭(即都设置为0),来看看timeout的情况,这里设置为3秒,因为方法休眠2s;
    重启并访问:
    浏览器正常输出hello cdfive,provider输出请求参数,consumer没有输出;

    总结:
    provider端设置timeout已生效。

    case 5: provider端timeout=1000, retries=0,consumer端retries=0

    provider端的超时设置为小于接口执行时间;
    重启并访问:
    跟case 1很像,因为重试为0,所以只调用了1次,唯一不同的provider端日志里多了1次WARN
    [ WARN ] [2019-01-01 15:19:49,482] [DubboServerHandler-192.168.1.100:20001-thread-2] com.alibaba.dubbo.rpc.filter.TimeoutFilter [71] - [DUBBO] invoke time out. method: hello arguments: [cdfive]...

    这是dubbo自带的filter类TimeoutFilter输出的。

    总结:
    provider端设置了timeout,如果接口调用超时,provider会打印WRAN信息。

    case 6: provider端timeout=1000, retries=0,consumer端timeout=3000, retries=0

    这里provider端的1秒小于接口方法的2秒,而consumer设置的3秒大于2秒,
    重启并访问:
    浏览器成功输出hello cdfive,provider输出请求参数,并打印了1次WARN,consumer没有输出;

    总结:
    consumer的timeout优先级较高,两端都设置的情况下,以consumer端的timeout为准


    参考dubbo官网的文档,并结合工作中项目实践,对超时和重试这2个参数做个总结:

    1.超时(timeout)默认1000毫秒,重试(retries)默认2次(即一共调用3次);
    2.provider端在<dubbo:service>中配置,consumer端在<dubbo:reference>中配置,consumer端的配置会覆盖provider配置;
    3.超时(timeout)建议在provider端配置,因为作为提供方,它更清楚自己接口的耗时情况,并且provider端设置了timeout,在日志中有TimeoutFilter的WARN信息;
    4.在provider端一般接口timeout设置为5秒或者10秒,如果是复杂查询、导出报表、调用第三方接口、本身是最上游的接口等,根据情况考虑设置大一点;
    5.在consumer端配置设置timeout会覆盖provider设置,但有时设置timeout能够让consumer快速失败,而不因为下游provider服务接口的问题拖垮consumer本身;
    6.retries建议在provider端设置为0,consumer根据情况也可以设置为0,因为重试可能因非幂等性原因导致重复数据,并且超时情况即便重试成功consumer端可能也收不到成功响应;


    参考:
    http://dubbo.apache.org/zh-cn/docs/user/recommend.html
    http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-service.html
    http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-reference.html

  • 相关阅读:
    Atitit 人脸识别 眼睛形态 attilax总结
    Atitit 手机号码选号 规范 流程 attilax总结 v2 r99.docx
    atitit 板块分类 上市公司 龙头企业公司 列表 attilax总结.docx
    Atititi atiitt eam pam资产管理 购物表去年.xlsx
    使用cmd查看电脑连接过的wifi密码(一)
    常见十大web攻击手段 悟寰轩
    常见web攻击方式 悟寰轩
    【MYSQL数据库】MYSQL学习笔记mysql分区基本操作 悟寰轩
    Filter及FilterChain的使用详解 悟寰轩
    启动tomcat spring初始化两次问题(eg:@PostConstruct) 悟寰轩
  • 原文地址:https://www.cnblogs.com/cdfive2018/p/10204720.html
Copyright © 2011-2022 走看看