对于程序及服务的控制,本质上而言就是正确的启动,并可控的停止或退出。在go语言中,其实就是程序安全退出、服务控制两个方面。核心在于系统信号获取、Go Concurrency Patterns、以及基本的代码封装

程序安全退出

执行代码非安全写法

在代码部署后,我们可能因为服务配置发生变化或其他各种原因,需要将服务停止或者重启。通常就是for循环阻塞,运行代码,然后通过control+C或者kill来强制退出。代码如下:

//file svc1.go
package main
import (
"fmt"
"time"
)
//当接收到Control+c,kill -1,kill -2,kill -9 均无法正常执行defer函数
func main() {
fmt.Println("application is begin.")
//以下代码不会执行
defer fmt.Println("application is end.")
for {
time.Sleep(time.Second)
fmt.Println("application is running.")
}
}

  这种方式简单粗暴,很多时候基本也够用。但这种情况下,程序是不会执行defer的代码的,因此无法正确处理结束操作,会丢失一些很关键的日志记录、消息通知,非常不安全的。这时,需要引入一个简单的框架,来执行退出

执行代码的基本:信号拦截

由于go语言中的关键字go很好用,通过标准库,我们可以很优雅的实现退出信号的拦截:

//file svc2.go
package main import (
"fmt"
"time"
"os/signal"
"os"
)
//当接收到Control+c,kill -1,kill -2 的时候,都可以执行执行defer函数
// kill -9依然不会正常退出。
func main() {
fmt.Println("application is begin.")
//当程序接受到退出信号的时候,将会执行
defer fmt.Println("application is end.")
//协程启动的匿名函数,模拟业务代码
go func(){
for {
time.Sleep(time.Second)
fmt.Println("application is running.")
}
}()
//捕获程序退出信号
msgChan:=make(chan os.Signal,1)
signal.Notify(msgChan,os.Interrupt,os.Kill)
<-msgChan
}

  此时,我们实现了程序退出时的信号拦截,补充业务代码就可以了。但实际业务逻辑至少涉及到初始化、业务处理、退出三大块,代码量多了,会显得比较混乱,这就需要规范代码的结构。

执行代码的改进:信号拦截包装器

考虑上述情况,我们将正常的程序定义为:

  • Init: 系统初始化,比如识别操作系统、初始化服务发现Consul、Zookeper的agent、数据库连接池等。
  • Start:程序主要业务逻辑,包括但不限于数据加载、服务注册、具体业务响应。
  • Stop: 程序退出时的业务,主要包括内存数据存储、服务注销。

基于这个定义,之前的svc2.go仅保留业务代码的情况下,可以这样改写:

//file svc3.go
package main import (
"fmt"
"time"
"study1/svc"
) type Program struct {} func (p *Program) Start()error {
fmt.Println("application is begin.")
//必须非阻塞,因此通过协程封装。
go func(){
for {
time.Sleep(time.Second)
fmt.Println("application is running.")
}
}()
return nil
}
func (p *Program)Init()error{
//just demon,do nothing
return nil
}
func (p *Program) Stop() error {
fmt.Println("application is end.")
return nil
}
//当接收到Control+C,kill -1,kill -2 的时候,都可执行defer函数
// kill -9依然不会正常退出。
func main() {
p:=&Program{}
svc.Run(p)
}

  上诉代码中的Program的Init、Start、Stop事实上是实现了相关的接口定义,该接口在svc包中,被Run方法使用。代码如下:

//file svc.go
package svc import (
"os"
"os/signal"
) //标准程序执行和退出的执行接口,运行程序要实现接口定义的方法
type Service interface {
Init() error
//当程序启动运行的时候,需要执行的代码。不得阻塞。
Start() error
//程序退出的时候,需要执行的代码。不得阻塞。
Stop() error
}
var msgChan = make(chan os.Signal, 1) // 程序运行、退出的包装容器,主程序直接调用。
func Run(service Service) error {
if err := service.Init(); err != nil {
return err
}
if err := service.Start(); err != nil {
return err
}
signal.Notify(msgChan, os.Interrupt, os.Kill)
<-msgChan
return service.Stop()
}
// 通常不需要调用,特殊情况下,在程序内其他模块中,需要通知程序退出才会使用。
func Interrupt(){
msgChan<-os.Interrupt
}

  这段代码中,svg包的Run只会被唯一的main调用。为了支持其他退出模式,比如用户敲入字符命令的退出,因此加入了“后门”——Interrupt方法。后边会有具体的使用案例。由于一个进程只会有一个svg.Service的实例,通常情况下足以使用

在网络应用,可能会有更复杂的情况,我们需要考虑:

  • 程序启动
  • 程序不退出的情况下,多服务启动、并行运行与退出
  • 程序退出,并清理运行中的服务

可以做一个简单的Demon程序,来实现以上三点,其中,程序退出可以通过键盘输入命令,也可以Control+D。基于golang1.7,我们可以采用以下知识点:

  • 利用cancelContext来控制服务的退出
  • 利用之前实现的svc来实现程序的安全退出
  • 利用os.Stdin来获取键盘输入命令来模拟服务加载与退出的消息驱动。实际可能是网络rpc或http数据触发

golang1.7的context包

我们知道,当通道chan被close之后,任何<-chan都会得到立即执行。如果不清楚,可以查阅相关资料或写个测试代码,最好研读

golang的官方资料:https://blog.golang.org/pipelines

利用这个特征,我们可以通过golang1.7标准库新增的context包,通过注入的方式来实现全局或单个服务的控制。
context中定义了Context接口,我们通过几种不同的方法来获取不同的实现。包括:

WithDeadline\WithTimeout,获取到基于时间相关的退出句柄,以控制服务退出。
WithCancel,获取到cancelFunc句柄,以控制服务的退出。
WithValue,获取到k-v键值对,实现类似于session信息保存的业务支持。
Background\TODO,conext的根,通常作为以上三种方法的parent。
context包不是新东西,2014年就已经在google.org/x/net中,作为扩展库被很多开源项目使用(GIN、IRIS等等)。其CSP的应用方式非常值得进一步研读。

捕获键盘输入
通过os.stdin来获取键盘输入,其解析需要bufilo.Reader来协助处理。通常代码格式就是:

//...
//初始化键盘读取
reader:=bufilo.NewReader(os.Stdin)
//阻塞,直到敲入Enter键
input, _, _ := reader.ReadLine()
command:=string(input)
//...

示例代码

有了这两个概念之后,就可以很方便的实现一个简单的微服务加载、退出的框架。参考代码如下:

//file svc4.go
package main import (
"bufio"
"context"
"errors"
"fmt"
"os"
"strings"
"study1/svc"
"sync"
"time"
) type Program struct {
ctx context.Context
exitFunc context.CancelFunc
cancelFunc map[string]context.CancelFunc
wg WaitGroupWrapper
} func main() {
p := &Program{
cancelFunc: make(map[string]context.CancelFunc),
}
p.ctx, p.exitFunc = context.WithCancel(context.Background())
svc.Run(p) }
func (p *Program) Init() error {
//just demon,do nothing
return nil
}
func (p *Program) Start() error {
fmt.Println("本程序将会根据输入,启动或终止服务。") reader := bufio.NewReader(os.Stdin)
go func() {
for {
fmt.Println("程序退出命令:exit;服务启动命令:<start||s>-[name];服务停止命令:<cancel||c>-[name]。请注意大小写!")
input, _, _ := reader.ReadLine()
command := string(input)
switch command {
case "exit":
goto OutLoop
default:
command, name, err := splitInput(input)
if err != nil {
fmt.Println(err)
continue
}
switch command {
case "start", "s":
newctx, cancelFunc := context.WithCancel(p.ctx)
p.cancelFunc[name] = cancelFunc p.wg.Wrap(func() {
Func(newctx, name)
}) case "cancel", "c":
cancelFunc, founded := p.cancelFunc[name]
if founded {
cancelFunc()
}
}
}
}
OutLoop:
//由于程序退出被Run的os.Notify阻塞,因此调用以下方法通知退出代码执行。
svc.Interrupt()
}()
return nil
}
func (p *Program) Stop() error {
p.exitFunc()
p.wg.Wait()
fmt.Println("所有服务终止,程序退出!")
return nil
} //用来转换输入字符串为输入命令
func splitInput(input []byte) (command, name string, err error) {
line := string(input)
strs := strings.Split(line, "-")
if strs == nil || len(strs) != 2 {
err = errors.New("输入不符合规则。")
return
}
command = strs[0]
name = strs[1]
return
} // 一个简单的循环方法,模拟被加载、释放的微服务
func Func(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
goto OutLoop
case <-time.Tick(time.Second * 2):
fmt.Printf("%s is running.\n", name)
}
}
OutLoop:
fmt.Printf("%s is end.\n", name)
} //WaitGroup封装结构
type WaitGroupWrapper struct {
sync.WaitGroup
} func (w *WaitGroupWrapper) Wrap(f func()) {
w.Add(1)
go func() {
f()
w.Done()
}()
}

  

代码运行的时候,可以:

  • 通过输入”s-“或者”start-“+服务名,来启动一个服务
  • 用”c-“或”cancel-“+服务名,来退出指定服务
  • 可以用 “exit”或者Control+C、kill来退出程序(除了kill -9)。

在此基础上,还可以利用context包实现服务超时退出,利用for range限制服务数量,利用HTTP实现微服务RestFUL信息驱动。由于扩展之后代码增加,显得冗余,这里不再赘述。

