可观测性
- 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
应用监控系统:
- OpenTelemetry:开放型遥测
- OpenTelemetry收集链路数据
- otlp的数据以grpc或者http的方式上报
- otlp可以将数据上报给kafka,Prometheus,Jaeger,时序数据库等
- Prometheus:
- Prometheus 是一个开源的监控系统,它可以接入时序数据库,比如InfluxDB、Cassandra 等
- Grafana:
- 一种可视化的监控工具
- 可以接入的数据源有Prometheus(Prometheus提供api查询数据)和一些时序数据库
- 平台ui
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满了之后,则抛弃。这样对性能来说是最好的,但是会造成数据丢失
- DefaultMaxQueueSize = 2048
- 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
}