Prometheus(普罗米修斯)是一套开源的监控&报警&时间序列数据库的组合,起始是由SoundCloud公司开发的。随着发展,越来越多公司和组织接受采用Prometheus,社会也十分活跃,他们便将它独立成开源项目,并且有公司来运作。Google SRE的书内也曾提到跟他们BorgMon监控系统相似的实现是Prometheus。现在最常见的Kubernetes容器管理系统中,通常会搭配Prometheus进行监控。
Prometheus基本原理是通过HTTP协议周期性抓取被监控组件的状态,这样做的好处是任意组件只要提供HTTP接口就可以接入监控系统,不需要任何SDK或者其他的集成过程。这样做非常适合虚拟化环境比如VM或者Docker 。
Prometheus应该是为数不多的适合Docker、Mesos、Kubernetes环境的监控系统之一。
输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux 系统信息 (包括磁盘、内存、CPU、网络等等),具体支持的源看:https://github.com/prometheus。
与其他监控系统相比,Prometheus的主要特点是:
Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标,它在本地存储所有抓取到的样本数据,并对此数据执行一系列规则,以汇总和记录现有数据的新时间序列或生成告警。可以通过 Grafana 或者其他工具来实现监控数据的可视化。
Prometheus 适用于什么场景
Prometheus 适用于记录文本格式的时间序列,它既适用于以机器为中心的监控,也适用于高度动态的面向服务架构的监控。在微服务的世界中,它对多维数据收集和查询的支持有特殊优势。Prometheus 是专为提高系统可靠性而设计的,它可以在断电期间快速诊断问题,每个 Prometheus Server 都是相互独立的,不依赖于网络存储或其他远程服务。当基础架构出现故障时,你可以通过 Prometheus 快速定位故障点,而且不会消耗大量的基础架构资源。
Prometheus 不适合什么场景
Prometheus 非常重视可靠性,即使在出现故障的情况下,你也可以随时查看有关系统的可用统计信息。如果你需要百分之百的准确度,例如按请求数量计费,那么 Prometheus 不太适合你,因为它收集的数据可能不够详细完整。这种情况下,你最好使用其他系统来收集和分析数据以进行计费,并使用 Prometheus 来监控系统的其余部分。
Prometheus 所有采集的监控数据均以指标(metric)的形式保存在内置的时间序列数据库当中(TSDB):属于同一指标名称,同一标签集合的、有时间戳标记的数据流。除了存储的时间序列,Prometheus 还可以根据查询请求产生临时的、衍生的时间序列作为返回结果。
指标名称和标签
每一条时间序列由指标名称(Metrics Name)以及一组标签(键值对)唯一标识。其中指标的名称(metric name)可以反映被监控样本的含义(例如,http_requests_total
— 表示当前系统接收到的 HTTP 请求总量),指标名称只能由 ASCII 字符、数字、下划线以及冒号组成,同时必须匹配正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
。
[info] 注意
冒号用来表示用户自定义的记录规则,不能在 exporter 中或监控对象直接暴露的指标中使用冒号来定义指标名称。
通过使用标签,Prometheus 开启了强大的多维数据模型:对于相同的指标名称,通过不同标签列表的集合,会形成特定的度量维度实例(例如:所有包含度量名称为 /api/tracks
的 http 请求,打上 method=POST
的标签,就会形成具体的 http 请求)。该查询语言在这些指标和标签列表的基础上进行过滤和聚合。改变任何度量指标上的任何标签值(包括添加或删除指标),都会创建新的时间序列。
标签的名称只能由 ASCII 字符、数字以及下划线组成并满足正则表达式 [a-zA-Z_][a-zA-Z0-9_]*
。其中以 __
作为前缀的标签,是系统保留的关键字,只能在系统内部使用。标签的值则可以包含任何 Unicode
编码的字符。
更多详细内容请参考 指标和标签命名最佳实践。
样本
在时间序列中的每一个点称为一个样本(sample),样本由以下三部分组成:
指标(metric):指标名称和描述当前样本特征的 labelsets;
时间戳(timestamp):一个精确到毫秒的时间戳;
样本值(value): 一个 folat64 的浮点型数据表示当前样本的值。
表示方式
通过如下表达方式表示指定指标名称和指定标签集合的时间序列:
{=, …}
例如,指标名称为 api_http_requests_total
,标签为 method="POST"
和 handler="/messages"
的时间序列可以表示为:
api_http_requests_total{method=“POST”, handler=“/messages”}
Counter用于累计值,例如记录请求次数、任务完成数、错误发生次数。一直增加,不会减少。重启进程后,会被重置。
例如:http_response_total{method=”GET”,endpoint=”/api/tracks”} 100,10秒后抓取http_response_total{method=”GET”,endpoint=”/api/tracks”} 100。
Gauge常规数值,例如 温度变化、内存使用变化。可变大,可变小。重启进程后,会被重置。
例如: memory_usage_bytes{host=”master-01″} 100 < 抓取值、memory_usage_bytes{host=”master-01″} 30、memory_usage_bytes{host=”master-01″} 50、memory_usage_bytes{host=”master-01″} 80 < 抓取值。
Histogram(直方图)可以理解为柱状图的意思,常用于跟踪事件发生的规模,例如:请求耗时、响应大小。它特别之处是可以对记录的内容进行分组,提供count和sum全部值的功能。
例如:{小于10=5次,小于20=1次,小于30=2次},count=7次,sum=7次的求和值。
Summary和Histogram十分相似,常用于跟踪事件发生的规模,例如:请求耗时、响应大小。同样提供 count 和 sum 全部值的功能。
例如:count=7次,sum=7次的值求值。
它提供一个quantiles的功能,可以按%比划分跟踪的结果。例如:quantile取值0.95,表示取采样值里面的95%数据。
docker pull prom/node-exporter
docker pull prom/prometheus
docker pull grafana/grafana
docker run -d -p 9100:9100 -v "/proc:/host/proc:ro" -v "/sys:/host/sys:ro" -v "/:/rootfs:ro" --net="host" prom/node-exporter
等几秒中后查看端口
访问如下地址:http://192.168.66.130:9100/metrics,则可看到相应的结果
先建立配置文件的目录
mkdir /opt/prometheus
cd /opt/prometheus
vim prometheus.yaml
prometheus.yaml的文件内容如下
global:
scrape_interval: 60s
evaluation_interval: 60s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
labels:
instance: prometheus
- job_name: linux
static_configs:
- targets: ['192.168.91.132:9100']
labels:
instance: localhost
启动
docker run -d -p 9090:9090 -v /opt/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
过几分钟后查看端口
mkdir /opt/grafana-storage
chmod 777 -R /opt/grafana-storage
docker run -d -p 3000:3000 --name=grafana -v /opt/grafana-storage:/var/lib/grafana grafana/grafana
启动http://192.168.66.130:3000,
首次访问要输入用户名和密码,默认 是admin:admin
进去后,选择prometheus,然后配置
package main
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"time"
)
func recordMetrics() {
for {
ops.Inc()
time.Sleep(2 * time.Second)
}
}
var (
ops = promauto.NewCounter(prometheus.CounterOpts{
Name: "mxshop_test",
Help: "just for test",
})
)
func main() {
go recordMetrics()
r := gin.Default()
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
r.Run(":8050")
}
在rpcserver端创建prometheus建建interceptor,具体的代码如下
package serverinterceptors
import (
"context"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"mxshop/gmicro/core/metric"
)
/**
两个基本指示,1.每个请求的耗时(hisogram)2.每个请求的状态计数器(counter)
/user 状态码 有label 主要是状态码
*/
const serverNamespace = "rpc_server"
/*
两个基本指标。 1. 每个请求的耗时(histogram) 2. 每个请求的状态计数器(counter)
/user 状态码 有label 主要是状态码
*/
var (
metricServerReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
Namespace: serverNamespace,
Subsystem: "requests",
Name: "chaos_duration_ms",
Help: "rpc server requests duration(ms).",
Labels: []string{"method"},
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
})
metricServerReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: serverNamespace,
Subsystem: "requests",
Name: "chaos_code_total",
Help: "rpc server requests code count.",
Labels: []string{"method", "code"},
})
)
func UnaryPrometheusInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (resp interface{}, err error) {
startTime := time.Now()
resp, err = handler(ctx, req)
//记录了耗时
metricServerReqDur.Observe(int64(time.Since(startTime)/time.Millisecond), info.FullMethod)
//记录了状态码
metricServerReqCodeTotal.Inc(info.FullMethod, strconv.Itoa(int(status.Code(err))))
return resp, err
}
rpcserver 方法中的NewServer中添加
package rpcserver
import (
"context"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
apimd "mxshop/api/metadata"
srvintc "mxshop/gmicro/server/rpcserver/serverinterceptors"
"mxshop/pkg/host"
"mxshop/pkg/log"
"net"
"net/url"
"time"
)
type ServerOption func(o *Server)
type Server struct {
*grpc.Server
address string
unaryInts []grpc.UnaryServerInterceptor
streamIns []grpc.StreamServerInterceptor
grpcOpts []grpc.ServerOption
lis net.Listener
timeout time.Duration
health *health.Server
metadata *apimd.Server
endpoint *url.URL
//是否开启 metric 监测
enableMetric bool
}
func NewServer(opts ...ServerOption) *Server {
srv := &Server{
address: ":0",
health: health.NewServer(),
//timeout: 1 * time.Second,
}
for _, o := range opts {
o(srv)
}
//TODO 我们现在希望用户不设置拦截器的情况下,我们会自动默认加上一些必须的拦截器 crash
unaryInts := []grpc.UnaryServerInterceptor{
srvintc.UnaryCrashInterceptor,
otelgrpc.UnaryServerInterceptor(),
}
//就是这一步了UnaryPrometheusInterceptor
if srv.enableMetric {
unaryInts = append(unaryInts, srvintc.UnaryPrometheusInterceptor)
}
if srv.timeout > 0 {
unaryInts = append(unaryInts, srvintc.UnaryTimeoutInterceptor(srv.timeout))
}
if len(srv.unaryInts) > 0 {
unaryInts = append(unaryInts, srv.unaryInts...)
}
//把我们传入的拦截器转换成grpc的ServerOption
grpcOpts := []grpc.ServerOption{grpc.ChainUnaryInterceptor(srv.unaryInts...)}
//把用户自已传入的grpc.ServerOption放在一起
if len(srv.grpcOpts) > 0 {
grpcOpts = append(grpcOpts, srv.grpcOpts...)
}
srv.Server = grpc.NewServer(grpcOpts...)
//注册metadata的server
srv.metadata = apimd.NewServer(srv.Server)
//解析address
if err := srv.listenAndEndpotion(); err != nil {
return nil
}
//注册health
grpc_health_v1.RegisterHealthServer(srv.Server, srv.health)
apimd.RegisterMetadataServer(srv.Server, srv.metadata)
reflection.Register(srv.Server)
//可以支持用户直接通过grpc的一个接口查看当前支持的所有的rpc服务
return srv
}
func (s *Server) Address() string {
return s.address
}
func WithAddress(address string) ServerOption {
return func(s *Server) {
s.address = address
}
}
func WithTimeout(timeout time.Duration) ServerOption {
return func(s *Server) {
s.timeout = timeout
}
}
func WithLis(lis net.Listener) ServerOption {
return func(s *Server) {
s.lis = lis
}
}
func WithUnaryInterceptor(in ...grpc.UnaryServerInterceptor) ServerOption {
return func(s *Server) {
s.unaryInts = in
}
}
func WithStreamInterceptor(in ...grpc.StreamServerInterceptor) ServerOption {
return func(s *Server) {
s.streamIns = in
}
}
func WithOptions(opts ...grpc.ServerOption) ServerOption {
return func(s *Server) {
s.grpcOpts = opts
}
}
// 完成ip和端口的提取
func (s *Server) listenAndEndpotion() error {
if s.lis == nil {
lis, err := net.Listen("tcp", s.address)
if err != nil {
return err
}
s.lis = lis
}
addr, err := host.Extract(s.address, s.lis)
if err != nil {
_ = s.lis.Close()
return err
}
s.endpoint = &url.URL{Scheme: "grpc", Host: addr}
return nil
}
func WithEnableMetric(enable bool) ServerOption {
return func(s *Server) { s.enableMetric = enable }
}
func (s *Server) Start(ctx context.Context) error {
log.Infof("[grpc] server listening on: %s", s.lis.Addr().String())
//改grpc核心变量 状态
//只有.Resume()之后,请求才能进来
//s.health.Shutdown()相反
s.health.Resume()
return s.Server.Serve(s.lis)
}
func (s *Server) Stop(ctx context.Context) error {
//设置服务的状态为not_serving 防止接受新的请求
s.health.Shutdown()
s.Server.GracefulStop()
log.Infof("[grpc] server stopped")
return nil
}
package clientinterceptors
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"mxshop/gmicro/core/metric"
"strconv"
"time"
)
const serverNamespace = "rpc_client"
/*
两个基本指标。 1. 每个请求的耗时(histogram) 2. 每个请求的状态计数器(counter)
/user 状态码 有label 主要是状态码
*/
var (
metricServerReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
Namespace: serverNamespace,
Subsystem: "requests",
Name: "chaos_duration_ms",
Help: "rpc server requests duration(ms).",
Labels: []string{"method"},
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
})
metricServerReqCodeTotal = metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: serverNamespace,
Subsystem: "requests",
Name: "chaos_code_total",
Help: "rpc server requests code count.",
Labels: []string{"method", "code"},
})
)
func PrometheusInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
startTime := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
//记录了耗时
metricServerReqDur.Observe(int64(time.Since(startTime)/time.Millisecond), method)
//记录了状态码
metricServerReqCodeTotal.Inc(method, strconv.Itoa(int(status.Code(err))))
return err
}
}
在rpcclient中的dial方法中添加这个interceptor
package rpcserver
import (
"context"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
grpcinsecure "google.golang.org/grpc/credentials/insecure"
"mxshop/gmicro/registry"
"mxshop/gmicro/server/rpcserver/clientinterceptors"
"mxshop/gmicro/server/rpcserver/resolver/discovery"
"mxshop/pkg/log"
"time"
)
type ClientOption func(o *clientOptions)
type clientOptions struct {
// 服务端的地址
endpoint string
// 超时时间
timeout time.Duration
// 服务发现接口
discovery registry.Discovery
// Unary 服务的拦截器
unaryInts []grpc.UnaryClientInterceptor
// Stream 服务的拦截器
streamInts []grpc.StreamClientInterceptor
// 用户自己设置 grpc 连接的结构体,例如: grpc.WithInsecure(), grpc.WithTransportCredentials()
rpcOpts []grpc.DialOption
// 根据 Name 生成负载均衡的策略
balancerName string
// 客户端的日志
logger log.Logger
// 是否开启链路追踪
enableTracing bool
//是否开启 metric 监测
enableMetric bool
}
// WithEndpoint 设置服务端的地址
func WithEndpoint(endpoint string) ClientOption {
return func(o *clientOptions) {
o.endpoint = endpoint
}
}
// WithClientTimeout 设置超时时间
func WithClientTimeout(timeout time.Duration) ClientOption {
return func(o *clientOptions) {
o.timeout = timeout
}
}
// WithDiscovery 设置服务发现
func WithDiscovery(d registry.Discovery) ClientOption {
return func(o *clientOptions) {
o.discovery = d
}
}
// WithClientUnaryInterceptor 设置拦截器
func WithClientUnaryInterceptor(in ...grpc.UnaryClientInterceptor) ClientOption {
return func(o *clientOptions) {
o.unaryInts = in
}
}
// WithClientStreamInterceptor 设置stream拦截器
func WithClientStreamInterceptor(in ...grpc.StreamClientInterceptor) ClientOption {
return func(o *clientOptions) {
o.streamInts = in
}
}
// WithClientOptions 设置grpc的dial选项
func WithClientOptions(opts ...grpc.DialOption) ClientOption {
return func(o *clientOptions) {
o.rpcOpts = opts
}
}
// WithBalancerName 设置负载均衡器
func WithBalancerName(name string) ClientOption {
return func(o *clientOptions) {
o.balancerName = name
}
}
// WithClientLogger 设置日志
func WithClientLogger(logger log.Logger) ClientOption {
return func(o *clientOptions) {
o.logger = logger
}
}
// WithClientTracing 设置链路追踪
func WithClientTracing() ClientOption {
return func(o *clientOptions) {
o.enableTracing = true
}
}
func WithClientEnableMetric(enable bool) ServerOption {
return func(s *Server) { s.enableMetric = enable }
}
// DialInsecure 非安全拨号
func DialInsecure(ctx context.Context, opts ...ClientOption) (*grpc.ClientConn, error) {
return dial(ctx, true, opts...)
}
func Dial(ctx context.Context, opts ...ClientOption) (*grpc.ClientConn, error) {
return dial(ctx, false, opts...)
}
func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.ClientConn, error) {
//默认配置
options := clientOptions{
timeout: 200 * time.Millisecond,
balancerName: "round_robin",
enableTracing: true,
}
for _, o := range opts {
o(&options)
}
//TODO 客户端默认拦截器
ints := []grpc.UnaryClientInterceptor{
//应该是闭包特性,直接调用后返回resp供grpc拦截器调用
clientinterceptors.TimeoutInterceptor(options.timeout),
}
//这个就是集成prometheus
if options.enableMetric {
ints = append(ints, clientinterceptors.PrometheusInterceptor())
}
if options.enableTracing {
ints = append(ints, otelgrpc.UnaryClientInterceptor())
}
streamInts := []grpc.StreamClientInterceptor{}
if len(options.unaryInts) > 0 {
ints = append(ints, options.unaryInts...)
}
if len(options.streamInts) > 0 {
streamInts = append(streamInts, options.streamInts...)
}
//可以由用户端自己传递 这些默认的
grpcOpts := []grpc.DialOption{
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "` + options.balancerName + `"}`),
grpc.WithChainUnaryInterceptor(ints...),
grpc.WithChainStreamInterceptor(streamInts...),
}
//TODO 服务发现的选项 这里调用 resolver 的直连模式或者是服务发现模式
if options.discovery != nil {
grpcOpts = append(grpcOpts, grpc.WithResolvers(
discovery.NewBuilder(options.discovery,
discovery.WithInsecure(insecure)),
))
}
if insecure {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(grpcinsecure.NewCredentials()))
}
if len(options.rpcOpts) > 0 {
grpcOpts = append(grpcOpts, options.rpcOpts...)
}
return grpc.DialContext(ctx, options.endpoint, grpcOpts...)
}
package restserver
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
ut "github.com/go-playground/universal-translator"
"github.com/penglongli/gin-metrics/ginmetrics"
mws "mxshop/gmicro/server/restserver/middlewares"
"mxshop/gmicro/server/restserver/pprof"
"mxshop/gmicro/server/restserver/validation"
"mxshop/pkg/log"
"net/http"
"time"
)
type JwtInfo struct {
//defaults to "JWT"
Realm string
//defaults to empty
Key string
//defaults to 7 days
Timeout time.Duration
//defaults to 7 days 刷新时长
MaxRefresh time.Duration
}
// Server wrapper for gin.Engine
type Server struct {
*gin.Engine
//端口号
port int
//开发模式
mode string
//是否开启健康检查接口,默认开启,如果开启会自动添加/health接口
healthz bool
//是否开启pprof接口,默认开启,如果开启会自动添加/debug/pprof接口
enableProfiling bool
//是否开启metrics接口,默认开启,如果开启会自动添加/metrics接口
enableMetrics bool
middlewares []string
customMiddlewares []gin.HandlerFunc
//jwt配置信息
jwt *JwtInfo
//翻译器 默认:zh
transName string
trans ut.Translator
server *http.Server
serviceName string
}
func NewServer(opts ...ServerOption) *Server {
srv := &Server{
port: 8080,
mode: "debug",
healthz: true,
enableProfiling: true,
jwt: &JwtInfo{
"JWT",
"Gd%YCfP1agNHo5x6xm2Qs33Bf!B#Gi!o",
1 * 24 * time.Hour,
7 * 24 * time.Hour,
},
Engine: gin.Default(),
transName: "zh",
serviceName: "gmicro",
}
for _, o := range opts {
o(srv)
}
srv.Use(mws.TracingHandler(srv.serviceName))
for _, m := range srv.middlewares {
mw, ok := mws.Middlewares[m]
if !ok {
log.Warnf("can not find middleware:%s", m)
continue
}
log.Infof("install middleware:%s", m)
srv.Use(mw)
}
return srv
}
// Start rest server
func (s *Server) Start(ctx context.Context) error {
/*
debug模式和release模式区别主要是打印的日志不同
环境变量的模式,在docker k8s部署中很常用
gin.SetMode(gin.ReleaseMode)
*/
if s.mode != gin.DebugMode && s.mode != gin.ReleaseMode && s.mode != gin.TestMode {
return errors.New("mode must be one of debug/release/test")
}
gin.SetMode(s.mode)
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Infof("%-6s %-s --> %s(%d handlers)", httpMethod, absolutePath, handlerName, nuHandlers)
}
//TODO 初始化翻译器
err := s.initTrans(s.transName)
if err != nil {
log.Errorf("initTrans error: %s", err.Error())
return err
}
//注册mobile验证器
validation.RegisterMobile(s.trans)
//根据配置初始化pprof路由
if s.enableProfiling {
pprof.Register(s.Engine)
}
//这个就是集成prometheus
if s.enableMetrics {
// get global Monitor object
m := ginmetrics.GetMonitor()
// +optional set metric path, default /debug/metrics
m.SetMetricPath("/metrics")
// +optional set slow time, default 5s
m.SetSlowTime(10)
// +optional set request duration, default {0.1, 0.3, 1.2, 5, 10}
// used to p95, p99
m.SetDuration([]float64{0.1, 0.3, 1.2, 5, 10})
//反向注入
m.Use(s)
}
log.Infof("rest server is running on port: %d", s.port)
_ = s.SetTrustedProxies(nil)
address := fmt.Sprintf(":%d", s.port)
s.server = &http.Server{
Addr: address,
Handler: s.Engine,
}
if err = s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
func (s *Server) Stop(ctx context.Context) error {
log.Infof("rest server is stopping on port: %d", s.port)
if err := s.server.Shutdown(ctx); err != nil {
log.Errorf("rest server is shutting down: %v", err)
return err
}
log.Infof("rest server stopped on port: %d", s.port)
return nil
}
在启动restserver的服务的方法增加启动prometheus
package admin
import (
"mxshop/app/user/srv/config"
"mxshop/gmicro/server/restserver"
)
func NewUserHTTPServer(cfg *config.Config) (*restserver.Server, error) {
urestServer := restserver.NewServer(restserver.WithPort(cfg.Server.HttpPort),
restserver.WithMiddlewares(cfg.Server.Middlewares),
restserver.WithMetrics(true),
)
//配置好路由
initRouter(urestServer)
return urestServer, nil
}
7.5启动测试
先启动服务端,再启动客户端,然后通过POSTMAN方法访问
服务端启动的情况如下
客户端启动的情况如下:
然后在grafanaa中就可以看到结果了