我们其实主要是站在之前对每个组件的核心源码的理解上,来思考每个组件的底层的工作机制和关键的运行流程,然后针对这些核心机制的关键参数,来考虑应该在生产环境下如何来配置和调整,以及做一些对应的测试

我们要对整个这次这个架构的各种细节都做比较深入的一个思考

1、服务注册中心


eureka server来做的,我们要来思考服务注册和发现的核心运行过程中,有哪些地方是需要我们来配置和调整的

1.1 eureka server的请求压力


他是双机部署,然后eureka server两台机器互相之间都会进行注册,完成eureka server集群的一个识别和构造

之前有同学问过我,就是说eureka server在线上的生产环境是否要部署很多台机器,没什么太大的必要,eureka server的设计原则,是纯粹基于自己的内存来设计的,也就是不会比如说依赖数据库,或者是网络请求,所以eureka server纯内存操作,都是很快的

每一台eureka server的机器都可以承载很多的并发请求,一台普通的4核8G的机器,部署eureka server,每秒钟接收个几百的请求都是问题不大的

你觉得eureka server的压力大吗?不大,其实各个服务都是eureka client,eureka client其实每隔30秒来一次心跳的请求,所以这个压力其实一点儿都不大,比如说你有100个服务,每隔服务部署2台机器,就有200个服务实例,30秒有200个心跳请求,每秒呢?每秒大概也就8~10个心跳请求

你的一个系统做到200个服务实例的时候,已经算是比较不错的了

除非是你如果是一个大的公司,大型互联网公司,上百个系统,几万个服务实例,几十万个服务实例,30秒,有10万个心跳请求,每秒要3000多的请求了,所以此时eureka server的压力就会比较大了,要部署一个比较大的eureka server的集群了

大部分的小公司而言,其实无所谓,几百个服务实例,每秒钟eureka server承载的请求可能就10个请求,或者几十个请求,这个压力很小

在中小型公司而言,部署2台eureka server组成一个集群,妥妥的

1.2 服务注册的时效性


在spring cloud环境下来验证一下

之前给大家讲过,spring cloud环境下对eureka client的封装在spring-cloud-netflix-eureka-client工程里面,在这个工程里,就会自动初始化一个DiscoveryClient,而且还有一个额外的附加的一个类,EurekaServiceRegistry,这个额外的类是负责在服务一启动的时候,就立马来向eureka server发起一个注册的请求

我们从源码层面来验证一下,服务注册实际上是,只要这个服务一启动,就会立马向eureka server,服务注册中心发起一个注册的请求

服务注册的时效性应该是毫秒级的,起码在1秒以内

spring-cloud-netflix-eureka-server工程,实际上在启动的时候,就会完成eureka-server的初始化的流程,然后就让eureka core基于了jersey来提供了一些restful的http接口,供eureka client来访问和请求

eureka-core工程里,resources包下,有一个ApplicationResource,里面有个addInstance()方法,是负责接收服务注册的请求

接下来依次请求eureka server和serviceA,来从源码级别验证一下服务只要一启动,就会完成服务注册

eureka server其实本质上也是一个eureka client,所以eureka server启动的时候自己本身的eureka client会尝试向自己来进行注册,但是这边是不会注册的,所以说在eureka server启动的时候不会发生这个注册

serviceA启动的时候,代表了一个eureka client,就是一个服务,肯定会初始化一个DiscoveryClient,这个东西是必须有的,我之前给大家讲过

EurekaServiceRegistry : Registering application ServiceA with eureka with status UP

在服务启动的时候,通过spring cloud额外封装的一个类,立即发起一次注册的请求

直接会发送一个请求到eureka server的ApplictionResource中去,完成一个服务的注册

从源码层面可以判断出来,其实就是说spring cloud对eureka做了额外的封装,只要服务一旦启动,立马就会发出注册的请求,时效性基本在毫秒级

1.3 服务发现的时效性


服务刚启动的时候,发现其他所有服务的时效性

模拟一个场景,比如说先启动一个服务A的一台机器,启动服务B,服务B是依赖于服务A的,所以说我们通过源码来看一下服务B能否立马发现这个服务A已有的服务实例

服务B一启动的时候,会去抓取注册表,直接发送一个http请求到eureka server抓取已有的全量的注册表

[Application [name=SERVICEA, isDirty=true, instances=[com.netflix.appinfo.InstanceInfo@a7d576d6], shuffledInstances=null, instancesMap={localhost:ServiceA:8080=com.netflix.appinfo.InstanceInfo@a7d576d6}]]

