百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Golang 服务平滑重启(优雅重启)

toyiye 2024-06-21 12:26 9 浏览 0 评论

问题背景

在一些日常开发复杂生产环境下,许多的操作需要在任何场景都要保证业务正常运行。

如果我们对线上服务进行简单粗暴更新,如下:

  1. kill -9 服务
  2. 再启动服务

那么将不可避免地出现以下两个问题:

  • 未处理完业务的请求,被迫中断,导致数据一致性遭到破坏
  • 重新启动服务过程中,一些请求无法进来,导致短暂的服务不可用现象

上面两种情况影响正常的业务,必然会造成一些影响,针对以上情形,一般有三种处理方案:

  1. 生产环境会通过四层(负载均衡器)->七层(网关)->服务,那么可以通过流量调度的方式实现平滑重启
  2. k8s容器编排管理,实现平滑重启
  3. 程序自身完成平滑重启,本文主要介绍这种方式

什么是平滑重启

进程在不关闭其所监听端口的情况下进行重启,并且重启的整个过程保证所有请求都能被正常不受影响的处理。

主要步骤:

  1. 原进程(父进程)先fork一个子进程出来,同时让fork出来的子进程继承父进程所监听的socket
  2. 子进程完成初始化后,开始接收socket的请求。
  3. 父进程停止接收新的请求,并将当下的请求处理完,等待连接空闲后,平滑退出。

信号(Signal)

服务的平滑重启,主要依赖进程接收的信号(实现进程间通信),这里简单的介绍Golang中信号的处理:

发送信号

  • kill命令允许用户发送一个特定的信号给进程
  • raise库函数可以发送特定的信号给当前进程

在Linux下运行man kill可以查看此命令的介绍和用法。

kill -- terminate or signal a process
The kill utility sends a signal to the processes specified by the pid operands.
Only the super-user may send signals to other users' processes.

常用信号类型

信号的默认行为:

  • term:信号终止进程
  • core:产生核心转储文件并退出
  • ignore:忽略信号
  • stop:信号停止进程
  • cont:信号恢复一个已停止的进程

信号

默认动作

说明

SIGHUP

1

Term

HUP (hang up):终端控制进程结束(终端连接断开)

SIGINT

2

Term

INT (interrupt):用户发送INTR字符(Ctrl+C)触发(强制进程结束)

SIGQUIT

3

Core

QUIT (quit):用户发送QUIT字符(Ctrl+/)触发(进程结束)

SIGKILL

9

Term

KILL (non-catchable, non-ignorable kill):无条件结束程序(不能被捕获、阻塞或忽略)

SIGUSR1

30,10,16

Term

用户自定义信号1

SIGUSR2

31,12,17

Term

用户自定义信号2

SIGKILL

15

KILL (non-catchable, non-ignorable kill)

TERM (software termination signal):程序终止信号

信号接收测试

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sigsChan := make(chan os.Signal)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

    // 监听所有信号
    log.Println("listen signal")
    signal.Notify(sigsChan)

    // 打印进程id
    log.Println("PID:", os.Getppid())

    s := <-sigsChan
    log.Println("退出信号", s)
}
go run main.go
## --> listen signal
## --> PID: 6808

kill -s HUP 6808
# --> Hangup: 1

实现案例

demo:

func main() {
   sigsChan := make(chan os.Signal)
   signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

   // 监听所有信号
   log.Println("listen signal")
   signal.Notify(sigsChan)

   // 打印进程pid
   log.Println("PID:", os.Getppid())
   go func() {
      for s := range sigsChan {
         switch s {
         case syscall.SIGHUP:
            log.Println("startNewProcess...")
            startNewProcess()
            log.Println("shutdownParentProcess...")
            shutdownParentProcess()
         case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
            log.PrintLn("Program Exit...", s)
         case syscall.SIGUSR1:
            log.Println("usr1 signal", s)
         case syscall.SIGUSR2:
            log.Println("usr2 signal", s)
         default:
            log.Println("other signal", s)
         }
      }
   }()

   <-sigsChan
}

推荐组件

Facebookarchive/grace

shutdown优雅退出

go 1.8.x后,golang在http里加入了shutdown方法,用来控制优雅退出。

package main

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

func main() {
    s := http.NewServeMux()
    s.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(3 * time.Second)
        log.Println(w, "Hello world!")
    })
    server := &http.Server{
        Addr:    ":8090",
        Handler: s,
    }
    go server.ListenAndServe()

    listenSignal(context.Background(), server)
}

