4

浅谈如何使用 github.com/kardianos/service - 画个一样的我

 1 year ago
source link: https://www.cnblogs.com/huageyiyangdewo/p/17367532.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在实际开发过程中,有时候会遇到如何编写Go开机自启服务的需求,在linux中我们可以使用systemd来进行托管,windows下可以通过注册表来实现,mac下可以通过launchd来实现,上面的方式对于开发者来说,并不是什么困难的事情,但是对于使用者而言,是并不希望通过这么复杂的方式来达到开机自启的功能的。这个时候,作为开发者,就需要使用其他的方式来实现开机自启的功能,下面讲一个Go中,借助这个库 github.com/kardianos/service 来简化如何实现开机自启功能。

1、github.com/kardianos/service 基础介绍

1.1 kardianos/service 简介

我们先来看一看 github.com/kardianos/service 上面的自我介绍:Run go programs as a service on major platforms.

如何理解上面这句话呢,上面这句话翻译出来的意思是:"在主要平台上将Go程序作为服务运行"。

这意味着我们可以将Go编写的程序以服务的形式在主要操作系统上运行,例如Windows、Linux、macOS等。这意味着程序可以在后台持续运行,而不需要用户手动启动或停止它们。这种方式可以提高程序的可靠性和稳定性,同时也方便了程序的管理和监控。

那该如何理解服务呢?

服务(Service)是指在计算机系统中,为用户或其他程序提供某种功能的程序或进程。服务通常在后台运行,可以长时间运行,不需要用户交互,可以自动启动和停止。服务可以提供各种功能,如网络服务、数据库服务、文件共享服务等。在操作系统中,服务通常以服务进程的形式运行,可以通过系统管理工具进行管理和配置。

有了上面的了解过后,再来看看官方自己的描述。service will install / un-install, start / stop, and run a program as a service (daemon). Currently supports Windows XP+, Linux/(systemd | Upstart | SysV), and OSX/Launchd. 如何理解上面这句话呢,我说说自己的理解。

我们可以将编写好的代码打包成二进制文件后,通过二进制文件名 + install / un-install, start / stop来运行我们的服务,程序将作为服务(守护进程)运行。目前支持Windows XP+、Linux/(systemd|Upstart|SysV)和OSX/Launchd。

Windows controls services by setting up callbacks that is non-trivial. This is very different then other systems. This package provides the same API despite the substantial differences. It also can be used to detect how a program is called, from an interactive terminal or from a service manager. 下面是我的理解:

Windows 通过设置回调来控制服务,这与其他系统非常不同。这个包提供了相同的API,尽管存在很大差异。它还可以用于检测程序是从交互式终端还是从服务管理器调用的。

看到这里的时候,我其他不太理解最后一句话,什么叫从服务管理器调用。将在 2.2 章节中介绍。

1.2 kardianos/service 安装

安装 github.com/kardianos/service 的方式和其他方式一样。

go get github.com/kardianos/service

指定版本方式
go get github.com/kardianos/[email protected]

2、kardianos/service 使用方式

以下介绍都是基于 github.com/kardianos/[email protected] 进行讲解的。

2.1 kardianos/service 简单的使用

我们先来看一个简单的例子,代码如下:

package main

import (
	"fmt"
	"github.com/kardianos/service"
	"os"
)

type SystemService struct {}

func (ss *SystemService) Start(s service.Service) error {
	fmt.Println("coming Start.......")
	go ss.run()
	return nil
}

func (ss *SystemService) run()  {
	fmt.Println("coming run.......")
}

func (ss *SystemService) Stop(s service.Service) error {
	fmt.Println("coming Stop.......")
	return nil
}

func main()  {
	fmt.Println("service.Interactive()---->", service.Interactive())
	svcConfig := &service.Config{
		Name: "custom-service",
		DisplayName: "custom service",
		Description: "this is github.com/kardianos/service test case",
	}

	ss := &SystemService{}
	s, err := service.New(ss, svcConfig)
	if err != nil {
		fmt.Printf("service New failed, err: %v\n", err)
		os.Exit(1)
	}

	if len(os.Args) > 1 {
		err = service.Control(s, os.Args[1])
		if err != nil {
			fmt.Printf("service Control 111 failed, err: %v\n", err)
			os.Exit(1)
		}
    return
	}

	// 默认 运行 Run
	err = s.Run()
	if err != nil {
		fmt.Printf("service Control 222 failed, err: %v\n", err)
		os.Exit(1)
	}
}