服务刚启动的时候,立马发现其他所有服务的时效性是毫秒级,立马就会去发送一个请求,拿到所有的注册表

后面,如果说其他服务加了一台机器,此时这个服务要发现别的服务新增了一台机器,要过多久可以发现,服务A新增了一台机器,服务B要发现服务A新增了一台机器,要过多久才可以发现

eureka server的多级缓存的机制

比如说,现在服务A新增了一台机器,会更新到注册表中去,而且会立马过期掉原有的缓存,会立马过期掉这个ReadWriteCacheMap

很多人会天真的认为,服务A新增了一台机器,其他服务最多30秒之内一定会感知到服务A新增了一台机器,你如果考虑到eureka server内部的多级缓存的机制,你就会发现说,其实在极端情况下,服务A新增了一台机器,可能会要1分钟的时间,60s的时间,才能让其他服务感知到

你要从源码层面确定,每个服务拉取增量注册表的间隔,就是在初始化调度任务的代码那里看一下,默认的时间是多少,看看我们可以通过设置哪个参数来改变这个时间

int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();

默认情况下,就是30秒,spring-cloud-netflix-eureka-client工程里的EurekaClientConfigBean里面,定义了所有参数的默认值,如果你要设置,就是eureka.client作为前缀,然后加上变量的名字,就可以设置自己的数值

eureka-core工程,ResponseCacheImpl,在spring-cloud-netflix-eureka-server工程里的EurekaServerConfigBean,里面对所有的参数都进行了定义,默认情况下也是30秒

如果要修改,eureka.server.responseCacheUpdateIntervalMs = 30000

eureka服务发现是分钟级的时效性

这个事情是否是可以接受的,其实人家eureka如此设置这个参数,是有道理的,还是可以接受的,你如果给某个服务新增一台机器,其实是在扩容,你扩容的话,就不是说很着急很着急,必须1秒之内要让其他的机器感知到

如果说你给某个服务新增了一台机器,其他的服务过了最长1分钟才能感知到,这个事情,我们认为是可以接受的,ok的

1.4 服务心跳的间隔


任何一个服务启动之后都会定时发送心跳的通知,通知eureka server自己还是存活的状态

心跳的间隔是多长时间

在spring-cloud-netflix-eureka-client工程里,有一个EurekaInstanceConfigBean,这个里面就定义了默认的发送心跳的时间,就是30秒

eureka.client.leaseRenewalIntervalInSeconds = 30

1.5 服务故障的自动感知的时效性


比如说财务服务故障了,首先你要先考虑一点,就是说eureka server过了多久可以感知到财务服务故障了,然后将这个服务给他下线呢?

首先在eureka server中,是每隔60s去执行一次evict task,去判断一下当前所有的服务实例,是否有的服务实例出现了故障,一直没有发送心跳过来,是否要将故障的服务实例给他下线呢

eureka.server.evictionIntervalTimerInMs = 60 1000

按照这套evict task执行的机制,还有eureka本身的一个bug(90s
2),很可能在极端情况下,从一个服务宕机之后,到evict task发现和判定这个服务已经故障,可能最多要差不多4分钟,最少也得2分钟~3分钟

发现故障了以后,从服务注册表中摘除,然后过期掉readWriteCacheMap缓存

加上两个缓存map的同步,以及其他服务30s一次增量拉取的成本,很可能在极端情况下,服务B是要过了将近5分钟才能感知到服务A的某台机器故障,宕机了。即使不在极端情况下,其他服务要感知到某个服务实例的故障,起码也要三分钟~四分钟

我们一般都是按照极端情况来计算的,服务实例故障,自动感知的时效性,服务A某台机器宕机了,服务B感知服务A那台机器宕机了,时效性在5分钟以内,起码三四分钟

服务实例上线(新增机器)的感知的时效性在1分钟以内

引申出来的一个问题,如果说服务A某台机器宕机了,要3分钟以后服务B才能感知到,此时3分钟以内,服务A请求服务B的那台机器就会是失败的,此时怎么办呢?所以才要做超时设置(多少秒之内请求不成功就timeout),失败重试(服务A的一台机器挂了,你可以重试服务A的其他机器)

这个就是下面结合feign那块的参数设计要考虑的

我们为什么要先读源码,读了源码之后,深入的理解所有的细节,站在源码的深层次和角度来思考微服务架构的详细设计,考虑清楚里面每个细节点,以及在线上生产环境可能出现的问题,通盘思考,通盘设计

