Golang 轮子之 Supervisor

golang 与Supervisor进程管理工具之间会擦出怎样的火花呢,来看看新轮子go-supervisor

Supervisor 是一个强大的 进程管理工具。

在非容器化管理的服务器上, Supervisor 是有非常广泛的使用场景的。

例如:

服务批量重启,多服务按顺序启动,服务oom后自动拉起,服务std日志收集等,甚至服务健康检查它都能做。

原 Supervisor (Python)

git: https://github.com/Supervisor/supervisor

doc: http://supervisord.org/

新轮子 Supervisor (Golang)

git: https://github.com/ochinchina/supervisord

对比

两个 Supervisor 对比

指标\语言 Python Golang
起源 2004 2017
当前版本 4.2.4 0.7.3
语言版本要求 2.7+ 或 3.4+ 1.11+
*unix 支持 支持
MacOS 支持 支持
Widnows 不支持 能跑
安装包大小 Pyton环境(40MB) + 脚本(490KB) 4.2MB
Web GUI 支持 支持

功能支持情况

共呢个\语言 Python Golang
分组 支持 支持
挂了自动拉起 支持 支持
定时重启 支持 支持
web端管理 支持 支持
监控文件自动重启 支持 支持
依赖顺序启动 支持 支持

这里只是列举了常用的功能,基本都实现了的,依靠golang按需runtime+可执行代码打包后,二进制部署相较 python 是更为方便和小巧的。

安装

gihub 上没有二进制包,需要clone代码,手动编译。

$ git clone https://github.com/ochinchina/supervisord
$ cd supervisord
$ go generate

# 以下代码会编译出 linux 平台二进制可执行文件
$ GOOS=linux go build -tags release -a -ldflags "-linkmode external -extldflags -static" -o supervisord
# mac 下
$ go build -tags release -o supervisord

试试

$ ./supervisord --help
Usage:
  supervisord [OPTIONS] <command>

Application Options:
  -c, --configuration= the configuration file
  -d, --daemon         run as daemon
      --env-file=      the environment file

Help Options:
  -h, --help           Show this help message

Available commands:
  ctl      Control a running daemon
  init     initialize a template
  service  install/uninstall/start/stop service
  version  show the version of supervisor

使用

  • 先创建一个配置文件
$ vi supervisor.conf
[program:test]
command = watch -n 5 "echo Hello!"
  • 启动
$ supervisord -c supervisor.conf
INFO[2022-10-15T17:31:24+08:00] load configuration from file                  file=./supervisor.conf
INFO[2022-10-15T17:31:24+08:00] create process:test
INFO[2022-10-15T17:31:24+08:00] stop listening
INFO[2022-10-15T17:31:24+08:00] try to start program                          program=test
DEBU[2022-10-15T17:31:24+08:00] wait program exit                             program=test
INFO[2022-10-15T17:31:25+08:00] success to start program                      program=test

## 此时该 supervisord 会前台运行,退出终端,或者 Ctrl+C 都会推出,会结束所有的程序。
^CINFO[2022-10-15T17:32:39+08:00] receive a signal to stop all process & exit   signal=interrupt
INFO[2022-10-15T17:32:39+08:00] stop the program                              program=test
INFO[2022-10-15T17:32:39+08:00] force to kill the program                     program=test
INFO[2022-10-15T17:32:39+08:00] Send signal to program                        program=test signal=killed
INFO[2022-10-15T17:32:39+08:00] program stopped with status:signal: killed    program=test
INFO[2022-10-15T17:32:39+08:00] program exited                                program=test
INFO[2022-10-15T17:32:39+08:00] Stopped by user, don't start it again         program=test
  • 启动并运行到后台
$ supervisord -c supervisor.conf -d

这样就启动了

http 管理

supervior 同样提供了 Web GUI 管理入口,我们来启用配置试试

[program:test]
command = watch -n 5 "echo Hello"

[inet_http_server]
port=127.0.0.1:9001

访问: http://127.0.0.1:9001

web_gui

同样支持 http Auth, 按照如下配置

[inet_http_server]
port=127.0.0.1:9001
username=test1
password=thepassword

注意: Shutdown 是停掉 supervisor 服务本身,包括 Web 入口,需要登陆到服务器,手动启动后,才能继续使用。要停掉所有自程序,选择全部然后点击 Stop Select

文件监控

当我们部署,或更新程序时,希望 supervisor 能自动关闭,并运行新的可执行文件,那么 文件监控 功能就派上用场了。

