Endless——平滑重启

https://deepwiki.com/fvbock/endless

https://github.com/fvbock/endless

alt

一、前置芝士

内核的文件描述符继承机制:

子进程一般默认继承父进程的stdin (0), stdout (1), 和 stderr (2)三类文件描述符。
那么其他的描述符怎么继承呢,Go用一个结构包裹传递 ExtraFiles。
它允许父进程将额外的、已经打开的文件描述符(比如一个正在监听的 TCP Socket)传递给子进程。

结构如下:

type Cmd struct {
	// ... 其他字段
	ExtraFiles []*os.File
}

二、结构

alt

var (
// 这三个结构完成了关于文件描述符的继承逻辑
  runningServers       map[string]*EndlessServer
  runningServersOrder  []string
  socketPtrOffsetMap   map[string]uint

  isChild     bool
  socketOrder string 
  
  runningServerReg     sync.RWMutex
  runningServersForked bool
  ……
)

type EndlessServer struct {
  http.Server
  EndlessListener  net.Listener
  SignalHooks      map[int]map[os.Signal][]func()
  tlsInnerListener *endlessListener
  wg               sync.WaitGroup
  sigChan          chan os.Signal
  isChild          bool
  state            uint8
  lock             *sync.RWMutex
  BeforeBegin      func(add string)
}
三、逻辑关系

alt

  1. 重启脚本实行 kill 命令(SIGHUP),信号触发父进程启动fork
  2. 父进程便利所有服务 runningServers,将相关socket套接字(socketPtrOffsetMap 反向映射顺序关系)全部存入ExtraFiles给子进程继承
  3. 子进程启动后获取套接字文件描述符并复用监听,程序启动成功后,再发送SIGUSR1信号,希望父进程主动调用shutdown退出
四、疑惑
  1. 为什么子进程获取到的这些文件描述符一定是顺序的?假设程序没有父进程,程序第一次启动,父进程此时的监听端口的文件描述符可能是不一样的啊,假设某一个描述符为可能为15啊,那子进程按照顺序下标读取的文件描述符不就是错误的吗?
Go 语言在启动子进程的瞬间,会把这个 FD “洗白” 成 FD 3,再把写入子进程的ExtraFiles(因为子进程是全新的,从3开始,合理)
Go 运行时(Runtime)在 fork 和 exec 之间,会调用 dup2(15, 3)
五、隐患
1. 当程序前后版本监听的addr列表发生了变化,这里顺序存入结果就有问题。
因为 runningServersOrder 顺序存入此次程序监听的地址列表。
但是 socketPtrOffsetMap 却是遍历ENDLESS_SOCKET_ORDER获取父进程的监听地址,
这样会导致地址和下标映射关系不一致,则从父进程获取到的监听socket不合理。

2. 当因为外部或错误操作导致此次平滑重启失败,父进程被表示为已经触发fork runningServersForked = true
但子进程又没有启动成功,无法重新触发fork,导致只能重启

六、其他推荐

https://github.com/cloudflare/tableflip 还在看

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务