引言
俗话说的好,请神容易,送神难。同样,一个Server启动起来也很容易,但怎么退出,直接kill,还是直接退出main函数?
优雅的停掉Server
直接kill,或是直接退出main函数,这种方式很粗暴,可能会导致业务数据的损坏,不完整,丢失。
那应该怎么停掉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
}
当调用Stop 停掉服务的时候关闭 p.quit chan,p.serverTransport.Interrupt()会关闭端口监听,这时候 client, err := p.serverTransport.Accept() 会抛出错误。这时 AcceptLoop 会结束,进程阻塞在p.Wait(),等待 processRequests goroutine 结束。
这种方式比直接kill,或是直接退出main函数优雅多了。但还是存在一些问题。
如果Server goroutine 死锁了,这时候服务都不能顺利退出。
只是针对了Server goroutine 等待, 但 Server goroutine 可能会开启一些 client goroutine ,而且可能还有一些manager goroutine。
Server在Wait 过程中,Client 还会尝试调用Server,这时候Client 会一直报错。
针对一中的问题,加入等待超时机制,防止这种问题。超时时间确保所有收到的请求能处理完。
// 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
}
针对二中的问题,定义一个 Observer interface,相应的goroutine 里注册监听,实现stop 处理。当Server stop的时候发送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
}
针对三中的问题, 以soa 服务为例,当stop 的时候,把当前机器从服务注册中心当前所在集群中踢掉,client 会更新集群机器列表,不去访问当前机器。
总结
以上是对停掉Server的一点思考,如有不对,欢迎指教。