有状态服务的滚动更新
文章目录
【注意】最后更新于 December 20, 2021,文中内容可能已过时,请谨慎使用。
前言
作为后端我们时常遇到服务的更新,而作为 http
服务的后端则一般是不用考虑这件事的,因为 http
服务的必须是无状态的,只需要在服务前加一个负载均衡就可以做到轻松的滚动更新,让用户无感知更新。但是我现在的工作的服务上包含的有状态的情况,但是更新又是必须的。
一、简单的构架介绍
|
|
这里不具体介绍我公司的游戏服务框架,用一个简单的模型来描述一下,api
和 game
都是需要支持多实例部署的,api
是连接客户端的实例,然后进入某个匹配房间后 api
会和 game
通信以对房间进行操作 game
则使用 mq
的订阅方式通知所有在对应房间的客户端进行推送消息,这里主要的问题在于用户进入房间后是有状态的,api
和 game
服务里都有着对应的 room
信息和游戏过程信息,这些都不好切换到 redis
这种地方去。
二、方案和思考
滚动更新需要的是:
- 服务必须是支持多实例运行的(用于新旧实例共存)。
- 服务健康检查是必须有的。
- 负载均衡必须支持滚动更新的流量切换。
2.1 去状态
迁移服务里的状态到 redis
这种地方,类似于 http
的 session
方案,但是由于切换到 redis
之前的设计是在内存中快速访问,增加了故障点,并且也会增加用户操作时的对 redis
的操作,而且内存与 redis
的同步也很麻烦,而且需要修改大量的逻辑。
2.2 等待到状态结束
即让用户把这次游戏结束,再退出服务,这种方案有好处就是不用处理任何状态转移,对原有逻辑几乎没有什么改动,但是又回引入其他问题,那就是什么时候才能退出该服务,而且也不能无限等待,这里我选择这种方式。
三、实现方式
3.1 去状态
这个去状态实际上只是将一部分的复杂性转移到了另一个系统上,获取倒是很简单和 http
的 session
一样的做法就行了,但是更新会很麻烦需要入侵到所有操作状态的地方,但是也就这么多了。
3.2 等待到状态结束
- 需要捕捉退出信号
SIGTERM
,SIGINT
。 - 拒绝所有新连接,防止有负载均衡的流量跑到这个服务节点上。
- 并手动处理各个连接的退出和等待状态已经不需要时断开。
- 由于保持状态是两个服务,所以
game
服也要做相同的处理,并通知客户端。 - 由于这里是通过信号后等待所以需要让滚动更新管理器去等待。
- 例如
k8s
需要设置spec.template.spec.terminationGracePeriodSeconds
为足够的长否则会直接发送SIGKILL
信号。