我正在尝试找到一种最佳方法来在 Kubernetes 上运行的 golang 服务器关闭期间处理正在进行的 PostgreSQL 事务。
当这些事务正在服务由已关闭的服务器发起的请求时,等待事务完成是否有意义?即使事务在正常关闭超时时间内完成 - 服务器是否能够发送响应?
即使无法在关闭期间响应正在进行的请求,我也更愿意取消所有正在运行的事务的上下文,以便它们在服务器终止后不会继续在数据库上运行,从而增加不必要的负载。但每当我等待事务完成时,似乎都存在一个权衡:等待正在进行的事务完成的时间越长,容器与无响应的服务器一起存在的时间就越长,该服务器在每个传入请求上都会出错。
这里有一些示例代码演示了这一点:
import (
"github.com/jackc/pgx/v5/pgxpool"
"os/signal"
"context"
"net/http"
"syscall"
"time"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
defer cancel()
// db is used by the API handler functions
db, err := pgxpool.NewWithConfig(ctx, <some_config>)
if err != nil {
logger.Error("server failed to Shutdown", err)
}
server := http.Server{<some_values>}
serverErr := make(chan error)
go func() {
serverErr <- server.ListenAndServe()
}()
select {
case <-ctx.Done():
if err := Shutdown(closeCtx, time.Second*10, server, db); err != nil {
logger.Error("server failed to Shutdown", err)
}
case err := <-serverErr:
logger.Error("server failed to ListenAndServe", err)
}
}
func Shutdown(ctx context.Context, timeout time.Duration, server *http.Server, db *pgxpool.Pool) error {
closeCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// first, shutdown the server to stop accepting new requests
if err := server.Shutdown(closeCtx); err != nil {
return err
}
// allow running transactions to finish, but if they don't finish within
// ten seconds, cancel the context of all running transactions so that they
// are forced to finish (albeit, with error)
transactionsComplete := waitForTransacitons(time.Second*10, db)
if !transactionsComplete {
cancelContextOfEveryTransaction()
}
// since this call blocks until all transactions finished we must call it
// only once we are sure that there are no more running transactions.
db.Close(ctx)
return nil
}
最佳的优雅终止序列是:
[编辑]:替代终止序列(更优雅):
server.Shutdown
返回,不会返回任何回复。等待正在进行的事务的唯一原因是后台工作人员完成其工作,例如将日志写入数据库。为什么要重新发明轮子而不使用一些现有的库,这对你来说很神奇?
在我们的生产服务中,我们经常使用这个优雅的关闭库并且从未遇到过问题。它会等待,直到所有 HTTP 请求都得到服务(在给定的超时时间内),然后关闭。
使用方法再简单不过了。安装后
go mod download github.com/TV4/graceful
(最终:
go get -u github.com/TV4/graceful
)
您只需导入它:
import (
// ...
"github.com/TV4/graceful"
)
然后您可以在实例化
server
(包括您的关闭函数)后用以下一行代码替换所有代码:
server := ...
graceful.LogListenAndServe(server, logger)