Go Micro + Gin 不同层级服务软停服(平滑关闭服务)的回顾一

Go Micro + Gin 不同层级服务软停服(平滑关闭服务)的回顾一

下面是我这个小菜鸡对一次软停服需求的处理的一个总结,有啥不对的地方麻烦各位大佬帮我这个小菜鸡纠正一下呀

服务软停服是指在关闭服务时,如果有请求在处理,应该等待请求处理完成,再关闭服务,从而达到平滑关闭服务的目的。基本思路如下:

  1. 监听到进程终止信号
  2. 把服务从注册中心摘除,不再接收后续请求
  3. 检测是否有请求在处理
  4. 当前请求已全部处理完成 即可停止服务

因为本人现在从事于Go开发,所以就从Go的角度说一下基于Go Micro + Gin 来实现这四大步。

先来大概说一下micro web 包下服务run的一点相关源码

在这里插入图片描述
在这里插入图片描述

run方法从上到下主要是做了以下事情:

  1. 调用 start 方法来启动服务
  2. 调用 register 方法向注册中心注册服务信息
  3. 创建一个通道ex 同时调用 s.run(ex)方法开始循环注册,run方法代码比较简短,如下,主要是当设置了RegisterInterval,开始建立定时任务,定时注册一下
func (s *service) run(exit chan bool) {
	if s.opts.RegisterInterval <= time.Duration(0) {
		return
	}

	t := time.NewTicker(s.opts.RegisterInterval)

	for {
		select {
		case <-t.C:
			s.register()
		case <-exit:
			t.Stop()
			return
		}
	}
}

4,监听进程退出信号

5,deregister反注册 把节点从注册中心摘除

6,stop停止服务

展开说说start方法

在这里插入图片描述

该方法里主要是执行自定的前置增强函数,创建网络监听器,构建service对象,调用serve方法接收请求和处理请求,然后执行自定义后置增强函数,同时把关闭监听器时可能出现的error放入exit中。至此,start函数就执行完成了,服务已经启动。然后继续往下执行注册等方法。然后通过select{}来阻塞,直到收到进程退出信号或者context cancel才会继续往下走。

在这里插入图片描述

stop方法非常的简单,但里面也会分别执行一个前置增强和后置增强函数,这是实现软停服的关键点之一

在这里插入图片描述

至此 micro web包下的service的启动到停止的一整个流程就大概梳理完成了呀。一般WEB包都是搭配Gin框架来放在API层面使用。现在就来说说在这一层面如何实现软停服:

监听到进程终止信号

在go里面可以使用 signal.Notify方法来监听进程的信号,至于各个信号在syscall包下对应的常量值这里就不累述了。前面提到的start方法中,恰好micro内部就自己监听了信号:

ch := make(chan os.Signal, 1)
if s.opts.Signal {
    signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT)
}
select {
    // wait on kill signal 等待一个进程退出信号
    case sig := <-ch:
    if logger.V(logger.InfoLevel, log) {
        log.Infof("Received signal %s", sig)
    }
    // 等待context cancel
    case <-s.opts.Context.Done():
    if logger.V(logger.InfoLevel, log) {
        log.Info("Received context shutdown")
    }
}

意味着这一步不需要我们去处理了,当然了,也可以通过设置options中的Signal来不让micro内部监听,而选择自己监听,像以下这样:

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	service := web.NewService()
	service.Init(
		func(o *web.Options) {
			o.BeforeStop = append(o.BeforeStop, BeforeStop)
		},
	)
	go func() {
		if err := service.Run(); err != nil {
			fmt.Println("err:", err)
		}
		fmt.Println("service exit")
	}()
	var state int32 = 1
	sc := make(chan os.Signal)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	select {
	case sig := <-sc:
        //手动从注册中心摘除节点 可以参考start方法中的deregister方法 这里不展开细写 因为我没有用这种方式 有轮子干嘛不用非要自己造呢 鹅鹅鹅鹅鹅鹅
		BeforeStop()
		atomic.StoreInt32(&state, 0)
		fmt.Printf("获取到退出信号[%s]", sig.String())
	}
	fmt.Println("cancel执行")
	cancel()
	if callFunc != nil {
		callFunc()
	}
	fmt.Printf("服务退出")
	os.Exit(int(atomic.LoadInt32(&state)))
	return
}
把服务从注册中心摘除,不再接收后续请求

一般注册中心都会提供一个方法来摘除节点的,micro的实现如下。注意:如果采用手动摘除节点时,一定要把service.Run()放在一条新的协程里面,不然其实这一段就是无用功。因为run方法会select{}阻塞着,过了select后micro内部自己就摘除节点了。放在run前的话 节点还没注册进去。

r := service.Options().Service.Client().Options().Registry
n := &registry.Node{
    Id: service.Options().Id,
}
var ns []*registry.Node
ns = append(ns, n)
r.Deregister(&registry.Service{
    Name: service.Options().Name,
    Nodes: ns,
})

因为博主是直接借助micro内部的监听信号的,顺手就再借助一下micro内部的这个反注册吧。毕竟有轮子干嘛不用呢。

检测是否有请求在处理

这一步的实现一开始我是想通过拦截器来做的,后来发现Gin支持自定义中间件,同时通过Use方法添加进去的中间会包含每一个请求的处理链路中,那这不就原地起飞了嘛。
在这里插入图片描述
因此,只要定义一个中间件,并添加到gin的引擎中,那么每一条请求都自然会触发中间件,那记录请求就简单了呀。中间件实现如下:

router := gin.Default()
router.Use(RecordApiHandlerNumber())

type ExitNotify struct {
	Channel chan bool
	exited  int32
}

func NewExitedNotifier() *ExitNotify {
	return &ExitNotify{
		Channel: make(chan bool),
		exited:  0,
	}
}

func (e *ExitNotify) Finish() {
	if a.AddInt32(&e.exited, 1) == 1 {
		close(e.Channel)
	}
}
var requestNumber atomic.Int32
var closingApiServer bool

var apiNotify = NewExitedNotifier()

func RecordApiHandlerNumber() gin.HandlerFunc {
	return func(c *gin.Context) {
		requestNumber.Inc()
		c.Next()
		requestNumber.Dec()

		if closingApiServer && requestNumber.Load() <= 0 {
			apiNotify.Finish()
		}
	}
}
当前请求已全部处理完成 即可停止服务

这时候节点已经不在注册中心了,后续不可能有请求进来了。这时候只要等待当前所有请求都处理完成即可退出进程。退出前的操作,是不是有点熟悉?是的,就是前面提到的stop方法中的前置增强函数。有一说一,micro的这一套前置后置增强,还有一系列的中间件Wrapper,能让我们非常方便的去扩展。

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	service := web.NewService()
	service.Init(
		func(o *web.Options) {
			o.BeforeStop = append(o.BeforeStop, BeforeStop)
		},
	)
    if err := service.Run(); err != nil {
		log.WithError(err).Error("service.Run")
	}
	cancel()
	return
}  
func BeforeStop() error {
	fmt.Println("当前请求数量:", requestNumber)
	closingApiServer = true
	select {
	case <-apiNotify.Channel:
	case <-time.After(time.Second * 30):
		return nil
	}
	return nil
}

以上就是go micro web包下的软停服的实现思路与一个方案。总结一下:

1,利用Gin的中间件会参与到每一条请求处理链路中来记录请求,也就是上文提到的Logger()

2,因为run方法中,会监听进程退出信号,同时会select{}阻塞着,直到收到进程退出或者contextcancel()才会往下走。然后调用deregister()从注册中心移除服务节点。这也就意味着监听信号和移除服务节点都不需要我们自己来处理了。

3,判断当前是否还有请求未处理完成,利用micro提供的BeforeStop函数数组,在stop方法执行前判断当前请求是否全部完成。

参考链接:
Go Micro文档:https://learnku.com/docs/go-micro/2.x/packer/8501

Go Micro一些wrapper:https://github.com/microhq/go-plugins/tree/master/wrapper

Go Micro防坑指南:https://magodo.github.io/go-micro-tips/#wrapper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang中的Gin框架提供了一种简单而强大的方法来构建Web应用程序。与此同时,Golang标准库中的"net/http"包提供了构建WebSocket服务器和客户端的功能。 首先,我们来看一下如何使用Gin和WebSocket构建WebSocket服务器。首先,需要导入相关的包: ```go import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) ``` 接下来,在Gin中创建一个WebSocket处理函数: ```go func WebSocketHandler(c *gin.Context) { upgrader := websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } for { messageType, message, err := conn.ReadMessage() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } err = conn.WriteMessage(messageType, message) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } } } ``` 上面的代码创建了一个基本的WebSocket处理函数。它使用WebSocket标准库中的Upgrader结构来处理升级连接并创建一个WebSocket连接。 然后,我们需要在Gin中设置路由来处理WebSocket请求: ```go router := gin.Default() router.GET("/ws", WebSocketHandler) ``` 以上代码将在根路径下创建一个WebSocket处理函数。 接下来,我们来看一下如何使用GolangGin构建WebSocket客户端。首先,我们需要导入所需的包: ```go import ( "github.com/gorilla/websocket" "net/http" ) ``` 然后,我们可以使用以下代码来创建一个WebSocket客户端: ```go func main() { c, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil) if err != nil { log.Fatal("dial:", err) } defer c.Close() done := make(chan struct{}) go func() { defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("read:", err) return } log.Printf("recv: %s", message) } }() ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-done: return case <-ticker.C: err := c.WriteMessage(websocket.TextMessage, []byte("Hello, Server!")) if err != nil { log.Println("write:", err) return } } } } ``` 上面的代码创建了一个WebSocket客户端,它使用WebSocket标准库中的`DefaultDialer`结构来建立WebSocket连接。 以上就是使用Golang Gin和WebSocket构建WebSocket客户端和服务器的简单示例。这些代码可以帮助我们使用GinGolang的标准库来构建强大的Web应用程序,并处理WebSocket通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值