GO 集成Prometheus

一、Prometheus介绍

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的主要特点是:

  • 一个多维数据模型(时间序列由指标名称定义和设置键/值尺寸)。
  • 非常高效的存储,平均一个采样数据占~3.5bytes左右,320万的时间序列,每30秒采样,保持60天,消耗磁盘大概228G。
  • 一种灵活的查询语言。
  • 不依赖分布式存储,单个服务器节点。
  • 时间集合通过HTTP上的PULL模型进行。
  • 通过中间网关支持推送时间。
  • 通过服务发现或静态配置发现目标。
  • 多种模式的图形和仪表板支持。

二、Prometheus的架构

img

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”}

四、四种数据类型

4.1Counter

Counter用于累计值,例如记录请求次数、任务完成数、错误发生次数。一直增加,不会减少。重启进程后,会被重置。

例如:http_response_total{method=”GET”,endpoint=”/api/tracks”} 100,10秒后抓取http_response_total{method=”GET”,endpoint=”/api/tracks”} 100。

4.2Gauge

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 < 抓取值。

4.3Histogram

Histogram(直方图)可以理解为柱状图的意思,常用于跟踪事件发生的规模,例如:请求耗时、响应大小。它特别之处是可以对记录的内容进行分组,提供count和sum全部值的功能。

例如:{小于10=5次,小于20=1次,小于30=2次},count=7次,sum=7次的求和值。

4.4Summary

Summary和Histogram十分相似,常用于跟踪事件发生的规模,例如:请求耗时、响应大小。同样提供 count 和 sum 全部值的功能。

例如:count=7次,sum=7次的值求值。

它提供一个quantiles的功能,可以按%比划分跟踪的结果。例如:quantile取值0.95,表示取采样值里面的95%数据。

五、安装

5.1下载镜像包
docker pull prom/node-exporter
docker pull prom/prometheus
docker pull grafana/grafana
5.2启动node-exporter
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,则可看到相应的结果

5.3启动prometheus

先建立配置文件的目录

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

过几分钟后查看端口

在这里插入图片描述

5.4启动grafana
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

六、grafana基本操作

6.1创建数据源

点击在这里插入图片描述

进去后,选择prometheus,然后配置

在这里插入图片描述

6.2导入模板

在这里插入图片描述
在这里插入图片描述

七、 golang集成promethes

7.1Gin集成promethes
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")
}
7.2、rpcserver的interceptor集成prometheus

在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
}
7.3. rpcclient的 interceptor集成prometheus
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...)
}
7.4restserver端集成interceptor
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中就可以看到结果了

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/198307.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Edge浏览器的跨域设置

关闭安全策略 复制一个浏览器的快捷方式&#xff0c;修改它的目标信息 在目标路径后加上这段命令&#xff1a;" --disable-web-security --user-data-dirD:/edgeCros" 没有引号&#xff0c;注意空格&#xff0c;D:/edgeCros是自定义文件夹&#xff0c;用来存放数据 …

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号livox 介绍更换环境更换livox激光雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 介绍 览沃科技有限公司&#xff08;Livox&#xff…

2021-07-31

单日3亿日志数据准实时存储和分析 –ClickHouse 在自如大前端研发中心的应用 第一章 架构设计 和 用户体系建设 文章目录 单日3亿日志数据准实时存储和分析前言一、pandas是什么&#xff1f;二、使用步骤1.引入库2.读入数据 总结 前言 用户行为数据的收集和分析&#xff0c;…

AI 学习笔记(持续更新)

What is AI PS &#xff1a;代码块里的统一是 gpt4 回复 在大模型中 1 b 10 亿参数的含义 AI 目前是什么&#xff1f; 目前的人工智能&#xff08;AI&#xff09;是指使计算机和机器能够模仿人类智能的技术&#xff0c;包括学习、推理、解决问题、知觉、语言理解等能力。A…

如何根据接口文档,轻松快速的模拟接口服务?

什么是WireMock? WireMock 是一个Http 模拟服务,其核心也是一个web服务,WireMock主要是为特定请求提供固定的返回值。 WireMock可以作为单独进程启动,模拟一个WEB服务器,提供一些API访问,并返回特定的返回值。也可以作为第三方库在项目中使用。 如何使用 standalone方…

HelpLook可以作为wordpress的替代品,帮助企业快速搭建博客

博客作为一个非常有价值的平台&#xff0c;在当今的数字时代具有重要的意义。对于个人和企业来说&#xff0c;选择一款适合自己需求的专业博客搭建软件至关重要。本篇文章将会通过对比两个专业的博客搭建软件——HelpLook和WordPress&#xff0c;看看为什么我说HelpLook可以作为…

华为P40无法链接adb的解决记录

