如何通过多进程提高Gin吞吐
以及优雅停机「独立协程接收停机/终止信号」
确保最终被关闭协程的服务日志可被记录
实现
StartGinServices控制同时启动服务进程数量 传入 全局WG实例
var (
GinInstanceWG sync.WaitGroup
)
server.StartGinServices(&GinInstanceWG, 4)
GinInstanceWG.Wait() // 等待所有服务启动的goroutine完成
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"golang.org/x/sync/errgroup"
)
type ServerInstance struct {
Http *http.Server
}
var (
DefaultAddr = 48080
ServerInstances []ServerInstance
acceptingRequests int32 = 1 // 原子标记位,初始允许接受请求
)
服务启停管理
func startGinServices(wg *sync.WaitGroup, starInstance ...int) {
var (
g errgroup.Group
)
if len(starInstance) != 0 && starInstance[0] != 0 {
for i := DefaultAddr; i < DefaultAddr+starInstance[0]; i++ {
httpServer := &http.Server{
Addr: ":" + strconv.Itoa(i),
Handler: SetupRouter(),
ReadTimeout: 300 * time.Second,
WriteTimeout: 300 * time.Second,
}
ServerInstances = append(ServerInstances, ServerInstance{
Http: httpServer,
})
server := httpServer
fmt.Printf("将启动:%v个实例,当前实例监听: %v\n", starInstance[0], server.Addr)
g.Go(func() error {
fmt.Printf("实例:%s 启动成功\n", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("实例:%s 启动失败:%v\n", server.Addr, err)
AccessLogger.Sugar().Errorf("实例:%s 启动失败:%v\n", server.Addr, err)
return err
}
return nil
})
}
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-stop
atomic.StoreInt32(&acceptingRequests, 0) // Stop accepting new requests
for _, server := range ServerInstances {
wg.Add(1) // 在启动服务器协程前增加
go ShutdownGinServices(server.Http, 5*time.Second, wg)
}
# 独立异步协程用来接收停止信号,并确保所有停止进程结束后,日志仍然可被记录
wg.Wait() // 等待所有服务器关闭和日志操作完成
}()
// 任意协程内ListenAndServe失败,会执行至这里得到错误;或所有成功执行,得到nil
if err := g.Wait(); err != nil {
fmt.Println("Error waiting for servers to start:", err)
}
fmt.Println("所有Gin进程,shutdown完毕")
AccessLogger.Sugar().Info("所有Gin进程,shutdown完毕")
}
}
- 通过更新全局原子标记位
acceptingRequests
来阻止新的请求被接受。这是通过atomic.StoreInt32(&acceptingRequests, 0)
实现的,确保所有新的入站请求都会被拒绝,从而减少正在处理的请求量,为服务关闭做准备。- 遍历所有服务器实例,并对每个实例调用
ShutdownGinServices
函数,传递一个超时时间和sync.WaitGroup
。这个函数尝试优雅地关闭服务器,允许当前正在处理的请求在超时时间内完成。- 使用
wg.Add(1)
在调用ShutdownGinServices
前递增WaitGroup
的计数,确保主协程在所有服务器关闭操作完成前不会退出。- 使用
wg.Wait()
阻塞协程,直到所有的ShutdownGinServices
操作(每个操作在完成时调用wg.Done()
)都执行完毕。这确保了在程序退出前,所有服务器都已经被正确关闭,所有关键的日志操作也已完成。
func StartGinServices(wg *sync.WaitGroup, starInstance ...int) {
startGinServices(wg, starInstance...) // 启动所有服务
}
func ShutdownGinServices(server *http.Server, timeout time.Duration, wg *sync.WaitGroup) {
defer wg.Done() // 确保在函数退出时调用 Done
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Printf("Gin实例:%v,执行shutdown失败:%v\n", server.Addr, err)
ErrorLogger.Sugar().Infof("Gin实例:%v,执行shutdown成功", server.Addr)
} else {
AccessLogger.Sugar().Infof("Gin实例:%v,执行shutdown成功", server.Addr)
fmt.Printf("Gin实例:%v,执行shutdown成功\n", server.Addr)
}
}
日志
最后一个被关闭的服务进程,其操作日志不会丢失
{"level":"info","timestamp":"2024-05-11 19:03:16.329","logger":"access","msg":"Gin实例::48083,执行shutdown成功"}
{"level":"info","timestamp":"2024-05-11 19:03:16.329","logger":"access","msg":"Gin实例::48082,执行shutdown成功"}
{"level":"info","timestamp":"2024-05-11 19:03:16.329","logger":"access","msg":"Gin实例::48081,执行shutdown成功"}
{"level":"info","timestamp":"2024-05-11 19:03:16.329","logger":"access","msg":"Gin实例::48080,执行shutdown成功"}
{"level":"info","timestamp":"2024-05-11 19:03:16.329","logger":"access","msg":"所有Gin进程,shutdown完毕"}