zuul网关重试机制探索

网关相关配置

1.zuul相关的默认配置 springcloud(F版)

属性 默认值 描述
zuul.retryable false 是否开启重试
ribbon.ConnectTimeout 1000 链接超时时间
ribbon.ReadTimeout 1000 读超时时间
ribbon.MaxAutoRetries 0 对第一次请求的服务的重试次数
ribbon.MaxAutoRetriesNextServer 1 要重试的下一个服务的最大数量(不包括第一个服务)
ribbon.OkToRetryOnAllOperations false 所有请求都重试
ribbon.MaxTotalHttpConnections 200 ?
ribbon.MaxConnectionsPerHost 50 ?
hystrix.command.default.execution.timeout.enabled true 是否开启

有些书上说,配置当中的ConnectTimeout和ReadTimeout是当HTTP客户使用HttpClient的时候生效的,参数会被设置到HttpClient中,但我在使用过程中,并不是只有HttpClient才会生效。

zuul网关重试机制探索_第1张图片
ribbon默认配置
zuul网关重试机制探索_第2张图片
ribbon默认配置

模拟场景

  1. 调用关系


    zuul网关重试机制探索_第3张图片
    模拟调用关系
  2. 代码说明
    zuul主要是负责把请求转发到后面的服务上,为了模拟业务处理场景,服务暴露了一个sleep借口,接口作用是根据传过来的参数模拟业务处理时间。
    @RequestMapping(value = "/testSleep",method = 
    {RequestMethod.GET,RequestMethod.POST} )
    public ApiResult testSleep(@RequestParam("sleep") int sleep){
        logger.info("sleep..........start");
       try{
           Thread.sleep(sleep*1000);
       }
       catch (Exception e){
           logger.error(e.toString());
       }
        logger.info("sleep..........end");
        return ApiResult.SUCCESS();
    }
    
    

实验

实验一 裸奔配置(zuul全部走默认配置)

单台服务

默认情况下ribbon超时时间为1秒,我们模拟一个2秒的业务让ribbon超时。

  • 此时客户端返回了错误结果,此处进行了zuul回退机制处理


    zuul网关重试机制探索_第4张图片
    客户端模拟
  • 业务处理2秒时,网关报了异常,日志中的ProducerFallback是因为在zuul网关中加了Fallback回退措施,打印出的异常为Read timed out


    zuul超时
  • 此时业务日志显示正常处理,时间大概为2秒


    业务调用一次
  • 结论
    我们发现现在这种情况并没有进行重试,因为我们可以看到业务日志只被调用了一次,咦!! 等等,还记得我们上面提到的MaxAutoRetries属性和MaxAutoRetriesNextServer吗,是不是因为这个所以不对当前机器进行重试,如果在不改变此值的前提下,我们再启动一台同等服务呢?

两台服务

网关超时
zuul网关重试机制探索_第5张图片
第一台服务命中
第二台服务未命中
  • 结论
    只有一台服务被调用,并没有重试到另一台服务上

单台服务 MaxAutoRetries修改为大于0

当MaxAutoRetries大于0时,调用也没有发生变化。
至此说明实验一中重试未起作用。

实验二 (使用okhttp)

两台服务(默认配置)

突然有一个想法,网关重试机制会不会和ribbon底层使用的http有关呢,默认情况下ribbon使用的是httpclient,那么我如果换成使用okhttp呢,于是,我把ribbon底层换成了okhttp又重复了一遍实验一


zuul网关重试机制探索_第6张图片
开启okhttp
  • 网关依旧超时
    但此时可以看出超时时间大致为4秒


    zuul超时3.png
  • 服务被调用了两次
    神奇的情况发生了,两台服务都被调用了,貌似自动开启了重试功能
    我们通过日志可以看出业务处理时间分别为
    18:18:11到18:18:13 处理时间2秒
    18:18:12到18:18:14 处理时间2秒
    在服务处理1秒后进行了重试


    第一台服务

    第二台服务
  • 为什么重试时网关会是4秒超时呢?
    还记我们之前的截图吗,ribbon的超时时间是通过公式计算出来的

    ribbonReadTimeout :1
    ribbonConnectTimeout:1
    maxAutoRetries:0
    maxAutoRetriesNextServer :1
    
    ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) 
                    * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)
    
    ribbonTimeout = (1000+1000)*(0+1)*(1+1)=4000(ms)
    
    
  • 结论
    服务打到其中一台服务器上,因为ribbon.ReadTimeout为1秒,此时业务处理时间需要2秒,所以1秒后,也就是18:18:12 的时候进行了重试,
    ribbon.MaxAutoRetries为0,所以没有在本服务进行重试,而是选择了另外一台服务。

