cmdr 04 - 簡單微服務 (daemon)

cmdr 04 - simple micro-service
based on cmdr v0.2.21

My ado is too much.html

因此此次直入主題,謝絕吐槽。不知道 cmdr 幹嗎用的,無妨看看前文git

那麼,golang適合作後端開發,不管是 gRPC 仍是 RESTful 都是它的強項。github

一旦咱們想要開發一個微服務時,拋開核心邏輯不談,也不論 DevOps 方面到底是 K8s,仍是 Docker,仍是裸機,總要面對一個啓動、調試、測試的平常問題。golang

cmdr 除了提供命令行參數的解釋能力以外,也額外提供了一個daemon插件,它能夠幫助你簡化平常開發工做,也令你沒必要關心 pid 文件、日誌、退出信號等等問題,也無需重複編排 daemon 相關的命令行指令。docker

下面介紹怎麼使用 daemon 插件,怎麼編寫實際的業務邏輯。咱們以 demo 爲例編寫一個簡單的示例性微服務,並解釋具體的作法。數據庫

使用 Daemon 插件

啓用 Daemon 插件

啓用 Daemon 插件只需一行代碼:ubuntu

// 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 以及你的業務邏輯。segmentfault

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 邏輯。centos

因此下面咱們首先完成必須的部分:

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 這種前臺運行模式來啓動。

參考

相關文章
相關標籤/搜索