真的很讨厌华为的设备&#xff0c;很多东西啥设备都能跑得好好的&#xff0c;就华为会出问题&#xff0c;简直就是手机界的IE。 情况&#xff1a;突然无法链接adb到P40&#xff0c;拔插无效&#xff0c;关闭开发人员选项再打开也无效&#xff0c;撤销USB调试授权也无效&#x…

英伟达“阉割版”AI芯片遇阻,推迟至明年发布 | 百能云芯

近日&#xff0c;英伟达&#xff08;Nvidia&#xff09;为遵守美国出口规定而推迟在中国市场推出的新款人工智能&#xff08;AI&#xff09;芯片引起了业界广泛关注。 据路透社报道&#xff0c;两位消息人士透露&#xff0c;该芯片被命名为H20&#xff0c;是英伟达为遵守美国最…

文本编辑 换行符CRLF/CR/LF问题

参考资料 Linux—CRLF/CR/LF等回车换行符问题详解改行コードCRはなぜ&#xff08;^M&#xff09;で\rなのかテキストファイルの行末に^Mが表示されるLinux 替换^M字符 方法 目录 一. 遇到的问题二. 换行符释义三. 换行符查看四. 去除 ^M4.1 通过文本编辑器转换换行符4.2 在lin…

【C++初阶(九)】 priority_queue的使用与模拟实现

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…

多平台小程序编译适配,是否会让更多App互联互通?

随着科技的飞速发展&#xff0c;我们正迅速进入一个以数字化为主导的时代。 在这个时代中&#xff0c;通信、小程序、快应用、云服务器等平台连接类软件如火如荼的发展&#xff0c;手机、手表、AR/VR眼镜等智能移动穿戴设备迅速的升级迭代&#xff0c;5G、芯片、算力等基础设施…

代码随想录算法训练营 ---第四十三天

前言&#xff1a; 今天同样是01背包问题&#xff0c;今天详细学习了背包问题在各种场景下的应用。今天一道也没做出来&#xff0c;有点废。好难啊&#xff01;就是思路不太清晰&#xff0c;不知道如何去做&#xff0c;看了题解后感觉原来如此&#xff0c;但是想不出来。今天做…

软件提示找不到“vcruntime140.dll丢失的五个解决方法”(有效方法)

“vcruntime140.dll丢失的五个解决方法”。在我们的日常生活和工作中&#xff0c;有时候会遇到一些电脑问题&#xff0c;而vcruntime140.dll丢失就是其中之一。那么&#xff0c;什么是vcruntime140.dll文件呢&#xff1f;它为什么会丢失&#xff1f;又该如何解决这个问题呢&…

SpringBoot快速体验

场景&#xff1a;浏览器发送/hello请求&#xff0c;返回"Hello,Spring Boot 3!" 1. 开发流程 1. 创建项目 maven 项目 <!-- 所有springboot项目都必须继承自 spring-boot-starter-parent --><parent><groupId>org.springframework.boot<…

OpenCV数字图像处理——检测出图像中的几何形状并测量出边长、直径、内角

一、简介 在传统的自动化生产尺寸测量中&#xff0c;常用的方法是利用卡尺或千分尺对被测工件的某个参数进行多次测量&#xff0c;并取这些测量值的平均值。然而&#xff0c;这些传统的检测设备或手动测量方法存在着一些问题&#xff1a;测量精度不高、测量速度缓慢&#xff0…

【离散数学】——期末刷题题库(命题逻辑)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

计算机杂谈系列精讲100篇-【计算机应用】PyTorch部署及分布式训练

目录 C平台PyTorch模型部署流程 1.模型转换 1. 不支持的操作 2. 指定数据类型 2.保存序列化模型 3.C load训练好的模型 4. 执行Script Module PyTorch分布式训练 分布式并行训练概述 Pytorch分布式数据并行 手把手渐进式实战 A. 单机单卡 B. 单机多卡DP C. 多机多卡DDP D. L…

小狐狸ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端最新去弹窗授权

ChatGPT付费创作系统V2.3.4版本优化了很多细节&#xff0c;如果使用着2.2.9版本建议没升级的必要。该版本为编译版无开源&#xff0c;2.3.X版本开始官方植入了更多的后门和更隐性的弹窗代码&#xff0c;后门及弹窗处理起来更麻烦。特别针对后台弹窗网址、暗链后门网址全部进行了…

2023年国赛试题:配置inux1 为 CA 服务器

试题内容:配置 linux1 为 CA 服务器,为 linux 主机颁发证书。证书颁发机构有 效期 10 年,公用名为 linux1.skills.lan。申请并颁发一张供 linux 服务器使用的证书,证书信息:有效期 =5 年,公用名=skills.lan, 国家=CN,省=Beijing,城市=Beijing,组织=skills,组织单位…

Java使用263和qq邮箱发邮件

一、添加依赖 <dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.6.2</version></dependency>二、263邮箱 1&#xff0c;邮箱配置 public static void sendEmail(String host, in…