本篇内容主要介绍了关于go-micro框架进行prometheus监控的整合,api层采用的是gin框架,服务注册采用 etcdv3(服务启动时确保etcd已启动),有关prometheus的安装可以参考项目监控之Prometheus初体验(一)。
备注:etcd可以到 https://github.com/etcd-io/etcd/releases下载,将压缩包下载解压运行 etcd.exe 即可启动服务。
1)、添加prometheus插件
2)、添加prometheus采集指标的路径 “/metrics”
go StartMonitor("127.0.0.1", 8888)
func StartMonitor(ip string, port int) error {
http.Handle("/metrics", promhttp.Handler())
err := http.ListenAndServe(fmt.Sprintf("%s:%d", ip, port), nil)
return err
}
1)、添加 prometheus中间件
2)、添加prometheus采集指标的路径 “/metrics”
prometheus中间件代码
package utils
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"net/http"
"time"
)
type PrometheusMonitor struct {
ServiceName string //监控服务的名称
APIRequestsCounter *prometheus.CounterVec
RequestDuration *prometheus.HistogramVec
RequestSize *prometheus.HistogramVec
ResponseSize *prometheus.HistogramVec
}
func NewPrometheusMonitor(namespace, serviceName string) *PrometheusMonitor {
APIRequestsCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Name: "http_requests_total",
Help: "A counter for requests to the wrapped handler.",
},
[]string{"handler", "method", "code", "micro_name"},
)
RequestDuration := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Name: "http_request_duration_seconds",
Help: "A histogram of latencies for requests.",
},
[]string{"handler", "method", "code", "micro_name"},
)
RequestSize := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Name: "http_request_size_bytes",
Help: "A histogram of request sizes for requests.",
},
[]string{"handler", "method", "code", "micro_name"},
)
ResponseSize := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Name: "http_response_size_bytes",
Help: "A histogram of response sizes for requests.",
},
[]string{"handler", "method", "code", "micro_name"},
)
//注册指标
prometheus.MustRegister(APIRequestsCounter, RequestDuration, RequestSize, ResponseSize)
return &PrometheusMonitor{
ServiceName: serviceName,
APIRequestsCounter: APIRequestsCounter,
RequestDuration: RequestDuration,
RequestSize: RequestSize,
ResponseSize: ResponseSize,
}
}
func (m *PrometheusMonitor) PromMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
//使用rest风格api路径时可结合group_wrapper使用
//relativePath := c.GetString(constant.RelativePathKey)
//if relativePath == "" {
// relativePath = c.Request.URL.Path
//}
relativePath := c.Request.URL.Path
start := time.Now()
reqSize := computeApproximateRequestSize(c.Request)
c.Next()
duration := time.Since(start)
code := fmt.Sprintf("%d", c.Writer.Status())
m.APIRequestsCounter.With(prometheus.Labels{"handler": relativePath, "method": c.Request.Method, "code": code, "micro_name": m.ServiceName}).Inc()
m.RequestDuration.With(prometheus.Labels{"handler": relativePath, "method": c.Request.Method, "code": code, "micro_name": m.ServiceName}).Observe(duration.Seconds())
m.RequestSize.With(prometheus.Labels{"handler": relativePath, "method": c.Request.Method, "code": code, "micro_name": m.ServiceName}).Observe(float64(reqSize))
m.ResponseSize.With(prometheus.Labels{"handler": relativePath, "method": c.Request.Method, "code": code, "micro_name": m.ServiceName}).Observe(float64(c.Writer.Size()))
}
}
// From https://github.com/DanielHeckrath/gin-prometheus/blob/master/gin_prometheus.go
func computeApproximateRequestSize(r *http.Request) int {
s := 0
if r.URL != nil {
s = len(r.URL.Path)
}
s += len(r.Method)
s += len(r.Proto)
for name, values := range r.Header {
s += len(name)
for _, value := range values {
s += len(value)
}
}
s += len(r.Host)
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
if r.ContentLength != -1 {
s += int(r.ContentLength)
}
return s
}
4.1.1、 api层
//访问路径为 /demo-api/sayHello
func (d *Demo) SayHello(c *gin.Context) {
req := &demoService.GetUsernameReq{}
res, err := d.demoService.GetUsername(context.TODO(), req)
if err != nil {
fmt.Print(err)
c.JSON(http.StatusOK, "inner err")
return
}
c.JSON(http.StatusOK, "hello,"+res.Username)
}
4.1.2、service层
1)、编写proto文件
syntax = "proto3";
package service;
message GetUsernameReq{
}
message GetUsernameRes{
string username = 1;
}
service DemoService {
rpc GetUsername(GetUsernameReq) returns(GetUsernameRes);
}
2)、生成proto代码
protoc --proto_path=. --go_out=. --micro_out=. *.proto
3)、实现protoc生成的接口DemoServiceHandler
//简单返回一个名字
func (d *Demo) GetUsername(ctx context.Context, in *service.GetUsernameReq, out *service.GetUsernameRes) error {
out.Username = "jack"
return nil
}
4.2、编写并启动api网关
1)、编写 main.go
package main
import (
"github.com/micro/micro/cmd"
)
func main() {
cmd.Init()
}
2)、编写 plugin.go,这里及以下服务演示注册采用的是注册到 etcdv3,使用默认的mdns可以不需要该文件(window上注册时网络连接有时会出现地址类似10.0.0.?的这种内部网络,导致转发时出现500异常)
package main
import _ "github.com/micro/go-plugins/registry/etcdv3"
import _ "github.com/micro/go-plugins/transport/grpc"
3)、启动网关接口
go run main.go plugin.go --registry_address localhost:2379 --registry etcdv3 api --handler=http --namespace go-micro.api
注:这里的namespace参数由注册时的服务名决定
4.3、分别启动api服务和service服务
下面为代码的结构
分别进入到两个服务的项目的目录下运行服务
go run main.go plugin.go --registry_address localhost:2379 --registry etcdv3
4.4、测试服务是否启动成功
1)、访问一下我们所写 demo-api 的路径,成功访问
2)、接下来来看看我们注册的指标是否存在,分别输入我们监控的两个服务的指标采集路径
3)、可以看到指标成功获取到了,到此我们的项目已经成功整合了prometheus
补充:micro service层使用prometheus插件生成的是以 micro_ 开头的标签,自定义的(如上图demo_api_*)标签则是以我们传入的namespace开头的
有什么不对的地方,欢迎指正!
获取项目源码