在工作中,有時(shí)需要對http服務(wù)實(shí)現(xiàn)多監(jiān)聽,http服務(wù)重啟等需求。大多數(shù)web框架只實(shí)現(xiàn)的是單ip監(jiān)聽,要實(shí)現(xiàn)多ip監(jiān)聽就需要循環(huán)監(jiān)聽ip;
而重啟http服務(wù),首先想到的是用endless來優(yōu)雅的實(shí)現(xiàn)服務(wù)的重啟,但是當(dāng)多ip監(jiān)聽時(shí),一個(gè)項(xiàng)目不能用一個(gè)endLess,多了會(huì)報(bào)錯(cuò),且windows環(huán)境也無法實(shí)現(xiàn)重啟;
所以,我在工作中最終使用了gracehttp(grace)來實(shí)現(xiàn)多ip監(jiān)聽及優(yōu)雅的重啟,但是grace也是只能做到linux的重啟和啟動(dòng),應(yīng)為window沒有定義signal,這里需要吐槽一下window;最借鑒了一下另外一個(gè)開源包window gracehttp,是多grace的擴(kuò)展,實(shí)現(xiàn)了http服務(wù)在window的多ip監(jiān)聽,但是重啟一樣報(bào)錯(cuò)(window 不支持signal), 只能再次修改開源包.
話不多說上代碼:
linux版:
//go:build !windows
// +build !windows
package utils
import (
"crypto/tls"
"github.com/facebookgo/grace/gracehttp"
"github.com/labstack/echo/v4"
"net/http"
"sync"
"time"
)
/**
http server 優(yōu)雅地重啟
*/
var grace *GraceHttp
var gOnce sync.Once
var SysRestart = make(chan int)
type GraceHttp struct {
SrvList sync.Map
}
func NewGrace() *GraceHttp {
gOnce.Do(func() {
grace = &GraceHttp{}
})
return grace
}
func (g *GraceHttp) AddService(name string, srv *http.Server) {
g.SrvList.Store(name, srv)
}
func (g *GraceHttp) Run() {
srvs := make([]*http.Server, 0)
g.SrvList.Range(func(key, value any) bool {
srvs = append(srvs, value.(*http.Server))
g.SrvList.Delete(key)
return true
})
// 調(diào)用grace啟動(dòng)
if err := gracehttp.ServeWithOptions(srvs); err != nil {
panic(err)
}
}
func InitServer(address string, router *echo.Echo, cfg *tls.Config) *http.Server {
s := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if cfg != nil {
s.TLSConfig = cfg
}
return s
}
windows版:
//go:build windows
// +build windows
package utils
import (
"crypto/tls"
"github.com/labstack/echo/v4"
"net/http"
"sync"
"time"
)
var grace *GraceHttp
var gOnce sync.Once
var SysRestart = make(chan int)
type GraceHttp struct {
SrvList sync.Map
}
func NewGrace() *GraceHttp {
gOnce.Do(func() {
grace = &GraceHttp{}
})
return grace
}
func (g *GraceHttp) AddService(name string, srv *http.Server) {
g.SrvList.Store(name, srv)
}
func (g *GraceHttp) Run() {
srvs := make([]*http.Server, 0)
g.SrvList.Range(func(key, value any) bool {
srvs = append(srvs, value.(*http.Server))
g.SrvList.Delete(key)
return true
})
// g.SrvList = sync.Map{}
// 使用改版的grace
if err := Serve(srvs...); err != nil {
panic(err)
}
}
func InitServer(address string, router *echo.Echo, cfg *tls.Config) *http.Server {
s := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if cfg != nil {
s.TLSConfig = cfg
}
return s
}
改版的windows grace,注這是對開源代碼的改版。
package utils
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"github.com/facebookgo/grace/gracenet"
"github.com/facebookgo/httpdown"
"log"
"net"
"net/http"
"os"
"sync"
"syscall"
)
var (
verbose = flag.Bool("gracehttp.log", true, "Enable logging.")
didInherit = os.Getenv("LISTEN_FDS") != ""
ppid = os.Getppid()
)
const SIGUSR2 = syscall.Signal(0x1f)
// An app contains one or more servers and associated configuration.
type app struct {
servers []*http.Server
http *httpdown.HTTP
net *gracenet.Net
listeners []net.Listener
sds []httpdown.Server
errors chan error
}
func newApp(servers []*http.Server) *app {
return &app{
servers: servers,
http: &httpdown.HTTP{},
net: &gracenet.Net{},
listeners: make([]net.Listener, 0, len(servers)),
sds: make([]httpdown.Server, 0, len(servers)),
// 2x num servers for possible Close or Stop errors + 1 for possible
// StartProcess error.
errors: make(chan error, 1+(len(servers)*2)),
}
}
func (a *app) listen() error {
for _, s := range a.servers {
// TODO: default addresses
l, err := a.net.Listen("tcp", s.Addr)
if err != nil {
return err
}
if s.TLSConfig != nil {
l = tls.NewListener(l, s.TLSConfig)
}
a.listeners = append(a.listeners, l)
}
return nil
}
func (a *app) serve() {
for i, s := range a.servers {
a.sds = append(a.sds, a.http.Serve(s, a.listeners[i]))
}
}
func (a *app) wait() {
var wg sync.WaitGroup
fmt.Println("wg lent:", len(a.sds))
wg.Add(len(a.sds) * 2) // Wait & Stop
fmt.Printf("wg:%+v", wg)
go a.signalHandler(&wg)
for _, s := range a.sds {
go func(s httpdown.Server) {
fmt.Println("等待啟動(dòng).........")
defer wg.Done()
if err := s.Wait(); err != nil {
a.errors <- err
}
}(s)
}
wg.Wait()
}
var WinRestart = make(chan int)
//func (a *app) term(wg *sync.WaitGroup) {
// for _, s := range a.sds {
// go func(s httpdown.Server) {
// defer wg.Done()
// if err := s.Stop(); err != nil {
// a.errors <- err
// }
// }(s)
// }
//}
func (a *app) signalHandler(wg *sync.WaitGroup) {
select {
case <-WinRestart:
fmt.Println("收到重啟請求....")
for _, s := range a.sds {
fmt.Println("關(guān)閉http。。。。。。。。")
if err := s.Stop(); err != nil {
a.errors <- err
}
wg.Done()
}
}
SysRestart <- 1
}
// Serve will serve the given http.Servers and will monitor for signals
// allowing for graceful termination (SIGTERM) or restart (SIGUSR2).
func Serve(servers ...*http.Server) error {
a := newApp(servers)
// Acquire Listeners
if err := a.listen(); err != nil {
return err
}
// Some useful logging.
if *verbose {
if didInherit {
if ppid == 1 {
log.Printf("Listening on init activated %s", pprintAddr(a.listeners))
} else {
const msg = "Graceful handoff of %s with new pid %d and old pid %d"
log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid)
}
} else {
const msg = "Serving %s with pid %d"
log.Printf(msg, pprintAddr(a.listeners), os.Getpid())
}
}
// Start serving.
a.serve()
// Close the parent if we inherited and it wasn't init that started us.
if didInherit && ppid != 1 {
if err := terminateProcess(ppid); err != nil {
return fmt.Errorf("failed to close parent: %s", err)
}
}
waitdone := make(chan struct{})
go func() {
defer close(waitdone)
a.wait()
}()
select {
case err := <-a.errors:
if err == nil {
panic("unexpected nil error")
}
return err
case <-waitdone:
if *verbose {
log.Printf("Exiting pid %d.", os.Getpid())
}
return nil
}
}
// Used for pretty printing addresses.
func pprintAddr(listeners []net.Listener) []byte {
var out bytes.Buffer
for i, l := range listeners {
if i != 0 {
fmt.Fprint(&out, ", ")
}
fmt.Fprint(&out, l.Addr())
}
return out.Bytes()
}
func terminateProcess(pid int) error {
process, err := os.FindProcess(pid)
if err != nil {
return err
}
return process.Signal(syscall.SIGTERM)
}
在需要重啟的地方調(diào)用重啟方法:
func Reload() error {
linuxVersion, err := GetLinuxPlatformFamily()
if err != nil {
return err
}
var cmd *exec.Cmd
pid := os.Getpid()
switch linuxVersion {
case "windows":
go func() {
fmt.Println("發(fā)送重啟命令")
WinRestart <- 1
}()
return nil
case LINUXRHEL:
cmd = exec.Command("bash", "-c", "rpm -qa | grep -i supervisor")
case LINUXDEBIAN:
cmd = exec.Command("bash", "-c", "service --status-all |grep supervisor")
}
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Run()
// 安裝了supervisor,就讓supervisor拉起,否則重啟
if out.String() != "" && strings.Contains(out.String(), "[ + ] supervisor") {
os.Exit(0)
return nil
}
cmd = exec.Command("kill", "-USR2", strconv.Itoa(pid))
return cmd.Run()
}
最后在main里面實(shí)現(xiàn)一個(gè)守護(hù)進(jìn)程,進(jìn)行windows的重啟:
// 守護(hù)進(jìn)程用于windows重啟
func (e *Engine) windowsDeamon() {
if runtime.GOOS == "windows" {
for {
select {
case <-utils.SysRestart:
fmt.Println("windows deamon program server restart................")
x.SafeGo(func() {
e.runBackend()
e.api.Start()
utils.NewGrace().Run()
})
}
}
}
}
最后就實(shí)現(xiàn)了,http 服務(wù)在windows和linux的多ip監(jiān)聽及優(yōu)雅重啟。?
注意事項(xiàng):文章來源:http://www.zghlxwxcb.cn/news/detail-411092.html
在wg.done后面執(zhí)行channel之類的阻塞,可能會(huì)出現(xiàn)wg.done成負(fù)值錯(cuò)誤,需要控制在毫米級以下。?文章來源地址http://www.zghlxwxcb.cn/news/detail-411092.html
到了這里,關(guān)于golang http服務(wù)實(shí)現(xiàn)多ip監(jiān)聽,及優(yōu)雅重啟的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!