程序 Hot reload config 的实现方式

有时候一些进程必可避免的有重启代价太大的问题。比如有一些长连接的进程,重启会断开连接,然后所有的客户端都需要重连,或者进程的内存中已经缓存了很多内容,如果启动的话缓存就凉了,要重新预热。

但是又有一些配置,我们希望修改进程的配置,而不需要重启进程。

在 像设计 UI 一样去设计配置项 中聊过有一种方式是做一个配置中心,然后如果有配置修改的时候,就推送一个请求给应用程序,这样程序那边收到配置的更新的时候做一个 callback 来执行配置的变更。

这个方案很好,但是依赖一些其他的组件,中心化的配置中心,以及程序内需要有 SDK 依赖去接收和解析配置更新的内容。

本来讨论不依赖其他组件的实现方式,即更新文件,不需要重启进程来让文件生效。

File Watch

这是一种比较自然想到的实现方式,也是最糟糕的方式。

它的原理是,进程在启动之后,就开一个线程负责 watch 这个文件所有的变化,一旦发现变化,就执行配置更新的 callback。

这种实现糟糕的地方在于:

  1. 不同系统的文件变更的 API 是不同的,比如 Linux 的 inotify,Mac/BSD 的 kqueue,和 Windows 的 ReadDirectoryChangesW. 为了兼容这些不同系统的 API,一般需要引入一个专业的 SDK 来封装,比如 watchdog
  2. 占用系统的 thread,虽然这个也有可能做成异步的
  3. 占用系统的 fd,这个看起来不严重,但是有的时候在系统的 fd 用完了的时候,我们想要用 hotreload 去修改一个参数来控制程序的行为,就麻烦了;

好处在于 import 一个 SDK 之后,不需要写很多代码就可以实现。

每一次都重新 load

这种方法就是,每次在使用配置项的时候,不使用全局变量,而每次都去解析配置文件。这样也就不存在说要 hot reload 的问题了,因为每一次使用都是一次 reload,所有用到配置的情况,都是最新的。

很多人乍一看觉得这种实现非常蠢,但是你仔细想一下,很多需要 hot reload 的配置的都不是读写很高的。

比如 lobbyboy 作为一个个人用的 ssh server,每次在有连接进来的时候,花几 ms 去 load 一下最新的 ssh key 配置,是完全可以接受的。

最近在写的 prometheus-http-sd 里面的加载 script 和文件的时候,也是对于每一个请求都是即时 load 的,即时这样,也能在 5ms 内完成请求。

这样节省了非常大的工作量:

  1. 不需要写更新配置的 callback 了,直接复用 load 的代码
  2. 不需要引入额外的依赖了
  3. 非常简单,每个人都能理解

这两种方式有一个问题,就是一旦文件发生更改,就立即生效了,没有时间让你去验证文件对不对,而且这是一个单向的 fire and forget 的操作,操作的时候不知道自己的动作对不对,必须要通过旁路去检查,比如看一下日志或者监控。虽然可以有其他 work around,比如先放到其他 location 验证。

SIGHUP

这是最好的方案。程序注册一个 signal handler,在收到 SIGHUP 信号的时候,reload config。

这样的好处是:

  1. reload 是明确的操作人的意图,而不是单纯文件修改了就 reload,语义没有歧义
  2. 实现成本很小

而且,我们一般可以控制 reload 的行为。比如说使用 systemctl reload service 的时候,我们可以自定义 reload 命令:

  1. 先检查配置文件是否合法,如果不合法,放弃 reload;
  2. 给程序发送 reload 信号;

以下是一个例子,在 systemd 的 unit 文件里面可以定义 ExecReload,即定义执行 systemctl reload serivce 的时候所做的事情。

这里有两个技巧:

  1. Systemd 里面会给你一个 $MAINPID 让你用,即主进程的 PID,直接往这里发送信号即可;
  2. 使用 ; 可以定义多个命令,一个启动成功才会执行下一个。要注意这不是 shell 的 ;

这样,我们在 reload 的时候,如果第一个检查不通过,systemctl reload 的 return code 就不是0,我们就知道 reload 失败了。

在操作的时候,我们可以直接在 Ansible 的操作结果中看到动作出错,而不是需要去 ssh 到机器上,查看执行结果是否正确。



程序 Hot reload config 的实现方式”已经有3条评论

  1. > 好处在于 import 一个 SDK 之后,不需要写很多代码就可以事先。
    Typo: 事先应为实现。

    发信号重载,也可以分为两种软重载和硬重载,软重载只加载新的无破坏性的配置,比如键位映射,这种可以动态绑定或释放的;硬重载就类似于重启应用再加载配置,挺多软件的关键配置的更改都要求重启软件再生效。

    • 谢谢,已经改正。

      是的,其实大部分情况下没有必要将所有的配置都做成 hot reload 的。有一些不好处理。一般都是只让有需要的部分配置在收到 signal 之后执行重载。

Leave a comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注