转自:http://blog.csdn.net/qq_26981997/article/details/52275456

标准库 svc—程序及服务控制的更多相关文章

  1. python标准库00 学习准备

    Python标准库----走马观花 python有一套很有用的标准库.标准库会随着python解释器一起安装在你的电脑上的.它是python的一个组成部分.这些标准库是python为你准备的利器,可以 ...

  2. 【python】标准库的大致认识

    正如那句 Python 社区中很有名的话所说的:“battery included”,Python 的一大好处在于它有一套很有用的标准库(standard library).标准库是随着 Python ...

  3. Python标准库——走马观花

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! Python有一套很有用的标准库(standard library).标准库会随着 ...

  4. Go 标准库 http.FileServer 实现静态文件服务

    http.FileServer 方法属于标准库 net/http,返回一个使用 FileSystem 接口 root 提供文件访问服务的 HTTP 处理器.可以方便的实现静态文件服务器. http.L ...

  5. windows下的c语言和linux 下的c语言以及C标准库和系统API

    1.引出我们的问题? 标准c库都是一样的!大家想必都在windows下做过文件编程,在linux下也是一样的函数名,参数都一样.当时就有了疑问,因为我们非常清楚 其本质是不可能一样的,源于这是俩个操作 ...

  6. Lua标准库(转)

    转载地址:http://www.yiibai.com/lua/lua_standard_libraries.html Lua的标准库提供了一组丰富的功能,与C的API直接实现,建立在Lua编程语言函数 ...

  7. Python 标准库 urllib2 的使用细节[转]

    转自[http://zhuoqiang.me/python-urllib2-usage.html] Python 标准库中有很多实用的工具类,但是在具体使用时,标准库文档上对使用细节描述的并不清楚,比 ...

  8. Python 标准库 urllib2 的使用细节

    刚好用到,这篇文章写得不错,转过来收藏.    转载自 道可道 | Python 标准库 urllib2 的使用细节 Python 标准库中有很多实用的工具类,但是在具体使用时,标准库文档上对使用细节 ...

  9. 走进C标准库(2)——"stdio.h"中的fopen函数

    其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...

随机推荐

  1. 如何在Android的ListView中构建CheckBox和RadioButton列表(支持单选和多选的投票项目示例)

    引言 我们在android的APP开发中有时候会碰到提供一个选项列表供用户选择的需求,如在投票类型的项目中,我们提供一些主题给用户选择,每个主题有若干选项,用户对这些主题的选项进行选择,然后提交. 本 ...

  2. linux 执行远程linux上的shell脚本或者命令以及scp 上传文件到ftp--免密码登陆

    场景:在linux A 上执行Linux B上的shell脚本和命令 步骤1.设置ssh免登陆 1.SSH无密码登录 # 本地服务器执行(A机器):生成密钥对 ssh-keygen -t dsa -P ...

  3. [LeetCode] 374. Guess Number Higher or Lower_Easy tag: Binary Search

    We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...

  4. react全局的公共组件-------弹框 (Alert)

    最近研究react,发现写一个组件很容易,但是要写一个全局的好像有点麻烦.不能像VUE一样,直接在原型上面扩展,注册全局组件 下面实现一个弹框,只需要引入之后,直接调用方法即可,不需要写入组件 给出我 ...

  5. IIS预编译提升加载速度

    当我们把网站部署在IIS7或IIS6S的时候,每当IIS或是ApplicationPool重启后,第一次请求网站反应总是很慢,原因大家都知道(不知道可以参考这个动画说明ASP.NET网页第一个Requ ...

  6. Kotlin enum class 匿名类实例

    Kotlin里的枚举类里有新玩意:就是枚举类的常量可以同时看成是一个同名匿名类 既然是类就可以与方法关联 看看官网的代码 如果你有过其它语言的使用枚举的经历,你可能对这个定义和说明很迷惑 我给你一个例 ...

  7. response.sendRedirect(url)与request.getRequestDispatcher(url).forward(request,response)的区别

    response.sendRedirect(url)跳转到指定的URL地址,产生一个新的request,所以要传递参数只有在url后加参数,如: url?id=1.request.getRequest ...

  8. Linux平台Oracle 12.1.0.2 单实例安装部署

    主题:Linux平台Oracle 12.1.0.2 单实例安装部署 环境:RHEL 6.5 + Oracle 12.1.0.2 需求:安装部署OEM 13.2需要Oracle 12.1.0.2版本作为 ...

  9. 大数据-05-Spark之读写HBase数据

    本文主要来自于 http://dblab.xmu.edu.cn/blog/1316-2/ 谢谢原作者 准备工作一:创建一个HBase表 这里依然是以student表为例进行演示.这里假设你已经成功安装 ...

  10. time_t time()

    time_t  atime,  btime; time(&atime); btime = time(0); 两种方式效果一样.