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 这种前台运行模式来启动。

参考

相关文章
相关标签/搜索