golang 程序启动一个 http 服务时,若服务被意外终止或中断,会让现有请求连接突然中断,未处理完成的任务也会出现不可预知的错误,这样即会造成服务硬终止;为了解决硬终止问题我们希望服务中断或退出时将正在处理的请求正常返回并且等待服务停止前作的一些必要的处理工作。

我们可以看一个硬终止的例子:

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "Hello world!")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
server.ListenAndServe()

启动服务后,我们可以访问 http://127.0.0.1:8080 页面等待 5s 会输出一个 “Hello world!”, 我们可以尝试 Ctrl+C 终止程序,可以看到浏览器立刻就显示无法连接,这表示连接立刻就中断了,退出前的请求也未正常返回。

在 Golang1.8 以后 http 服务有个新特性 Shutdown 方法可以优雅的关闭一个 http 服务, 该方法需要传入一个 Context 参数,当程序终止时其中不会中断活跃的连接,会等待活跃连接闲置或 Context 终止(手动 cancle 或超时)最后才终止程序,官方文档详见:https://godoc.org/net/http#Server.Shutdown

在具体用应用中我们可以配合 signal.Notify 函数来监听系统退出信号来完成程序优雅退出;

特别注意:server.ListenAndServe() 方法在 Shutdown 时会立刻返回,Shutdown 方法会阻塞至所有连接闲置或 context 完成,所以 Shutdown 的方法要写在主 goroutine 中。

优雅退出实验1:

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "Hello world!")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
go server.ListenAndServe() listenSignal(context.Background(), server)
} func listenSignal(ctx context.Context, httpSrv *http.Server) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) select {
case <-sigs:
fmt.Println("notify sigs")
httpSrv.Shutdown(ctx)
fmt.Println("http shutdown")
}
}

我们创建了一个 listenSignal 函数来监听程序退出信号 listenSignal 函数中的 select 会一直阻塞直到收到退出信号,然后执行 Shutdown(ctx) 。

可以看到,我们是重新开启了一个 goroutine 来启动 http 服务监听,而 Shutdown(ctx) 在主 goroutine 中,这样才能等待所有连接闲置后再退出程序。

启动上述程序,我们访问  http://127.0.0.1:8080 页面等待 5s 会输出一个 “Hello world!” 在等待期间,我们可以尝试 Ctrl+C 关闭程序,可以看程序控制台会等待输出后才打印 http shutdown 同时浏览器会显示输出内容;而关闭程序之后再新开一个浏览器窗口访问 http://127.0.0.1:8080 则新开的窗口直接断开无法访问。(这些操作需要在 5s 内完成,可以适当调整处理时间方便我们观察实验结果)

通过该实验我们能看到,Shutdown(ctx) 会阻止新的连接进入并等待活跃连接处理完成后再终止程序,达到优雅退出的目的。

当然我们还可以进一步证明 Shutdown(ctx) 除了等待活跃连接的同时也会监听 Context 完成事件,二者有一个触发都会触发程序终止;

我们将代码稍作修改如下:

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(10 * time.Second)
fmt.Fprintln(w, "Hello world!")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
go server.ListenAndServe() listenSignal(context.Background(), server)
} func listenSignal(ctx context.Context, httpSrv *http.Server) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) select {
case <-sigs:
timeoutCtx,_ := context.WithTimeout(ctx, 3*time.Second)
fmt.Println("notify sigs")
httpSrv.Shutdown(timeoutCtx)
fmt.Println("http shutdown")
}
}

我们将 http 服务处理修改成等待 10s, 监听到退出事件后 ctx 修改成 3s 超时的 Context,运行上述程序,然后 Ctrl+C 发送结束信号,我们可以直观的看到,程序在等待 3s 后就终止了,此时即使 http 服务中的处理还没完成,程序也终止了,浏览器中也直接中断连接了。

需要注意的问题:我们在 HandleFunc 中编写的处理逻辑都是在主 goroutine 中完成的和 Shotdown 方法是一个同步操作,因此 Shutdown(ctx) 会等待完成,如果我们的处理逻辑是在新的 goroutine 中或是一个像 Websock 这样的长连接,则Shutdown(ctx) 不会等待处理完成,如果需要解决这类问题还是需要利用 sync.WaitGroup 来进行同步等待。

技术总结:

1. Shutdown 方法要写在主 goroutine 中;

2.在主 goroutine 中的处理逻辑才会阻塞等待处理;

3.带超时的 Context 是在创建时就开始计时了,因此需要在接收到结束信号后再创建带超时的 Context。

给大家推荐一个框架来快速构建带优雅退出功能的 http 服务,详见: https://www.cnblogs.com/zhucheer/p/12341595.html

