青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Fork me on GitHub
隨筆 - 215  文章 - 13  trackbacks - 0
<2016年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567


專注即時通訊及網(wǎng)游服務(wù)端編程
------------------------------------
Openresty 官方模塊
Openresty 標準模塊(Opm)
Openresty 三方模塊
------------------------------------
本博收藏大部分文章為轉(zhuǎn)載,并在文章開頭給出了原文出處,如有再轉(zhuǎn),敬請保留相關(guān)信息,這是大家對原創(chuàng)作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

常用鏈接

留言簿(1)

隨筆分類

隨筆檔案

相冊

Awesome

Blog

Book

GitHub

Link

搜索

  •  

積分與排名

  • 積分 - 220950
  • 排名 - 117

最新評論

閱讀排行榜

來自:https://segmentfault.com/a/1190000004445975
原文鏈接:http://tabalt.net/blog/gracef...
Golang支持平滑升級(優(yōu)雅重啟)的包已開源到Github:https://github.com/tabalt/gracehttp,歡迎使用和貢獻代碼。

前段時間用Golang在做一個HTTP的接口,因編譯型語言的特性,修改了代碼需要重新編譯可執(zhí)行文件,關(guān)閉正在運行的老程序,并啟動新程序。對于訪問量較大的面向用戶的產(chǎn)品,關(guān)閉、重啟的過程中勢必會出現(xiàn)無法訪問的情況,從而影響用戶體驗。

使用Golang的系統(tǒng)包開發(fā)HTTP服務(wù),是無法支持平滑升級(優(yōu)雅重啟)的,本文將探討如何解決該問題。

一、平滑升級(優(yōu)雅重啟)的一般思路

一般情況下,要實現(xiàn)平滑升級,需要以下幾個步驟:

  1. 用新的可執(zhí)行文件替換老的可執(zhí)行文件(如只需優(yōu)雅重啟,可以跳過這一步)

  2. 通過pid給正在運行的老進程發(fā)送 特定的信號(kill -SIGUSR2 $pid)

  3. 正在運行的老進程,接收到指定的信號后,以子進程的方式啟動新的可執(zhí)行文件并開始處理新請求

  4. 老進程不再接受新的請求,等待未完成的服務(wù)處理完畢,然后正常結(jié)束

  5. 新進程在父進程退出后,會被init進程領(lǐng)養(yǎng),并繼續(xù)提供服務(wù)

二、Golang Socket 網(wǎng)絡(luò)編程

Socket是程序員層面上對傳輸層協(xié)議TCP/IP的封裝和應(yīng)用。Golang中Socket相關(guān)的函數(shù)與結(jié)構(gòu)體定義在net包中,我們從一個簡單的例子來學習一下Golang Socket 網(wǎng)絡(luò)編程,關(guān)鍵說明直接寫在注釋中。

1、服務(wù)端程序 server.go

package main

import (
    "fmt"
    "log"
    "net"
    "time"
)

func main() {
    // 監(jiān)聽8086端口
    listener, err := net.Listen("tcp", ":8086")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()

    for {
        // 循環(huán)接收客戶端的連接,沒有連接時會阻塞,出錯則跳出循環(huán)
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err)
            break
        }

        fmt.Println("[server] accept new connection.")

        // 啟動一個goroutine 處理連接
        go handler(conn)
    }
}

func handler(conn net.Conn) {
    defer conn.Close()

    for {
        // 循環(huán)從連接中 讀取請求內(nèi)容,沒有請求時會阻塞,出錯則跳出循環(huán)
        request := make([]byte, 128)
        readLength, err := conn.Read(request)

        if err != nil {
            fmt.Println(err)
            break
        }

        if readLength == 0 {
            fmt.Println(err)
            break
        }

        // 控制臺輸出讀取到的請求內(nèi)容,并在請求內(nèi)容前加上hello和時間后向客戶端輸出
        fmt.Println("[server] request from ", string(request))
        conn.Write([]byte("hello " + string(request) + ", time: " + time.Now().Format("2006-01-02 15:04:05")))
    }
}

2、客戶端程序 client.go

package main

import (
    "fmt"
    "log"
    "net"
    "os"
    "time"
)