eureka.instance.leaseExpirationDurationInSeconds = 90

1.6 服务下线的感知的时效性


服务正常下线的话,会怎么样呢?这块的时效性,跟之前说的那个服务新增了一台机器,新增了一个服务实例,那么一般感知时效性在1分钟以内。如果服务正常下线,执行DiscoveryClient的shutown()方法,此时其他服务感知到这个服务实例下线,也是在1分钟以内。

1.7 eureka server的自我保护的稳定性


之前已经给大家分析的非常详细了,在eureka server源码的时候

在eureka的自我保护机制的代码里,大量的运用了hard code硬编码,惨痛一点,他默认你的心跳的时间间隔是30s,一分钟就2次心跳,也就是说你压根儿就不能去修改心跳的间隔,否则就会跟eureka自我保护机制的hard code硬编码的代码出现冲突

在eureka的自我保护代码里,充斥了大量的问题和bug,在尤其是测试环境下,你会发现经常就是动不动就进入自我保护的模式,说实话自我保护模式非常不稳定,完全不适合生产环境来使用

否则如果在生产环境中,动不动进入自我保护的模式,你平时服务故障了,他都不会去进行服务的故障感知和实例摘除,那这个事情就坑了

我之前做测试的时候,包括我们的一些同学做测试的时候,经常会发现每分钟期望的心跳次数是算错了,跟我们期望的是不符合的,计算每分钟的心跳次数,如果心跳次数<期望的心跳次数,就进入自我保护机制,不让evict task摘除任何服务实例

不要用,这么垃圾的代码,写的这么不靠谱的机制,不要用,在生产环境,测试环境都直接关了

eureka.server.enableSelfPreservation = false

1.8 eureka server集群的负载均衡


如果eureka server部署集群的时候,各个服务在注册或者是发送心跳,是如何请求eureka server集群的呢?你必须得通过源码来思考

其实服务,第一次注册的时候,走的就是8761那台机器

http://peer1:8761/eureka/apps/SERVICEA

后面又开始发送心跳,我们来看看他使用的是eureka server的哪台机器呢?走的地址,还是8761那台机器。等个30秒,下次发送心跳的时候,再看一下,走的是哪台机器呢?每次发送心跳,其实走的都是8761那台机器

http://peer1:8761/eureka/apps/SERVICEA/192.168.56.1:ServiceA:8080

到目前为止,我们可以做出第一个推断,也就是说,你的服务访问的是eureka server哪台机器,其实是通过你的application.yml配置的,先配置了8761那台机器,其实所有的服务都是优先就是走那台机器的

下面我们来做个试验,如果8761那台机器挂掉了,那么会怎么样呢?会重试几次尝试访问8761那台机器,但是如果还是不行的话,就会换成8762那台机器。而且后面主要都会请求8762那台机器了,每次发送心跳都会请求8762的机器。

如果后面8761那台机器又活过来了呢?也没什么用,其他所有的服务都是在访问8762那台机器了。除非说8762那台机器死掉了,那么其他的服务才会再次尝试8761那台机器。模拟8762已经死了,然后直接会重试8761那台机器,因为8761已经活了,所以就ok。这之后再发送心跳,走的都是8761了。

结论:你的每个服务里,会配置一个eureka server列表,谁配置在第一个,所有的服务优先就是访问那个eureka server。然后如果那台eureka server宕机了,那么此时所有的服务在重试过后都会访问其他的eureka server,而且此后大家都会去访问那台eureka server。

他优先选择哪个eureka server,如果失败了如何重试,重试之后如何选择其他的eureka server的代码在哪里呢?我跟大家说,eureka代码写的很烂,所以这块逻辑他给隐藏起来了,我们就不带着大家去找了,但是起码在两台eureka server的情况下,你通过部分源码的调试,加上做了一些实验,搞清楚了eureka server集群的时候,各个服务是如何请求和访问他们的。。。。。

1.9 eureka server集群同步的时效性


观察一下,源码调试一下集群同步的代码,接收注册请求的是8761的机器,8761的机器是将8762的机器作为自己的peer nodes,所以说你配置了多少台server机器,其中接收到请求的那条机器,就会将请求转发给其他所有的机器

将这个集群数据同步的任务,会放入一个acceptorQueue里面去,AcceptorRunner后台线程来处理,每隔10ms会执行一次业务逻辑