单台服务 MaxAutoRetries修改为1

ribbon.MaxAutoRetries: 1

  • 网关超时
    但是我们细心一点可以发现,此时的网关也是4秒后超时的


    网关超时
  • 服务调用
    日志显示业务被触发了4次。1秒一次


    zuul网关重试机制探索_第7张图片
    服务调用日志
  • 为什么服务被调用了4次
    现象应该是

    4次重试顺序.png

  • 为什么重试时网关也是4秒超时呢?
    按照我们之前的计算公式应该是8秒才对啊

     ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) 
            * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)
     ribbonTimeout =(1000+100)*(1+1)*(1+1)=8000(ms)
    
    

通过打印异常堆栈,可以发现其中的端倪

异常.png

异常已经清晰的告诉我们了原因:
Number of retries on next server exceeded max 1 retries ,就是重试机器的次数已经答到了设置的上限,因为我们MaxAutoRetriesNextServer设置的是1,意思就是我们重试一台。因此我们吧

单台服务 MaxAutoRetries修改为2

ribbon.MaxAutoRetries: 2

  • 网关超时时间已经变为了大概6秒


    网关6秒.png
  • 业务被调用了6次 也是一秒一次


    zuul网关重试机制探索_第8张图片
    6次调用.png
  • 结论
    ribbon超时时间虽然是8秒,但实际过程中还要看重试策略。

实验三

zuul使用httpClient并支持重试

  • 修改pom 增加重试包
      
            org.springframework.retry
            spring-retry
        
  • 打开重试开关
    zuul.retryable=true

  • 网关发起调用


    网关日志
  • 服务调用日志


    zuul网关重试机制探索_第9张图片
    1机器被调用

    zuul网关重试机制探索_第10张图片
    另一台机器被调用

实验总结

  • 当ribbon使用的是httpclient时,重试机制是默认关闭的,如果要启动重试机制需要在项目中引用spring-retry包,以及手工打开zuul.retryable=true设置,其实除此之外spring.cloud.loadbalancer.retry.enabled=true 也是需要设置的只不过这个值默认为true,所以此处可以忽略设置。

  • 当ribbon使用的是okhttp时,重试机制是自动打开的,重试的效果与我们设置的ribbon超时时间以及重试次数都有关系。

  • 虽然二种机制都能达到zuul网关调用的重试,但细心一点的朋友还是应该可以看出一点区别的,使用httpclient进行重试时,客户端进行重试超时返回时,控制台并没有打印Exception日志,但是当我们替换成okhttp后,控制台会打印出Exception日志 如下图对比


    日志对比

    okhttp是通过AbstractRibbonCommand.getFallbackResponse,


    zuul网关重试机制探索_第11张图片
    AbstractRibbonCommand

    httclient是通过RibbonRoutingFilter.setResponse(ClientHttpResponse resp)
    zuul网关重试机制探索_第12张图片
    RibbonRoutingFilter
  • 在AbstractRibbonCommand中我们可以看到两个计算时间的方式
    getRibbonTimeout 上面我们已经说过,除些之外还有一个计算hystrix的时间方法getHystrixTimeout,默认情况下hystrix超时时间defaultHystrixTimeout为0,网上大部分都说默认是1秒,但其实我认为在zuul网关这个场景下,这种说法不对的。如果我们设置了hystrix超时时间,刚会已我们设置的为准,但如果我们不设置,代码会使用ribbon的超时时间为hystrix超时时间。


    zuul网关重试机制探索_第13张图片
    hystrixTimeout计算

    而且当hystrixTimeout设置的值小于ribbonTimeout,则会打印警告。也就是说当hystrixTimeoutribbonTimeout ribbonTimeout的设置也没意义了啊,因为ribbon超时了,也触发网关回退机制了,hystrixTimeout就没意思了,但感觉这点设计的比较乱。

  • 此外,重试默认都是只支持get请求,如果我把请求方式修改为post重试是不生效的,我们需要设置OkToRetryOnAllOperations为true, 这种情况不太建议,因为post请求大多都是写入请求,如果要支持重试,服务自身的幂等性一定要健壮。

zuul网关重试就先写到这,主要是记录了一下我自己的使用当中遇到的情况。欢迎拍砖。

你可能感兴趣的:(zuul网关重试机制探索)