func main() {

    // 從命令行中讀取第二個參數(shù)作為名字,如果不存在第二個參數(shù)則報錯退出
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s name ", os.Args[0])
        os.Exit(1)
    }
    name := os.Args[1]

    // 連接到服務(wù)端的8086端口
    conn, err := net.Dial("tcp", "127.0.0.1:8086")
    checkError(err)

    for {
        // 循環(huán)往連接中 寫入名字
        _, err = conn.Write([]byte(name))
        checkError(err)

        // 循環(huán)從連接中 讀取響應(yīng)內(nèi)容,沒有響應(yīng)時會阻塞
        response := make([]byte, 256)
        readLength, err := conn.Read(response)
        checkError(err)

        // 將讀取響應(yīng)內(nèi)容輸出到控制臺,并sleep一秒
        if readLength > 0 {
            fmt.Println("[client] server response:", string(response))
            time.Sleep(1 * time.Second)
        }
    }
}

func checkError(err error) {
    if err != nil {
        log.Fatal("fatal error: " + err.Error())
    }
}

3、運行示例程序

# 運行服務(wù)端程序
go run server.go

# 在另一個命令行窗口運行客戶端程序
go run client.go "tabalt"

三、Golang HTTP 編程

HTTP是基于傳輸層協(xié)議TCP/IP的應(yīng)用層協(xié)議。Golang中HTTP相關(guān)的實現(xiàn)在net/http包中,直接用到了net包中Socket相關(guān)的函數(shù)和結(jié)構(gòu)體。

我們再從一個簡單的例子來學習一下Golang HTTP 編程,關(guān)鍵說明直接寫在注釋中。

1、http服務(wù)程序 http.go

package main

import (
    "log"
    "net/http"
    "os"
)

// 定義http請求的處理方法
func handlerHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("http hello on golang\n"))
}

func main() {

    // 注冊http請求的處理方法
    http.HandleFunc("/hello", handlerHello)

    // 在8086端口啟動http服務(wù),會一直阻塞執(zhí)行
    err := http.ListenAndServe("localhost:8086", nil)
    if err != nil {
        log.Println(err)
    }

    // http服務(wù)因故停止后 才會輸出如下內(nèi)容
    log.Println("Server on 8086 stopped")
    os.Exit(0)
}

2、運行示例程序

# 運行HTTP服務(wù)程序
go run http.go

# 在另一個命令行窗口curl請求測試頁面
curl http://localhost:8086/hello/

# 輸出如下內(nèi)容:
http hello on golang

四、Golang net/http包中 Socket操作的實現(xiàn)

從上面的簡單示例中,我們看到在Golang中要啟動一個http服務(wù),只需要簡單的三步:

  1. 定義http請求的處理方法

  2. 注冊http請求的處理方法

  3. 在某個端口啟動HTTP服務(wù)

而最關(guān)鍵的啟動http服務(wù),是調(diào)用http.ListenAndServe()函數(shù)實現(xiàn)的。下面我們找到該函數(shù)的實現(xiàn):

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

這里創(chuàng)建了一個Server的對象,并調(diào)用它的ListenAndServe()方法,我們再找到結(jié)構(gòu)體Server的ListenAndServe()方法的實現(xiàn):

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

從代碼上看到,這里監(jiān)聽了tcp端口,并將監(jiān)聽者包裝成了一個結(jié)構(gòu)體 tcpKeepAliveListener,再調(diào)用srv.Serve()方法;我們繼續(xù)跟蹤Serve()方法的實現(xiàn):

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve()
    }
}

可以看到,和我們前面Socket編程的示例代碼一樣,循環(huán)從監(jiān)聽的端口上Accept連接,如果返回了一個net.Error并且這個錯誤是臨時性的,則會sleep一個時間再繼續(xù)。 如果返回了其他錯誤則會終止循環(huán)。成功Accept到一個連接后,調(diào)用了方法srv.newConn()對連接做了一層包裝,最后啟了一個goroutine處理http請求。

五、Golang 平滑升級(優(yōu)雅重啟)HTTP服務(wù)的實現(xiàn)

我創(chuàng)建了一個新的包gracehttp來實現(xiàn)支持平滑升級(優(yōu)雅重啟)的HTTP服務(wù),為了少寫代碼和降低使用成本,新的包盡可能多地利用net/http包的實現(xiàn),并和net/http包保持一致的對外方法。現(xiàn)在開始我們來看gracehttp包支持平滑升級 (優(yōu)雅重啟)Golang HTTP服務(wù)涉及到的細節(jié)如何實現(xiàn)。