go-supervisor 支持多种文件监控模式:

  1. 执行的程序本身监控
  2. 某个文件夹内监控
  3. 文件监控
  • 配置方式
[program:golang]
command = /Users/paulxu/golang/go-learn/main -conf ./Users/paulxu/golang/go-learn/config.toml
restart_when_binary_changed=true

这里的测试代码我放到文章最后了

INFO[2022-10-15T20:33:20+08:00] program is changed, restart it                program=golang
INFO[2022-10-15T20:33:20+08:00] stop the program                              program=golang
INFO[2022-10-15T20:33:20+08:00] force to kill the program                     program=golang
INFO[2022-10-15T20:33:20+08:00] Send signal to program                        program=golang signal=killed
INFO[2022-10-15T20:33:20+08:00] program stopped with status:signal: killed    program=golang
INFO[2022-10-15T20:33:20+08:00] program exited                                program=golang
INFO[2022-10-15T20:33:20+08:00] Stopped by user, don't start it again         program=golang
INFO[2022-10-15T20:33:21+08:00] try to start program                          program=golang
DEBU[2022-10-15T20:33:21+08:00] wait program exit                             program=golang
INFO[2022-10-15T20:33:22+08:00] success to start program                      program=golang

监控到变化后,重启方式也有两种,一种是:直接kill。另一种是发送信号量给程序,让程序自行处理。

注意: 如果 supervisor 本身发了 kill 信号给程序,程序自己结束了,superviosr 默认也不会帮你在重启程序,它的设计逻辑时,我只负责发信号,其他程序自理。这里你可以手动新增一条配置:

[program:golang]
command = /Users/paulxu/golang/go-learn/main -conf ./Users/paulxu/golang/go-learn/config.toml
restart_when_binary_changed=true
autostart=true #  这行配置

如果管理了在线的大流量服务,推荐使用第二种,平滑重启,因为直接kill程序,会导致请求处理一半,或事务进行到一半中止,进而数据不一致。

好的,我们再次调整配置

[program:golang]
command = /Users/paulxu/golang/go-learn/main -conf ./Users/paulxu/golang/go-learn/config.toml
restart_when_binary_changed=true
restart_signal_when_binary_changed=9 # SIGKILL

来看下日志:

INFO[2022-10-15T20:37:58+08:00] program is changed, restart it                program=golang
INFO[2022-10-15T20:37:58+08:00] Send signal to program                        program=golang signal=terminated
INFO[2022-10-15T20:37:58+08:00] program stopped with status:exit status 0     program=golang
INFO[2022-10-15T20:37:58+08:00] program exited                                program=golang
INFO[2022-10-15T20:37:58+08:00] Don't start the stopped program because its autorestart flag is false  program=golang

注意这里的日志,说的是,supersivor 给程序发了 信号,但是程序退出了,由于,你启用自动重启配置,所有,没有启动该程序。

这里是信号发错了,调整一下:

[program:golang]
command = /Users/paulxu/golang/go-learn/main -conf ./Users/paulxu/golang/go-learn/config.toml
restart_when_binary_changed=true
restart_signal_when_binary_changed=SIGHUP # 1 这里填数字字符都行

这下重新启动supervisor,看下效果。

程序运行日志:

2022-10-16 11:00:27.754 [INFO] main.go:13: start
2022-10-16 11:00:27.754 [INFO] main.go:21: waiting signal~
2022-10-16 11:00:28.755 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:29.757 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:30.761 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:31.765 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:32.768 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:33.771 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:34.774 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:35.779 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:36.783 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:37.659 [INFO] main.go:32: golang get signal hangup [sighup]
2022-10-16 11:00:37.788 [INFO] main.go:17: golang program is running~
2022-10-16 11:00:38.790 [INFO] main.go:17: golang program is running~

这样之后,我们就能实现部署新的程序后,自动平滑重启程序了。

注意:在 Web 端,手动 stop/start 程序,不会发信号量到程序!

监控文件夹

刚刚展示目标程序变更,自动重启。那么配置文件更新了,自动重启如何配置呢?

注意:如果程序内自动监控了文件变化并更新配置(推荐这样做),则不需要 supervisor 来发信号给程序本身了。

这里新增了两行配置,1.配置监控存放配置文件的文件夹,2. 配置文件夹内文件变化时,发什么信号通知程序。

[program:golang]
command = /Users/paulxu/golang/go-learn/main -conf /Users/paulxu/golang/go-learn/config/config.toml
restart_when_binary_changed=true
restart_signal_when_binary_changed=SIGHUP
restart_directory_monitor=/Users/paulxu/golang/go-learn/config/
restart_signal_when_file_changed=SIGHUP

