Spring Cloud 基本组件总结|牛气冲天新年征文

1,873 阅读8分钟

生产环境使用

一、eureka

1、服务注册的时效性

在服务启动的时候, 会通过 EurekaServiceRegistry 立即进行注册, 到 ApplicationResource 中, 完成注册. 只要这个服务一旦启动, 就会立即发起注册, 可以达到毫秒级的时效性

2、服务发现的时效性

服务在一启动的时候, 就会去抓取注册表, 直接发送一个 http 请求, 从 eureka server 中抓取到全量的注册表信息, 所以, 这个也可以说是 毫秒级别的 (刚启动时的全量抓取) . 当服务新增机器之后,会更新信息,并将 读写缓存直接失效掉,但是由于 eureka server 的多级缓存机制,导致只是失效了 读写缓存,只读缓存中的数据并没有发生更新,在 eureka server 内部,有一个定时调度任务,每30秒进行一次 读写缓存和只读缓存的信息同步,对于 eureka client 而言,内部也是有一个定时调度任务,每隔30s 从 eureka server 中获取注册表信息,因此,这么看的话,只有在 eureka server 内部将只读缓存和读写缓存数据进行同步之后,下一次 eureka client 进行增量注册表同步的时候,才可以更新的数据,这个时效性是分钟级别,差不多一分钟的时候,才能够获取到。 这边注册表抓取的调度任务默认都是 30s 执行一次, 可以通过 eureka.client.registryFetchIntervalSeconds 参数来调整, 在 EurekaClientConfigBean 里面, 定义了所有参数的默认值, 它会读取以 eureka.client 为前缀的参数, 加上变量的名字, 如果我们要设置的话, 也是这么设置参数, 就和前面说的那个一样. 另外一个就是 eureka server 内部默认 30s 同步一次读写缓存和只读缓存的任务, 在 EurekaServiceConfigBean 对参数进行了定义, 如果要修改的话, eureka.server.responseCacheUpdateIntervalMs , 修改同步时间

QQ20200908-0.png

3、服务心跳的间隔

任何一个服务在启动之后, 都会有一个调度任务, 默认每 30 秒调度一次, 会发送一次心跳, 可以通过参数 eureka.client.leaseRenewalInSeconds 参数进行控制, 在 EurekaInstanceConfigBean 类中

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

在 eureka server 中, 会每隔 60 秒去执行下调度任务, 去判断一下当前所有的服务实例, 是否出现了故障 (没有心跳) , 通过参数 eureka.server.evictionIntervalTimerInMs 进行控制, 主要判断逻辑就是看最后一次心跳的时间, 和现在时间做个对比, 还有补偿时间 (比如因为 gc 导致没有执行的情况) , 目前是 180s , 是 eureka 的一个 bug . 如果是过期的话, 会进行清除, 默认是清除掉 15% 的数据, 如果低于 15%的话, 会全部都清掉. 清掉之后, 会过期掉 读写缓存 (ReadWriteCacheMap) 这个在极端情况下, 可能会到达四分钟才能判断这个服务故障, 最少得是 2 ~ 3 分钟, 时效性也是分钟级别, 在加上两个缓存 map , 服务的增量同步注册表, 别的服务可能将近五分钟才能感受到那个服务宕机了 (极端) , 普通的话, 也差不多要 3~4 分钟左右. 一般都按照极端情况进行估计. 所以, 在这里就可以引申出来, 服务的超时, 重试环节. (因为要感知到服务已经失效了, 是比较慢的, 再次期间还是会调用已经坏了的服务, 所以要有超时和重试等机制)

5、服务下线感知的时效性

当服务下线的时候, 调用 shutdown , 删除注册表中的数据, 将自己加入到 recentChangeQueue 中, 将 读写缓存过期掉, eureka server 每 30s 同步一下 读写缓存和只读缓存, eureka client 每 30 秒执行一下增量同步, 这个感知的时效性, 也是一分钟左 .

6、eureka server 的自我保护的稳定性

公式:eureka client 的数量 * (60s / 心跳间隔) * 0.85 。 计算出来一个期望的每分钟心跳次数, 然后有一个调度任务, 每一分钟执行一次, 通过对比这一分钟收到的心跳个数和期望的心跳次数进行对比, 如果小于期望的心跳个数, 那么这时候就会进入自我保护模式, 不会摘除任何服务实例. 且这个自我保护模式十分的不稳定, 完全不适合生产环境来使用, 通过参数 eureka.server.enableSelfPreservation 给关闭掉, 默认是开启的 (true) .

7、eureka server 的负载均衡