中间有一个大包的过程,他默认会将500ms内的请求,注册、心跳、下线,打成一个batch,再一次性将这个batch发送给其他的机器,减少网络通信的次数,减少网络通信的开销,集群同步的批量处理的机制

eureka server集群同步的时效性,基本上是在1s内,几百毫秒都是正常的

后面我们会专门针对每个特性的疑问和考量,构造一些demo和例子,然后站在源码的层面,去观察一下整个eureka这块各个机制的关键的度量,从源码的层面彻底理解和悟透,要做到心里有数

然后这些核心机制对应的关键参数,在生产环境中,如何设置,是否要设置,怎么设置

整个我给大家演示的这个过程,是完全按照我们在生产环境中做这个线上的项目,引入一个新的技术,完整的一个全流程,读源码,理解的很透彻,站在对源码的一个理解和思考的基础之上,完成架构设计的各种细节

2、服务调用


ribbon + feign,我们主要是面向feign来做,ribbon作为feign底层依赖的这么一套机制来搞的,但是我们把源码全部都读透了,都读过

2.1 ribbon + eureka服务发现与故障感知的时效性


这块非常简单的,首先我们先考虑一下,eureka client感知到其他服务上线了一个新的服务实例,1分钟以内,几十秒;eureka client感知到其他服务有个服务实例宕机了,大概是5分钟以内,三四分钟

比如说购物车服务要访问库存服务,刚开始库存服务就一台机器

后来某天,库存服务进行服务扩容,新增了1台机器,此时购物车服务本地的eureka client大概是1分钟才感知到人家新增了1台机器,ribbon的PollingServerListUpdater,刚好是30秒过后去刷新本地的eureka client的注册表到ribbon内部去

ribbon感知到库存服务新增1台机器,可能又过了30秒了,1.5分钟,1分30秒,如果比较快呢,1分钟组左右

目前库存服务有2台机器,其中1台机器宕机了,此时购物车服务本地的eureka client大概也需要三四分钟,最长是5分钟时间感知到,ribbon每隔30秒刷新eureka client的注册表到ribbon内部,极限情况下,ribbon感知到库存服务某台机器宕机了,可能需要5.5分钟,正常也需要个4分钟左右

2.2 ribbon的负载均衡算法


spring cloud环境下,ZoneAwareLoadBalancer,机房感知负载均衡器,比如说如果是多机房部署的话,比如在上海和北京,各部署了一部分机器在一个机房里,你一个系统,购物车服务有10台机器,库存服务有10台机器

在北京机房里,购物车服务放了5台机器,库存服务放了5台机器;在上海机房里,购物车服务也是5台机器,库存服务也是5台机器

然后ZoneAwareLoadBalancer,比如说北京机房里的购物车服务,现在要访问库存服务,是针对库存服务两个机房的10台机器去做负载均衡吗?优先是同机房访问,北京机房的机器,尽可能就是优先访问自己北京机房里的库存服务的5台机器

那么就是在北京机房的库存服务的5台机器中进行负载均衡

对于绝对多数的中小型公司来说,没有机房的概念,抛弃掉机房的事儿不考虑的话,那么他的负载均衡的算法,就是最最基础的round robin轮询算法,每次请求下一台机器,对一个机器列表,循环往复的请求

2.3 feign + ribbon的服务调用的超时与重试


我们细细的分析,spring cloud,从源码来出发,这种先研究源码,再设计架构的感觉,真的很爽的,因为你把握住了各种底层的东西,对很多深入的东西你都会考虑的很清楚,这种才叫做架构设计

比如库存服务有2台机器,其中一台机器故障宕机了,如果要让依赖库存服务的购物车服务感知到,可能需要几分钟的时间,三四分钟,五六分钟,都有可能

比如说在这几分钟的时间里面,ribbon内部的保存的库存服务的server list,还是2台机器,此时不断的请求过来,ribbon负载均衡算法还是会不断的将请求流量分发给库存服务已经宕机的那台机器

如果请求已经宕机的一台机器,一定是会有问题的,连接一定会连接不上去的,connect timeout异常和报错,如果说你不做任何处理的话,可能会在短时间内导致大量的请求都会失败

超时和重试的参数

ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
OkToRetryOnAllOperations: true
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 3

ConnectTimeout:连接一台机器的超时时间
ReadTimeout:向一台机器发起请求的时间

重点是通过实验来设置好MaxAutoRetries和MaxAutoRetriesNextServer这两个参数

