通过查看nsqd.go的主应用入口,会发现其使用了svc来管理应用的生命周期。
func main() { prg := &program{} if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { log.Fatal(err) } }
可以看到svc参数主要有两部分,第一个是svc.Service接口 即管理生命周期的接口,另一个是signal的不定长参数 也就是响应的信号。而这个program则是nsq自己实现svc.Service接口的结构体。我们可以看下这个接口
type Service interface { //可添加一些应用初始化时的操作 Init(Environment) error //应用启动操作,但注意不能在这儿阻塞 Start() error //应用被停止时的操作 Stop() error }
里面主要包含了应用的一些生命周期的操作,包括初始化,启动,以及销毁方法。具体的我们可以写一个案例来说明
svc案例
引入依赖,对于的go.mod文件如下
module application go 1.14 require ( github.com/judwhite/go-svc v1.1.2 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect )
编写对应的应用程序
package main import ( "github.com/judwhite/go-svc/svc" "log" "os" "syscall" "time" ) type pro struct { } func main() { pro :=&pro{} svc.Run(pro,syscall.SIGINT) } func (p *pro)Init( e svc.Environment) error{ port := os.Getpid() log.Printf("程序已经初始化 端口为%d ",port) return nil } func (p *pro)Start() error{ log.Printf("程序已经start ") go func() { tik := time.NewTicker(3*time.Second) for { c := <-tik.C log.Printf("当前时间为:%s ",c.Format("2006-01-02 15:04:05")) } }() return nil } func (p *pro)Stop() error{ log.Printf("程序已经stop ") return nil }
可以看到我们在Init方法中可以获取到例如程序的pid等信息,在Start方法中运行应用,在Stop方法中则可以监听信号,例如中断(SIGINT)等。运行程序可以在控制台看到对应的生命周期信息,使用例如Ctrl+c程序便可以监听到中断信息而做出响应,运行后效果如下
可以看到对于管理应用的生命周期还是很方便的。
当然svc之所以最终能够监视到信号信息还是依赖go的signal包下的Notify方法,我们可以写一个demo来看一下go中signal的Notify方法如何使用
package main import ( "fmt" "log" "os" "os/signal" "syscall" "time" ) func main() { sin := make(chan os.Signal, 1) fmt.Printf("系统启动pid为: %d ",os.Getpid()) signal.Notify(sin,syscall.SIGINT,syscall.SIGKILL,syscall.SIGTERM) go func() { tik := time.NewTicker(3*time.Second) for { <-tik.C log.Printf("主业务执行中。。。 ") } }() <-sin fmt.Println("系统关闭") }
本方法系统启动后会一直阻塞,直到监听到信号监听列表里面的信号后会向我们创建的chan里边放入一条信息,而这个时候阻塞的chan则会执行后面的逻辑
实现了一个简易版的svc
当然了 ,总结下来实现逻辑很简单,就是利用了go的信号监听和chan的通信便利性来实现的一个解耦的应用生命周期管理工具,将应用的初始化,启动,销毁独立开来。