1818 - [ 功能特色] ( #功能特色 )
1919 - [ 安装方式] ( #安装方式 )
2020 - [ 使用说明] ( #使用说明 )
21+ - [ 基本使用] ( #基本使用 )
2122 - [ 新增运行中任务] ( #新增运行中任务 )
2223 - [ 新增关闭清理任务] ( #新增关闭清理任务 )
24+ - [ 设置关闭超时] ( #设置关闭超时 )
25+ - [ 错误处理] ( #错误处理 )
2326 - [ 自定义 Logger] ( #自定义-logger )
27+ - [ 配置选项] ( #配置选项 )
2428 - [ 示例] ( #示例 )
29+ - [ 最佳实践] ( #最佳实践 )
30+ - [ 1. 务必等待 Done()] ( #1-务必等待-done )
31+ - [ 2. 响应 Context 取消] ( #2-响应-context-取消 )
32+ - [ 3. 让关闭任务具有幂等性] ( #3-让关闭任务具有幂等性 )
33+ - [ 4. 设置适当的超时] ( #4-设置适当的超时 )
34+ - [ 5. 关闭后检查错误] ( #5-关闭后检查错误 )
35+ - [ 6. 多个任务的关闭顺序] ( #6-多个任务的关闭顺序 )
2536 - [ 许可证] ( #许可证 )
2637
2738---
2839
2940## 功能特色
3041
31- - 支持 Go 服务的优雅关闭(graceful shutdown)
32- - 管理多个运行中任务,并可通过 context 取消
33- - 注册关闭时的清理 hook
34- - 支持自定义 logger
35- - API 简单,易于集成
42+ - ** 优雅关闭** - 自动处理系统信号(SIGINT、SIGTERM)的 Go 服务
43+ - ** 超时保护** - 可配置超时时间防止无限期等待(默认:30 秒)
44+ - ** 重复关闭保护** - 确保关闭逻辑只执行一次,即使收到多个信号
45+ - ** 基于 Context 的取消** - 运行中任务会收到 context 取消信号
46+ - ** 并行关闭 Hook** - 清理任务并行执行以加快关闭速度
47+ - ** 错误报告** - 收集并报告所有任务的错误
48+ - ** 自定义 Logger 支持** - 整合现有的日志解决方案
49+ - ** 线程安全** - 所有操作都是并发安全的
50+ - ** 零依赖** - 轻量且精简
51+ - ** 简单的 API** - 易于集成到现有服务
3652
3753---
3854
@@ -46,6 +62,42 @@ go get github.com/appleboy/graceful
4662
4763## 使用说明
4864
65+ ### 基本使用
66+
67+ 创建 manager 并等待优雅关闭:
68+
69+ ``` go
70+ package main
71+
72+ import (
73+ " context"
74+ " log"
75+ " time"
76+
77+ " github.com/appleboy/graceful"
78+ )
79+
80+ func main () {
81+ // 使用默认设置创建 manager
82+ m := graceful.NewManager ()
83+
84+ // 添加你的任务...
85+
86+ // 等待关闭完成(会阻塞直到收到 SIGINT/SIGTERM)
87+ <- m.Done ()
88+
89+ // 检查错误
90+ if errs := m.Errors (); len (errs) > 0 {
91+ log.Printf (" 关闭过程中发生 %d 个错误" , len (errs))
92+ for _ , err := range errs {
93+ log.Printf (" - %v " , err)
94+ }
95+ }
96+
97+ log.Println (" 服务已优雅地停止" )
98+ }
99+ ```
100+
49101### 新增运行中任务
50102
51103注册长时间运行的任务,服务关闭时会自动取消:
@@ -132,6 +184,93 @@ func main() {
132184}
133185```
134186
187+ ### 设置关闭超时
188+
189+ 设置等待优雅关闭的最长时间(默认:30 秒):
190+
191+ ``` go
192+ package main
193+
194+ import (
195+ " time"
196+ " github.com/appleboy/graceful"
197+ )
198+
199+ func main () {
200+ // 设置 10 秒超时
201+ m := graceful.NewManager (
202+ graceful.WithShutdownTimeout (10 * time.Second ),
203+ )
204+
205+ // 或禁用超时(无限期等待)
206+ m := graceful.NewManager (
207+ graceful.WithShutdownTimeout (0 ),
208+ )
209+
210+ // ... 添加任务 ...
211+
212+ <- m.Done ()
213+
214+ // 检查是否发生超时
215+ if errs := m.Errors (); len (errs) > 0 {
216+ for _ , err := range errs {
217+ if err.Error () == " shutdown timeout exceeded: 10s" {
218+ log.Println (" 部分任务未在超时内完成" )
219+ }
220+ }
221+ }
222+ }
223+ ```
224+
225+ ** 为什么超时很重要:**
226+
227+ - 防止任务未响应取消信号时无限期挂起
228+ - 对容器化环境(Kubernetes terminationGracePeriodSeconds)至关重要
229+ - 确保生产环境中可预测的关闭行为
230+
231+ ### 错误处理
232+
233+ 访问关闭期间发生的所有错误:
234+
235+ ``` go
236+ package main
237+
238+ import (
239+ " log"
240+ " github.com/appleboy/graceful"
241+ )
242+
243+ func main () {
244+ m := graceful.NewManager ()
245+
246+ m.AddRunningJob (func (ctx context.Context ) error {
247+ // ... 执行任务 ...
248+ return fmt.Errorf (" 发生错误" ) // 错误会被收集
249+ })
250+
251+ m.AddShutdownJob (func () error {
252+ // ... 清理 ...
253+ return nil
254+ })
255+
256+ <- m.Done ()
257+
258+ // 获取所有错误(包含任务错误、panic 和超时错误)
259+ errs := m.Errors ()
260+ if len (errs) > 0 {
261+ log.Printf (" 关闭错误:%v " , errs)
262+ os.Exit (1 ) // 以错误代码退出
263+ }
264+ }
265+ ```
266+
267+ ** 收集的错误类型:**
268+
269+ - 运行中任务返回的错误
270+ - 关闭任务返回的错误
271+ - 从任务中恢复的 panic(转换为错误)
272+ - 如果关闭超过设置时间的超时错误
273+
135274### 自定义 Logger
136275
137276你可以使用自定义 logger(参考 [ zerolog 示例] ( ./_example/example03/logger.go ) ):
@@ -144,12 +283,131 @@ m := graceful.NewManager(
144283
145284---
146285
286+ ## 配置选项
287+
288+ 所有配置都通过传递给 ` NewManager() ` 的功能选项完成:
289+
290+ | 选项 | 说明 | 默认值 |
291+ | ------------------------------- | ------------------------------------------------------- | ---------------------- |
292+ | ` WithContext(ctx) ` | 使用自定义的父 context。当 context 被取消时会触发关闭。 | ` context.Background() ` |
293+ | ` WithLogger(logger) ` | 使用自定义的 logger 实现。 | 内置 logger |
294+ | ` WithShutdownTimeout(duration) ` | 等待优雅关闭的最长时间。设为 ` 0 ` 表示无超时。 | ` 30 * time.Second ` |
295+
296+ ** 多个选项的示例:**
297+
298+ ``` go
299+ m := graceful.NewManager (
300+ graceful.WithContext (ctx),
301+ graceful.WithShutdownTimeout (15 * time.Second ),
302+ graceful.WithLogger (customLogger),
303+ )
304+ ```
305+
306+ ---
307+
147308## 示例
148309
149- - [ 基本用法] ( ./_example/example01/main.go )
150- - [ 多个任务] ( ./_example/example02/main.go )
151- - [ 自定义 logger] ( ./_example/example03/main.go )
152- - [ Gin 集成] ( ./_example/example04-gin/main.go )
310+ - [ ** 示例 01** :基本用法] ( ./_example/example01/main.go ) - 简单的运行中任务
311+ - [ ** 示例 02** :多个任务] ( ./_example/example02/main.go ) - 运行中 + 关闭任务
312+ - [ ** 示例 03** :自定义 logger] ( ./_example/example03/main.go ) - 与 zerolog 集成
313+ - [ ** 示例 04** :Gin 网页服务器] ( ./_example/example04-gin/main.go ) - 优雅的 HTTP 服务器关闭
314+ - [ ** 示例 05** :关闭超时] ( ./_example/example05-timeout/main.go ) - 超时设置与处理
315+
316+ ---
317+
318+ ## 最佳实践
319+
320+ ### 1. 务必等待 Done()
321+
322+ ``` go
323+ m := graceful.NewManager ()
324+ // ... 添加任务 ...
325+ <- m.Done () // ✅ 必要:等待关闭完成
326+ ```
327+
328+ ** 为什么:** 如果程序在调用 ` <-m.Done() ` 前就退出,清理可能无法完成,导致:
329+
330+ - 资源泄漏(打开的连接、文件)
331+ - 数据丢失(未刷新的缓冲区)
332+ - 孤儿 goroutine
333+
334+ ### 2. 响应 Context 取消
335+
336+ ``` go
337+ m.AddRunningJob (func (ctx context.Context ) error {
338+ ticker := time.NewTicker (1 * time.Second )
339+ defer ticker.Stop ()
340+
341+ for {
342+ select {
343+ case <- ctx.Done ():
344+ // ✅ 务必处理 ctx.Done() 以启用优雅关闭
345+ log.Println (" 正在优雅地关闭..." )
346+ return ctx.Err ()
347+ case <- ticker.C :
348+ // 执行任务
349+ }
350+ }
351+ })
352+ ```
353+
354+ ** 为什么:** 不尊重 ` ctx.Done() ` 的任务会阻塞关闭直到超时。
355+
356+ ### 3. 让关闭任务具有幂等性
357+
358+ ``` go
359+ m.AddShutdownJob (func () error {
360+ // ✅ 多次调用也安全(虽然 graceful 确保只会调用一次)
361+ if db != nil {
362+ db.Close ()
363+ db = nil
364+ }
365+ return nil
366+ })
367+ ```
368+
369+ ** 为什么:** 虽然 manager 确保关闭任务只执行一次,防御性编程可防止问题。
370+
371+ ### 4. 设置适当的超时
372+
373+ ``` go
374+ // 对于 terminationGracePeriodSeconds: 30 的 Kubernetes Pod
375+ m := graceful.NewManager (
376+ graceful.WithShutdownTimeout (25 * time.Second ), // ✅ 留 5 秒缓冲给 SIGKILL
377+ )
378+ ```
379+
380+ ** 为什么:** 如果关闭超时超过容器终止期限,进程会被强制终止(SIGKILL)。
381+
382+ ### 5. 关闭后检查错误
383+
384+ ``` go
385+ <- m.Done ()
386+
387+ if errs := m.Errors (); len (errs) > 0 {
388+ log.Printf (" 关闭错误:%v " , errs)
389+ os.Exit (1 ) // ✅ 以错误代码退出以便监控/告警
390+ }
391+ ```
392+
393+ ** 为什么:** 让你能在生产环境中检测并响应关闭问题。
394+
395+ ### 6. 多个任务的关闭顺序
396+
397+ 关闭任务默认** 并行执行** 。如果你需要顺序关闭:
398+
399+ ``` go
400+ m.AddShutdownJob (func () error {
401+ // 在单一任务内依序执行所有关闭步骤
402+ stopAcceptingRequests ()
403+ waitForInflightRequests ()
404+ closeDatabase ()
405+ flushLogs ()
406+ return nil
407+ })
408+ ```
409+
410+ ** 为什么:** 并行执行速度较快,但某些清理需要特定顺序。
153411
154412---
155413
0 commit comments