来决定一下我们应该如何进行重试

我们知道下一次请求一定是8088那台机器的,所以此时我们手动将8088器给停掉,模拟宕机了

要请求8088,肯定是不行的,请求失败

第一次重试,还是8088
第二次重试,还是8088
第三次重试,还是8088
第四次重试,才是8080

ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
OkToRetryOnAllOperations: true
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 1

模拟8088故障

请求,8088,失败
第一次重试,还是8088
第二次重试,就是8080

3、服务网关


剧透一下,我们之前给大家写的一个大纲,其实是在高并发亿级流量的那个环节,我们其实会构造真正的高并发 + 各种统一处理的机制的复杂的网关系统

在spring cloud全家桶里面,初期,架构演进来的,千万不要想着,上来就把这个架构做的很复杂,很完美

在我们好多年前,刚开始实践,大数据,微服务,当时都没有网关的这么一个概念

刚开始,我们就是觉得很简单,单块的大系统太恶心了,5个人共同维护一个单块系统,还凑合,很勉强了。10个人维护一个单块系统,其实就很难受了,每个人都是开发里面不同的一块东西,有很多个需求并行的在做,一个单块,在git代码仓库,拉了很多版本同时在开发,测试、部署、调试、上线,各种冲突。

代码冲突,恶心,超过5个人共同维护一个大系统之后,各种代码冲突就很恶心了

RPC服务框架,拆,这个大系统就开始拆分,每个人维护一个服务,一般来说不会让两个人维护一个服务,每个人维护一个或者多个服务。

前后端分离,很早很早以前开始我们就是前后端分离了,我们就是对外提供一些http接口,我们后端系统都是采用的rpc调用,当时就是先用RPC服务框架进行系统拆分,都没有网关的概念,前端就是得自己记住有哪些那些那些服务

自己研发的rcp服务框架,前端也是支持的,前端可以通过rpc服务框架,通过rpc调用的模式,伪rpc,底层走的还是http请求,来调用后端各个服务的接口。前端还是要care很多个后端服务的。

PC端(前端),html5端,android/ios,等等

后端服务太多了,越来越多,一开始可能就10个服务,到了后面,服务越来越多,20个,30个,50个,100个服务

我们一开始都是自己提供了入口服务,接口服务,API服务,=> 网关系统

所有的前端、html5、android、ios,全部是访问一个API服务,API服务就是做各种后端服务的一个接口的调用、转发、接口合并、数据处理、打印日志

API服务(网关的前身),就是用他来做统一的服务接口调用的入口就可以了,统一限流、统一降级、流量分发、灰度发布、统一异常、统一日志

网关,最原始的一个作用,实际上就是对前端、android、ios屏蔽掉后台系统100+个服务,你只能对外暴露一些有业务含义的接口,人家就记住接口,就访问你一个网关服务就可以了。。。

zuul,定位,也可以这样子,就是做最最基础的请求的转发,降低和减轻前端的一个复杂度,前端只要知道接口就可以了,不要care这个服务,直接请求网关就ok了

3.1 ribbon预加载


第一次请求zuul的时候是很慢的,很容易超时,演示过很多次了

zuul:
ribbon:
eager-load:
enabled: true

3.2 zuul + ribbon + eureka感知服务上线和故障的时效性


zuul要将请求转发给购物车服务,购物车服务一开始就部署了1台机器,那么zuul就直接转发给那台机器就可以了。但是后面购物车服务又新增了1台机器,zuul要感知到购物车服务新增了1台机器,要过多久呢?

zuul 和 feign在这块是类似的,ribbon + eureka,eureka client感知到服务新实例上线,大概可能要个1分钟以内,ribbon那里,1.5分钟,1分钟左右,zuul也是一样的

如果说购物车服务现在有2台机器,但是不幸的是有1台机器宕机了,此时怎么办呢?eureka client大概可能需要5分钟以内的时间才能感知到,ribbon,5.5分钟,三四分钟,四五分钟。zuul的ribbon,感知到服务实例故障了,四五分钟

在某个服务实例宕机了,zuul还是不停的将请求转发过去会怎么样呢?肯定会失败,请求不了,此时zuul默认就是整合hystrix,hystrix会感知请求失败,异常了,直接会走hystrix的降级的逻辑,是什么都没有的,所以异常直接就会抛出来

zuul会有一个统一的error filter,会将这个异常给打印出来,反馈到调用方那里去