测试代码

package main

import (
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gogf/gf/frame/g"
)

func main() {
    g.Log().Line().Info("start!!")
    go func() {
        for {
            time.Sleep(time.Second)
            g.Log().Line().Info("golang program is running~")
        }
    }()

    g.Log().Line().Info("waiting signal~")
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
    for {
        select {
        case s := <-c:
            switch s {
            case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
                g.Log().Line().Infof("golang get signal %+v", s)
                return
            case syscall.SIGHUP:
                g.Log().Line().Infof("golang get signal %+v [sighup]", s)
            default:
                g.Log().Line().Infof("golang get other signal %+v", s)
                return
            }
        }
    }
}

最后

好的,今天给大家介绍了一款 superviorgolang轮子,以及基本使用方法。可以看到一些常用的基础和golang碰撞后,擦出了不一样的火花。由于 Go 语言的编译工具链会全静态链接构建二进制文件,All in One 的设计理念,对运维部署时非常友好的。期待更多这样的轮子。

虽然,在当前容器化时代,它的使用场景被进一步挤压,但是在小型站点,实体机上使用还是很方便的。

关注我,了解更多golang知识~

qrcode

Memcached Mac

Memcached Mac 安装 与 使用

Memcached Mac 安装

安装

  • Homebrew
brew install memcached

启动

brew  services start memecached

OR

memcached -p 11211 -u nobody -c 1024 -m 64m -d -vv

停止

brew services stop memcached 

重启

brew services restart memcached 

连接

telnet 127.0.0.1 11211

插入数据测试

set test 0 0 13
memcachedTest

参考

  • https://blog.csdn.net/qq_39052513/article/details/111741430
  • https://www.jianshu.com/p/4984c652161f

Windows下Electron 执行 exec console输出stderr 乱码

windows下用Electron启动别的二进制程序,打印报错信息是乱码

electron nodejs exec windows 下 stderr 乱码

解决否

已解决

方案

原因 :出现原因 , 因为CMD默认的是gbk2312 编码 , 而nodejs 默认的是utf-8的格式, 所以在exec 运行的时候接受的cmd命令返回值回事乱码情况

$ npm install iconv-lite
const iconv = require('iconv-lite');
iconv.skipDecodeWarning = true; // 这个主要是它会警告 , 大体意思是转化编码失去精度啥的 , 对于我来说不需要, 不想看见控制台报那种一片黄色警告的就加上这句 , 不加也行
const childProcess = require('child_process');

childProcess.exec(`dir`,{ encoding: 'binary' },(err, stdout, stderr)=>{
    if(err){
      // console.log('stdout1', iconv.decode(o, 'cp936'));
      console.log(iconv.decode(err, 'cp936'))
      return false;
    }else{
      let ress = iconv.decode(stdout, 'cp936');
  }
})

参考

  • https://cnodejs.org/topic/5a533acd99d207fa49f5cd11
  • https://www.cnblogs.com/sunjinggege/p/12606310.html

windows 下 npm 拉取 github 仓库问题

npm拉私有仓库报错:git@github.com: Permission denied (publickey) 怎么办呢?

npm 拉私有仓库报错无权限问题

$ npm i https://github.com/littlewrite/frameless-titlebar@v2.1.4-reatv18
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
npm ERR! code 128
npm ERR! An unknown git error occurred
npm ERR! command git --no-replace-objects ls-remote ssh://git@github.com/littlewrite/frameless-titlebar@v2.1.4-reatv18.git
npm ERR! git@github.com: Permission denied (publickey).
npm ERR! fatal: Could not read from remote repository.
npm ERR!
npm ERR! Please make sure you have the correct access rights
npm ERR! and the repository exists.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\wojia\AppData\Local\npm-cache\_logs\2022-07-27T03_44_39_166Z-debug-0.log

解决否

已解决

解决方案

  1. 在本地 生成 ssh key,然后添加到 github 配置。
  2. 添加到本地项目
  3. windows 下要启动一个啥 服务
  4. 可以开始 npm install 了, 带上https:// 后缀 .git ,如果要制定tag 再添加 #V1.0.0

参考

  • https://docs.github.com/cn/authentication/troubleshooting-ssh/error-permission-denied-publickey
  • https://www.zhihu.com/question/21402411
  • https://stackoverflow.com/questions/17846529/could-not-open-a-connection-to-your-authentication-agent
  • https://github.com/npm/npm/issues/14447

