From d92b0f911cf469cd6f5e53b378d15eb8b1e98a0d Mon Sep 17 00:00:00 2001 From: eltrac Date: Thu, 29 Oct 2026 13:42:53 +0800 Subject: [draft] new --- content/article/functional-shutdown.md | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 content/article/functional-shutdown.md (limited to 'content/article') diff --git a/content/article/functional-shutdown.md b/content/article/functional-shutdown.md new file mode 100644 index 00000000..7b51e2b5 --- /dev/null +++ b/content/article/functional-shutdown.md @@ -0,0 +1,43 @@ ++++ +title = '如何优雅地优雅退出' +slug = 'functional-shutdown' +tags = ["函数式","编程","Clojure"] +categories = '' +date = 2026-10-29T09:14:34+08:00 +draft = true +toc = true +banner = 'https://img.eltr.ac/geedeapro/article/functional-shutdown/i-quit.avif' +endnotes = [ + '封面是摇滚乐队 Haim 的一张专辑封面,专辑名为《I Quit》', + '是的,这是饺子醋,我就是为了在文章里用这个封面才写的这篇文章。' +] ++++ + +程序退出往往不只是停止运行那么简单,还要回收资源,尤其是释放系统资源。一些异步程序还可能在共享内存区域或消息队列里存了尚未保存的数据,必然不能直接丢掉。让程序自己响应中断信号,在收到信号之后自行处理然后主动退出,这种做法一般叫优雅退出(graceful shutdown)。 + +Java 的 Spring 框架就会在接收到信号的时候自动优雅退出,首先它会停止接收新的请求,然后处理完剩下的请求,最后停止进程。 + +我本来想看看 Spring 是怎么实现优雅退出的,结果只翻到了一大堆接口和抽象类(我第一次知道接口还能继承接口,而且可以多继承),我懒得去找实现类了,所以就直接开写吧。 + +## 当导演迟早会疯的 + +程序运行时,`main` 的入口位置会被写入[程序计数器](https://zh.wikipedia.org/zh-hans/%E7%A8%8B%E5%BC%8F%E8%A8%88%E6%95%B8%E5%99%A8),是第一段被执行的代码。它负责启动系统,需要知道应该调用那些组件的哪些函数,要把哪个组件注入哪个组件,配置要从哪里加载等等。`main` 在我看来很像导演的角色,它了解所有肮脏的细节,什么都要管。 + +既然启动是它负责,那么停止也必然要经过它。很多 `main` 函数里只有启动逻辑,如果执行时按下 `Ctrl + C` 或者用 rc、systemd、Docker 和 Podman 等服务管理工具向程序发送停止信号,程序就会很生硬地终止。或许框架和语言会做某些处理,但写在内存里的数据是不会保存的,启动的子进程也会很生硬地结束。 + +假设你的程序启动了一个 Web 服务器,接收 HTTP 请求并发送响应,其中有些请求是异步的,要放进消息队列里处理,所以还要起一个处理器线程,而处理器是并发的,为了保证并发安全,避免竞争条件,还得起一个专门管理数据读取和写入的线程。 + +- 中断 Web 服务器,可能会导致一部分还没有完成的 HTTP 请求中断,用户要么一直收不到响应,最后超时了,要么会得到 `EOF` 或「服务器意外关闭了连接」之类的问题。 +- 中断处理器线程,会导致消息队列里还没处理完的任务得不到处理,程序退出之后,写在内存里的数据就全部丢失了。就算把消息写进数据库,也有可能出现某些消息还没来得及写进数据库的情况。 +- 中断数据管理线程,除了没来得及持久化的数据会丢失之外,还有可能出现 I/O 操作被打断,只写入了一半的情况。不过后者其实比较少见,只要不是直接读写文件,有用事务(transaction),完整性还是能够保证的。 + +不做优雅退出的后果可能没有那么严重,编程语言、数据库和成熟的软件库能保障数据安全,不过「中断」依旧是程序应当考虑的外部事件。==设计系统的时候应该评估有哪些数据可能会在停止的时候丢失,哪些资源可能不会被自动回收。== + + + +## 用闭包隐藏状态 + +Go 语言是用 `context` 处理取消逻辑的,也可以用在退出上。 + +既然要谈闭包(closure),不用 *Clojure* 就说不过去了。(什么逻辑?) + -- cgit v1.2.3