乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Golang開發(fā)支持平滑升級(優(yōu)雅重啟)的HTTP服務(wù)(轉(zhuǎn))

       博雅書屋lhs 2017-04-23

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

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

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

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

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

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

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

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

      5. 新進(jìn)程在父進(jìn)程退出后,會被init進(jìn)程領(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包中,我們從一個(gè)簡單的例子來學(xué)習(xí)一下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)接收客戶端的連接,沒有連接時(shí)會阻塞,出錯(cuò)則跳出循環(huán)
      		conn, err := listener.Accept()
      		if err != nil {
      			fmt.Println(err)
      			break
      		}
      
      		fmt.Println("[server] accept new connection.")
      
      		// 啟動一個(gè)goroutine 處理連接
      		go handler(conn)
      	}
      }
      
      func handler(conn net.Conn) {
      	defer conn.Close()
      
      	for {
      		// 循環(huán)從連接中 讀取請求內(nèi)容,沒有請求時(shí)會阻塞,出錯(cuò)則跳出循環(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和時(shí)間后向客戶端輸出
      		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() {
      
      	// 從命令行中讀取第二個(gè)參數(shù)作為名字,如果不存在第二個(gè)參數(shù)則報(bào)錯(cuò)退出
      	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)時(shí)會阻塞
      		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、運(yùn)行示例程序

      # 運(yùn)行服務(wù)端程序
      go run server.go
      
      # 在另一個(gè)命令行窗口運(yùn)行客戶端程序
      go run client.go "tabalt"
      
      

      三、Golang HTTP 編程

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

      我們再從一個(gè)簡單的例子來學(xué)習(xí)一下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、運(yùn)行示例程序

      # 運(yùn)行HTTP服務(wù)程序
      go run http.go
      
      # 在另一個(gè)命令行窗口curl請求測試頁面
      curl http://localhost:8086/hello/
      
      # 輸出如下內(nèi)容:
      http hello on golang
      
      

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

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

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

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

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

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

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

      這里創(chuàng)建了一個(gè)Server的對象,并調(diào)用它的ListenAndServe()方法,我們再找到結(jié)構(gòu)體Server的ListenAndServe()方法的實(shí)現(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)聽者包裝成了一個(gè)結(jié)構(gòu)體 tcpKeepAliveListener,再調(diào)用srv.Serve()方法;我們繼續(xù)跟蹤Serve()方法的實(shí)現(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連接,如果返回了一個(gè)net.Error并且這個(gè)錯(cuò)誤是臨時(shí)性的,則會sleep一個(gè)時(shí)間再繼續(xù)。 如果返回了其他錯(cuò)誤則會終止循環(huán)。成功Accept到一個(gè)連接后,調(diào)用了方法srv.newConn()對連接做了一層包裝,最后啟了一個(gè)goroutine處理http請求。

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

      我創(chuàng)建了一個(gè)新的包gracehttp來實(shí)現(xiàn)支持平滑升級(優(yōu)雅重啟)的HTTP服務(wù),為了少寫代碼和降低使用成本,新的包盡可能多地利用net/http包的實(shí)現(xiàn),并和net/http包保持一致的對外方法?,F(xiàn)在開始我們來看gracehttp包支持平滑升級 (優(yōu)雅重啟)Golang HTTP服務(wù)涉及到的細(xì)節(jié)如何實(shí)現(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,
      	)
      
      	// 輸出當(dāng)前進(jìn)程的pid
      	fmt.Println("pid is: ", os.Getpid())
      
      	// 處理信號
      	for {
      		sig := <-signalChan
      		fmt.Println("get signal: ", sig)
      	}
      }
      

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

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

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

      // 啟動子進(jì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è)置標(biāo)識優(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、父進(jìn)程等待已有連接中未完成的請求處理完畢

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

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

      幸運(yùn)的是,我們還發(fā)現(xiàn)在 ListenAndServe()方法里傳遞了一個(gè)listener給Serve()方法,并最終調(diào)用了這個(gè)listener的Accept()方法,這個(gè)方法返回了一個(gè)Conn的示例,最終在連接斷開的時(shí)候會調(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()方法,實(shí)現(xiàn)對連接的計(jì)數(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的項(xiàng)目中,也開源到了github上:github.com/tabalt/gracehttp,使用起來非常簡單。

      如以下示例代碼,引入包后只需修改一個(gè)關(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)雅重啟)的效果,可以參考下面這個(gè)頁面的說明:

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

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

      本文鏈接:http:///blog/graceful-http-server-for-golang/,轉(zhuǎn)載請注明。

        本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多