go如何优雅关闭Graceful Shutdown服务

为了实现优雅关闭(Graceful Shutdown)服务,我们需要了解两个重要的概念:闲置连接(idle connections)和上下文(context)。

在 Go 语言中,服务器和客户端之间的连接是通过 net.Conn 实现的,服务器在和客户端建立连接之后就可以可以向客户端发送数据,同时也可以从客户端读取数据。在大多数情况下,服务器与客户端之间的交互是请求/响应(request/response)模式。但是,如果长时间没有数据交互,连接就会变得闲置,此时服务器需要适时地关闭这些闲置的连接。这种被闲置的连接就是闲置连接(idle connections)。

上下文(context)是一种用于设置截止时间、取消信号和元数据信息等的机制。在 Go 语言中,上下文通常用于给多个 goroutine 设置超时,或者用于取消一些不需要的操作。在服务器中,我们可以使用上下文来实现优雅关闭服务的功能。

下面是一个基本的示例,展示如何使用闲置连接和上下文实现优雅关闭服务:

package main

import (
    "context"
    "net/http"
    "os"
    "os/signal"
    "time"
)

func main() {
    server := &http.Server{
        Addr:         ":8080",
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
    }

    // 启动 HTTP 服务
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            // 正常情况下,ErrServerClosed 表示服务器已经关闭
            // 如果不是 ErrServerClosed 错误,则表示出现了其他的错误
            panic(err)
        }
    }()

    // 创建一个通道,用于等待操作系统中断信号
    interruptChan := make(chan os.Signal, 1)
    signal.Notify(interruptChan, os.Interrupt)

    <-interruptChan // 等待操作系统中断信号

    // 创建一个上下文,并向它发送一个超时信号
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel() // 释放上下文资源

    // 关闭 HTTP 服务,并等待所有闲置连接关闭
    if err := server.Shutdown(ctx); err != nil {
        panic(err)
    }
}

在上面的示例中,首先我们创建了一个 http.Server 实例,并设置其 ReadTimeout、WriteTimeout 和 IdleTimeout 参数,分别控制读取数据、写入数据和闲置连接的超时时间。需要注意的是,IdleTimeout 的值应该比 ReadTimeout 和 WriteTimeout 的值更大,否则客户端可能会因为比较频繁的重新建立连接而感到不满。

然后,在启动 HTTP 服务之后,我们创建了一个通道 interruptChan,用于等待操作系统的中断信号。当接收到操作系统中断信号时,我们会创建一个上下文 ctx,并向它发送一个超时信号。最后,我们使用 http.Server 的 Shutdown 方法关闭 HTTP 服务,并使用上下文来等待所有闲置连接关闭。

下面是另一个示例,它演示了如何在 HTTP Handler 中使用上下文,并且在多个 goroutine 中使用上下文来实现优雅关闭服务:

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

func main() {
    server := &http.Server{
        Addr:         ":8080",
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
    }

    // 添加 HTTP Handler,并对处理过程中的上下文进行追踪
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
        defer cancel()

        log.Println("handler started")
        defer log.Println("handler finished")

        select {
        case <-time.After(5 * time.Second):
            fmt.Fprintf(w, "Hello World!")
        case <-ctx.Done():
            // 当上下文被取消时,我们可以通过 err 来判断是超时错误还是其他的错误
            err := ctx.Err()
            log.Printf("handler error: %v", err)
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    // 启动多个 goroutine,并在启动的时候传递上下文
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup
    wg.Add(3)
    for i := 0; i < 3; i++ {
        go func(index int) {
            defer wg.Done()

            for {
                select {
                case <-time.After(2 * time.Second):
                    log.Printf("goroutine %d running...", index)
                case <-ctx.Done():
                    log.Printf("goroutine %d stopped", index)
                    return
                }
            }
        }(i)
    }

    // 启动 HTTP 服务
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            panic(err)
        }
    }()

    // 等待操作系统中断信号
    interruptChan := make(chan os.Signal, 1)
    signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
    <-interruptChan

    // 发送取消信号,并等待所有 goroutine 停止
    cancel()
    wg.Wait()

    // 关闭 HTTP 服务,并等待所有闲置连接关闭
    ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        panic(err)
    }
}

在这个示例中,我们不仅在 HTTP Handler 中追踪了上下文,还在多个 goroutine 中使用了上下文。当接收到操作系统中断信号时,我们首先向全局上下文发送取消信号,这样所有的 goroutine 都会随着上下文被取消而停止执行。然后,我们使用上下文来关闭 HTTP 服务,并等待所有闲置连接关闭。

