• golang 热重启


    热重启的意义

    • 可以让用户神不知鬼不觉的,更新后端的项目
    • 测试的时候,也不用停止项目然后再开启项目,降低项目开启时间

    golang热重启的主要步骤

    • 将编译好的项目覆盖进行

    golang热重启的思想

    • 监听重启信号
    • 收到信号之后,进行fork子进程,将服务监听的socket文件描述符传递给子进程
    • 子进程监听父进程的socket,这个时候父进程和子进程都可以接收请求
    • 子进程启动成功之后,父进程停止接收新的连接,等待旧连接处理完成(或超时)
    • 父进程退出,重启完成

    源码

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net"
    	"net/http"
    	"os"
    	"os/exec"
    	"os/signal"
    	"syscall"
    	"time"
    	"flag"
    )
    var (
       listener net.Listener
       err error
       server http.Server
       graceful =  flag.Bool("g", false, "listen on fd open 3 (internal use only)")
    )
    
    func init(){
    	log.SetFlags(log.Ldate | log.Lshortfile)
    }
    // MyHandler 控制台
    type MyHandler struct {
    
    }
    
    func (*MyHandler)ServeHTTP(w http.ResponseWriter, r *http.Request){
       	log.Println("request start at ", time.Now(),  r.URL.Path+"?"+r.URL.RawQuery,  "request done at ", time.Now(), "  pid:", os.Getpid())
       	time.Sleep(10 * time.Second)
    	fmt.Fprintln(w,"cout")
       	log.Println("request done at ", time.Now(), "  pid:", os.Getpid() )
       	log.Println(r.RemoteAddr)
    }
    
    func main() {
    	flag.Parse()
       	fmt.Println("start-up at " , time.Now(), *graceful)
    	if *graceful {
    		// 这暗藏杀机,先复制fd到新的fd, 然后设置子进程exec时自动关闭父进程的fd,即“F_DUPFD_CLOEXEC”
    		// 要是接收到信号后,不执行这里,就会发生端口被占用的错误
    		f := os.NewFile(3, "")
    		listener, err = net.FileListener(f)
    		fmt.Printf( "graceful-reborn  %v %v  %#v 
    ", f.Fd(), f.Name(), listener)
    	}else{
    		listener, err = net.Listen("tcp", ":8080")
    	}
       	server := http.Server{
        	Handler: &MyHandler{},
        	ReadTimeout: 6 * time.Second,
       	}
       	log.Printf("Actual pid is %d
    ", syscall.Getpid())
       	if err != nil {
          	log.Println(err)
          	return
       	}
    	log.Printf(" listener: %v
    ",   listener)
    
       	go func(){//不要阻塞主进程
          	err := server.Serve(listener)
          	if err != nil {
             	log.Println(err)
          	}
       	}()
    
       	//signals
       	func(){
          	ch := make(chan os.Signal, 1)
          	signal.Notify(ch, syscall.SIGINT)
          	for{
    			//阻塞主进程, 不停的监听系统信号
             	sig := <- ch
            	log.Printf("signal: %v", sig)
             	ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)  // 这里就是上下文
            	switch sig {
    			// 我用终止信号,做例子,大家可以使用别的信号
            	case syscall.SIGINT:
                	println("signal cause reloading")
                	signal.Stop(ch)
                	{
    					// fork子进程
                   		tl, ok := listener.(*net.TCPListener)
                   		if !ok {
                      		log.Println("listener is not tcp listener")
                      		return
    					}
    					// 接收父进程的socket文件描述符
                   		currentFD, err := tl.File()
                   		if err != nil {
                      		log.Println("acquiring listener file failed")
                      		return
    					}
    					//  好坑啊,这里,“-g"是必须的,因为再次运行之后,g=true
    			   		cmd := exec.Command(os.Args[0],"-g")
    			   		log.Println(cmd.Args)
    					// currentFD 这里是子进程了,fork的时候,将socket文件描述传给子进程
    					// 父进程将socket文件描述符传递给子进程可以通过命令行
    					// f := os.NewFile(3, "")  []*os.File{currentFD} 想到对应
                   		cmd.ExtraFiles, cmd.Stdout,cmd.Stderr = []*os.File{currentFD} ,os.Stdout, os.Stderr
    					err = cmd.Start()
    					// 重启运行   
                   		if err != nil {
                      		log.Println("cmd.Start fail: ", err)
                      		return
                   		}
                   		log.Println("forked new pid : ",cmd.Process.Pid)
    				}
    				// 阻塞到父进程将所有的请求处理完毕
                	server.Shutdown(ctx) // 优雅的等待关闭
    				log.Println("graceful shutdown at ", time.Now())
             	}
    		}
       	}()
    }
    
    • flag.Bool好坑啊!你设置默认值为false,但是你写命令的时候,-g它的值就会变为true
  • 相关阅读:
    idea 导入(非maven)web项目并发布到tomcat服务器
    EasyUI-combotree 下拉树 数据回显时默认选中
    千万级别数据量mysql优化策略
    MySQL忘记密码,或:root密码重置报错:mysqladmin: connect to server at 'localhost' failed的解决方案
    centOS 6.5下升级mysql,从5.1升级到5.7
    查看mysql数据库版本方法总结
    【转】App开发者必备的运营、原型、UI设计工具整理
    APP原型设计工具,哪家强?转自知乎
    数据库为什么要分库分表
    20180925-1 每周例行报告
  • 原文地址:https://www.cnblogs.com/MyUniverse/p/13143937.html
Copyright © 2020-2023  润新知