func listenSignal(ctx context.Context, httpSrv *http.Server) {
    sigsChan := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

    select {
    case <-sigsChan:
        log.Println("notify sigsChan")
        httpSrv.Shutdown(ctx)
        log.Println("http shutdown")
    }
}

小结

在日常的生产环境中,优雅地重启某个服务是一个不可缺少的环节,无论是在go进程层间,或者上层的服务流量调度层面,都有许多的不同方案,选择最适合方案,保证服务稳定才是最重要的。

相关推荐

「linux专栏」top命令用法详解,再也不怕看不懂top了

在linux系统中,我们经常使用到的一个命令就是top,它主要是用来显示系统运行中所有的进程和进程对应资源的使用等信息,所有的用户都可以使用top命令。top命令内容量丰富,可令使用者头疼的是无法全部...

Linux 中借助 perf 对 php 程序模拟CPU高的案例分析

导语本文是一篇Linux借助工具分析CPU高的优化案例,没有任何干货内容,很详细的展示了优化CPU高的具体步骤,非常适合初中级读者阅读!...

centos漏洞处理方法(centos podman)

centos服务器最近有诸多漏洞,修复命令及对应的漏洞整理后,分享给大家RHSA-2020:1176-低危:avahi安全更新yumupdateavahi-libsRHSA-2017:326...

Linux上的free命令详解(Buffer和Cache)

解释一下Linux上free命令的输出。下面是free的运行结果,一共有4行。为了方便说明,我加上了列号。这样可以把free的输出看成一个二维数组FO(FreeOutput)。例如:FO[2][1]...

linux 命令行之你真的会用吗?--free 基本用法篇

free命令行统计内存使用率及swap交换分区的使用率数据。是由sourceforge负责维护的,在ubuntu上其包名为procps,这个源码包中,除了free还有ps,top,vmstat,ki...

kong api gateway 初体验(konga github)

kongapigateway初体验(firstsight?)。Kong是一个可扩展的开源API层(也称为API网关或API中间件)。Kong运行在任何RESTfulAPI的前面,并通过插件...

在Ubuntu下开启IP转发的方法(ubuntu20 ip)

IP地址分为公有ip地址和私有ip地址,PublicAddress是由INIC(internetnetworkinformationcenter)负责的,这些IP地址分配给了注册并向INIC提...

基于 Kubernetes 的 Serverless PaaS 稳定性建设万字总结

作者:许成铭(竞霄)数字经济的今天,云计算俨然已经作为基础设施融入到人们的日常生活中,稳定性作为云产品的基本要求,研发人员的技术底线,其不仅仅是文档里承诺的几个九的SLA数字,更是与客户切身利益乃...

跟老韩学Ubuntu Linux系列-sysctl 帮助文档

sysctl一般用于基于内核级别的系统调优,man帮助手册如下。...

如何在 Linux/Unix/Windows 中发现隐藏的进程和端口

unhide是一个小巧的网络取证工具,能够发现那些借助rootkit、LKM及其它技术隐藏的进程和TCP/UDP端口。这个工具在Linux、UNIX类、MS-Windows等操作系统下都...

跟老韩学Ubuntu Server 2204-Linux性能管理-uptime指令帮助手册

uptime指令是每个从事Linux系统工作的相关同学必知必会的指令之一,如下是uptime指令的帮助手册。UPTIME(1)...

Openwrt+Rclone+emby+KODI搭建完美家庭影音服务器

特别声明:本篇内容参考了波仔分享,在此表示感谢!上一篇《Openwrt+emby+KODI搭建家庭影音服务器》只适用影音下载到本地的情形,不能播放云盘中的影音,内容较少,缺少了趣味性,也不直观。...

Linux Shell脚本经典案例(linux shell脚本例子)

编写Shell过程中注意事项:开头加解释器:#!/bin/bash语法缩进,使用四个空格;多加注释说明。命名建议规则:变量名大写、局部变量小写,函数名小写,名字体现出实际作用。默认变量是全局的,在函数...

解决 Linux 性能瓶颈的黄金 60 秒

如果你的Linux服务器突然负载暴增,告警短信快发爆你的手机,如何在最短时间内找出Linux性能问题所在?来看Netflix性能工程团队的这篇博文,看它们通过十条命令在一分钟内对机器性能问题进行诊断。...

跟老韩学Ubuntu Server 2204-Linux性能管理-vmstat指令帮助手册

vmstat可查看ubuntlinux的综合性能,是每个从事Linux人员必知必会、需掌握的核心指令之一。vmstat指令帮助手册如下。VMSTAT(8)...

取消回复欢迎 发表评论:

请填写验证码