cmdr 04 - 簡單微服務 (daemon)

hedzr發表於2019-06-03

cmdr 04 - simple micro-service
based on cmdr 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)
}
複製程式碼

對於一個微服務來說,你一定要編寫的是 OnRunOnStop 兩個方法。其他的幾個方法,通常是可選的,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 來看看:

cmdr 04 - 簡單微服務 (daemon)

可以注意到 s, server, serve, svr, daemon 命令是新增的,它包含一組子命令以提供 daemon 相關的操作。

其中 server start 子命令的解說是這樣的:

cmdr 04 - 簡單微服務 (daemon)

也就是說,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在前臺:

cmdr 04 - 簡單微服務 (daemon)

然後按下 CTRL-C 終止它,可以看到這個”微服務“能夠正常地跑起來,也能正常地自行銷燬。

守護程式執行

而如果我們要執行 demo 為守護程式的話,首先你需要將它編譯成可執行檔案,然後才能啟動為守護程式模式。

cmdr 04 - 簡單微服務 (daemon)

通過 vagrant 環境,我們可以看到守護程式啟動了,然後被我們的 stop 指令正確地關閉了。

systemd 服務執行

在支援 systemd 的 Linux 發行版中,我們可以測試將微服務安裝為 systemd 服務。

cmdr 04 - 簡單微服務 (daemon)

其中,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 這種前臺執行模式來啟動。

參考

相關文章