设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客

Go语言中实现优雅的停止程序

2013-5-6 10:47| 发布者: joejoe0332| 查看: 1825| 评论: 0|原作者: fbm|来自: oschina.net

摘要:   过早地部署这种行为非常粗鲁,尤其是在部署时还要中断用户请求的情况下更是如此,因此,我们在Betable构建的Go服务要在不中断任何用户请求的情况 下优雅地中止服务。其基本思想就是停止侦听(llistening),假定 ...

  过早地部署这种行为非常粗鲁,尤其是在部署时还要中断用户请求的情况下更是如此,因此,我们在Betable构建的Go服务要在不中断任何用户请求的情况 下优雅地中止服务。其基本思想就是停止侦听(llistening),假定会有一个新的进程来接管这些侦听,让所有已经建立起来的连接在最终停止服务前继 续处理进行中的请求。顺便说一句,我们采用了goagain,从而可以甚至在不停止侦听的情况下重启服务,但这个话题超出了本文的讨论范围。

  main.main做了四件事情:侦听、生成一个Service并将其发送到后台(background)执行、阻塞直到收到相应的信号 (signal)、 然后优雅的停止服务。侦听以及信号处理完全出自于标准库的一般套路,只有一点令我十分恼火,就是,不仅需要使用anet.Listener或者 net.Connto调用SetDeadline,竟然还需要*net.TCPListener或者*net.TCPConnand。

  service.Serve(listener)用来接受连接请求并在它自己的goroutine之内处理它所接受的每个连接请求。由于它设置了一个截至 时间,所以listener.AcceptTCP()不会永久性地处于阻塞状态,而且它还会在下一轮循环时检查它是否应该停止侦听。

  service.serve(conn)进行读写操作,而且同样的,它也具有一个截至时间。由于它设置了截至时间,所以conn.Read(buf)不会 永久性地处于阻塞状态,而且在写入响应数据和读取下一个请求之间或者超过了conn.Read(buf)截至时间后,检查它是否应该关闭该连接。

  因为不会有别的东西发送到service的channel中,只有在service.Stop()关闭该channel后才会向该channel中发送一个值,所以,只要从service的channel中接收到一个值,各个goroutine就会决定关闭相关的连接和侦听器(listener)。

  难题的最后一个部分是通过调用标准库sync.WaitGroup实现等待所有的goroutine结束执行。

https://gist.github.com/rcrowley/5474430包含了所有的代码:

001package main
002 
003import (
004    "log"
005    "net"
006    "os"
007    "os/signal"
008    "sync"
009    "syscall"
010    "time"
011)
012 
013// An uninteresting service.
014type Service struct {
015    ch        chan bool
016    waitGroup *sync.WaitGroup
017}
018 
019// Make a new Service.
020func NewService() *Service {
021    return &Service{
022        ch:        make(chan bool),
023        waitGroup: &sync.WaitGroup{},
024    }
025}
026 
027// Accept connections and spawn a goroutine to serve each one.  Stop listening
028// if anything is received on the service's channel.
029func (s *Service) Serve(listener *net.TCPListener) {
030    s.waitGroup.Add(1)
031    defer s.waitGroup.Done()
032    for {
033        select {
034        case <-s.ch:
035            log.Println("stopping listening on", listener.Addr())
036            listener.Close()
037            return
038        default:
039        }
040        listener.SetDeadline(time.Now().Add(1e9))
041        conn, err := listener.AcceptTCP()
042        if nil != err {
043            if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
044                continue
045            }
046            log.Println(err)
047        }
048        log.Println(conn.RemoteAddr(), "connected")
049        go s.serve(conn)
050    }
051}
052 
053// Stop the service by closing the service's channel.  Block until the service
054// is really stopped.
055func (s *Service) Stop() {
056    close(s.ch)
057    s.waitGroup.Wait()
058}
059 
060// Serve a connection by reading and writing what was read.  That's right, this
061// is an echo service.  Stop reading and writing if anything is received on the
062// service's channel but only after writing what was read.
063func (s *Service) serve(conn *net.TCPConn) {
064    defer conn.Close()
065    s.waitGroup.Add(1)
066    defer s.waitGroup.Done()
067    for {
068        select {
069        case <-s.ch:
070            log.Println("disconnecting", conn.RemoteAddr())
071            return
072        default:
073        }
074        conn.SetDeadline(time.Now().Add(1e9))
075        buf := make([]byte, 4096)
076        if _, err := conn.Read(buf); nil != err {
077            if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
078                continue
079            }
080            log.Println(err)
081            return
082        }
083        if _, err := conn.Write(buf); nil != err {
084            log.Println(err)
085            return
086        }
087    }
088}
089 
090func main() {
091 
092    // Listen on 127.0.0.1:48879.  That's my favorite port number because in
093    // hex 48879 is 0xBEEF.
094    laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:48879")
095    if nil != err {
096        log.Fatalln(err)
097    }
098    listener, err := net.ListenTCP("tcp", laddr)
099    if nil != err {
100        log.Fatalln(err)
101    }
102    log.Println("listening on", listener.Addr())
103 
104    // Make a new service and send it into the background.
105    service := NewService()
106    go service.Serve(listener)
107 
108    // Handle SIGINT and SIGTERM.
109    ch := make(chan os.Signal)
110    signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
111    log.Println(<-ch)
112 
113    // Stop the service gracefully.
114    service.Stop()
115 
116}

酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部