SpringCloud Ribbon和Feign重试参数性能实测对比

2020年9月28日21:34:31 发表评论 3,129 ℃

阿汤博客前两篇文章《SpringCloud Zuul(Ribbon)重试配置不生效解决办法》和《SpringCloud Feign重试不生效问题排查》已经介绍了Ribbon和Feign重试不生效的原因,且已经给出了解决办法。

但是在经过了两天的实际测试后发现,不同的超时时间配置、重试机制和熔断时间,都会影响重试的实际效果。这里分几个场景:

场景一:请求直接通过Zuul调用服务A,而此服务A的接口没有其他服务的Feign调用,这种场景比较简单,主要受Zuul的ribbon配置影响。

SpringCloud Ribbon和Feign重试参数性能实测对比

Zuul的重试参数配置:

ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1
  OkToRetryOnAllOperations: false
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000 
zuul:
  retryable: true

这里A服务节点健康状态分为两种情况

情况一:

当A服务节点都出现故障,此时请求首先通过Zuul负载均衡访问任意A服务节点比如A1,A1节点访问超时,然后触发MaxAutoRetriesNextServer=1的重试请求A2,然后A2节点返回超时,最后浏览器响收到非200状态的返回,请求总用时ribbon.ReadTimeout * (1+MaxAutoRetriesNextServer)=2s左右,如下图:

SpringCloud Ribbon和Feign重试参数性能实测对比

情况二:

当A2节点出现故障的时候,此时请求首先通过Zuul负载均衡访问任意A服务节点,如果刚好此时负载到A2,那么请求超时触发MaxAutoRetriesNextServer=1的重试请求A1,然后A1返回正常结果,最后浏览器响收到200状态的正常结果,请求总用时为ribbon.ReadTimeout + A1处理请求的时间,大约1s左右。实际上当A2响应的失败请求到达一定的数量或者百分比之后会触发熔断,那么在熔断时间内,请求不会转发到A2,熔断的默认配置如下:

#当在配置时间窗口内达到此数量的失败后,进行短路。默认20个

hystrix.command.default.circuitBreaker.requestVolumeThreshold=20

#短路多久以后开始尝试是否恢复,默认5s

hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5

#出错百分比阈值,当达到此阈值后,开始短路。默认50%

hystrix.command.default.circuitBreaker.errorThresholdPercentage=50%

当把参数MaxAutoRetries更改为1时:

上面的情况一定会有4次超时请求,即A1节点超时,首先触发MaxAutoRetries=1本节点A1重试和MaxAutoRetriesNextServer=1的A2节点重试,然后A2节点返回超时,再次触发MaxAutoRetries=1本节点A2的重试,所以A1和A2都有2次处理请求,最后浏览器返回的请求总时间为:ribbon.ReadTimeout * (1+MaxAutoRetriesNextServer)*(1+MaxAutoRetries)=4s左右。

SpringCloud Ribbon和Feign重试参数性能实测对比

情况二最多会有3次请求,第一次请求故障节点A2,会有本节点A2的一次重试并超时,和A1节点的重试,并访问正常结果,此时请求总用时为ribbon.ReadTimeout *(MaxAutoRetries +MaxAutoRetriesNextServer)+ A1处理请求的时间=2s左右。

场景二:这种场景实际使用过程中并不多见,这里主要为了测试Feign默认配置。

SpringCloud Ribbon和Feign重试参数性能实测对比

服务A已引入Spring Retry组件配置如下:

${spring.application.name}:  
  ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 1 
    OkToRetryOnAllOperations: false
    NFLoadBalancerRuleClassName: AvailabilityFilteringRule
feign:
  client:
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

这里简单说下测试结果,不做详细分析:

1、服务B节点都故障:

没有配置ribbon的情况下,B1节点处理1次,重试1次,两次失败以后B1对B2重试1次,B2对B2重试一1次,或者访问(B1/B2)1次,然后重试3次,然而和之前查的资料feign默认重试5次并不相同,最后总的超时请求时间为 feign.readTimeout * 4。

按照上面的配置以后,请求访问任意节点一次,然后对另外一个节点进行重试一次,总的超时请求时间为feign.readTimeout * 2。

另外实际测试结果不管怎么更改服务A配置里面的ribbon.MaxAutoRetries和ribbon.MaxAutoRetriesNextServer次数,重试结果都不变,后续再对源码进行分析。

2、服务B有一个节点故障的情况这里也不多做介绍,实际场景并不多见。

场景三:生产过程中大部分场景都类似此场景,即多服务之间的多跨度调用。

SpringCloud Ribbon和Feign重试参数性能实测对比

由于此场景比较复杂,而且影响重试效果的配置比较多,这里以网关的MaxAutoRetries和MaxAutoRetriesNextServer配置不变,并且B1节点故障,B2节点正常;分为A服务是否引入spring-retry组件,Zuul网关的ribbon.ReadTimeout等于和大于服务端feign.client.config.default.readTimeout4种情况测试分析。

