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 demo@msuser.service
。
然後我們通過 sudo systemctl status demo@vagrant.service
檢視到 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
這種前臺執行模式來啟動。