cmdr 04 - simple micro-service
based oncmdr
v0.2.21
My ado is too much.
所以這次直入主題,謝絕吐槽。不知道 cmdr
幹嘛用的,無妨看看前文
那麼,golang適合做後端開發,無論是 gRPC 還是 RESTful 都是它的強項。
一旦我們想要開發一個微服務時,拋開核心邏輯不談,也不論 DevOps 方面究竟是 K8s,還是 Docker,還是裸機,總要面對一個啓動、調試、測試的日常問題。
cmdr
除了提供命令行參數的解釋能力之外,也額外提供了一個daemon插件,它可以幫助你簡化日常開發工作,也令你不必關心 pid 文件、日誌、退出信號等等問題,也無需重複編排 daemon 相關的命令行指令。
下面介紹怎麼使用 daemon 插件,怎麼編寫實際的業務邏輯。我們以 demo 爲例編寫一個簡單的示例性微服務,並解釋具體的做法。
使用 Daemon 插件
啓用 Daemon 插件
啓用 Daemon 插件只需一行代碼:
// Entry is app main entry
func Entry() {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
daemon.Enable(svr.NewDaemon(), nil, nil, nil)
if err := cmdr.Exec(rootCmd); err != nil {
logrus.Errorf("Error: %v", err)
}
}
實現 daemon.Daemon
接口
啓用 daemon 插件,需要你實現 daemon.Daemon
接口,並編寫一定的包裝代碼來連接 cmdr
, daemon
以及你的業務邏輯。
daemon.Daemon
接口
daemon.Daemon
接口是這樣的:
// Daemon interface should be implemented when you are using `daemon.Enable()`.
type Daemon interface {
OnRun(cmd *cmdr.Command, args []string, stopCh, doneCh chan struct{}) (err error)
OnStop(cmd *cmdr.Command, args []string) (err error)
OnReload()
OnStatus(cxt *Context, cmd *cmdr.Command, p *os.Process) (err error)
OnInstall(cxt *Context, cmd *cmdr.Command, args []string) (err error)
OnUninstall(cxt *Context, cmd *cmdr.Command, args []string) (err error)
}
對於一個微服務來說,你一定要編寫的是 OnRun
,OnStop
兩個方法。其他的幾個方法,通常是可選的,daemon插件針對 RESTful http2 完成了默認的邏輯來支持 reload,status。此外,daemon插件還針對 systemd 實現了默認的 install 和 uninstall 邏輯。
所以下面我們首先完成必須的部分:
OnRun
type daemonImpl struct {}
func (*daemonImpl) OnRun(cmd *cmdr.Command, args []string, stopCh, doneCh chan struct{}) (err error) {
logrus.Debugf("demo daemon OnRun, pid = %v, ppid = %v", os.Getpid(), os.Getppid())
go worker(stopCh, doneCh)
return
}
func worker(stopCh, doneCh chan struct{}) {
LOOP:
for {
time.Sleep(time.Second) // this is work to be done by worker.
select {
case <-stopCh:
break LOOP
default:
}
}
doneCh <- struct{}{}
}
daemon 提供兩個 channels,stopCh
應該促使你的代碼結束無限循環,當你的代碼退出循環之後應該觸發 doneCh
事件。這樣的邏輯保證了整個微服務的 graceful shutdown。
至於核心的邏輯,我們的 worker
,現在僅僅是個無限循環而已,你可以根據實際業務需要對其完成替換。
下一次我們也許提供一個 RESTful micro-service 的樣本。
OnStop
以及其他
func (*daemonImpl) OnStop(cmd *cmdr.Command, args []string) (err error) {
logrus.Debugf("demo daemon OnStop")
return
}
func (*daemonImpl) OnReload() {
logrus.Debugf("demo daemon OnReload")
}
func (*daemonImpl) OnStatus(cxt *daemon.Context, cmd *cmdr.Command, p *os.Process) (err error) {
fmt.Printf("%v v%v\n", cmd.GetRoot().AppName, cmd.GetRoot().Version)
fmt.Printf("PID=%v\nLOG=%v\n", cxt.PidFileName, cxt.LogFileName)
return
}
func (*daemonImpl) OnInstall(cxt *daemon.Context, cmd *cmdr.Command, args []string) (err error) {
logrus.Debugf("demo daemon OnInstall")
return
}
func (*daemonImpl) OnUninstall(cxt *daemon.Context, cmd *cmdr.Command, args []string) (err error) {
logrus.Debugf("demo daemon OnUninstall")
return
}
其它的接口方法基本上什麼也不做,因爲對於我們的worker來說,不需要在 OnStop
時清理數據庫連接、釋放其它資源,也不需要在 OnReload
時加載新的配置文件。
測試 demo
現在我們可以將 demo
跑起來看看了。首先研究下有什麼命令行指令可供使用,我們採用 --tree
來看看:
可以注意到 s, server, serve, svr, daemon
命令是新增的,它包含一組子命令以提供 daemon 相關的操作。
其中 server start
子命令的解說是這樣的:
也就是說,start
子命令的兩個變形允許你在前臺運行微服務,這是爲了便於 docker 集成,以及在 IDE 中調試微服務的目的:
# 在前臺運行微服務,而不是進入 daemon 模式
demo run
demo start --foreground
對於 daemon 模式,沒有標準的規範定義來要求一定具備哪些要素,不過大體上還是有約定俗成的東西。daemon 在中文中常常被稱作
守護進程
。daemon 模式一般來說包含這些要素:
- 進程啓動後,fork自己的一份副本在操作系統中運行,這樣副本和 tty 的關聯就被detach了,此外子進程也具有獨立的環境和進程空間,甚至是身份,不會收到其它服務、其它 ttys 的干擾。
- 子進程在 /var/run 中保持一個 pid 文件,這指示了子進程的基本狀態
- 子進程通過 syscall signals 來與前臺交互,一般地說,SIGHUP信號使得子進程 reload 配置信息完成重啓動、卻不被關閉進程和重新啓動進程;SIGTERM等信號通知子進程結束服務。等等。
- 子進程將日誌輸出爲 /var/log/ 下的日誌文件
前臺運行
所以,我們運行下demo在前臺:
然後按下 CTRL-C 終止它,可以看到這個”微服務“能夠正常地跑起來,也能正常地自行銷燬。
守護進程運行
而如果我們要運行 demo 爲守護進程的話,首先你需要將它編譯成可執行文件,然後才能啓動爲守護進程模式。
通過 vagrant 環境,我們可以看到守護進程啓動了,然後被我們的 stop 指令正確地關閉了。
systemd 服務運行
在支持 systemd 的 Linux 發行版中,我們可以測試將微服務安裝爲 systemd 服務。
其中,sudo /vagrant/demo server install
完成安裝動作,成功之後demo服務就安裝就緒了,並且已經被預設爲隨系統啓動而自動啓動的模式。
然後我們依照 systemd 的規範啓動它:sudo systemctl start demo@$USER.service
。
值得注意的是,我們將 demo 安裝爲了通配模式,因此 demo 是可以在不同用戶身份下被啓動的。如果你想用專用的微服務賬戶啓動它,你可以使用:sudo systemctl start [email protected]
。
然後我們通過 sudo systemctl status [email protected]
查看到 demo 已經啓動成功了,其中有三個錯誤,然而他們是可以被忽略的,它們都是爲了嘗試建立幾個相關文件夾的目的,所以只是預防性的指令。而 demo 的正主,也就是 ExecStart 行表示啓動時成功的,而且 Active 的狀態也是 running 狀態。
此時,log/logrus 等日誌輸出也被轉發到了日誌文件 /var/log/demo/demo.log 中。
那麼我們也可以通過 sudo systemctl stop demo@$USER.service
來停止服務。
小結
由於 systemd 在 macOS 中並不被直接支持,所以對於這個部分的測試是放在 vagrant 中完成的。
對於 Windows 來說,你只能使用 server run
前臺運行的方式,我們也暫無支持 NT Service 的計劃。但你可以通過前臺運行的方式完成日常開發調試工作。
實際的生產環境中,你可以選擇 centos,ubuntu 等發行版,部署需要的只是 sudo demo server install
一條指令。
對於容器的環境,你應該使用 demo server run
這種前臺運行模式來啓動。