golang中使用Shutdown特性对http服务进行优雅退出使用总结的更多相关文章

  1. 优雅退出在Golang中的实现

    背景 为什么需要优雅关停 在Linux下运行我们的go程序,通常有这样2种方式: 前台启动.打开终端,在终端中直接启动某个进程,此时终端被阻塞,按CTRL+C退出程序,可以输入其他命令,关闭终端后程序 ...

  2. Java语言中的面向对象特性总结

    Java语言中的面向对象特性 (总结得不错) [课前思考]  1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类?  2. 面向对象编程的特性有哪三个?它们各自又有哪些特性?  3. 你知 ...

  3. ASP.NET 5与MVC 6中的新特性

    差点忘了提一句,MVC 6中默认的渲染引擎Razor也将得到更新,以支持C# 6中的新语法.而Razor中的新特性还不只这一点. 在某些情况下,直接在Web页面中嵌入某些JSON数据的方式可能比向服务 ...

  4. Golang的iota的特性

    Golang的iota的特性: 1. iota在每个ConstBlock中自动归0. 2. iota在每个ConstSpec后自动增1. 换言之: iota是ConstBlock中ConstSpec的 ...

  5. golang中defer的使用规则

    转自个人博客chinazt.cc 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用. ...

  6. Golang中的坑二

    Golang中的坑二 for ...range 最近两周用Golang做项目,编写web服务,两周时间写了大概五千行代码(业务代码加单元测试用例代码).用Go的感觉很爽,编码效率高,运行效率也不错,用 ...

  7. google的grpc在golang中的使用

    GRPC是google开源的一个高性能.跨语言的RPC框架,基于HTTP2协议,基于protobuf 3.x,基于Netty 4.x. 前面写过一篇golang标准库的rpc包的用法,这篇文章接着讲一 ...

  8. golang中Context的使用场景

    golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...

  9. 说说不知道的Golang中参数传递

    本文由云+社区发表 导言 几乎每一个C++开发人员,都被面试过有关于函数参数是值传递还是引用传递的问题,其实不止于C++,任何一个语言中,我们都需要关心函数在参数传递时的行为.在golang中存在着m ...

随机推荐

  1. vue根据选择的月份动态展示当前月份的每一天并展示每一天所对应的星期几

    我们在开发过程中所遇到的所有的奇奇怪怪的交互美其名曰用(奇)户(葩)体(需)验(求),而产品所谓的良好的交互效果,在我等开发人员眼里不值一提.不屑一顾.讨厌至极! 对于这样的需求,我通常都是: 但胳膊 ...

  2. 做前端的你还没用这些软件?? out 啦

    1. 编辑器 写代码只是生产软件过程中的一环.无论是数据结构.编译原理.操作系统还是组成原理都是编码的重要基础,试问没有学过编译原理的人能够针对性地进行编译优化吗?不懂操作系统的人能玩得转linux吗 ...

  3. cogs 1361. 树 线段树

    1361. 树 ★   输入文件:treed.in   输出文件:treed.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 在一个凉爽的夏夜,xth和rabbit来到 ...

  4. P3369 【模板】普通平衡树 01Trie树

    P3369 [模板]普通平衡树 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入xx数 删除xx数(若有多个相同的数,因只删除一个) 查询xx数的排名(排名 ...

  5. 区间 dp

    以一个经典题目引入到正题 : 有 n 堆石子 , 每两堆石子合并会花费一定的价值,所花费的价值即为 两堆石子上的价值和 , 问合并所有的石子后的最小花费 ? 思路分析 : 因为题干可以看成是对每个区间 ...

  6. 位运算上的小技巧 - AtCoder

    Problem Statement There is an integer sequence of length 2N: A0,A1,…,A2N−1. (Note that the sequence ...

  7. hdu 5139 数据的离线处理

    所谓的数据离线处理,就是将所有的输入数据全部读入后,在进行统一的操作,这样当然有好处,比如让你算好多数的阶层,但是输入的每个数是没有顺序的,其实跟可以线性的解决,但是由于没有顺序的输入,这样处理的话复 ...

  8. java 魔术

    每4个字节都有对应的含义

  9. python的logging模块使用方法

    logging模块 logging模块是Python内置的日志模块,用来生成程序的日志.一条日志对应一个事件的发生,一个事件一般包括:事件发生时间.事件发生位置.事件内容.事件严重程度-日志级别.(还 ...

  10. 19秦皇岛现场赛F题 dfs

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6736 如果环的边长为k,那么环的删边方案数是2k-1.如果链的边长为k,那么链的删边方案数是2k.环的 ...