gin支持prometheus

2022年8月03日 15:25

 

起因

 
gin使用了微服务架构, 如何将自己的服务都监控起来?
 

疑问列表

 
 
1. grafana是什么, 能做什么?
 
grafana是一个监控平台,支持不同数据源的可视化。也就是说支持从prometheuse,influxdb等数据源, 并将他们的数据形式可视化的图表.
 
2. prometheus能做什么?
prometheuse是一个监控系统,通过定时http pull采集数据. 并且支持http push.
 
3. prometheus和grafana是什么关系?
prometheuse是监控系统, 界面展示比较粗糙, grafana主要是可视化, 并且支持多种类型数据源。
 
4. gin如何与prometheuse结合?
见后面
 
5. gin如何与grafana?
grafana不支持直接接入gin服务。需要gin先接入prometheuse,再将prometheus接入grafana。
 
6. gin服务的metrics数据格式如何解析?
暂时未找到
 
7. gin自定义监控项
略. 不是本文主题
 
 

下载安装

 
grafana
https://github.com/grafana/grafana

prometheus
https://github.com/prometheus/prometheus
 

gin支持prometheuse

 
1. 修改gin服务代码
 
import (
    ...
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
    ...
)


//metrics, promhttp默认可以查看cpu,mem,线程等信息
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
 
2.修改prometheuse配置(prometheuse.yml)
 
scrape_configs:
  - job_name: "prometheus-demo"
    static_configs:
      - targets: ["192.168.28.26:8011"]
 
3. 启动服务
 
a. 启动gin和prometheuse服务后, 访问localhost:9090
 
b. 在"Expression"输入框选择"go_goroutines", 点击"Execute"
 
c. 刚开始数据比较少,可以过一段时间后再刷新页面
 
净土大经科注2014-doc
此生必看的科学实验-水知道答案
印光大师十念法(胡小林主讲第1集)
 

Tags: go prometheus
评论(9) 阅读(67)

golang实现枚举类型

2022年7月25日 10:50

描述

 
golang无枚举类型, 但可以借助自定义类型实现
 

什么是枚举类型

 
枚举类型是一种类型, 它的值只有有限个. 换而言之, 枚举类型=一个类型+N个离散值
 
go的实现, 可以自定义一个类型, 把它作为枚举类型, 然后使用该类型定义N个常量.
 
 

示例

 
type FormatType int

const (
    FTByte FormatType = iota
    FTArray
    FTDefine
)

func (ft FormatType) String() string {
    switch ft{
    case FTByte:
        return "byte"
    case FTArray:
        return "array"
    case FTDefine:
        return "define"
    }
    return ""
}
 
 

Tags: golang
评论(11) 阅读(126)

基于ectd实现go的服务注册

2021年11月27日 18:42

 

描述

 
    基于etcd实现go程序的服务注册,所看的学习资料均比较简单, 实际中要经过反复测试,自己写了一个先用着,有坑再填.
 

需求

 
1. 注册一个服务: 程序启动时
2. 注销服务: a. 程序异常退出时, 自动注销, 有5秒TTL延迟; b. 调用Stop()接口主动注销
3. 健壮性: a.ectd单节点重启, ectd本身支持; b. etcd全部重启后, 能够恢复正常
4. key的格式: 前缀/ip/pid
5. value: 字符串.(可以先转为json, 再转string)
 
 

创建etcd客户端

 

	
//不是重点, 摘要如下代码
 
cli, err := clientv3.New(clientv3.Config{
Endpoints:   ec.Endpoints,
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Fatal(err)
}
 

创建Service对象, 包含服务注册

 
package main

import (
	"context"
	"errors"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/clientv3util"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net"
	"os"
	"strings"
	"sync"
	"test/etcd"
	"time"
)


var IP string
var PID int


func init(){
	//日志
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	//创建
	var clevel zapcore.Level
	clevel.Set("debug")
	log := zap.New(zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), clevel))
	//设为全局
	zap.ReplaceGlobals(log)

	//
	IP, _ = GetLocalIP()
	PID = os.Getpid()
}

