cmdr 04 - 简单微服务 (daemon)

反射星云
• 阅读 2538
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 这种前台运行模式来启动。

参考

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MBR笔记
<bochs:100000000000e\WGUI\Simclientsize(0,0)!stretchedsize(640,480)!<bochs:2b0x7c00<bochs:3c00000003740i\BIOS\$Revision:1.166$$Date:2006/08/1117
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这