用前端构建工具打包后端服务,我到底经历什么

node.js 能用 wepack打包么?node.js 代码可以打包成一个js 文件么?来这里看看吧

看到这个 标题,是的,我本是个后端,最近要写点 node.js, 之前写前端,知道 npm build 一下,那么用 javascript 写的后端程序也要 npm build 吧,好的,作为个 gopher , 带着对 javascript 的刻板印象就开干了。

我本 gopher,奈何没有一个会前端的老婆,就自己干前端了,如果干的不对,请及时纠正

项目背景

还是简单介绍下项目背景,屏幕前专业前端同学可以先猜猜这样干行还是不行,行的话,该怎么做。

这个程序运行的 node.js 端,提供一个简单的 http 服务,内部逻辑涉及:

  1. toml2json
  2. curl-to-go
  3. toml-to-go
  4. toml2xml
  5. xml2json
  6. json2toml

这些逻辑,可用内建包能解决,有些依赖第三方包,例如:

  1. json2toml
  2. object-to-xml

还有些组件,未提交到npm,也未做模块化处理,需要把代码找出来,单独处理,例如:

  1. curl-to-go
  2. toml-to-go

这些第三方包,感觉都是运行在前端环境的,(刚开始,我只是通过npm拉下来,也不知道能不能运行在 node.js 上,npm 也没指明这些包在前后端 runtime 兼容性)现在我想用 webpack 将其所有的代码打包成一个 js 文件,并能运行在 node.js 环境,

好,问题和背景如上。

别说:node.js 打什么包!

钟薛高 你还要不要

开整

我面临如下问题:

  1. 如何引入前端 包
  2. 如何引入 未模块化改造的包
  3. 如何让 webpack 编译的成果在 node.js 环境也能运行

关于第一个问题,首先得搞清楚在 Javascriptimportrequire 区别。

import 与 require 区别

require/exports 属于社区自己选举出的方案。import/export 属于是 ECMAScript 规范。

这里我们用到了 webpack , 那么就得使用 import/export 作为代码引入语法。所以,对 curl-to-go 代码改一下,在最后新增:

function curlToGo(curl) {
...
}

+ export default curlToGo;

这样就解决了,各个js文件组合问题。

第二个问题,需要弄清楚,node.js 的包管理机制

node.js 模块

node.js 模块管理,用的是 require ,由于我需要用 node.js 起一个 http 服务,需要使用 require 语句引入 http 模块。

但这还没完,webpack 是不能很好区分,哪些是外部依赖,哪些是内部依赖。所以这里需要告知 webpack :

module.exports = {

    target: "node", 

    }

第三个问题,那就随缘了,build之后,run一下试试咯。

最终 webpack 配置如下。

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,5 +1,6 @@
 const path = require('path');
 const webpack = require('webpack');
+var fs = require("fs")

 /*
  * 引入 ParallelUglifyPlugin 插件
@@ -13,9 +14,20 @@ const PATHS = {
     build: path.join(__dirname, 'build'),
 };

+var nodeModules = {};
+fs.readdirSync('node_modules')
+    .filter(function(x) {
+        return ['.bin'].indexOf(x) === -1;
+    })
+    .forEach(function(mod) {
+        nodeModules[mod] = 'commonjs ' + mod;
+    });
+
 module.exports = {
     mode: 'development',
     devtool: false, // 编译成果 保留换行
+    target: "node",
+    externals: nodeModules,
     entry: {
         entry: path.join(__dirname, 'src/index.js'),
     },

最后

又是折腾前端的一天,哦不,是好几天。

javascript 和 node.js 包都放在 npm 里,有点让人搞不清楚适用环境,当然,这次引入的包都没有调用底层 API,所以,没出现兼容性问题。

importrequire 傻傻分不清楚,CommonJS, ES6ES5 各种标准算是给我好好上了一课。

好了,简单聊到这,不是太难的东西,只是表达一下一个刚入门前端的人碰到这些问题后的,“幸”路历程。

参考

  • https://zhuanlan.zhihu.com/p/28483358
  • https://sazzer.github.io/blog/2015/05/12/Javascript-modules-ES5-vs-ES6/
  • https://juejin.cn/post/6896397110078504973
  • https://zh.quish.tv/how-package-nodejs-application-using-webpack
  • https://blog.anymelon.com/2020/05-22-nodejs-webpack-record/
  • https://www.zhihu.com/question/56820346