通过go run main.go得到如下结果,注意:程序并不会终止,而是阻塞住了

service.Interactive()----> true
coming Start.......
coming run.......

实际上,kardianos/service为我们提供了下面的参数使用,我们可以通过go build -o main main.go编译得到二进制文件,然后使用下面的命令来运行服务。

# 生成开机自启服务所需要的文件,文件位置根据操作系统的不同而不用,linux在 /etc/systemd/system 或者 /lib/systemd/system 下
./main install

# 删除上面生成的文件
./main uninstall

# 开启服务
./main start

# 重启服务
./main restart

# 停止服务
./main stop

2.2 kardianos/service 如何做开机自启服务

接下来以 Linux 为例,进行讲解。其他系统大家可自行尝试。

具体的步骤如下:

1、第一步是编写代码,编写完成后,编译成二进制文件。

代码就以 2.1 中的为例。首先编译成二进制文件。

go build -o main main.go

2、运行 可执行文件。

# 这将在 /etc/systemd/system 或者 /lib/systemd/system 中生成 custom-service.service 文件
# 我这里测试的时候是在 /etc/systemd/system 中生成的
./main install

看到这里,用过systemd的朋友应该可以猜到 kardianos/service 背后是通过什么来实现开机自启的。就是通过systemd来管理的。

3、将 custom-service.service 服务设置为开机自启.

运行下面命令将我们编写的程序设置为开机自启服务。

# 设置服务开机自启动
systemctl enable test-service.service
# 启动
systemctl start test-service.service

下面是systemctl常用的命令。

# 启动
systemctl start test-service.service

# 停止
systemctl stop test-service.service


# 设置服务开机自启动
systemctl enable test-service.service

# 查询是否自启动服务 
systemctl is-enabled test-service.service

# 取消服务器开机自启动 
systemctl disable test-service.service

# 列出正在运行的服务
systemctl list-units --type=service

接下来我们看看服务管理器是什么意思?
1、./main install

2280011-20230502133135041-509575370.png


2、查看custom-service.service文件

2280011-20230502133247887-1783966406.png

3、执行systemctl start custom-service.service后,查看服务运行过程

2280011-20230502133417716-111487417.png

这里就是上面 服务管理器 的作用,也就是说,如何服务是手动运行的,那么 service.Interactive()返回 true,比如:./main start。如何是系统管理器运行的,则返回 false,比如:systemctl start custom-service.service。

2.3 结合 cli 使用

通过上面的例子,我们大概知道了如何使用 github.com/kardianos/service 。实际使用中,一般的服务都可以通过-h来查看帮助文档,但是我们我们通过./main -h会报错,所以需要完善下代码,使我们的程序更容易使用。下面,我们一起看看,借助 github.com/urfave/cli/v2 来完成上面的需求。

package main

import (
	"fmt"
	"github.com/kardianos/service"
	"github.com/urfave/cli/v2"
	"os"
)

type SystemService struct {}

func (ss *SystemService) Start(s service.Service) error {
	fmt.Println("coming Start.......")
	go ss.run()
	return nil
}

func (ss *SystemService) run()  {
	fmt.Println("coming run.......")
}

func (ss *SystemService) Stop(s service.Service) error {
	fmt.Println("coming Stop.......")
	return nil
}

func main()  {
	app := cli.NewApp()
	app.Name = "custom-service"
	app.Usage = "how to use custom service"
	app.Commands = []*cli.Command{
		{
			Name: "install",
			Action: ctrlAction,
		},
		{
			Name: "uninstall",
			Action: ctrlAction,
		},
		{
			Name: "start",
			Action: ctrlAction,
		},
		{
			Name: "restart",
			Action: ctrlAction,
		},
		{
			Name: "stop",
			Action: ctrlAction,
		},
	}
	app.Flags = []cli.Flag{
		&cli.StringFlag{
			Name: "install",
			Value: "install",
			Usage: "Write the files required for startup",
		},
		&cli.StringFlag{
			Name: "uninstall",
			Value: "uninstall",
			Usage: "Delete startup files",
		},
		&cli.StringFlag{
			Name: "start",
			Value: "start",
			Usage: "start the service",
		},
		&cli.StringFlag{
			Name: "stop",
			Value: "stop",
			Usage: "stop the service",
		},
		&cli.StringFlag{
			Name: "restart",
			Value: "restart",
			Usage: "restart the service",
		},
	}

	app.Action = startAction

	app.Run(os.Args)
}

