Fork me on GitHub
摘要: minio的安装和基本使用 阅读全文
posted @ 2023-10-19 16:55 Mr.YF 阅读(2310) 评论(0) 推荐(0)

1. 背景

  项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?

 

为什么需要优雅的退出?

  • 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据。
  • 你的协程 worker 的一个任务运行了一半,程序退出了,结果不符合预期。

 

如下我们以 http 服务,gRPC 服务,单独的 woker 协程为例子,一步步说明平滑关闭的写法。

 

2. 常见的几种平滑关闭

为了解决退出可能出现的潜在问题,平滑关闭一般做如下一些事情

  • 关闭对外的监听端口,拒绝新的连接
  • 关闭异步运行的协程
  • 关闭依赖的资源
  • 等待如上资源关闭
  • 然后平滑关闭

2.1 http server 平滑关闭

原来的写法

1
2
3
4
5
6
7
8
// startHttpServer start http server
func startHttpServer() {
    mux := http.NewServeMux()
    // mux.Handle("/metrics", promhttp.Handler())
    if err := http.ListenAndServe(":1608", mux); err != nil {
        log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
    }
}

  

带平滑关闭的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// startHttpServer start http server
func startHttpServer() {
    mux := http.NewServeMux()
    // mux.Handle("/metrics", promhttp.Handler())
    srv := &http.Server{
        Addr:    ":1608",
        Handler: mux,
    }
    // 注册平滑关闭,退出时会调用 srv.Shutdown(ctx)
    quit.GetQuitEvent().RegisterQuitCloser(srv)
    if err := srv.ListenAndServe(); err != nil {
        log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
    }
}

把平滑关闭注册到http.Server的关闭函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// startHttpServer start http server
func startHttpServer() {
    mux := http.NewServeMux()
    // mux.Handle("/metrics", promhttp.Handler())
    srv := &http.Server{
        Addr:    ":1608",
        Handler: mux,
    }
    // 把平滑退出注册到http.Server中
    srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop)
    if err := srv.ListenAndServe(); err != nil {
        log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
    }
}

  

2.2 gRPC server 平滑关闭

原来的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
// startGrpcServer start grpc server
func startGrpcServer() {
    listen, err := net.Listen("tcp", "0.0.0.0:9999")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
        return
    }
    grpcServer := grpc.NewServer()
    // helloBoot.GrpcRegister(grpcServer)
    go grpcServer.Serve(listen)
    defer grpcServer.GracefulStop()
    // ...
}

带平滑关闭的写法 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// startGrpcServer start grpc server
func startGrpcServer() {
    listen, err := net.Listen("tcp", "0.0.0.0:9999")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
        return
    }
    grpcServer := grpc.NewServer()
    // helloBoot.GrpcRegister(grpcServer)
    go grpcServer.Serve(listen)
    // 把 grpc 的GracefulStop注册到退出事件中
    quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
    quit.WaitSignal()
}

  

2.3 worker 协程平滑关闭

单独的协程启停,可以通过计数的方式注册到退出事件处理器中。

  • 启动协程 增加计数
    •  quit.GetQuitEvent().AddGoroutine()
  • 停止协程 减计数 
    •  quit.GetQuitEvent().DoneGoroutine()
  • 常驻后台运行的协程退出的条件改成退出事件是否结束的条件 
    • !quit.GetQuitEvent().HasFired()
  • 常驻后台运行的协程若通过 select 处理 chan,同时增加退出事件的chan
    •  case <-quit.GetQuitEvent().Done()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// myWorker my worker
type myWorker struct {
}
 
// RunWorkerWithChan run Goroutine worker
func (m *myWorker) RunWorkerWithChan() {
    // 启动一个Goroutine时,增加Goroutine数
    quit.GetQuitEvent().AddGoroutine()
    defer func() {
        // 一个Goroutine退出时,减少Goroutine数
        quit.GetQuitEvent().DoneGoroutine()
    }()
    // 退出时,此次退出
    for !quit.GetQuitEvent().HasFired() {
        select {
        // 退出时,收到退出信号
        case <-quit.GetQuitEvent().Done():
            break
            //case msg := <- m.YouChan:
            // handle msg
        }
    }
}
 
// RunWorker run Goroutine worker
func (m *myWorker) RunWorker() {
    // 启动一个Goroutine时,增加Goroutine数
    quit.GetQuitEvent().AddGoroutine()
    defer func() {
        // 一个Goroutine退出时,减少Goroutine数
        quit.GetQuitEvent().DoneGoroutine()
    }()
 
    // 退出时,此次退出
    for !quit.GetQuitEvent().HasFired() {
        // ...
    }
}

  

2.4 实现 io.Closer 接口的自定义服务平滑关闭

实现 io.Closer 接口的结构体,增加到退出事件处理器中 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// startMyService start my service
func startMyService() {
    srv := NewMyService()
    // 注册平滑关闭,退出时会调用 srv.Close()
    quit.GetQuitEvent().RegisterCloser(srv)
    srv.Run()
}
 
// myService my service
type myService struct {
    isStop bool
}
 
// NewMyService new
func NewMyService() *myService {
    return &myService{}
}
 
// Close my service
func (m *myService) Close() error {
    m.isStop = true
    return nil
}
 
// Run my service
func (m *myService) Run() {
    for !m.isStop {
        // ....
    }
}

  

2.5 集成其他框架怎么做

退出信号处理由某一框架接管,寻找框架如何注册退出函数,优秀的框架一般都会实现安全实现退出的机制。

如下将退出事件注册到某一框架的平滑关闭函数中

1
2
3
4
5
6
7
func startMyServer() {
    // ...
    // xxx框架退出函数注册退出事件
    xxx.RegisterQuitter(func() {
        quit.GetQuitEvent().GracefulStop()
    })
}

 

参考:

https://github.com/mygityf/go-library/blob/main/quit/quit.go

 

完。

祝玩的开心~

posted @ 2022-06-15 15:07 Mr.YF 阅读(1102) 评论(0) 推荐(0)
摘要: 写在前面的话 Golang中构建结构体的时候,需要通过可选参数方式创建,我们怎么样设计一个灵活的API来初始化结构体呢。 让我们通过如下的代码片段,一步一步说明基于可选参数模式的灵活 API 怎么设计。 灵活 API 创建结构体说明 v1版本 如下 Client 是一个 客户端的sdk结构体,有 h 阅读全文
posted @ 2022-06-10 12:24 Mr.YF 阅读(1455) 评论(5) 推荐(0)
摘要: 一、背景 有些业务需要判断图片的宽高,来做一些图片相关缩放,旋转等基础操作。 但是图片缩放,旋转,拼接等操作需要将图片从某一格式(JPG/PNG/GIF...)转成 RGBA 格式操作,操作完毕后,再转回 (JPG/PNG/GIF...) 图片。 那如何不做 RGBA 的转换就得到图片的宽和高呢? 阅读全文
posted @ 2022-04-06 21:20 Mr.YF 阅读(4823) 评论(0) 推荐(1)
摘要: 一、背景 在业务场景开发的过程中, 随着数据量的增加,相同表结构不同表名的分表策略是常用的方案选择之一。如下以golang做为后端业务开发,尝试修改beego的orm库做一个相同表结构不同表名的分表实现。 二、orm相同表结构不同表名的修改逻辑 三、orm分表对比 操 作 不分表代码使用 分表代码使 阅读全文
posted @ 2021-11-22 20:32 Mr.YF 阅读(1459) 评论(0) 推荐(1)
摘要: 背景 直方图均衡化归类于图像增强的一种图像处理方式。 图像的直方图是衡量图像像素分布的一种方式,可以通过分析像素分布,处理太亮或太暗的图像,通过均衡化处理使用直方图均衡化对图像进行优化,让图像变的清晰。 opencv官方对图像直方图的定义如下: 直方图是图像中像素强度分布的图形表达方式. 它统计了每 阅读全文
posted @ 2021-10-08 19:55 Mr.YF 阅读(1539) 评论(0) 推荐(1)
摘要: 名词解释 Namespace 表示命名空间 Deployment 表示pod发布 Service 表示多个pod做为一组的集合对外通过服务的表示 kubectl 是k8s的命令行操作命令,可以创建和更新,删除,列表和查详情等一系列的操作 部署步骤 同样的方法将deployment改成service, 阅读全文
posted @ 2021-10-08 12:32 Mr.YF 阅读(22364) 评论(1) 推荐(1)
摘要: 背景 gRPC是Google开始的一个RPC服务框架, 是英文全名为Google Remote Procedure Call的简称。 广泛的应用在有RPC场景的业务系统中,一些架构中将gRPC请求都经过一个gRPC服务代理节点或网关,进行服务的权限限制,限流,服务调用监控,增加请求统计等等诸多功能。 阅读全文
posted @ 2021-09-29 19:00 Mr.YF 阅读(8583) 评论(0) 推荐(2)
摘要: 背景 在Golang代码中,使用的比较多的defer是延时语句,按照倒序执行。 Go代码的简单的demo func DemoFunc() { fmt.Println("Demo Func...") } func Main() { defer DemoFunc() fmt.Println("Main 阅读全文
posted @ 2021-09-10 15:45 Mr.YF 阅读(284) 评论(0) 推荐(0)
摘要: 背景 近几年,互联网企业从消费互联网向产业互联网转型。在消费互联网时期,企业面对的时C端消费者,而产业互联网面对的是B端用户。产业互联网涉及方方面面,企业信息化的建设就是B端用户的业务之一,在企业就存在上下级关系,存在审批业务,需要流程管理。在企业信息化建设中流程管理也是重要的一部分。 流程引擎是低 阅读全文
posted @ 2021-09-10 12:30 Mr.YF 阅读(2471) 评论(2) 推荐(3)
点击右上角即可分享
微信分享提示