平滑升级, 连接平滑迁移
在客户端连接不断开的情况下升级系统
client.go
package main
import (
"fmt"
"net"
"time"
)
// Example command: go run client.go
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:9000")
if err != nil {
panic(err)
}
defer conn.Close()
for {
conn.Write([]byte("hello"))
time.Sleep(time.Millisecond * 100)
var data = make([]byte, 1024)
conn.Read(data)
fmt.Println(string(data))
}
}
server.go
package main
import (
"fmt"
"github.com/pubgo/xerror"
"net"
"os"
"os/signal"
"syscall"
"time"
)
var forkExec = func(argv0 string, argv []string, files ...uintptr) (pid int, err error) {
lll := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
lll = append(lll, files...)
fmt.Println(lll)
return syscall.ForkExec(argv0, argv, &syscall.ProcAttr{
Env: os.Environ(),
Files: lll,
Sys: &syscall.SysProcAttr{
Setsid: true,
},
})
}
// Example command: go run client.go
func main() {
var connList []uintptr
isUpgrade := os.Getenv("fork") != ""
var err error
var ln net.Listener
if isUpgrade {
ln, err = net.FileListener(os.NewFile(3, ""))
if err != nil {
panic(err)
}
} else {
ln, _ = net.Listen("tcp", "localhost:9000")
}
if isUpgrade {
conn, err := net.FileConn(os.NewFile(4, ""))
xerror.Panic(err)
go func() {
for {
conn.Write([]byte("hello" + os.Getenv("fork")))
time.Sleep(time.Millisecond * 500)
var data = make([]byte, 1024)
conn.Read(data)
fmt.Println(string(data))
}
}()
}
//defer ln.Close()
rawConn, ok := ln.(syscall.Conn)
if !ok {
panic("not raw")
}
raw, err := rawConn.SyscallConn()
xerror.Panic(err)
var dupfd uintptr
xerror.Panic(raw.Control(func(fd uintptr) {
dupfd, err = dupFd(fd)
if err != nil {
panic(err)
}
xerror.Panic(err)
}))
connList = append(connList, dupfd)
go func() {
for i := 0; ; i++ {
conn, err := ln.Accept()
if err != nil {
break
}
rawConn, ok := conn.(syscall.Conn)
if !ok {
panic("not raw")
}
raw, err := rawConn.SyscallConn()
xerror.Panic(err)
var dupfd uintptr
xerror.Panic(raw.Control(func(fd uintptr) {
dupfd, err = dupFd(fd)
xerror.Panic(err)
}))
connList = append(connList, dupfd)
go func() {
for {
conn.Write([]byte("hello"))
time.Sleep(time.Millisecond * 500)
var data = make([]byte, 1024)
conn.Read(data)
fmt.Println(string(data))
}
}()
}
}()
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP)
<-ch
os.Setenv("fork", "true")
pid, err := forkExec(os.Args[0], os.Args, connList...)
xerror.Panic(err)
go func() {
// 防止子进程变成僵尸进程
for {
_, _ = syscall.Wait4(pid, nil, syscall.WNOWAIT, nil)
time.Sleep(time.Second * 5)
return
}
}()
time.Sleep(time.Second * 5)
}
func dupFd(fd uintptr) (uintptr, error) {
dupfd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_DUPFD_CLOEXEC, 0)
if errno != 0 {
return 0, fmt.Errorf("can't dup fd using fcntl: %s", errno)
}
return dupfd, nil
}
- 获取Listener, Conn的fd
- 通过fork传递给子进程, 然后退出
- 子进程判断, 然后获取fd地址, 还原Listener和Conn
Go使用unixSocket实现进程间传递文件描述符
- 感谢 chrispink_yang老哥,讲解的很详细
- 这种方法也可以实现平滑重启
go优雅升级/重启工具调研
作者:百里求一
出处:http://www.cnblogs.com/bergus/
我的语雀: https://www.yuque.com/barry.bai
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署