需要注意的是,在上面的两个示例中,我们都使用了 http.Server 的 Shutdown 方法来关闭 HTTP 服务。这个方法会等待所有闲置连接关闭之后再返回。如果你是使用自己的 WebSocket 实现,那么也需要使用类似的方法来关闭 WebSocket。并且在你的 WebSocket 实现中,应该实现对上下文进行追踪,并根据上下文的状态优雅地关闭 WebSocket 服务。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:go如何优雅关闭Graceful Shutdown服务 - Python技术站

(0)
上一篇 2023年5月25日
下一篇 2023年5月25日

相关文章

  • Windows下用Nginx配置https服务器及反向代理的问题

    下面我将为您介绍如何在Windows下使用Nginx配置HTTPS服务器及反向代理的完整攻略。 确定所需软件及工具 在开始之前,请确认您已经安装以下软件及工具: Windows操作系统 Nginx OpenSSL curl 生成SSL证书 打开命令提示符窗口。 进入OpenSSL的目录下,并执行以下命令生成SSL证书: openssl req -x509 -…

    人工智能概览 2023年5月25日
    00
  • OpenCV实战案例之车道线识别详解

    OpenCV实战案例之车道线识别详解 引言 车道线识别是自动驾驶领域中重要的一环,本文介绍了使用OpenCV进行车道线识别的完整攻略。 前置知识 本文假设读者已经掌握以下知识: Python编程语言基础 OpenCV基本操作和图像处理 准备工作 安装OpenCV 为了使用OpenCV进行图像处理操作,需要先安装OpenCV。可以使用pip命令来安装openc…

    人工智能概览 2023年5月25日
    00
  • Python flask框架实现浏览器点击自定义跳转页面

    下面我将详细讲解Python Flask框架实现浏览器点击自定义跳转页面的完整攻略。 一、搭建Flask环境 要使用Flask框架,需要先安装Flask,并在本地搭建好Flask环境。具体的安装方法和环境搭建方法可以参考Flask官方文档。下面是安装Flask的简单步骤: 使用pip安装Flask:pip install Flask 创建Flask应用:在代…

    人工智能概论 2023年5月25日
    00
  • 在Django中动态地过滤查询集的实现

    在Django中,我们可以使用QuerySet对象来进行数据库操作,包括增删改查等。而有时候我们需要在查询的时候进行动态的过滤,通常是由用户选择输入不同的过滤条件导致的。 以下是在Django中动态地过滤查询集的实现的完整攻略: 步骤一:建立基础查询集 首先,我们需要建立一个基础的QuerySet对象,这个QuerySet对象是没有经过任何过滤的,可以通过以…

    人工智能概论 2023年5月25日
    00
  • C++右值引用与move和forward函数的使用详解

    C++右值引用与move和forward函数的使用详解 什么是右值引用 C++11引入了右值引用,即将“&&”符号用于声明右值引用。 右值引用的本质是一个临时对象的引用,它的生命周期受到限制,在语句执行完毕后,其所引用的对象就会被销毁。 右值引用可以作为函数的参数,用于传入临时对象(即将被销毁的对象),从而避免了不必要的对象拷贝。 示例代码如…

    人工智能概览 2023年5月25日
    00
  • Python 机器学习之线性回归详解分析

    Python 机器学习之线性回归详解分析 1. 什么是线性回归 线性回归是机器学习中最基础和最常见的模型之一。它是一种用来预测连续数值输出的算法,可以帮助我们建立输入特征和输出之间的线性关系。 2. 线性回归原理 线性回归的核心是建立输入特征与输出之间的线性关系。假设有一个简单的线性回归模型: y = β0 + β1×1 + ε 其中,y 是输出变量,x1 …

    人工智能概论 2023年5月24日
    00
  • Centos7启动流程及Systemd中Nginx启动配置

    我来详细讲解“Centos7启动流程及Systemd中Nginx启动配置”的完整攻略。 Centos7启动流程 Centos7的启动流程如下: BIOS: 在计算机加电后,首先运行的是BIOS程序,它检查系统硬件,并加载存储在CMOS中的前期可执行环境(PE)。 MBR: 该环境启动引导装置,如GRUB或LILO,读取主启动记录(MBR)。 GRUB: 然后…

    人工智能概览 2023年5月25日
    00
  • memset函数的使用分析

    memset函数的使用分析 什么是memset函数? memset函数是C标准库中的函数,用于对内存数组进行初始化赋值操作。通过一次性对数组的所有元素进行赋值操作,可以提高程序的执行效率和代码可读性。在头文件string.h中定义,函数原型为: void* memset(void* ptr, int value, size_t num); 该函数的三个参数含…

    人工智能概论 2023年5月25日
    00
合作推广
合作推广
分享本页
返回顶部