在默认情况下,某个服务实例突然宕机,zuul的整个行为,下一讲我们来做个试验在演示一下,我们就知道,我们现在担心的就是这种情况,某个服务实例宕机了,可能会有问题,配置一下ribbon的超时和重试

3.3 请求超时和重试


为什么配置了zuul的预加载ribbon之后,第一次请求还是会超时呢?各个服务自己本身还是在第一次请求的时候会去初始化ribbon,会造成请求超时。

基于ribbon + hystrix来做的,这里的超时的时间应该如何来设置呢?
请求失败的话,ribbon + hystrix配合的时候,这个请求重试的次数是如何处理的呢?

如果说某个服务实例突然挂了,zuul默认情况下是无法处理这种情况的,所以在线上生产环境下是很要命的,绝对不能让zuul直接这样子裸奔,不设置任何重试的机制直接就上了。如果某个服务实例挂了,打印error日志,给前端返回一段异常的json串:

{
“timestamp”: 1532067539032,
“status”: 500,
“error”: “Internal Server Error”,
“exception”: “com.netflix.zuul.exception.ZuulException”,
“message”: “GENERAL”
}

默认情况下,hystrix实际上虽然是用了,但是没有这个降级的策略,默认情况下,无论是被hystrix线程池拒绝(被限流)、请求超时(出现过几次请求超时的现象)、发生了异常(某个服务实例宕机了)

抛出异常,hystrix降级没有办法做任何处理,异常只能被打印出来,封装成一个json串法功给前端浏览器来显示,或者是将json串发送给调用zuul网关的android、ios、PC前端,抛出来的这个异常,统一都是ZuulException,其实具体是什么异常,他是不告诉你的,他只告诉你内部出现了异常和错误

不是特别好,起码针对服务实例可能会挂、或者是可能会偶尔出现网络调用超时(失败),设置个最基本的重试的策略

人zuul也是用的hystrix + ribbon那套东西,所以说,超时这里要考虑hystrix和ribbon的,而且hystrix的超时要考虑ribbon的重试次数和单次超时时间

ribbon的超时,hystrix的超时,zuul默认启用了hystrix,你要考虑到hystrix的超时

hystrix是包裹了ribbno的使用的,一般来说,hystrix的超时时长必须大于ribbon的超时时长,否则如果hystrix设置了超时是1s,ribbon设置的超时时长是2s,那么ribbon其实还没超时,hystrix直接就超时了

必须是hystrix超时时长最好是远大于ribbon超时时长,超时和重试尽量都是以ribbon为主

hystrix的超时时间计算公式如下:

(ribbon.ConnectTimeout + ribbon.ReadTimeout) (ribbon.MaxAutoRetries + 1) (ribbon.MaxAutoRetriesNextServer + 1)

feign和zuul基于ribbon进行重试的机制给搞混了,feign和zuul重试稍微有些是不太一样的


org.springframework.retry
spring-retry


zuul:
retryable: true

ribbon:
ReadTimeout:1000
ConnectTimeout:1000
MaxAutoRetries:1
MaxAutoRetriesNextServer:1

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000

如果不配置ribbon的超时时间,默认的hystrix超时时间是4000ms(4s)

(1 + 1) (1 + 1) (1 + 1) = 8s

直接如果你断点调试的话,直接就给你判定为hystrix层面就超时了,hystrix timeout异常,no fallback

正常情况下是用的:RibbonLoadBalancingHttpClient
但是如果将zuul.retrayable设置为true之后:RetryableRibbonLoadBalancingHttpClient

如果是之前,没有设置zuul的重试,retryableClient是false,但是现在设置了重试之后,这个变量就是true了

请求,9099机器,失败
第一次重试,9099机器,失败
第二次重试,9090机器,成功

zuul的重试机制就成功了

否则会很坑的,因为spring cloud eureka这一块来做服务发现,服务实例故障的感知达到了几分钟的级别,这是比较差的,必须配合上超时 + 重试的机制。如果某个服务实例挂了,feign / zuul在1s之内连接不上去(超时)直接timeout,然后对故障机器重试一次,不行,就重试其他的机器

就可以保证,如果比如说你在运维某个服务,购物车服务其中的某台机器,有点问题,或者是在做一些操作,停机了,在几分钟之内,zuul / feign是感知不到的,如果你不做超时 + 重试的机制,在几分钟之内,大量的请求是失败的

但是如果你配置了超时 + 重试的参数,可以保证在上述情况下,在几分钟内,请求还是可以成功的,因为他会自动进行重试