golang的监控

733 阅读4分钟

可观测性

image.png

  • Metrics:
    • 接口监控:接口失败率
    • 资源监控:cpu指标,内存指标等。
  • Traces:链路追踪
  • Logs:日志

Grafana 全家桶

grafana全家桶对于可观测性的支持:

  • Metrics:Prometheus
  • Traces:tempo
  • Logs:Loki

golang项目接入链路追踪流程

  • golang项目引入OpenTelemetry
  • opentelemetry sdk agent将监控数据上报到OpenTelemetry Collector Contrib,接收otlp的grpc,导出到kafka
  • 从OpenTelemetry Collector Contrib接收kafka,然后导出到tempo
  • tempo利用grafana查看

网络监控系统

  • 夜莺平台
    • 可以设置cpu,内存等告警
    • 可以设置pod重启等情况
    • 平台ui image.png

应用监控系统:

  • OpenTelemetry:开放型遥测
    • OpenTelemetry收集链路数据
    • otlp的数据以grpc或者http的方式上报
    • otlp可以将数据上报给kafka,Prometheus,Jaeger,时序数据库等
  • Prometheus:
    • Prometheus 是一个开源的监控系统,它可以接入时序数据库,比如InfluxDB、Cassandra 等
  • Grafana:
    • 一种可视化的监控工具
    • 可以接入的数据源有Prometheus(Prometheus提供api查询数据)和一些时序数据库
    • 平台ui image.png

image.png

Prometheus 和 Grafana的各自使用场景

  • Prometheus 和 Grafana 都是监控和可视化工具,但它们的使用场景略有不同。
  • Prometheus 是一个开源的监控系统,它可以收集和存储各种指标数据,例如服务器的 CPU 利用率、内存使用情况、网络带宽等。Prometheus 可以通过 HTTP 接口提供数据查询服务,并支持各种查询语言和查询方式。Prometheus 适用于监控大规模的分布式系统,它可以帮助用户快速发现和诊断系统问题。
  • Grafana 是一个开源的可视化平台,它可以将 Prometheus 等监控系统收集的数据进行可视化展示。Grafana 提供了丰富的图表和面板,可以帮助用户快速了解系统的性能和健康状况。Grafana 适用于监控各种类型的系统,包括服务器、网络、应用程序等。
  • 总的来说,Prometheus 和 Grafana 通常一起使用,Prometheus 负责收集和存储监控数据,而 Grafana 负责将这些数据进行可视化展示。通过使用 Prometheus 和 Grafana,用户可以快速了解系统的性能和健康状况,并及时发现和解决问题。

go-zero项目接入链路追踪配置config如下:

  • go-zero的config中配置 Telemetry: Endpoint: XXX:4317 Sampler: 1.0 Batcher: otlpgrpc
  • OpenTelemetry Collector Contrib
    • 收集器的config配置
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
processors:
  batch:
exporters:
  kafka:
    endpoint: "kafka:9092" # 替换为您的 Kafka 服务器地址和端口
# 配置服务和管道
service:
  pipelines:
    trace:
      receivers: [otlp]
      processors: [batch]
      exporters: [kafka]
  • OpenTelemetry Collector Contrib
    • 收集器的config配置
receivers:
  kafka:
    endpoint: "kafka:9092" # 替换为您的 Kafka 服务器地址和端口
processors:
  batch:
exporters:
  tempo:
    endpoint: "http://tempo:9411/api/v1/trace"
# 配置服务和管道
service:
  pipelines:
    trace:
      receivers: [kafka]
      processors: [batch]
      exporters: [tempo]

