引子?關(guān)閉?還特么優(yōu)雅? 實(shí)際上優(yōu)雅關(guān)閉還有另外一個(gè)名詞, 叫“平滑退出'。如果你打算自己造輪子, 優(yōu)雅關(guān)閉將是你要掌握的第一個(gè)知識(shí)點(diǎn)。 在生活中,如果有一個(gè)名詞確實(shí)過于難以理解,我們不妨來(lái)看這個(gè)名詞的反面是什么。 舉個(gè)簡(jiǎn)單的例子
可能有同學(xué)會(huì)有疑問了: 我平時(shí)電腦卡住的時(shí)候都是直接斷電重啟的啊,也沒見有啥大問題啊? 計(jì)算機(jī)與計(jì)算機(jī)的體質(zhì)不能一概而論, 如果你的計(jì)算機(jī)安裝的是Linux系統(tǒng),恰好這又是一臺(tái)服務(wù)器的話,強(qiáng)制重啟的話,你大概率會(huì)丟掉部分?jǐn)?shù)據(jù),如果是生產(chǎn)環(huán)境的話,那就準(zhǔn)備提桶跑路吧。 為什么呢? 如果你曾經(jīng)想過要做MySQL或者Redis的調(diào)優(yōu), 或多或少接觸過以下參數(shù):
出現(xiàn)以上參數(shù)的原因是因?yàn)閷?shù)據(jù)持久化到存儲(chǔ)設(shè)備是一個(gè)耗時(shí)相對(duì)較高的行為, Linux采取的優(yōu)化措施是,當(dāng)你往一個(gè)文件中數(shù)據(jù)時(shí)會(huì)暫存到系統(tǒng)的緩存中, 等待時(shí)機(jī)再批量持久化到存儲(chǔ)設(shè)備中。除非進(jìn)程指定使用DirectIO的方式或者調(diào)用fsync,操作系統(tǒng)才會(huì)主動(dòng)將數(shù)據(jù)寫入存儲(chǔ)設(shè)備。 因此MySQL和Redis紛紛開放了調(diào)優(yōu)參數(shù)用來(lái)控制日志持久化行為,并將鍋甩回給了程序員。
優(yōu)雅關(guān)閉現(xiàn)在,從不太優(yōu)雅關(guān)閉的例子了解到優(yōu)雅關(guān)閉要做什么了:
但是,我們還需要加一個(gè)限制條件:
線程池的優(yōu)雅關(guān)閉線程池(ThreadPoolExecutor)在JDK的并發(fā)包中占據(jù)了重要的位置, 我們可以來(lái)看看如此重要的一個(gè)基礎(chǔ)組件是如何處理優(yōu)雅關(guān)閉的。 該類將是否需要優(yōu)雅關(guān)閉的權(quán)限開放給程序員, 并提供了兩個(gè)方法,分別是:
優(yōu)雅關(guān)閉進(jìn)程如何優(yōu)雅關(guān)閉進(jìn)程呢? 首先我們需要搞清楚進(jìn)程什么情況下會(huì)關(guān)閉:
在企業(yè)級(jí)應(yīng)用中,一個(gè)進(jìn)程通常不止有業(yè)務(wù)邏輯,還有圍繞著業(yè)務(wù)而開發(fā)的日志服務(wù)/MQ服務(wù)/運(yùn)維服務(wù)等等, 那么當(dāng)某個(gè)業(yè)務(wù)出現(xiàn)可能導(dǎo)致進(jìn)程崩潰的問題時(shí),我們就需要將進(jìn)程即將關(guān)閉的消息廣播給其他服務(wù), 并調(diào)用這些服務(wù)提供的優(yōu)雅關(guān)閉方法, 以上措施全部完成后再退出進(jìn)程, 如日志服務(wù)的優(yōu)雅關(guān)閉是確保日志落盤, MQ服務(wù)的優(yōu)雅關(guān)閉是確保消息被投遞出去或者被消費(fèi)完等等。
我們以Golang為例來(lái)描述如何優(yōu)雅關(guān)閉進(jìn)程, 首先我們需要對(duì)進(jìn)程中的服務(wù)做一個(gè)抽象,以便實(shí)現(xiàn)生命周期管理, 每個(gè)服務(wù)提供均需要提供Serve和Shutdown方法。 type Service interface { Serve(ctx context.Context) error Shutdown() error}復(fù)制代碼接下來(lái)我們定義一個(gè)ServiceGroup用來(lái)管理Service生命周期, 當(dāng)任意Service運(yùn)行出錯(cuò)或接收系統(tǒng)信號(hào)SIGINT(Ctrl+C觸發(fā))和SIGTREM(kill 不加參數(shù)), ServiceGroup將負(fù)責(zé)關(guān)閉關(guān)閉由此管理的Service并調(diào)用Shutdown方法。 接下來(lái),我們定義一個(gè)會(huì)隨機(jī)panic的業(yè)務(wù)服務(wù)以及日志服務(wù)。 type BusinessService struct {}func (b *BusinessService) Serve(ctx context.Context) (err error) { times := 0 for { fmt.Printf('業(yè)務(wù)運(yùn)行中 %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('業(yè)務(wù)服務(wù), 關(guān)閉!') return nil}type LogService struct { buffer []string}func (l *LogService) Serve(ctx context.Context) (err error) { for { select { case <- ctx.Done(): return default: // 投遞日志到消息隊(duì)列 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('日志服務(wù), 關(guān)閉! 有[%d]條日志待發(fā)送\n', len(l.buffer)) if len(l.buffer) == 0 { return } for _, log := range l.buffer { // 發(fā)送日志或者持久化到硬盤 fmt.Printf('Send Log [%s]\n', log) } fmt.Println('緩沖區(qū)日志清理完畢') return}復(fù)制代碼運(yùn)行 運(yùn)行輸出如下所示: 以上代碼還有諸多優(yōu)化的地方, 讀者可自行改進(jìn)。如可以使用errorgroup對(duì)服務(wù)進(jìn)行管理, 以及Shutdwon的時(shí)候也可傳入上下文做超時(shí)管理。 總結(jié)什么是優(yōu)雅關(guān)閉?
|
|
|