Zuul重试参数配置如下(hystrix的时间,根据readtimeout的值动态变化,后面不另做说明,公式为(ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)):

ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1
  OkToRetryOnAllOperations: false
  NFLoadBalancerRuleClassName: AvailabilityFilteringRule
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000 
zuul:
  retryable: true

服务A的配置如下:

${spring.application.name}:  
  ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 1 
    OkToRetryOnAllOperations: false
    NFLoadBalancerRuleClassName: AvailabilityFilteringRule
feign:
  client:
    config:
      default:
        connectTimeout: 1000
        readTimeout: 1000

情况一:服务A未引入spring-retry组件,Zuul网关的ribbon.ReadTimeout等于服务端feign.client.config.default.readTimeout 都为1s。

1、此种情况,小概率会出现A1访问B1超时,触发Zuul ribbon重试A2,A2又通过Feign访问到B1的情况(因为前面介绍过,B1失败请求到达一定的数量或者百分比之后会触发熔断),通过JMeter并发测试5组,大概会有一组中的1个请求会出现超时的情况如下图,其他时候第一次访问B1超时,重试的时候A服务不会再负载到B1服务。

2、单次访问测试和JMeter并发测试,得到的结果一致。

SpringCloud Ribbon和Feign重试参数性能实测对比

SpringCloud Ribbon和Feign重试参数性能实测对比

情况二:服务A未引入spring-retry组件,Zuul网关的ribbon.ReadTimeout=3000 大于服务端 feign.client.config.default.readTimeout =1000的值。

1、单次访问的时候,当A服务feign负载到B1,浏览器马上就返回了500错误,总耗时1s,而且通过B1和B2服务的日志观察并没有进行重试。

2、通过JMeter并发测试5组,即使有些请求超过1s(表示负载到了B1),但是实际返回的状态码是200,说明已经进行了重试,并且5组1万次请求,未出现非200状态的请求,如下图:

3、得出结论高并发的时候重试逻辑并非和低频访问得到的结果一致,这个只有等空闲的时候研究源码才知道其中的处理逻辑。

SpringCloud Ribbon和Feign重试参数性能实测对比

SpringCloud Ribbon和Feign重试参数性能实测对比

情况三:服务A引入spring-retry组件,Zuul网关的ribbon.ReadTimeout=3000 大于服务端 feign.client.config.default.readTimeout =1000的值。

1、单次访问的时候,当A1服务feign负载到B1返回超时,马上A1就会重试访问B2,随后浏览器马上就返回了请求成功,总耗时1s,观察B1和B2服务的日志,的确进行了重试,观察A1和A2服务日志,调用B1的时候并没有返回接口超时的日志,也说明了A1进行的重试调用了B1,所以不管怎么样,浏览器都会返回正常结果。

2、通过JMeter并发测试5组,实际得到结果,也是和单次访问一样,100%请求都会返回正常结果,但是单次请求最大时间却达到了2s,加上1s的请求,基本上占了整个请求数量的50%以上。

3、通过JMeter的聚合结果和之前情况一和情况二对比,性能下降了7倍左右,QPS只有23-28左右如下图,而前面的实测QPS基本保持在150以上。

SpringCloud Ribbon和Feign重试参数性能实测对比

SpringCloud Ribbon和Feign重试参数性能实测对比

情况四:服务A引入spring-retry组件,Zuul网关的ribbon.ReadTimeout等于服务端feign.client.config.default.readTimeout 都为1s。

1、单次访问的时候,只有A服务第一次就负载到B2节点浏览器100%才会返回正常结果,A服务第一次访问到B1超时,刚好A服务重试访问B2返回了结果,此时刚好zuul还没触发A服务的重试,浏览器才会返回正常结果,其他情况浏览器都会返回500错误,而且B服务大概率都会产生2、3或者4次请求。

2、通过JMeter并发测试5组,实际得到结果,也是和单次访问一样,返回成功的请求占很少一部分,基本在5%左右,如下图:

SpringCloud Ribbon和Feign重试参数性能实测对比

SpringCloud Ribbon和Feign重试参数性能实测对比

通过上面四种情况的测试,得出结论:

1、高并发的时候开启feign的重试,当有一个服务某个节点故障时,会严重影响系统性能。

2、服务端开启feign重试机制后,如果网关ribbon超时时间和服务feign超时时间设置不当,当某个服务某一个节点出现故障时,会严重影响请求返回的成功率。

3、建议不要开启服务端的feign的重试机制,只要重试就会产生多余的请求,影响系统性能,所以建议把ribbon.MaxAutoRetries设置为0。

4、当服务端未开启feign重试时,建议ribbon.ReadTimeout和feign.client.config.default.readTimeout设置为一样,这样即使某个服务某个节点故障,即便是高并发或者低频访问,都不会对访问返回成功率和性能造成太大的影响。

5、而实际生产过程中,一般A服务的接口都不止调用一个B服务,甚至可能同时调用C、D、E服务;B服务可能还会调用F服务等等非常复杂,所以情况二,实际过程中也可以把ribbon.ReadTimeout设置稍微大于feign.client.config.default.readTimeout,模拟某个服务某个节点故障,进行并发性能测试对比。

【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: