优雅终止 | 高雅模版 | 基于 go 的 http 库实现

这篇具有很好参考价值的文章主要介绍了优雅终止 | 高雅模版 | 基于 go 的 http 库实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

优雅终止 | 高雅模版 | 基于 go 的 http 库实现,golang,golang,http,开发语言,后端,服务器

博客原文

源码地址

思考优雅终止目标

  1. 应用开始关闭时, 对于服务器的连接
    1. 对已有连接: 等待其处理
    2. 拒绝新请求
  2. 是否需要将 cache 中数据保存到 db
  3. 释放服务器资源

对象关系图

应用启动:

优雅终止 | 高雅模版 | 基于 go 的 http 库实现,golang,golang,http,开发语言,后端,服务器

优雅终止:

优雅终止 | 高雅模版 | 基于 go 的 http 库实现,golang,golang,http,开发语言,后端,服务器

优雅的优雅终止实现

需监听的信号量

windows
var Signals = []os.Signal{
	os.Interrupt, os.Kill, syscall.SIGKILL,
	syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,
	syscall.SIGABRT, syscall.SIGTERM,
}
linux
var Signals = []os.Signal{
	os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP,
	syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,
	syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM,
}

Server

一个 server 对应一个 service

定义
// Server 对应一个服务
type Server struct {
	svc  *http.Server
	name string
	mux  *serverMux
}

// serverMux 请求锁
// 装饰器模式
type serverMux struct {
	reject bool
	*http.ServeMux
}

其中使用 http.ServeMux 是为确保一个 server, 只能持有一个正在处理的 request

new

传入 server 名字, 监听地址

func NewServer(name, addr string) *Server {
	mux := &serverMux{ServeMux: http.NewServeMux()}
	return &Server{
		name: name,
		mux:  mux,
		svc: &http.Server{
			Handler: mux,
			Addr:    addr,
		},
	}
}
server 的启动与终止
func (s *Server) Start() error {
	return s.svc.ListenAndServe()
}

func (s *Server) Handle(pattern string, handler http.HandlerFunc) {
	s.mux.Handle(pattern, handler)
}

func (s *Server) reject() {
	s.mux.reject = true
}

func (s *Server) stop(ctx context.Context) error {
	log.Println("server: ", s.name, "关闭中")
	return s.svc.Shutdown(ctx)
}

App

定义
// 扩展 App (option 设计模式)
type AppOption func(app *App)

type App struct {
	servers []*Server

	// app 关闭的最长时间
	shutdownTimeout time.Duration

	// 留给 server 处理已有请求的时间
	waitTime time.Duration

	// 回调函数超时控制
	cbTime time.Duration

	cbs []ShutdownCallBack
}
new
// NewApp 新建 App 
// 需用户传入 server
func NewApp(servers []*Server, opts ...AppOption) *App {
	app := &App{
		servers:         servers,
		shutdownTimeout: time.Second * 30,
		waitTime:        time.Second * 10,
		cbTime:          3 * time.Second,
	}
	for _, opt := range opts {
		opt(app)
	}
	return app
}

// WithShutDownCallBack 向 App 传入 callback 函数
func WithShutDownCallBack(cbs ...ShutdownCallBack) AppOption {
	return func(app *App) {
		app.cbs = cbs
	}
}
app 启动与优雅终止
func (app *App) StartAndServer() {
	// 启动服务
	for _, s := range app.servers {
		svc := s
		go func() {
			if err := svc.Start(); err != nil {
				if errors.Is(err, http.ErrServerClosed) {
					log.Printf("服务器%s已关闭", svc.name)
				} else {
					log.Printf("服务器%s异常退出", svc.name)
				}
			}
		}()
	}
	
	log.Println("应用启动成功")

	// app 启动成功
	// 监听退出信号
	// 监听两次信号, 第一次优雅终止, 第二次强行终止
	ch := make(chan os.Signal, 2)
	signal.Notify(ch, sig.Signals...)
	<-ch
	fmt.Println("应用开始关闭...")
	go func() {
		select {
		case <-ch:
			log.Println("强制退出")
			os.Exit(1)
		case <-time.After(app.shutdownTimeout):
			log.Println("超时强行退出")
			os.Exit(1)
		}
	}()

	app.shutdown(context.Background())
}

func (app *App) shutdown(ctx context.Context) {
	log.Println("app start to shutdown")
	// 将 app 下所有 server 拒绝新请求
	for _, svc := range app.servers {
		svc.reject()
	}
	
	log.Println("等待已有请求处理")
	// 这里可以改造为实时统计正在处理的请求数量,为0 则下一步
	time.Sleep(time.Second * app.waitTime)
	
	log.Println("开始关闭应用")
	var wg sync.WaitGroup
	wg.Add(len(app.servers))
	for _, s := range app.servers {
		svc := s
		go func() {
			if err := svc.stop(ctx);err != nil {
				log.Println("服务器关闭失败", svc.name)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	
	// 执行回调函数
	wg.Add(len(app.cbs))
	log.Println("开始执行回调函数")
	for _, cb := range app.cbs {
		c := cb
		go func() {
			ctx2, cancal := context.WithCancel(ctx)
			c(ctx2)
			cancal()
			wg.Done()
		}()
	}
	wg.Wait()
	
	// 释放资源
	log.Println("开始释放资源")
	app.close()
}

CallBackFunc

执行时间: 在优雅终止期间 app 确保所有 server 已关闭后调用

由用户自定义实现

示例
func StoreCacheToDBCallBack(ctx context.Context) {
	done := make(chan struct{}, 1)
	go func() {
		// 这里将 cache 中数据刷到 db
		log.Println("缓存刷新中...")
		time.Sleep(time.Second)
		done <- struct{}{}
	}()

	select {
	case <-done:
		log.Printf("缓存被刷新到了 DB")
	case <-ctx.Done():
		log.Printf("刷新缓存超时")
	}
}

完整代码

main.go

package main

import (
	"context"
	"github.com/Ai-feier/http/server"
	"log"
	"net/http"
	"time"
)

func main() {
	// 新建两个 server
	s1 := server.NewServer("service1", "localhost:8081")
	s1.Handle("/", func(writer http.ResponseWriter, request *http.Request) {
		_, _ = writer.Write([]byte("hello world"))
	})
	s2 := server.NewServer("service2", "localhost:8082")
	
	// 新建应用, 包含 service1, service2
	app := server.NewApp([]*server.Server{s1, s2}, server.WithShutDownCallBack(StoreCacheToDBCallBack))
	app.StartAndServer()
}

func StoreCacheToDBCallBack(ctx context.Context) {
	done := make(chan struct{}, 1)
	go func() {
		// 这里将 cache 中数据刷到 db
		log.Println("缓存刷新中...")
		time.Sleep(time.Second)
		done <- struct{}{}
	}()

	select {
	case <-done:
		log.Printf("缓存被刷新到了 DB")
	case <-ctx.Done():
		log.Printf("刷新缓存超时")
	}
}

server.go

package server

import (
	"context"
	"errors"
	"fmt"
	"github.com/Ai-feier/sig"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

type AppOption func(app *App)

// ShutdownCallBack App 关闭时的回调函数
// 默认超时 3s
// 用户可通过 context 自行控制
type ShutdownCallBack func(ctx context.Context)

// WithShutDownCallBack 向 App 传入 callback 函数
func WithShutDownCallBack(cbs ...ShutdownCallBack) AppOption {
	return func(app *App) {
		app.cbs = cbs
	}
}

type App struct {
	servers []*Server

	// app 关闭的最长时间
	shutdownTimeout time.Duration

	// 留给 server 处理已有请求的时间
	waitTime time.Duration

	// 回调函数超时控制
	cbTime time.Duration

	cbs []ShutdownCallBack
}

// NewApp 新建 App 
// 需用户传入 server
func NewApp(servers []*Server, opts ...AppOption) *App {
	app := &App{
		servers:         servers,
		shutdownTimeout: time.Second * 30,
		waitTime:        time.Second * 10,
		cbTime:          3 * time.Second,
	}
	for _, opt := range opts {
		opt(app)
	}
	return app
}

func (app *App) StartAndServer() {
	// 启动服务
	for _, s := range app.servers {
		svc := s
		go func() {
			if err := svc.Start(); err != nil {
				if errors.Is(err, http.ErrServerClosed) {
					log.Printf("服务器%s已关闭", svc.name)
				} else {
					log.Printf("服务器%s异常退出", svc.name)
				}
			}
		}()
	}
	
	log.Println("应用启动成功")

	// app 启动成功
	// 监听退出信号
	// 监听两次信号, 第一次优雅终止, 第二次强行终止
	ch := make(chan os.Signal, 2)
	signal.Notify(ch, sig.Signals...)
	<-ch
	fmt.Println("应用开始关闭...")
	go func() {
		select {
		case <-ch:
			log.Println("强制退出")
			os.Exit(1)
		case <-time.After(app.shutdownTimeout):
			log.Println("超时强行退出")
			os.Exit(1)
		}
	}()

	app.shutdown(context.Background())
}

func (app *App) shutdown(ctx context.Context) {
	log.Println("app start to shutdown")
	// 将 app 下所有 server 拒绝新请求
	for _, svc := range app.servers {
		svc.reject()
	}
	
	log.Println("等待已有请求处理")
	// 这里可以改造为实时统计正在处理的请求数量,为0 则下一步
	time.Sleep(time.Second * app.waitTime)
	
	log.Println("开始关闭应用")
	var wg sync.WaitGroup
	wg.Add(len(app.servers))
	for _, s := range app.servers {
		svc := s
		go func() {
			if err := svc.stop(ctx);err != nil {
				log.Println("服务器关闭失败", svc.name)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	
	// 执行回调函数
	wg.Add(len(app.cbs))
	log.Println("开始执行回调函数")
	for _, cb := range app.cbs {
		c := cb
		go func() {
			ctx2, cancal := context.WithCancel(ctx)
			c(ctx2)
			cancal()
			wg.Done()
		}()
	}
	wg.Wait()
	
	// 释放资源
	log.Println("开始释放资源")
	app.close()
}

func (app *App) close() {
	// 可补充释放资源逻辑
	time.Sleep(time.Second)
	log.Println("应用关闭")
}

// Server 对应一个服务
type Server struct {
	svc  *http.Server
	name string
	mux  *serverMux
}

// serverMux 请求锁
//装饰器模式
type serverMux struct {
	reject bool
	*http.ServeMux
}

func (s *serverMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if s.reject {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("服务器已关闭"))
		return
	}
	s.ServeMux.ServeHTTP(w, r)
}

func NewServer(name, addr string) *Server {
	mux := &serverMux{ServeMux: http.NewServeMux()}
	return &Server{
		name: name,
		mux:  mux,
		svc: &http.Server{
			Handler: mux,
			Addr:    addr,
		},
	}
}

func (s *Server) Start() error {
	return s.svc.ListenAndServe()
}

func (s *Server) Handle(pattern string, handler http.HandlerFunc) {
	s.mux.Handle(pattern, handler)
}

func (s *Server) reject() {
	s.mux.reject = true
}

func (s *Server) stop(ctx context.Context) error {
	log.Println("server: ", s.name, "关闭中")
	return s.svc.Shutdown(ctx)
}

效果演示

C:\Users\26645\Desktop\goproject\gracefulshutdown\http>go build .

C:\Users\26645\Desktop\goproject\gracefulshutdown\http>http.exe
2024/01/02 15:32:08 应用启动成功
应用开始关闭...
2024/01/02 15:32:10 app start to shutdown
2024/01/02 15:32:10 等待已有请求处理
2024/01/02 15:32:10 开始关闭应用
2024/01/02 15:32:10 server:  service2 关闭中
2024/01/02 15:32:10 server:  service1 关闭中
2024/01/02 15:32:10 服务器service2已关闭
2024/01/02 15:32:10 服务器service1已关闭
2024/01/02 15:32:10 开始执行回调函数
2024/01/02 15:32:10 缓存刷新中...
2024/01/02 15:32:11 缓存被刷新到了 DB
2024/01/02 15:32:11 开始释放资源
2024/01/02 15:32:12 应用关闭

优雅终止 | 高雅模版 | 基于 go 的 http 库实现,golang,golang,http,开发语言,后端,服务器文章来源地址https://www.toymoban.com/news/detail-833936.html

到了这里,关于优雅终止 | 高雅模版 | 基于 go 的 http 库实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 组件分享之后端组件——基于Golang实现的全局的、版本化的、点对点的文件系统go-ipfs...

    近期正在探索前端、后端、系统端各类常用组件与工具,对其一些常见的组件进行再次整理一下,形成标准化组件专题,后续该专题将包含各类语言中的一些常用组件。欢迎大家进行持续关注。 组件: go-ipfs 开源协议:The go-ipfs project is dual-licensed under Apache 2.0 and MIT terms: Ap

    2024年02月01日
    浏览(42)
  • k8s优雅终止pod

    Pod 销毁时,会停止容器内的进程,通常在停止的过程中我们需要执行一些善后逻辑,比如等待存量请求处理完以避免连接中断,或通知相关依赖进行清理等,从而实现优雅终止目的。本文介绍在 Kubernetes 场景下,实现容器优雅终止的最佳实践。 我们先了解下容器在 Kubernete

    2024年02月15日
    浏览(34)
  • 使用 System.exit() 来优雅地终止 Spring Boot 项目

    😊 @ 作者: 一恍过去 💖 @ 主页: https://blog.csdn.net/zhuocailing3390 🎊 @ 社区: Java技术栈交流 🎉 @ 主题: 使用 System.exit() 来优雅地终止 Spring Boot 项目 ⏱️ @ 创作时间: 2023年09月04日 System.exit(): System.exit() 方法是 Java 中用于退出程序的方法。它接受一个整数参数,通常被用

    2024年02月09日
    浏览(39)
  • C#多线程精解:优雅终止线程的实用方法与技巧

      概述: 在C#多线程编程中,合理终止线程是关键挑战。通过标志位或CancellationToken,实现安全、协作式的线程终止,确保在适当时机终止线程而避免资源泄漏。 在C#多线程编程中,有时需要终止正在运行的线程,例如在用户取消操作、程序关闭等情况下。 线程终止通常涉及

    2024年02月19日
    浏览(37)
  • AsyncContext优雅实现HTTP长轮询接口

    接到一个需求,实现方案时需要提供一个HTTP接口,接口需要hold住5-8秒,轮询查询数据库,一旦数据库中值有变化,取出变化的值进行处理,处理完成后返回响应。这不就是长轮询吗,如何优雅的实现呢? 在这之前先简单介绍下长连接和短连接 HTTP长链接(Keep-Alive) 概念:

    2024年02月09日
    浏览(35)
  • Golang中基于HTTP协议的网络服务

    HTTP协议是基于TCP/IP协议栈的,并且它也是一个面向普通文本的协议。 只要搞清楚了HTTP请求的报文(报文的头部(header)和主体(body))应该包含的内容,使用任何一个文本编译器,就饿可以编写一个完整的HTTP请求报文。 在这种情况下,直接使用 net.Dial 函数,就可以。 使

    2023年04月09日
    浏览(31)
  • 互联网大厂技术-HTTP请求-Springboot整合Feign更优雅地实现Http服务调用

    目录 一、SpringBoot快速整合Feign 1.添加Pom依赖 2.启动类添加注解 3.引用Feign服务 二、为请求添加Header的3种方式 1.添加固定header 2.通过接口签名添加header 3.动态添加header 三、为请求添加超时配置 1.默认超时时间 3.超时异常 4.全局超时配置 5.为单个服务设置超时配置 四、为请求配

    2024年02月04日
    浏览(58)
  • Golang 实现http协议的心跳检测程序

    本文介绍如何使用Golang实现心跳程序。 实现心跳程序,其他应用可以简单集成。客户端程序通过HTTP协议进行检测,返回当前程序状态、版本ID以及已运行时间。 首先定义了两个变量,CommitHash、StartTime,然后定义结构体HeartbeatMessage封装返回值。 接着在init方法中给StartTime变量

    2023年04月09日
    浏览(28)
  • 使用Golang实现HTTP代理突破IP访问限制

    在当今互联网时代,网站和服务商为了维护安全性和保护用户隐私,常常会对特定的IP地址进行封锁或限制。但是,有时候我们可能需要访问这些被限制的网站或服务。为了突破这种限制,我们可以使用HTTP代理来隐藏真实的客户端IP地址,从而绕过限制。 本文将介绍如何使用

    2024年02月07日
    浏览(41)
  • Golang实现更安全的HTTP基本认证(Basic Authentication)

    当搜索使用Go的HTTP基本身份验证示例时,我能找到的每个结果都不幸包含了过时的代码(即不使用Go1.4中引入的r.BasicAuth()功能)或不能防止定时攻击。本文介绍如何实现更安全的HTTP基本认证代码。 你一定遇到,当通过浏览器访问一些URL时提示输入用户名和密码。再你输入用户名

    2024年02月10日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包