func createSystemService() (service.Service, error) {
  fmt.Println("service.Interactive()---->", service.Interactive())
	svcConfig := &service.Config{
		Name: "custom-service",
		DisplayName: "custom service",
		Description: "this is github.com/kardianos/service test case",
	}

	ss := &SystemService{}
	s, err := service.New(ss, svcConfig)
	if err != nil {
		return nil, fmt.Errorf("service New failed, err: %v\n", err)
	}
	return s, nil
}

func ctrlAction(c *cli.Context) error  {
	s, err := createSystemService()
	if err != nil {
		fmt.Printf("createSystemService failed, err: %v\n", err)
		return err
	}
	err = service.Control(s, c.Command.Name)
	if err != nil {
		fmt.Printf("service Run 222 failed, err: %v\n", err)
		return err
	}
	return nil
}

func startAction(c *cli.Context) error  {
	s, err := createSystemService()
	if err != nil {
		fmt.Printf("createSystemService failed, err: %v\n", err)
		return err
	}
	// 默认 运行 Run
	err = s.Run()
	if err != nil {
		fmt.Printf("service Run failed, err: %v\n", err)
		return err
	}

	return nil
}

大家可以根据自己的需求进行开发,这里只是讲一个简单的案例而已。

go build -o main main.go
./main -h

NAME:
   custom-service - how to use custom service

USAGE:
   custom-service [global options] command [command options] [arguments...]

COMMANDS:
   install    
   uninstall  
   start      
   restart    
   stop       
   help, h    Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --install value    Write the files required for startup (default: "install")
   --uninstall value  Delete startup files (default: "uninstall")
   --start value      start the service (default: "start")
   --stop value       stop the service (default: "stop")
   --restart value    restart the service (default: "restart")
   --help, -h         show help

3、浅谈 service 的执行过程

这里以 mac 为例,通过 goland 来查看调用的链路方便些。

使用 github.com/kardianos/service 的步骤大概是这样的:

1、定义 service.Config

2、通过 service.New 创建 service

3、通过 service.Control 来运行上面 生成好的 service。

第一步没啥好说的,注意 service.Config 中的 Name 是必须的,且生成的开机自启文件名就是以他命名的。

重点看看第二步,service.New源码入下:

// New creates a new service based on a service interface and configuration.
func New(i Interface, c *Config) (Service, error) {
	//这就是为啥 Name 是必填的原因
	if len(c.Name) == 0 {
		return nil, ErrNameFieldRequired
	}
	// 注意看这里,system 在使用到时候就已经初始化了,但是我们使用的时候,并没有做初始化 system 的动作。
	// 那么什么时候初始化的 system 呢?
	// 这个时候就会想到 init() 这个函数,这个函数在 import 时就会自动运行。
	if system == nil {
		return nil, ErrNoServiceSystemDetected
	}
	return system.New(i, c)
}

System 接口如下:

var (
	system         System
	systemRegistry []System
)


// System represents the service manager that is available.
type System interface {
	// String returns a description of the system.
	String() string

	// Detect returns true if the system is available to use.
	Detect() bool

	// Interactive returns false if running under the system service manager
	// and true otherwise.
	Interactive() bool

	// New creates a new service for this system.
	New(i Interface, c *Config) (Service, error)
}

以 mac 的系统为例, 讲讲system.New(i, c)

type darwinSystem struct{}

func (darwinSystem) String() string {
	return version
}

func (darwinSystem) Detect() bool {
	return true
}

func (darwinSystem) Interactive() bool {
	return interactive
}

func (darwinSystem) New(i Interface, c *Config) (Service, error) {
	s := &darwinLaunchdService{
		i:      i,
		Config: c,

		userService: c.Option.bool(optionUserService, optionUserServiceDefault),
	}

	return s, nil
}

func init() {
  //这里就是给 system 变量赋值
  //这里赋值有点不同,是在编译阶段由编译器根据系统的不同,初始化不同的 结构体。
  //这个我也不敢确定,希望知道的朋友不吝赐教,感谢感谢!
	ChooseSystem(darwinSystem{})
}

// ChooseSystem chooses a system from the given system services.
// SystemServices are considered in the order they are suggested.
// Calling this may change what Interactive and Platform return.
func ChooseSystem(a ...System) {
	systemRegistry = a
	system = newSystem()
}

darwinLaunchdService实现了 Service interface 定义的方法。这里就不复制源代码了,有兴趣可以看看源代码。

2280011-20230502144358847-1602431750.png

以上就是我对 github.com/kardianos/service 的理解,有不对的地方,请不吝赐教。谢谢!

__EOF__


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK