引言
俗話說的好,請(qǐng)神容易,送神難。同樣,一個(gè)Server啟動(dòng)起來也很容易,但怎么退出,直接kill,還是直接退出main函數(shù)?
優(yōu)雅的停掉Server
直接kill,或是直接退出main函數(shù),這種方式很粗暴,可能會(huì)導(dǎo)致業(yè)務(wù)數(shù)據(jù)的損壞,不完整,丟失。
那應(yīng)該怎么停掉Server。這里筆者以Thrift Serve為列子。看看Thrift 源碼里面的一段停掉Server代碼:
func (p *TSimpleServer) AcceptLoop() error {
for {
client, err := p.serverTransport.Accept()
if err != nil {
select {
case <-p.quit:
return nil
default:
}
return err
}
if client != nil {
p.Add(1)
go func() {
if err := p.processRequests(client); err != nil {
log.Println("error processing request:", err)
}
}()
}
}
}
var once sync.Once
func (p *TSimpleServer) Stop() error {
q := func() {
close(p.quit)
p.serverTransport.Interrupt()
p.Wait()
}
once.Do(q)
return nil
}
.......
func (p *TServerSocket) Interrupt() error {
p.mu.Lock()
p.interrupted = true
p.Close()
p.mu.Unlock()
return nil
}
func (p *TServerSocket) Close() error {
defer func() {
p.listener = nil
}()
if p.IsListening() {
return p.listener.Close()
}
return nil
}
當(dāng)調(diào)用Stop 停掉服務(wù)的時(shí)候關(guān)閉 p.quit chan,p.serverTransport.Interrupt()會(huì)關(guān)閉端口監(jiān)聽,這時(shí)候 client, err := p.serverTransport.Accept() 會(huì)拋出錯(cuò)誤。這時(shí) AcceptLoop 會(huì)結(jié)束,進(jìn)程阻塞在p.Wait(),等待 processRequests goroutine 結(jié)束。
這種方式比直接kill,或是直接退出main函數(shù)優(yōu)雅多了。但還是存在一些問題。
如果Server goroutine 死鎖了,這時(shí)候服務(wù)都不能順利退出。
只是針對(duì)了Server goroutine 等待, 但 Server goroutine 可能會(huì)開啟一些 client goroutine ,而且可能還有一些manager goroutine。
Server在Wait 過程中,Client 還會(huì)嘗試調(diào)用Server,這時(shí)候Client 會(huì)一直報(bào)錯(cuò)。
針對(duì)一中的問題,加入等待超時(shí)機(jī)制,防止這種問題。超時(shí)時(shí)間確保所有收到的請(qǐng)求能處理完。
// AcceptLoop loops and accepts connections.
func (p *TSimpleServer) AcceptLoop() error {
for {
client, err := p.serverTransport.Accept()
if err != nil {
select {
case <-p.quit:
return nil
default:
}
return err
}
if client != nil {
p.Add(1)
go func() {
if err := p.processRequests(client); err != nil {
log.Println("error processing request:", err)
}
}()
}
}
}
var once sync.Once
func (p *TSimpleServer) Stop() {
q := func() {
close(p.quit)
p.serverTransport.Interrupt()
timer := time.NewTimer(p.GracefulTimeout)
waitCh := make(chan struct{})
go func() {
p.Wait()
close(waitCh)
}()
select {
case <-waitCh:
case <-timer.C:
}
}
once.Do(q)
return nil
}
針對(duì)二中的問題,定義一個(gè) Observer interface,相應(yīng)的goroutine 里注冊(cè)監(jiān)聽,實(shí)現(xiàn)stop 處理。當(dāng)Server stop的時(shí)候發(fā)送Shutdown消息。
type ShutdownObserver interface {
ShutdownNotify(evt interface{})
}
......
func (n *ShutdownNotifier) Notify(evt interface{}) {
n.RLock()
for o := range n.observers {
o.ShutdownNotify(evt)
}
n.RUnlock()
}
func (p *TSimpleServer) Stop() {
q := func() {
close(p.quit)
p.serverTransport.Interrupt()
timer := time.NewTimer(p.GracefulTimeout)
waitCh := make(chan struct{})
go func() {
p.shutdown.Notify(nil)
p.Wait()
close(waitCh)
}()
select {
case <-waitCh:
case <-timer.C:
}
}
once.Do(q)
return nil
}
針對(duì)三中的問題, 以soa 服務(wù)為例,當(dāng)stop 的時(shí)候,把當(dāng)前機(jī)器從服務(wù)注冊(cè)中心當(dāng)前所在集群中踢掉,client 會(huì)更新集群機(jī)器列表,不去訪問當(dāng)前機(jī)器。
總結(jié)
以上是對(duì)停掉Server的一點(diǎn)思考,如有不對(duì),歡迎指教。