SpringCloud 服务的平滑上下线

Easter79
• 阅读 391

吐槽

以前都是手撸 RPC,最近接触 SpringCloud,深感痛心。主要有以下几点:
1)代码量巨大,找 BUG 时间长,超级复杂的设计
2)版本管理混乱,经常出现莫名其妙的配置错误(所以 2.0 是打死不敢上生产啊)
3)Netflix 公司的有些代码,实在是让人费解,根本就不考虑扩展性
4)生态链庞大,学习成本大

建议准备上微服务的同学,固定下一个版本,不要随意更新或降级。拿 tomcat 的 basedir来说, 1.5.8到 1.5.13到 1.5.16版本是换来换去,不小心点会出事故的。

  1. server:

  2. port: 21004

  3. context-path: /

  4. tomcat:

  5. basedir: file:.

如上, basedir先是从 .换到 file:.,又从 file:.换成 .,连兼容代码都木有。有木有想打死工程师?

前言

今天主要谈的话题,是 平滑的上下线功能。所谓平滑,指的是发版无感知,不至于等到夜深人静的时候偷偷去搞。某些请求时间可以长点,但不能失败,尤其是对支付来说,想花钱花不出去是很让人苦恼的;花了钱买不到东西是很让人恼火的。整体来说,SpringCloud 功能齐全,经过一段时间的踩坑后使用起来还是非常舒服的。

我们的微服务,大体集成了以下内容。

SpringCloud 服务的平滑上下线

嗯,一个庞大的生态

问题

那么问题来了,SpringCloud 到注册中心的注册是通过 Rest接口调用的。它不能像 ZooKeeper那样,有问题节点反馈及时生效。也不能像 Redis那么快的去轮训,太娇贵怕轮坏了。如下图:

SpringCloud 服务的平滑上下线

有三个要求:

1)ServiceA 下线一台实例后,Zuul 网关的调用不能失败
2)ServiceB 下线一台实例后,ServiceA 的 Feign 调用不能失败
3)服务上线下线,Eureka 服务能够快速感知

说白了就一件事,怎样尽量缩短服务下线后 Zuul 和其他被依赖服务的发现时间,并在这段时间内保证请求不失败。

解决时间问题

影响因子

1) Eureka 的两层缓存问题 (这是什么鬼

 

EurekaServer 默认有两个缓存,一个是 ReadWriteMap,另一个是 ReadOnlyMap。有服务提供者注册服务或者维持心跳时时,会修改 ReadWriteMap。当有服务调用者查询服务实例列表时,默认会从 ReadOnlyMap 读取(这个在原生 Eureka 可以配置,SpringCloud Eureka 中不能配置,一定会启用 ReadOnlyMap 读取),这样可以减少 ReadWriteMap 读写锁的争用,增大吞吐量。EurekaServer 定时把数据从 ReadWriteMap 更新到 ReadOnlyMap 中

2) 心跳时间

 

服务提供者注册服务后,会定时心跳。这个根据服务提供者的 Eureka 配置中的服务刷新时间决定。还有个配置是服务过期时间,这个配置在服务提供者配置但是在 EurekaServer 使用了,但是默认配置 EurekaServer 不会启用这个字段。需要配置好 EurekaServer 的扫描失效时间,才会启用 EurekaServer 的主动失效机制。在这个机制启用下:每个服务提供者会发送自己服务过期时间上去,EurekaServer 会定时检查每个服务过期时间和上次心跳时间,如果在过期时间内没有收到过任何一次心跳,同时没有处于保护模式下,则会将这个实例从 ReadWriteMap 中去掉

3)调用者服务从 Eureka 拉列表的轮训间隔

4) Ribbon 缓存

解决方式

1) 禁用 Eureka 的 ReadOnlyMap 缓存 (Eureka 端)

  1. eureka.server.use-read-only-response-cache: false

2) 启用主动失效,并且每次主动失效检测间隔为 3s (Eureka 端)

  1. eureka.server.eviction-interval-timer-in-ms: 3000

像 eureka.server.responseCacheUpdateInvervalMs和 eureka.server.responseCacheAutoExpirationInSeconds在启用了主动失效后其实没什么用了。默认的 180s 真够把人给急疯的。

3) 服务过期时间 (服务提供方)

  1. eureka.instance.lease-expiration-duration-in-seconds: 15

 

超过这个时间没有接收到心跳 EurekaServer 就会将这个实例剔除。EurekaServer 一定要设置 eureka.server.eviction-interval-timer-in-ms 否则这个配置无效,这个配置一般为服务刷新时间配置的三倍。默认 90s!

4) 服务刷新时间配置,每隔这个时间会主动心跳一次 (服务提供方)

  1. eureka.instance.lease-renewal-interval-in-seconds: 5

默认 30s

5) 拉服务列表时间间隔 (客户端)

  1. eureka.client.registryFetchIntervalSeconds: 5

默认 30s

6) ribbon 刷新时间 (客户端)

  1. ribbon.ServerListRefreshInterval: 5000

ribbon 竟然也有缓存,默认 30s

这些超时时间相互影响,竟然三个地方都需要配置,一不小心就会出现服务不下线,服务不上线的囧境。不得不说 SpringCloud 的这套默认参数简直就是在搞笑。

重试

那么一台服务器下线,最长的不可用时间是多少呢?(即请求会落到下线的服务器上,请求失败)。赶的巧的话,这个基本时间就是 eureka.client.registryFetchIntervalSeconds+ribbon.ServerListRefreshInterval, 大约是 8秒的时间。如果算上服务端主动失效的时间,这个时间会增加到 11秒

如果你只有两个实例,极端情况下服务上线的发现时间也需要 11 秒,那就是 22 秒的时间。

理想情况下,在这 11 秒之间,请求是失败的。假如你的 QPS 是 1000,部署了四个节点,那么在 11 秒中失败的请求数量会是 1000 / 4 * 11 = 2750,这是不可接受的。所以我们要引入重试机制。

SpringCloud 引入重试还是比较简单的。但不是配置一下就可以的,既然用了重试,那么就还需要控制超时。可以按照以下的步骤:

  1. 引入 pom (千万别忘了哦)

    1. <dependency>

    2. <groupId>org.springframework.retry</groupId>

    3. <artifactId>spring-retry</artifactId>

    4. </dependency>

  2. 加入配置

    1. ribbon.OkToRetryOnAllOperations:true

    2. #(是否所有操作都重试,若false则仅get请求重试)

    3. ribbon.MaxAutoRetriesNextServer:3

    4. #(重试负载均衡其他实例最大重试次数,不含首次实例)

    5. ribbon.MaxAutoRetries:1

    6. #(同一实例最大重试次数,不含首次调用)

    7. ribbon.ReadTimeout:30000

    8. ribbon.ConnectTimeout:3000

    9. ribbon.retryableStatusCodes:404,500,503

    10. #(那些状态进行重试)

    11. spring.cloud.loadbalancer.retry.enable:true

    12. # (重试开关)

发布系统

OK, 机制已经解释清楚,但是实践起来还是很繁杂的,让人焦躁。比如有一个服务有两个实例,我要一台一台的去发布,在发布第二台之前,起码要等上 11 秒。如果手速太快,那就是灾难。所以一个配套的发布系统是必要的。

首先可以通过 rest 请求去请求 Eureka,主动去隔离一台实例,多了这一步,可以减少至少 3 秒服务不可用的时间(还是比较划算的)。

然后通过打包工具打包,推包。依次上线替换。

市面上没有这样的持续集成工具,那么发布系统就需要定制,这也是一部分工作量。

到此,仅仅是解决了 SpringCloud 微服务平滑上下线的功能,至于灰度,又是另外一个话题了。有条件的公司选择自研还是很明智的,不至于将功能拉低到如此的水平。

不过大体不用担心,你的公司能不能活下去,还是一个未知数。Netflix 都忍了,在座的各位能比它强大么?

SpringCloud的存在是视觉污染,而你我的存在是星光点点。

可我的钱包,还是空空的。

要不要帮帮忙?

本文分享自微信公众号 - 小姐姐味道(xjjdog)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Java修道之路,问鼎巅峰,我辈代码修仙法力齐天
<center<fontcolor00FF7Fsize5face"黑体"代码尽头谁为峰,一见秃头道成空。</font<center<fontcolor00FF00size5face"黑体"编程修真路破折,一步一劫渡飞升。</font众所周知,编程修真有八大境界:1.Javase练气筑基2.数据库结丹3.web前端元婴4.Jav
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
京东云开发者 京东云开发者
6个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k