go-zero基于prometheus的指标监控

基于prometheus的指标监控

prometheus的架构

prometheus的工作基本原理是通过HTTP协议周期性抓取被监控组件的状态,这样做的好处是任意组件只要提供HTTP接口就可以接入监控系统,不需要任何SDK或者其他的集成过程。这样做非常适合虚拟化环境比如VM或者Docker

Prometheus 的整体架构以及生态系统组件如下图所示:

Prometheus 的整体架构以及生态系统组件

从上面的架构图我们可以知道

  • Prometheus Server通过注册中心/配置文件找到需要收集指标的目标

  • Prometheus Server的工作流:

    • 业务系统将数据推送到推送网关,然后Prometheus Server通过网关拉取监控指标
    • 业务系统直接将指标数据推送到Prometheus Server

prometheus的数据模型

Prometheus对所有的存储都是按时间序列去实现的

通过 metrics (指标名称) label (一个或多个标签) 组成一条时间序列,不同的label表示不同的时间序列,可以把metrics看作是传统数据库的表名,label是字段, timestamp是主键,还有一个float64字段则是值。(prometheus里所有的值都是按float64存储的)

最后组成的标示类似这样:http_requests_total{method=”POST”,endpoint=”/api/tracks”} 100

prometheus的四种数据类型

Counter

用于累计值,例如记录请求次数、错误发生次数(value就代表了当前累计的次数,只会一直增加,不会减少)

Gauge

常规数值,例如 温度变化、内存使用变化。可变大,可变小

Histogram

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

Summary

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


go-zero中的指标监控

go-zero实现了上面Prometheus架构图中获取指标数据两种方式以及一些监控指标

rpc服务 和 api网关 启动时都会注册handler(exporter)或者启动一个reporter (当然,启用的前提是在配置文件中配置了prometheus的host等信息),然后通过拦截器对指标数据进行收集存储,最后再主动上报或者等待Prometheus Server抓取(定时flush)

  • 初始化prometheus、注册handler、启动reporter
// core/service/serviceconf.go
func (sc ServiceConf) SetUp() error {
	if len(sc.Log.ServiceName) == 0 {
		sc.Log.ServiceName = sc.Name
	}
  // 初始化日志
	if err := logx.SetUp(sc.Log); err != nil {
		return err
	}

	sc.initMode()
  
  // 启动 prometheus client handler
  // 属于上面提到的第一种方式
	prometheus.StartAgent(sc.Prometheus)
  
  // 如果配置了推送地址,则创建一个reporter
  // 属于上面提到的第二种方式
	if len(sc.MetricsUrl) > 0 {
		stat.SetReportWriter(stat.NewRemoteWriter(sc.MetricsUrl))
	}

	return nil
}
// core/prometheus/agent.go
func StartAgent(c Config) {
   once.Do(func() {
   // 表明未开启prometheus监控,直接返回
   	if len(c.Host) == 0 {
   		return
   	}

   // 监听端口,等待Prometheus Server的调用
   // 这种方式的话不用我们做什么
   	threading.GoSafe(func() {
   		http.Handle(c.Path, promhttp.Handler())
   		addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
   		logx.Infof("Starting prometheus agent at %s", addr)
   		if err := http.ListenAndServe(addr, nil); err != nil {
   			logx.Error(err)
   		}
   	})
   })
}
// core/stat/metrics.go
func SetReportWriter(writer Writer) {
	writerLock.Lock()
  // 指定reporter
  // 通过reporter将数据推送给Prometheus Server
	reportWriter = writer
	writerLock.Unlock()
}
  • 拦截器
// rpc server拦截器 rpc client、api 类似
func UnaryStatInterceptor(metrics *stat.Metrics) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler) (resp interface{}, err error) {
		defer handleCrash(func(r interface{}) {
			err = toPanicError(r)
		})

		startTime := timex.Now()
		defer func() {
			duration := timex.Since(startTime)
      // 通过拦截器添加任务
			metrics.Add(stat.Task{
				Duration: duration,
			})
			logDuration(ctx, info.FullMethod, req, duration)
		}()

		return handler(ctx, req)
	}
}

func UnaryPrometheusInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (
		interface{}, error) {
		startTime := timex.Now()
		resp, err := handler(ctx, req)
    // histogram
		metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), info.FullMethod)
    // counter
		metricServerReqCodeTotal.Inc(info.FullMethod, strconv.Itoa(int(status.Code(err))))
		return resp, err
	}
}
  • 执行者(最终是通过他们上报数据到prometheus server)

go-zero的定时任务结构体及任务容器的接口

主动上报数据的方式也有两种:定时上报、主动触发上报

// core/executors/periodicalexecutor.go
type (
	// A type that satisfies executors.TaskContainer can be used as the underlying
	// container that used to do periodical executions.
	TaskContainer interface {
		// AddTask adds the task into the container.
		// Returns true if the container needs to be flushed after the addition.
		AddTask(task interface{}) bool
		// Execute handles the collected tasks by the container when flushing.
		Execute(tasks interface{})
		// RemoveAll removes the contained tasks, and return them.
		RemoveAll() interface{}
	}

	PeriodicalExecutor struct {
		commander chan interface{}
		interval  time.Duration
		container TaskContainer
		waitGroup sync.WaitGroup
		// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
		wgBarrier   syncx.Barrier
		confirmChan chan lang.PlaceholderType
		guarded     bool
		newTicker   func(duration time.Duration) timex.Ticker
		lock        sync.Mutex
	}
)

// 起一个goroutine来处理任务
func (pe *PeriodicalExecutor) backgroundFlush() {
	threading.GoSafe(func() {
		ticker := pe.newTicker(pe.interval)
		defer ticker.Stop()

		var commanded bool
		last := timex.Now()
		for {
			select {
      // 主动触发上报
			case vals := <-pe.commander:
				commanded = true
				pe.enterExecution()
				pe.confirmChan <- lang.Placeholder
				pe.executeTasks(vals)
				last = timex.Now()
      // 定时上报
			case <-ticker.Chan():
				if commanded {
					commanded = false
				} else if pe.Flush() {
					last = timex.Now()
				} else if timex.Since(last) > pe.interval*idleRound {
					pe.lock.Lock()
					pe.guarded = false
					pe.lock.Unlock()

					// flush again to avoid missing tasks
					pe.Flush()
					return
				}
			}
		}
	})
}
// core/stat/metrics.go
func (c *metricsContainer) Execute(v interface{}) {
	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,
	}

  ......
  
	log(report)
}

func log(report *StatReport) {
	writeReport(report)
	logx.Statf("(%s) - qps: %.1f/s, drops: %d, avg time: %.1fms, med: %.1fms, "+
		"90th: %.1fms, 99th: %.1fms, 99.9th: %.1fms",
		report.Name, report.ReqsPerSecond, report.Drops, report.Average, report.Median,
		report.Top90th, report.Top99th, report.Top99p9th)
}

func writeReport(report *StatReport) {
	writerLock.Lock()
	defer writerLock.Unlock()
 		......
		if err := reportWriter.Write(report); err != nil {
    ......
}

func (rw *RemoteWriter) Write(report *StatReport) error {
	bs, err := json.Marshal(report)
	.....
  // endpoint就是推送到prometheus server的地址
	resp, err := client.Post(rw.endpoint, "application/json", bytes.NewBuffer(bs))
	.....
}

Docker运行Prometheus

  • 首先编写配置文件
global:
  scrape_interval: 1m
  scrape_timeout: 10s
  evaluation_interval: 1m
scrape_configs:
- job_name: api
  static_configs:
  - targets:
    - docker.for.mac.localhost:9102
    labels:
      app: gym
      env: test
      instance: 127.0.0.1:8888

这里有几点要注意的:

  1. 在终端写配置文件的时候尽量用空格来代替tab,我本地是mac环境,写完之后放到docker里就报解析错误。。。
  2. 我的示例里服务是跑在本地的,配置文件中的targets写127.0.0.1:9102是访问不了的,即使使用–net=host在docker for mac中也是不行的,如果想要在docker 容器中访问宿主机的端口就必须要用docker.for.mac.localhost(我试了试用docker0的网关地址和–net=host都不成功)
  3. 确保端口没有被占用😭,我本地开了clashx的客户端,刚好占用了9090端口,结果导致访问的时候一直显示个"hello":“clash”。。。。
  • 启动Prometheus容器

映射9090端口并且将配置文件挂载

docker run -itd -p 9090:9090 \ 
-v /Users/johopig/Documents/etc/prometheus.yml:/etc/prometheus/prometheus.yml \
--name prom prom/prometheus
  • 浏览器访问

在浏览器中输入http://127.0.0.1:9090/然后点击status就可以看到如下画面。可以清晰的看到job的状态还有label

点击Graph,并且先提前在浏览器get调用一下你的api网关,过一分钟之后再再搜索框中输入http_server_requests_code_total,可以看到以下的画面

其中http_server_requests_code_total指标中code值为http的状态码

image-20201115174350944

Console界面主要展示了查询的指标结果,Graph界面为我们提供了简单的图形化的展示界面,我们一般使用Grafana做图形化的展示,用docker安装的话也是非常便利的,只不过要结合指标来进行可视化绘制,这里就不讲了(其实是因为我也还没弄哈哈哈哈…)

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Micrometer-registry-prometheus是一个用于将Micrometer指标导出到Prometheus的注册表。它允许开发人员使用Micrometer来收集应用程序的度量,并将这些度量导出到Prometheus,以便进行监视和分析。Micrometer-registry-prometheus提供了一种简单的方法来将Micrometer指标Prometheus集成,使开发人员可以轻松地监视和分析应用程序的性能。 ### 回答2: Micrometer是一个metrics库,可以将metrics与不同的监控系统(如Prometheus)集成。Micrometer让开发人员可以更容易地收集和监控系统的metrics数据,以便更好地了解系统的运行状态和性能,以及及早发现可能存在的问题。 Prometheus是一个开源的监控系统和时序数据库,它可以通过拉取方式(pull)收集和存储监控数据,并提供强大的查询和可视化功能。Micrometer使用Registry来对接不同的监控系统,比如在使用Prometheus时,可以通过Micrometer-registry-prometheus将Micrometer的metrics数据导入到Prometheus中。 Micrometer-registry-prometheus扮演着Micrometer metrics在Prometheus中注册的角色,它可以通过一系列API方法对Micrometer的metrics进行注册和导出。这些API方法包括:Gauge,Counter,Timer,DistributionSummary和LongTaskTimer等。它还支持通过tags对metrics数据进行不同的分类和聚合,以便更好地展示和分析数据。 总之,Micrometer-registry-prometheus提供了一种简单,方便,可靠的方法来将Micrometer的metrics数据导入到Prometheus中,以便更好地监控和了解系统运行状态和性能。 ### 回答3: Micrometer是一种Java度量度量库,用于在应用程序中收集指标和度量,以及推送到不同的监视系统中,以便进行分析和可视化。Micrometer提供了一系列通用的度量API,并支持集成到众多开源监控系统,比如Prometheus、Graphite、InfluxDB等等。 Prometheus是一种基于开源的监控系统,采用拉模型(pull model)来采集和存储时间序列数据,并根据用户定义的规则定期进行数据聚合和警报。Prometheus提供了强大的查询语言和可扩展的图形化界面,以便用户更好地查看和理解系统的性能和行为。 Micrometer与Prometheus集成,允许开发人员捕获细粒度的指标和计数器,并在Prometheus中公开指标,以便进行监视和分析。此外,Micrometer提供了许多其他有用的特性,比如对分布式跟踪的支持,以及数据导出和聚合处理。 总的来说,Micrometer-registry-prometheus是一种强大的Java度量和监控解决方案,能够帮助开发人员轻松地收集、公开和分析指标和度量,以便更好地处理和维护应用程序的性能和健康状况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值