如何优雅的停掉Server

引言

俗话说的好,请神容易,送神难。同样,一个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函数优雅多了。但还是存在一些问题。

  1. 如果Server goroutine 死锁了,这时候服务都不能顺利退出。

  2. 只是针对了Server goroutine 等待, 但 Server goroutine 可能会开启一些 client goroutine ,而且可能还有一些manager goroutine。

  3. 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的一点思考,如有不对,欢迎指教。

你可能感兴趣的:(如何优雅的停掉Server)