看完此篇你会知道,如何优雅的使用 HTTP Server
问题背景
httpkill -9
RSTconnection refusedkill -9open too many files
Zero Downtime
热启动Zero Downtime
解决问题
平滑启动
一般情况下,我们是退出旧版本,再启动新版本,总会有时间间隔,时间间隔内的请求怎么办?而且旧版本正在处理请求怎么办?
那么,针对这些问题,在升级应用过程中,我们需要达到如下目的:
- 旧版本为退出之前,需要先启动新版本;
- 旧版本继续处理完已经接受的请求,并且不再接受新请求;
- 新版本接受并处理新请求的方式;
Zero Downtime
实现原理
linuxsignalsignal-continue
HUPkill -HUP pid
package gracehttp
import (
"fmt"
"os"
"os/signal"
"syscall"
)
var sig chan os.Signal
var notifySignals []os.Signal
func init() {
sig = make(chan os.Signal)
notifySignals = append(notifySignals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGTSTP, syscall.SIGQUIT)
signal.Notify(sig, notifySignals...) // 注册需要拦截的信号
}
// 捕获系统信号,并处理
func handleSignals() {
capturedSig := <-sig
srvLog.Info(fmt.Sprintf("Received SIG. [PID:%d, SIG:%v]", syscall.Getpid(), capturedSig))
switch capturedSig {
case syscall.SIGHUP: // 重启信号
startNewProcess() // 开启新进程
shutdown() // 退出旧进程
case syscall.SIGINT:
fallthrough
case syscall.SIGTERM:
fallthrough
case syscall.SIGTSTP:
fallthrough
case syscall.SIGQUIT:
shutdown()
}
}
startNewProcessshutdown
过载保护
HTTP Serveraccept
实现原理
channelgoselectaccept
处理代码如下:
package gracehttp
// about limit @see: "golang.org/x/net/netutil"
import (
"net"
"sync"
"time"
)
type Listener struct {
*net.TCPListener
sem chan struct{}
closeOnce sync.Once // ensures the done chan is only closed once
done chan struct{} // no values sent; closed when Close is called
}
func newListener(tl *net.TCPListener, n int) net.Listener {
return &Listener{
TCPListener: tl,
sem: make(chan struct{}, n),
done: make(chan struct{}),
}
}
func (l *Listener) Fd() (uintptr, error) {
file, err := l.TCPListener.File()
if err != nil {
return 0, err
}
return file.Fd(), nil
}
// override
func (l *Listener) Accept() (net.Conn, error) {
acquired := l.acquire()
tc, err := l.AcceptTCP()
if err != nil {
if acquired {
l.release()
}
return nil, err
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(time.Minute)
return &ListenerConn{Conn: tc, release: l.release}, nil
}
// override
func (l *Listener) Close() error {
err := l.TCPListener.Close()
l.closeOnce.Do(func() { close(l.done) })
return err
}
// acquire acquires the limiting semaphore. Returns true if successfully
// accquired, false if the listener is closed and the semaphore is not
// acquired.
func (l *Listener) acquire() bool {
select {
case <-l.done:
return false
case l.sem <- struct{}{}:
return true
}
}
func (l *Listener) release() { <-l.sem }
type ListenerConn struct {
net.Conn
releaseOnce sync.Once
release func()
}
func (l *ListenerConn) Close() error {
err := l.Conn.Close()
l.releaseOnce.Do(l.release)
return err
}
gracehttp
现在我们把这个功能做得更优美有点,并提供一个开箱即用的代码库。
地址:Github-gracehttp
支持功能
Zero-DowntimeServerHTTPHTTPS
使用指南
添加服务
import "fevin/gracehttp"
....
// http
srv1 := &http.Server{
Addr: ":80",
Handler: sc,
}
gracehttp.AddServer(srv1, false, "", "")
// https
srv2 := &http.Server{
Addr: ":443",
Handler: sc,
}
gracehttp.AddServer(srv2, true, "../config/https.crt", "../config/https.key")
gracehttp.Run() // 此方法会阻塞,直到进程收到退出信号,或者 panic
Servergracehttp.AddServer
退出或者重启服务
kill -HUP pidkill -QUIT pid
添加自定义日志组件
gracehttp.SetErrorLogCallback(logger.LogConfigLoadError)
Set*
SetInfoLogCallbackSetNoticeLogCallbackSetErrorLogCallback
最后
实际中,很多情况会用到这种方式,不妨点个 star 吧!
欢迎一起来完善这个小项目,共同贡献代码。