1、Golang處理信號

Golang的os/signal包封裝了對信號的處理。簡單用法請看示例:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {

    signalChan := make(chan os.Signal)

    // 監(jiān)聽指定信號
    signal.Notify(
        signalChan,
        syscall.SIGHUP,
        syscall.SIGUSR2,
    )

    // 輸出當前進程的pid
    fmt.Println("pid is: ", os.Getpid())

    // 處理信號
    for {
        sig := <-signalChan
        fmt.Println("get signal: ", sig)
    }
}

2、子進程啟動新程序,監(jiān)聽相同的端口

在第四部分的ListenAndServe()方法的實現(xiàn)代碼中可以看到,net/http包中使用net.Listen函數(shù)來監(jiān)聽了某個端口,但如果某個運行中的程序已經(jīng)監(jiān)聽某個端口,其他程序是無法再去監(jiān)聽這個端口的。解決的辦法是使用子進程的方式啟動,并將監(jiān)聽端口的文件描述符傳遞給子進程,子進程里從這個文件描述符實現(xiàn)對端口的監(jiān)聽。

具體實現(xiàn)需要借助一個環(huán)境變量來區(qū)分進程是正常啟動,還是以子進程方式啟動的,相關(guān)代碼摘抄如下:

// 啟動子進程執(zhí)行新程序
func (this *Server) startNewProcess() error {

    listenerFd, err := this.listener.(*Listener).GetFd()
    if err != nil {
        return fmt.Errorf("failed to get socket file descriptor: %v", err)
    }

    path := os.Args[0]

    // 設(shè)置標識優(yōu)雅重啟的環(huán)境變量
    environList := []string{}
    for _, value := range os.Environ() {
        if value != GRACEFUL_ENVIRON_STRING {
            environList = append(environList, value)
        }
    }
    environList = append(environList, GRACEFUL_ENVIRON_STRING)

    execSpec := &syscall.ProcAttr{
        Env:   environList,
        Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
    }

    fork, err := syscall.ForkExec(path, os.Args, execSpec)
    if err != nil {
        return fmt.Errorf("failed to forkexec: %v", err)
    }

    this.logf("start new process success, pid %d.", fork)

    return nil
}

func (this *Server) getNetTCPListener(addr string) (*net.TCPListener, error) {

    var ln net.Listener
    var err error

    if this.isGraceful {
        file := os.NewFile(3, "")
        ln, err = net.FileListener(file)
        if err != nil {
            err = fmt.Errorf("net.FileListener error: %v", err)
            return nil, err
        }
    } else {
        ln, err = net.Listen("tcp", addr)
        if err != nil {
            err = fmt.Errorf("net.Listen error: %v", err)
            return nil, err
        }
    }
    return ln.(*net.TCPListener), nil
}

3、父進程等待已有連接中未完成的請求處理完畢

這一塊是最復(fù)雜的;首先我們需要一個計數(shù)器,在成功Accept一個連接時,計數(shù)器加1,在連接關(guān)閉時計數(shù)減1,計數(shù)器為0時則父進程可以正常退出了。Golang的sync的包里的WaitGroup可以很好地實現(xiàn)這個功能。

然后要控制連接的建立和關(guān)閉,我們需要深入到net/http包中Server結(jié)構(gòu)體的Serve()方法。重溫第四部分Serve()方法的實現(xiàn),會發(fā)現(xiàn)如果要重新寫一個Serve()方法幾乎是不可能的,因為這個方法里調(diào)用了好多個不可導(dǎo)出的內(nèi)部方法,重寫Serve()方法幾乎要重寫整個net/http包。

幸運的是,我們還發(fā)現(xiàn)在 ListenAndServe()方法里傳遞了一個listener給Serve()方法,并最終調(diào)用了這個listener的Accept()方法,這個方法返回了一個Conn的示例,最終在連接斷開的時候會調(diào)用Conn的Close()方法,這些結(jié)構(gòu)體和方法都是可導(dǎo)出的!

我們可以定義自己的Listener結(jié)構(gòu)體和Conn結(jié)構(gòu)體,組合net/http包中對應(yīng)的結(jié)構(gòu)體,并重寫Accept()和Close()方法,實現(xiàn)對連接的計數(shù),相關(guān)代碼摘抄如下:

