go reload

关键词预热

  • 热启动
  • 进程间通信
  • 信号量
  • 信号
  • 套接字

    问题背景

    重启服务或者使用kill -9 或者control + c 不可避免会遇到下面两个问题

  1. 未处理完的请求终端,破坏数据一致性
  2. 新服务启动期间,请求无法进来导致服务一段时间不可用

解决方案

  • 生产环境应用层通过四层或者七层负载均衡,通过流量调度来实现平滑重启【待更新】
  • 通过Kubernetes实现对Go服务进行扩容、更新、回滚、平滑重启【待更新】
  • 进程间通信来解决热更新【下文介绍】

    生产环境集群一般的做法是 ApiGateway + CD, 发布的时候自动摘除机器,等待程序处理完现有请求再做发布处理,也可以借助SLB或者LVS手动切换流量

    实现原理

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

    服务升级/更新时不关闭现有连结,不会出现拒绝访问的情况,对用户友好/用户无感知

进程间通信来解决热更新两种方式

  1. 设置套接字,将多个socket绑定在同一个监听端口,复制套接字【学习中】
  2. 用父子进程fork-exec继承文件描述符的特性,在父子进程之间维护传递监听socket。在升级/重启的过程中,父进程将监听socket继承给子进程,使得整个过程没有监听 socket 被关闭,从而不产生拒绝服务的问题。【本文demo使用方式】

实现方式

常见的开源实现graceful和endless。

graceful:https://github.com/facebookgo/grace

endless:https://github.com/fvbock/endless

Golang >= 1.8可以使用 http.Server 的 Shutdown 方法实现
##代码实现

endless方式实现,lesof + 端口 查看启动的进程
kill -1 +端口 发送请求是查看请求被fork子进程收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
endless.DefaultReadTimeOut = setting.ReadTimeout
endless.DefaultWriteTimeOut = setting.WriteTimeout
endless.DefaultMaxHeaderBytes = 1 << 20
endPoint := fmt.Sprintf(":%d",setting.HTTPPort)

server := endless.NewServer(endPoint,routers.InitRouter())
server.BeforeBegin = func(add string) {
log.Printf("Actual pid is %d",syscall.Getpid())
}

err := server.ListenAndServe()
if err != nil {
log.Printf("server err : %v",err)
}

http.Serve 实现demo,kill -1 +端口 同时发送请求,请求没有中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go func() {
if err := s.ListenAndServe();err !=nil{
logs.Info("Listen: %s\n", err)
}
}()

//监听所有信号
quit := make(chan os.Signal)
signal.Notify(quit,os.Interrupt)
log.Println("PID:",os.Getppid())
<- quit
log.Println("shut down server .....",<- quit)
ctx,cancel := context.WithTimeout(context.Background(),5*time.Second)
defer cancel()
if err := s.Shutdown(ctx);err != nil {
log.Fatal("server shutdown :",err)
}
log.Println("Server exiting")

unix基础(补充)

进程间通信场景

  • 数据传输
  • 共享数据
  • 通知事件
  • 资源共享
  • 进程控制

    进程之间通信方式

  • 管道
  • 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
  • 消息队列
  • 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生【本文demo采用的方式】
  • 共享内存
  • 套接字

信号说明

脚本输入:++kill -l++


常用信号列表

命令行实现 start stop reload