go-zero项目接入链路追踪代码分析:

  • 接入OpenTelemetry的sdk
    • main函数启动agent
    • 实例化一个TracerProvider
    • 使用spanProcessors上报对象
    • 一些上报时候的重要参数如下:
      • DefaultMaxQueueSize = 2048
        • 发送队列的slice的大小是2048
      • DefaultScheduleDelay = 5000
        • 每隔5s将会上报一次数据
      • DefaultExportTimeout = 30000
        • 如果超过了30s,数据还没上报成功,则丢弃
      • DefaultMaxExportBatchSize = 512
        • 如果slice的大小已经是等于或者超过了512,则上报
      • BlockOnQueueFull = false
        • 如果queue满了之后,则抛弃。这样对性能来说是最好的,但是会造成数据丢失
  • redis请求上报
    • redis的golang源码包中,请求保留了hook钩子。
    • go-zero在每次请求之前,执行hook的BeforeProcessPipeline,开启span
    • 每次请求之后,执行hook的AfterProcessPipeline,结束span
  • mysql请求上报
    • 在具体的请求中,比如QueryRowCtx,添加startSpan和endSpan 所以在设计框架的的时候,其中一个准则是,所有的网络请求都需要包一遍,为了预留hook,给trace等使用

go-zero接入Prometheus的metrics

配置:

DevServer:

Enabled: true

Host: 0.0.0.0

Port: 6060

MetricsPath: /metrics

EnableMetrics: true

EnablePprof: true

启动Prometheus的客户端:

// StartAsync start inner http server background.
func (s *Server) StartAsync() {
   s.addRoutes()
   threading.GoSafe(func() {
      addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
      logx.Infof("Starting dev http server at %s", addr)
      if err := http.ListenAndServe(addr, s.server); err != nil {
         logx.Error(err)
      }
   })
}

go-zero服务获取客户端数据

rpc使用一元拦截器"(Unary Interceptor): 它指的是在单个请求中,在请求的前后增加拦截功能,比如记录日志,上报请求信息等。
metrics信息:


// UnaryStatInterceptor returns a func that uses given metrics to report stats.
func UnaryStatInterceptor(metrics *stat.Metrics, conf StatConf) grpc.UnaryServerInterceptor {
   staticNotLoggingContentMethods := collection.NewSet()
   staticNotLoggingContentMethods.AddStr(conf.IgnoreContentMethods...)

   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo,
      handler grpc.UnaryHandler) (resp any, err error) {
      startTime := timex.Now()
      defer func() {
         duration := timex.Since(startTime)
         metrics.Add(stat.Task{
            Duration: duration,
         })
         logDuration(ctx, info.FullMethod, req, duration,
            staticNotLoggingContentMethods, conf.SlowThreshold)
      }()

      return handler(ctx, req)
   }
}

http使用中间件链模式,metrics信息:

// MetricHandler returns a middleware that stat the metrics.
func MetricHandler(metrics *stat.Metrics) func(http.Handler) http.Handler {
   return func(next http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         startTime := timex.Now()
         defer func() {
            metrics.Add(stat.Task{
               Duration: timex.Since(startTime),
            })
         }()

         next.ServeHTTP(w, r)
      })
   }
}

组织好数据,准备上报

func (c *metricsContainer) Execute(v any) {
   pair := v.(tasksDurationPair)
   tasks := pair.tasks
   duration := pair.duration
   drops := pair.drops
   size := len(tasks)
   report := &StatReport{
      Name:          c.name,
      Timestamp:     time.Now().Unix(),
      Pid:           c.pid,
      ReqsPerSecond: float32(size) / float32(logInterval/time.Second),
      Drops:         drops,
   }
   ........
   }

上报给prometheus客户端数据


func (rw *RemoteWriter) Write(report *StatReport) error {
   bs, err := json.Marshal(report)
   if err != nil {
      return err
   }

   client := &http.Client{
      Timeout: httpTimeout,
   }
   resp, err := client.Post(rw.endpoint, jsonContentType, bytes.NewReader(bs))
   if err != nil {
      return err
   }
   defer resp.Body.Close()

   if resp.StatusCode != http.StatusOK {
      logx.Errorf("write report failed, code: %d, reason: %s", resp.StatusCode, resp.Status)
      return ErrWriteFailed
   }

   return nil
}