作为一个广泛存在于众多设备上的常驻程序如何更新?他可以是一个agent,也可以是一个service,可以运行在众多容器中作为基础支持的程序,也可以运行在路由器、空调、洗衣机等家用设备或者物联设备上的支持程序。或许有些程序可以通过第三方平台进行更新,比如某些服务程序,配有专门的发版平台。但是更多的是无法通过发版平台发布的程序,比如是linux上的一个自启动的service,这个linux是你家里的路由器。这时候就需要程序自己更新自己了。
程序更新通常有下列步骤,下载新的程序,替换旧的可执行文件和配置文件,重启程序(退出旧进程,拉起新进程)。
程序自己如何去做这些操作?通常下载和替换文件并不难,只需要有一个接口能查询到新的程序版本和下载地址,很容易实现下载新版程序和替换。唯一需要注意一点的是重启程序这一步骤,这一步骤在业务程序进程上是无法实现的,因为它要拉起一个业务进程(worker),并且退出自己,在它拉起业务进程的时候,如果自己本身就是业务进程(worker),这样就会有两个业务进程(worker),这对于大多数服务是不允许的,会出错。那么我们可以专门设计一个守护进程(watcher)仅用于程序的更新与重启。
因此watcher进程里,更新程序和重启程序有如下流程
下载替换与杀旧进程(worker)放在一个线程中,另一个线程只负责监视worker进程是否存在,如果worker进程不存在,则拉起worker进程。
同一个程序中,如何区分worker进程与watcher进程?我这里使用的是特定环境变量来区分的。终端中拉起程序,默认启动的是watcher进程,watcher进程启动后检测到worker进程不存在,那么它将会通过exec.Command
拉起进程,并且注入特定环境变量,如下
cmd := exec.Command(procName, os.Args[1:]...)
cmd.Env = append(os.Environ(), "PROC_FLAG=worker")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if e := cmd.Start(); nil != e {
log.Printf("ERROR: %v\n", e)
return
}
而且程序启动的时候,首先也会判断该环境变量是否存在,如果存在该环境变量,那么就进入业务进程的业务逻辑。如果不存在该环境变量,则进入守护进程的守护逻辑。
这样一来就能够实现程序自己更新自己了。