有时候一些进程必可避免的有重启代价太大的问题。比如有一些长连接的进程,重启会断开连接,然后所有的客户端都需要重连,或者进程的内存中已经缓存了很多内容,如果启动的话缓存就凉了,要重新预热。
但是又有一些配置,我们希望修改进程的配置,而不需要重启进程。
在 像设计 UI 一样去设计配置项 中聊过有一种方式是做一个配置中心,然后如果有配置修改的时候,就推送一个请求给应用程序,这样程序那边收到配置的更新的时候做一个 callback 来执行配置的变更。
这个方案很好,但是依赖一些其他的组件,中心化的配置中心,以及程序内需要有 SDK 依赖去接收和解析配置更新的内容。
本来讨论不依赖其他组件的实现方式,即更新文件,不需要重启进程来让文件生效。
File Watch
这是一种比较自然想到的实现方式,也是最糟糕的方式。
它的原理是,进程在启动之后,就开一个线程负责 watch 这个文件所有的变化,一旦发现变化,就执行配置更新的 callback。
这种实现糟糕的地方在于:
- 不同系统的文件变更的 API 是不同的,比如 Linux 的 inotify,Mac/BSD 的 kqueue,和 Windows 的 ReadDirectoryChangesW. 为了兼容这些不同系统的 API,一般需要引入一个专业的 SDK 来封装,比如 watchdog;
- 占用系统的 thread,虽然这个也有可能做成异步的;
- 占用系统的 fd,这个看起来不严重,但是有的时候在系统的 fd 用完了的时候,我们想要用 hotreload 去修改一个参数来控制程序的行为,就麻烦了;
好处在于 import 一个 SDK 之后,不需要写很多代码就可以实现。
每一次都重新 load
这种方法就是,每次在使用配置项的时候,不使用全局变量,而每次都去解析配置文件。这样也就不存在说要 hot reload 的问题了,因为每一次使用都是一次 reload,所有用到配置的情况,都是最新的。
很多人乍一看觉得这种实现非常蠢,但是你仔细想一下,很多需要 hot reload 的配置的都不是读写很高的。
比如 lobbyboy 作为一个个人用的 ssh server,每次在有连接进来的时候,花几 ms 去 load 一下最新的 ssh key 配置,是完全可以接受的。
最近在写的 prometheus-http-sd 里面的加载 script 和文件的时候,也是对于每一个请求都是即时 load 的,即时这样,也能在 5ms 内完成请求。
这样节省了非常大的工作量:
- 不需要写更新配置的 callback 了,直接复用 load 的代码
- 不需要引入额外的依赖了
- 非常简单,每个人都能理解
这两种方式有一个问题,就是一旦文件发生更改,就立即生效了,没有时间让你去验证文件对不对,而且这是一个单向的 fire and forget 的操作,操作的时候不知道自己的动作对不对,必须要通过旁路去检查,比如看一下日志或者监控。虽然可以有其他 work around,比如先放到其他 location 验证。
SIGHUP
这是最好的方案。程序注册一个 signal handler,在收到 SIGHUP 信号的时候,reload config。
这样的好处是:
- reload 是明确的操作人的意图,而不是单纯文件修改了就 reload,语义没有歧义
- 实现成本很小
而且,我们一般可以控制 reload 的行为。比如说使用 systemctl reload service
的时候,我们可以自定义 reload 命令:
- 先检查配置文件是否合法,如果不合法,放弃 reload;
- 给程序发送 reload 信号;
以下是一个例子,在 systemd 的 unit 文件里面可以定义 ExecReload
,即定义执行 systemctl reload serivce
的时候所做的事情。
1 |
ExecReload=/usr/local/bin/promtool check config /etc/prom-conf.yaml ; /bin/kill -HUP $MAINPID |
这里有两个技巧:
- Systemd 里面会给你一个
$MAINPID
让你用,即主进程的 PID,直接往这里发送信号即可; - 使用
;
可以定义多个命令,一个启动成功才会执行下一个。要注意这不是 shell 的;
;
这样,我们在 reload 的时候,如果第一个检查不通过,systemctl reload
的 return code 就不是0,我们就知道 reload 失败了。
在操作的时候,我们可以直接在 Ansible 的操作结果中看到动作出错,而不是需要去 ssh 到机器上,查看执行结果是否正确。
> 好处在于 import 一个 SDK 之后,不需要写很多代码就可以事先。
Typo: 事先应为实现。
发信号重载,也可以分为两种软重载和硬重载,软重载只加载新的无破坏性的配置,比如键位映射,这种可以动态绑定或释放的;硬重载就类似于重启应用再加载配置,挺多软件的关键配置的更改都要求重启软件再生效。
谢谢,已经改正。
是的,其实大部分情况下没有必要将所有的配置都做成 hot reload 的。有一些不好处理。一般都是只让有需要的部分配置在收到 signal 之后执行重载。
实用,感谢分享!