当服务启动的时候, 会向集群进行注册和发送心跳, 一般来说, 在 application.yml 配置了 eureka 集群的地址, 那台机器配置在前面, 就会先走哪台机器, 如果前面的机器挂掉了, 会重试几次, 尝试访问 8761 , 如果还是不行, 就会走后面的机器, 8762, 后续也都会走 8762 , 这时 8761 活过来的话, 也不会走, 除非 8762 挂掉了. 结论:在每个服务里,都会配置一个 eureka server 的列表,谁配置在第一个,就会优先访问那个服务,后续也会一直访问这个,除非这个服务宕机了,会在重试之后,走它后面的服务地址,并且后续都会走这个地址。

8、eureka server 集群同步的时效性

我们在向集群注册 (心跳) 的时候, 请求的是一台服务的地址, 由这台服务向集群中其他的服务进行同步, 接收到请求的机器, 就会讲请求转发给其他所有的机器, 就是一个 for 循环 (去除自身之后的) , 会将集群任务放入到 queue 中, 由对应的 runner 后台线程进行处理, 每隔 10ms 处理一次, 会有线程每 500 ms 将请求, 打成一个 batch , 每个 batch 最多有 250 个 task , 然后一次性, 将 batch 发给其他服务, 减少网络开销. eureka server 集群同步的实效性, 大约在几百毫秒, 一秒以内.

二、服务调用

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

ribbon 会有一个调度任务, 每 30s, 从 eureka client 中, 拉取注册表信息, eureka client 自身获取到新的服务, 大约是 1 分钟左右, 那么 ribbon 获取到新的服务的信息, 大约就是 1 分半的样子, 服务故障的时候同理, eureka client 极限可能四分钟才能够获取到最新的数据, 那么加上 ribbon 的话, 在四分半左右.

2、负载均衡算法

ribbon 默认用的负载均衡算法就是 轮询 , 由 ZoneAwareLoadBalancer 进行负责, 这个类主要可以进行 机房 分类, 比如北京就是北京的, 上海就是上海的.

for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }

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

配置相应的超时和重试机制

ribbon:
	ConnectTimeout: 1000				# 链接超时时间
  ReadTimeout: 1000						# 像一台机器发起请求的时间
  OkToRetryOnAllOperations: true # 所有的请求都要进行重试
  MaxAutoRetries: 1						# 每台机器请求一次,重试一次
  MaxAutoRetriesNextServer: 3 # 可以重试三次一台机器(故障机器重试三次,再去重试其他的机器)

可是如果我们设置成这样的话, 我们现在有两台机器 , 8080, 8088 , 我们把 8088 停掉, 这次请求要请求 8088 了, 可以有这样的结果 第一次: 8088 第二次: 8088 第三次: 8088 第四次: 8080

也就是说,MaxAutoRetriesNextServer 指的是,将故障机器重试三次 , 所以我们设置的时候,应该设置为1,让他重试一次就可以了。 **

三、网关

1、通过 Ribbon 预加载,解决第一次超时问题

zuul 在第一次访问的时候, 是要加载 ribbon 从 eureka client 上拉取注册表的, 这个过程会比较慢, 容易造成超时, 我们可以配置相关参数, 让 ribbon 进行预加载, 防止出现加载 ribbon 超时的情况发生

zuul:
	ribbon: 
  	eager-load:
    	enabled: true

但就算配置了, 也还是会出现超时的情况, 因为各个服务自己本身还是在第一次请求的时候去初始化 ribbon , 会造成请求超时

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

zuul 和 feign 是类型的, 都是通过 ribbon + eureka , eureka client 感知服务上线, 大约一分钟左右, 到 ribbon 也是一分钟, 一分半左右, zuul 也一样. 如果购物车服务有两台机器, 有一个宕机了, eureka client 大约五分钟以内才能感知到, ribbon 大约就是 5.5 分钟以内, zuul 也类似. 负载均衡的话, 默认也是轮询. 在某个服务宕机了, zuul 还是不停的请求, zuul 默认是整合 hystrix 的, 这时就会走降级逻辑, 但默认是什么都没有的, 就会将异常抛出, 由 errorfilter 打印出来, 返回给调用方.

3、超时

这里的超时分为 ribbon 的超时和 hystrix 的超时, 因为 zuul 默认启用了 hystrix , hystrix 又包裹了 ribbon 进行使用, 所以一般来说, hystrix 的超时时长必须大于 ribbon 的超时时间, 要不逻辑上就不合理. hystrix 超时计算公示: (ribbon.connectTimeOut + ribbon.ReadTimeOut) * (ribbon.MaxAutoRetires + 1) * (ribbon.MaxAutoRetriesNextServer + 1) 如果没有配置 ribbon 的超时时间,默认的 hystrix 超时时间是 4000 ms

zuul:
	ribbon: 
  	eager-load:
    	enabled: true
  retryable: trues
ribbon:   ### 超时重试
	ReadTimeOut: 100
  ConnectTimeOut: 500
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 1