type Listener struct {
    *net.TCPListener

    waitGroup *sync.WaitGroup
}

func (this *Listener) Accept() (net.Conn, error) {

    tc, err := this.AcceptTCP()
    if err != nil {
        return nil, err
    }
    tc.SetKeepAlive(true)
    tc.SetKeepAlivePeriod(3 * time.Minute)

    this.waitGroup.Add(1)

    conn := &Connection{
        Conn:     tc,
        listener: this,
    }
    return conn, nil
}

func (this *Listener) Wait() {
    this.waitGroup.Wait()
}

type Connection struct {
    net.Conn
    listener *Listener

    closed bool
}

func (this *Connection) Close() error {

    if !this.closed {
        this.closed = true
        this.listener.waitGroup.Done()
    }

    return this.Conn.Close()
}

4、gracehttp包的用法

gracehttp包已經(jīng)應(yīng)用到每天幾億PV的項目中,也開源到了github上:github.com/tabalt/gracehttp,使用起來非常簡單。

如以下示例代碼,引入包后只需修改一個關(guān)鍵字,將http.ListenAndServe 改為 gracehttp.ListenAndServe即可。

package main

import (
    "fmt"
    "net/http"

    "github.com/tabalt/gracehttp"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello world")
    })

    err := gracehttp.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println(err)
    }
}

測試平滑升級(優(yōu)雅重啟)的效果,可以參考下面這個頁面的說明:

https://github.com/tabalt/gracehttp#demo

使用過程中有任何問題和建議,歡迎提交issue反饋,也可以Fork到自己名下修改之后提交pull request。

https://gocn.io/question/520
可以用golang自己實現(xiàn),但是只在類unix系統(tǒng)中有用,利用系統(tǒng)信號量和啟動子進程,將舊的socket描述符傳遞給新的socket描述符,github已經(jīng)有不少這樣的庫,很多golang的http框架也實現(xiàn)了。這種實現(xiàn)叫“graceful restart”或者“zero downtime server”,實現(xiàn)不中斷服務(wù)更新。
具體參考可以看看這些項目和幾篇文章:


posted on 2016-12-26 14:47 思月行云 閱讀(518) 評論(0)  編輯 收藏 引用 所屬分類: Golang
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产一区二区三区久久精品| 欧美岛国激情| 国产一区二区日韩精品| 欧美日韩亚洲一区| 欧美日韩午夜| 国产女精品视频网站免费| 国产精品一区亚洲| 国产亚洲成av人在线观看导航| 国产一区二区三区高清| 亚洲国产一区在线观看| 亚洲国产精品成人久久综合一区| 亚洲电影免费在线| 日韩视频免费观看高清在线视频 | 一区二区国产日产| 日韩亚洲综合在线| 亚洲人成网站999久久久综合| 欧美激情性爽国产精品17p| 欧美jizz19性欧美| 一本色道久久加勒比88综合| 欧美亚洲一区二区三区| 六月婷婷久久| 国产精品久久久久久久午夜| 国内精品国产成人| 亚洲深夜影院| 久久久精品欧美丰满| 亚洲电影在线看| 性色av香蕉一区二区| 欧美成人69| 国产亚洲一区二区三区| 亚洲啪啪91| 亚洲欧美日韩在线| 欧美成人午夜| 午夜免费日韩视频| 欧美精品二区| 一区二区在线免费观看| 亚洲欧美在线一区| 亚洲精品少妇30p| 久久久国产精品一区二区中文| 欧美日韩成人| 亚洲级视频在线观看免费1级| 性久久久久久| 一区二区黄色| 欧美日韩中文字幕在线视频| 亚洲国产日韩在线一区模特| 欧美在线亚洲在线| 亚洲一区三区电影在线观看| 欧美日韩aaaaa| 日韩亚洲国产欧美| 欧美激情一区二区三区高清视频 | 欧美区一区二区三区| 亚洲激情自拍| 亚洲韩国青草视频| 久久久久国产精品麻豆ai换脸| 国产精品久久77777| 一本大道久久a久久精二百| 亚洲第一区在线| 美女久久一区| 亚洲精品欧美日韩专区| 亚洲大胆人体视频| 欧美gay视频激情| 亚洲精品视频啊美女在线直播| 免费不卡视频| 久久在精品线影院精品国产| 精品99一区二区| 噜噜噜在线观看免费视频日韩 | 欧美激情1区2区| 亚洲精品少妇30p| 日韩一级免费| 国产精品永久免费视频| 欧美一区激情| 久久久久.com| 亚洲国产小视频| 亚洲久色影视| 国产精品视频免费| 久久亚洲精品中文字幕冲田杏梨| 久久精品亚洲精品| 亚洲国产日韩在线| 99视频在线观看一区三区| 国产精品久久久久久久久免费樱桃 | 好看的av在线不卡观看| 免费日韩av电影| 欧美高清免费| 午夜欧美精品| 久久婷婷麻豆| 亚洲免费小视频| 久久久7777| av成人免费| 欧美一级理论性理论a| 亚洲第一中文字幕| 一区二区三区毛片| 在线观看国产精品网站| 亚洲美女视频在线免费观看| 国产精品亚洲综合一区在线观看| 久久久久免费| 欧美日韩在线免费观看| 久久理论片午夜琪琪电影网| 欧美国产日韩一区二区| 性色av一区二区三区红粉影视| 久久婷婷麻豆| 欧美尤物一区| 欧美日韩在线电影| 欧美国产日韩免费| 国产精品入口| 亚洲破处大片| 韩国免费一区| 亚洲综合色丁香婷婷六月图片| 99re亚洲国产精品| 欧美大色视频| 久久久久久久综合狠狠综合| 欧美日韩精品不卡| 亚洲福利在线看| 好吊视频一区二区三区四区| 国产精品99久久久久久久久| 亚洲精品在线视频| 久久久久久久高潮| 欧美一级在线亚洲天堂| 欧美成人精品在线| 久久综合综合久久综合| 国产精品亚洲人在线观看| 亚洲人成毛片在线播放| 亚洲国产精品悠悠久久琪琪| 亚洲欧美日韩国产另类专区| 日韩视频在线一区二区三区| 久热精品视频在线观看| 羞羞视频在线观看欧美| 欧美日韩中文字幕在线视频| 欧美电影在线| 国产一区视频网站| 欧美一区二区三区四区在线观看地址| 亚洲网址在线| 欧美视频一区| 91久久综合亚洲鲁鲁五月天| 伊人色综合久久天天五月婷| 西西裸体人体做爰大胆久久久| 亚洲欧美成人| 国产精品日韩二区| 亚洲性图久久| 久久狠狠亚洲综合| 国产欧美日韩一区二区三区在线观看 | 亚洲最快最全在线视频| 亚洲精品美女久久久久| 欧美国产日韩一区二区在线观看| 久久综合久久88| 国产午夜精品久久| 午夜精品在线视频| 欧美在线中文字幕| 国产亚洲福利一区| 欧美一级淫片aaaaaaa视频| 亚洲欧美日韩精品一区二区| 欧美日韩亚洲综合| 亚洲国产美国国产综合一区二区| 日韩一级精品| 欧美日韩国产综合网| 日韩一级黄色片| 午夜精品网站| 国产精品一区二区在线观看| 亚洲天堂第二页| 乱码第一页成人| 亚洲精品一区二区在线观看| 欧美屁股在线| 午夜久久久久久久久久一区二区| 久久精品国产综合精品| 亚洲另类一区二区| 国产精品日韩久久久久| 久久av红桃一区二区小说| 欧美高清影院| 国产精品视频一二三| 欧美激情国产日韩| 一区二区福利| 国产精品美女久久久久久2018| 亚洲自拍啪啪| 欧美不卡一卡二卡免费版| 99视频日韩| 黄色影院成人| 欧美日韩另类国产亚洲欧美一级| 在线视频你懂得一区二区三区| 亚洲天堂成人在线观看| 国产精品一区二区久久久久| 久久在线91| 亚洲最新视频在线| 久久久噜噜噜久久久| 亚洲精品自在在线观看| 国产精品丝袜白浆摸在线| 欧美成人福利视频| 亚洲免费视频观看| 亚洲国产激情| 欧美一区二区三区在线免费观看| 揄拍成人国产精品视频| 免费成人你懂的| 欧美在线视频免费| 最新国产成人在线观看| 国产精品视频1区| 老司机午夜精品| 亚洲精品美女在线| 激情文学综合丁香| 欧美网站大全在线观看| 久久人人爽国产| 午夜欧美精品| 亚洲精品中文字| 亚洲国产日韩欧美在线图片|