引子?关闭?还特么优雅? 实际上优雅关闭还有另外一个名词, 叫“平滑退出'。如果你打算自己造轮子, 优雅关闭将是你要掌握的第一个知识点。 在生活中,如果有一个名词确实过于难以理解,我们不妨来看这个名词的反面是什么。 举个简单的例子
可能有同学会有疑问了: 我平时电脑卡住的时候都是直接断电重启的啊,也没见有啥大问题啊? 计算机与计算机的体质不能一概而论, 如果你的计算机安装的是Linux系统,恰好这又是一台服务器的话,强制重启的话,你大概率会丢掉部分数据,如果是生产环境的话,那就准备提桶跑路吧。 为什么呢? 如果你曾经想过要做MySQL或者Redis的调优, 或多或少接触过以下参数:
出现以上参数的原因是因为将数据持久化到存储设备是一个耗时相对较高的行为, Linux采取的优化措施是,当你往一个文件中数据时会暂存到系统的缓存中, 等待时机再批量持久化到存储设备中。除非进程指定使用DirectIO的方式或者调用fsync,操作系统才会主动将数据写入存储设备。 因此MySQL和Redis纷纷开放了调优参数用来控制日志持久化行为,并将锅甩回给了程序员。
优雅关闭现在,从不太优雅关闭的例子了解到优雅关闭要做什么了:
但是,我们还需要加一个限制条件:
线程池的优雅关闭线程池(ThreadPoolExecutor)在JDK的并发包中占据了重要的位置, 我们可以来看看如此重要的一个基础组件是如何处理优雅关闭的。 该类将是否需要优雅关闭的权限开放给程序员, 并提供了两个方法,分别是:
优雅关闭进程如何优雅关闭进程呢? 首先我们需要搞清楚进程什么情况下会关闭:
在企业级应用中,一个进程通常不止有业务逻辑,还有围绕着业务而开发的日志服务/MQ服务/运维服务等等, 那么当某个业务出现可能导致进程崩溃的问题时,我们就需要将进程即将关闭的消息广播给其他服务, 并调用这些服务提供的优雅关闭方法, 以上措施全部完成后再退出进程, 如日志服务的优雅关闭是确保日志落盘, MQ服务的优雅关闭是确保消息被投递出去或者被消费完等等。
我们以Golang为例来描述如何优雅关闭进程, 首先我们需要对进程中的服务做一个抽象,以便实现生命周期管理, 每个服务提供均需要提供Serve和Shutdown方法。 type Service interface { Serve(ctx context.Context) error Shutdown() error}复制代码 接下来我们定义一个ServiceGroup用来管理Service生命周期, 当任意Service运行出错或接收系统信号SIGINT(Ctrl+C触发)和SIGTREM(kill 不加参数), ServiceGroup将负责关闭关闭由此管理的Service并调用Shutdown方法。
接下来,我们定义一个会随机panic的业务服务以及日志服务。 type BusinessService struct {}func (b *BusinessService) Serve(ctx context.Context) (err error) { times := 0 for { fmt.Printf('业务运行中 %d\n', times) select { case <- ctx.Done(): fmt.Printf('BusinessService receive cancel signal\n') return default: if n := rand.Intn(256); n > 200 { panic(fmt.Errorf('random panic on %d', n)) } } time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) times++ } return}func (b *BusinessService) Shutdown() error { fmt.Println('业务服务, 关闭!') return nil}type LogService struct { buffer []string}func (l *LogService) Serve(ctx context.Context) (err error) { for { select { case <- ctx.Done(): return default: // 投递日志到消息队列 time.Sleep(time.Millisecond * time.Duration(rand.Intn(500))) l.buffer = append(l.buffer, fmt.Sprintf('Time: %d', time.Now().Unix())) } }}func (l *LogService) Shutdown() (err error) { fmt.Printf('日志服务, 关闭! 有[%d]条日志待发送\n', len(l.buffer)) if len(l.buffer) == 0 { return } for _, log := range l.buffer { // 发送日志或者持久化到硬盘 fmt.Printf('Send Log [%s]\n', log) } fmt.Println('缓冲区日志清理完毕') return}复制代码 运行
运行输出如下所示: 以上代码还有诸多优化的地方, 读者可自行改进。如可以使用errorgroup对服务进行管理, 以及Shutdwon的时候也可传入上下文做超时管理。 总结什么是优雅关闭?
|
|
来自: 菌心说 > 《编程+、计算机、信息技术》