func GetLocalIP() (string,error){
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		zap.S().Warn(err)
		return "", err
	}
	for _, addr := range addrs{
		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback(){
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String(), nil
			}
		}
	}
	return "", errors.New("unable to determine local ip")
}


func RegisterService(endpoints []string, key string, value string) *Service{
	s := &Service{
		Value: value,
		endpoints: endpoints,
	}

	s.TTL = 5
	s.Key = strings.Join([]string{key, IP, fmt.Sprintf("%d", PID)}, "/")

	s.init()

	return s
}


type Service struct{
	Key          	string		//key 格式: /前缀/名称/ip/pid
	Value           string		//存放信息
	endpoints    	[]string	//etcd地址
	//心跳
	IsAlive      	bool		//是否存活
	heartCancel 	context.CancelFunc	//取消心跳
	//租约
	TTL             int64		//租约(秒)
	grant			*clientv3.LeaseGrantResponse	//租约
}


func (s *Service) init(){
	//启用etcd连接
	etcd.EnableEtcd(s.endpoints)

	//心跳
	go s.start()
}

func (s *Service) start(){
	defer func(){
		s.IsAlive = false

		//注销服务
		s.deleteService()
		//关闭续约(如果共用会误关?)
		etcd.Cli().Lease.Close()
	}()

	var aliveRsp <-chan *clientv3.LeaseKeepAliveResponse
	var err error
	var ctx context.Context
	for{
		if !s.IsAlive{
			// 注册服务
			s.registerService()

			// 续期信号chan
			aliveRsp, err = etcd.Cli().KeepAlive(context.TODO(), s.grant.ID)
			if err != nil{
				zap.S().Warn(err)
			}

			ctx, s.heartCancel = context.WithCancel(context.Background())
		}

		// 监听心跳信号
		select{
		case rsp := <- aliveRsp:
            //异常时无信号
			if rsp ==nil{
				s.IsAlive = false
				zap.S().Info("service missing signal")
				time.Sleep(time.Second*5)
				continue
			}

			s.IsAlive = true
			zap.S().Debugf("service alive %v", rsp.ID)
		case <- ctx.Done():
			zap.S().Info("service stopping")
			return
		}
	}
}

//停止

func (s *Service) Stop(){
	s.heartCancel()
}
 
