平滑升级, 连接平滑迁移
在客户端连接不断开的情况下升级系统
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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。