服务如何实现优雅重启
平滑重启主要考虑两点:
-
如何做到不中断接收新请求
-
老的请求不能被中断
实现思路
1、老进程在启动时,启动tcp listen监听,就是开启对外服务的端口,如果有请求过来,开启一个协程处理这个请求。
2、老进程启动一个unix Socket监听,通过switch.sock和新进程进行通信,一旦由请求过来,就把自己的listener文件描述符发给新进程,然后通过发送信号触发迁移流程,同时等待一段时间关闭自己的listener,进程退出。
3、新进程在启动前,会尝试连接旧进程的switch.sock,如果通信成功,就知道它需要开始接收旧的listener 和 connection了,此时会分别启动listen.sock监听和conn.sock监听,来获取旧进程的listener信息和conn信息。如果收到了listener信息,则会用这个listener启动服务。
4、接步骤1,协程在处理请求的过程中,如果收到了迁移信号,则会判断是否将现有的客户端连接发给新进程,如果连接里还有可读的数据,则需要迁移。
到此整个迁移流程就完毕了。
测试
代码在 https://gitee.com/zqwlai/proxy,服务端在处理TCP请求时,是以“#”为分隔符来处理的,会返回“进程标志 reply:截断部分#”。如果客户端没有发送“#”,则连接会一直保持(阻塞),这样可以模拟TCP的长连接,方便连接迁移的测试。
先启动服务:
go run main.go -l :8081 -p hello #-p表示进程标识符
客户端采用telnet的方式与服务端通信
可以看到当发送“123#456”时,会返回“hello reply:123#”,同时查看连接状态:
$ netstat -an |grep 8081
tcp 0 0 127.0.0.1:38514 127.0.0.1:8081 ESTABLISHED
tcp6 0 0 :::8081 :::* LISTEN
tcp6 0 0 127.0.0.1:8081 127.0.0.1:38514 ESTABLISHED
然后再次启动服务端(也可以用fork子进程的方式来实现,需要代码做下改动):
go run main.go -l :8081 -p BBB
再次发送一个“#”,可以看到结果能被正确返回