[佛說大乘無量壽莊嚴清淨平等覺經pdf](http://www.sxjy360.top/page-download/)
[净土大经科注2014-doc](http://www.sxjy360.top/page-download/)
[此生必看的科学实验-水知道答案](http://www.sxjy360.top/page-download/)
[印光大师十念法(胡小林主讲第1集)](http://www.sxjy360.top/page-download/)
 

Tags: etcd
评论(106) 阅读(783)

go和python解码msgpack不一致

2021年11月27日 18:37

 

描述

 
    有一个消息字典类型, 使用go进行编码, 传入消息中间件。同时有一个python程序监听, 监听到消息之后进行解码, 遇到了编码问题
 
 

环境

 
go使用:     github.com/vmihailenco/msgpack/v5 v5.3.4
python使用: msgpack==1.0.2
 
 

python解码

 
# go编码的对象是个字典,value可能包含了数组,并且数组类型复杂
msgpack.unpackb(xxx)
 
 

错误提示

 
UnicodeDecodeError: 'utf-8' codec can't decode byte 0bx0 in position 0: invalid start byte
 

解决办法

 
msgpack.unpackb(xxx, raw=True)

#raw=True  解析为python字节类型
#raw=False 解析为python字符串, 默认使用utf-8编码
 

评论(73) 阅读(566)

golang位移操作的一个小坑

2021年11月25日 16:08

问题描述

 
    现有int16类型的整数,将它转为两个字节存放。在还原时,一不小心会就被坑了。
 
 

错误用法

 
length := int(keyBytes[cursor] + keyBytes[cursor+1] <<8)
 

正确用法

 
keyBytes := []byte{ 16, 1}
length := int(uint16(keyBytes[cursor]) + uint16(keyBytes[cursor+1]) <<8)
 

错误原因

 
字节byte位移时类型不会自动变化, 先要转换类型再位移。
 
 

评论(79) 阅读(564)

go版本的supervisord

2021年11月14日 13:27

 

描述

 
python版本的supervisrod, 在linux、windows系统中离线安装都不怎么方便.  是否有go版本的呢?
 

github地址

 
[https://github.com/ochinchina/supervisord/](https://github.com/ochinchina/supervisord/)
 

踩坑记录

 
* 编译32版本, 有些报错, 需要修改源码
 
* 在ui界面快速点击start/stop, 进程可能会启动多个。需要加锁处理处理.
 
## 用法
 
* 配置文件查找顺序
 
$CWD/supervisord.conf
$CWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
../etc/supervisord.conf (Relative to the executable)
../supervisord.conf (Relative to the executable)
 
 
* 最简单配置supervisord.conf
 
[program:test]
command = /your/program args
 
 
* 常用配置
 
[inet_http_server]          ;http接口
port=:9002
;username=xxxx
;password=xxxx

[supervisorctl]             ;ui界面
serverurl=http://127.0.0.1:9002

[supervisord]               ;日志
logfile=%(here)s/../log/supervisord.log
logfileMaxbytes=50MB        ;文件大小
logfileBackups=10           ;10个文件
loglevel=info               ;日志级别

[program:middleware1]       ;进程
command = %(here)s/../middleware1.exe   ;here指的是supervisord.conf的路径,而不是supervisord.exe的路径
autorestart = true
numprocs=1                  ;进程数量

[program:programname1]
command = %(here)s/../programname1.exe
autorestart = true
numprocs=1
depends_on=middleware1      ;依赖进程


[program:programname2]
command = %(here)s/../programname2.exe
autorestart = true
numprocs=1
depends_on=middleware1
 

Tags: supervisord go
评论(69) 阅读(650)

nats批量publish最后一条疑似丢失

2021年11月14日 13:24

 

问题描述

 
    写了一个测试脚本,从数据读取一组数据,然后逐条publish到nats-server中, 确定每条都publish了
 
    有另外一个程序中subscribe订阅,在该程序发现最后一条一直没收到
 
 

环境

 
* go: 1.16
 
* nats-server
 

程序摘要

 
* 发布
 
for _, record := range records{
    ...
    gNatsConn.Publish(Topic, record)
    time.Sleep(time.Second*0.1)    //间隔越小出现的概率越大. 间隔1秒好像就没有
}
 
* 订阅
 
natsConn.Subscribe(topic, func(m *nats.Msg){
    fmt.Println(m.Data)
    ....
}
 

原因以及解决办法

 
    nats在发布消息时,有应该是用了缓存通道, 大小是1。最后一条数据有可能没被读走
 
    发布频率较高,最后需要flush一下
 
 
 

Tags: nats go
评论(98) 阅读(772)

golang编译-ldflags -H windowsgui被认为是病毒

2021年8月26日 17:22

 
 

介绍

 
    使用walk为程序做了一个windows界面, cmd窗口需要隐藏。使用了 go build -ldflags="-H windowsgui"
 
    结果exe程序被杀毒软件认为是病毒
 

为什么会认为是病毒?

 
    隐藏cmd窗口, 双击exe,可以直接启动,并且看不到任何窗口。这不就是病毒的喜欢干的事吗.
 
    所以有些杀毒软件, 干脆通杀, 使用了 "-H windowsgui" 直接被认为是病毒
 
    我们这些正常的需求该咋办呢?
 

解决办法

 
    在程序中,使用代码隐藏cmd窗口
 
 
import "github.com/lxn/win"

//隐藏cmd窗口
win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
 
    缺点:启动时会有很短暂的黑框闪现,不过问题不大
 
 

评论(211) 阅读(1349)

golang离线环境依赖包问题

2021年8月21日 17:06

 

问题描述

 
在一个win10虚拟机中安装了go(go1.16.4)的开发环境, 其中有自己的项目,此时正常编译。
    
然后将这个虚拟机放到内网,内网网络受限,不允许访问外网
    
如果新一个依赖包,该怎么办呢?
 
 
 

方法一: 拷贝vendor目录(推荐)

 
1. 在外网环境项目目录,生成vendor
 
go mod vendor
 
2. 将vendor拷贝进内网,放在内网项目目录
 
 
缺点:只能当前项目使用
 
优点:体积小
 
 
类似python的pip download -r requipment.txt,将包下载后放到一个目录
 
 

方法二: 拷贝$GOPATH/pkg/mod目录

 
1. 外在更新全局依赖
 
go mod download
 
2.拷贝进入内网,替换mod目录
 
优点:可以被所有项目公用
 
缺点:有可能会比较大
 
 

方法三: 使用自己的goproxy

 
详细见
https://github.com/goproxyio/goproxy
 
 
 

吐槽

 
不少资料都是介绍GO111MODULE=on/off/auto之类的查找顺序,看得云山雾绕
 
假如GO111MODULE=on,并且在内网,此时无法访问网络,难道它不会找一下当前目录中的vendor?
 
 

评论(94) 阅读(720)

nats-server系统服务只能使用sc命令注册

2021年8月19日 02:42

 

起因描述

 
windows环境下nats-server注册成系统, 官方推荐是sc命令
 
而我觉得sc命令台太麻烦, 为什么不用github.com/kardianos/service,或者更方便的工具呢?
 

方法一:使用nssm通过界面进行配置

 
1. 下载地址
 
http://nssm.cc/download
 
2. 过程省略
 
3. 结果死活不行,由于是第三方工具,出错找不到原因,放弃
 
 

方法二:通过服务外壳作为系统服务

 
* 介绍
 
使用go生成一个demo.exe,再将demo.exe注册成系统,demo.exe读取配置文件,然后demo.exe通过exec.Command启动nats-server.exe
 
go比较好用的库可以用github.com/kardianos/service
 
 
* demo.exe启动nats-server.exe关键源码
 
p.cmd = exec.Command(fullExec, p.Args...)
p.cmd.Dir = p.Dir
p.cmd.Env = append(os.Environ(), p.Env...)
 
* demo.json配置示例
 
{
"Name": "demo",
"DisplayName": "demo",
"Description": "demo for nats-server",

"Dir": "d:\\nats-server",
"Exec": "xxxx\\nats-server.exe",
"Args": ["-c ","xxx\\nats-server.conf"],
"Env": [
],

"Stderr": "C:\\log\\nats_err.log",
"Stdout": "C:\\log\\nats_out.log"
}
 
* 结果报错
 
The service process could not connect to the service controller.
 
意思是一个服务不能连接到另一个服务,啥意思呢? 也就是通过exec.Command启动的竟然是服务,不应该是exe么.
 
* nats-server启动源码分析
 
nats-server.exe的启动入口源码
 
func Run(server *Server) error {
    //入口1:docker
if dockerized {
server.Start()
return nil
}
isInteractive, err := svc.IsAnInteractiveSession()
if err != nil {
return err
}
    //入口2: 交互模式
if isInteractive {
server.Start()
return nil
}
    //入口3: 启动服务(serviceName被写死了,只能是nats-server)
return svc.Run(serviceName, &winServiceWrapper{server})
}
 
* 错误原因总结
 
当以外壳方式启动时,走的是"入口3",也是启动服务,如果没有nats-server,它就啥事也没干,结果nats-server并没有启动
 

方法三:sc命令

 
官方推荐
 

总结

 
1、nat-server只能以sc命令注册成服务。怕麻烦可以写一个bat。
 
2、nats-server服务名只能是nats-server
 
 

Tags: nats
评论(69) 阅读(1030)