前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rk-boot/v2: 干净的 Prometheus 监控方案 (Golang)

rk-boot/v2: 干净的 Prometheus 监控方案 (Golang)

原创
作者头像
尹东勋
发布2022-03-25 19:53:39
8020
发布2022-03-25 19:53:39
举报
文章被收录于专栏:开源 & 技术分享

针对中小型项目,介绍一下简单的 Prometheus 监控方案。

Prometheus 帮助我们解决了 Metrics 监控的难题,后续出现的 Thanos 解决了 Prometheus 存储扩展的难题。总体来说,Prometheus 已经是一个非常成熟的监控方案。

各大云厂商也相继推出了云版本的 Prometheus,用户可以不用去考虑如何运维 Prometheus,降低了人工运维成本。

既然 Prometheus 的运行问题已经得到解决,接下来就看看,如何使用它。

棘手的问题

Prometheus 的 Client 接口设计的很合理,使用上也没什么问题。随之会出现几个棘手的问题。

  • 监控什么
  • 代码里怎么写监控指标,Label 该怎么带
  • 怎么配置图形化

不要小看这个问题,监控什么跟设计 API 一样不是一件容易的事。如果我们强行往代码里【塞入】Prometheus 监控相关代码,我敢保证,代码会很乱。

举个例子,监控一个函数运行了多长时间,我们要做如下几个事。

  • 合理命名监控项
  • 合理配置 Label
  • 代码里嵌入计时代码
  • 处理错误逻辑
  • 图像化

光上面几个逻辑,至少需要20+行代码,如果每个函数都是如此,整个项目的代码会非常【难看】。

对于追求【干净,简介】代码的我们来说,这是一个难受的体验。

干净的解决方案

这里我们介绍 rk-boot/v2 + Prometheus + Grafana 的解决方案。这个方案里,我们使用两个方法解决上述棘手的问题。

简单来说,就是在函数里面添加两行代码,监控这个函数。

  • Wrap Prometheus 相关逻辑
  • 现成 Grafana 图表

代码干净

Golang 不像 Spring 一样有 annotation 的支持,可以在函数上面标记一个 @xxx 可以轻松实现函数监控。不过,我们可以在代码里添加【两行代码】,以最低成本实现函数监控。

现成图表(Grafana)

有一个通用 Grafana 监控 Dashboard 是一个很幸福的事情,毕竟上手 Grafana 不是几个小时就能搞定的事情。

Demo

1.Docker 启动 Prometheus

我们需要创建一个 prometheus.yml 文件,告诉 prometheus 从哪里收集监控数据。

  • prometheus.yml
代码语言:yaml
复制
global:
  scrape_interval: 1s

scrape_configs:
  - job_name: 'greeter'
    scrape_interval: 1s
    metrics_path: "/metrics"
    static_configs:
      - targets: ['host.docker.internal:8080']        # Prometheus 运行在 docker 里面,不使用 localhost
  • 启动 Prometheus
代码语言:txt
复制
$ docker run -p 9090:9090 -v /Users/dongxuny/workspace/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

2.使用 Docker 启动 grafana 并配置 Prometheus

初始化账号: admin

初始化密码:admin

代码语言:txt
复制
$ docker run -p 3000:3000 grafana/grafana
  • 选择 Prometheus 为数据源
  • 配置 Prometheus 地址

3.下载 rk-boot/v2

rk-boot/v2 是可以通过 YAML 文件启动 Golang 流行框架的依赖库,里面包含了很多实用中间件。

为了模拟微服务,我们同时还下载 rk-gin/v2 来启动 gin-gonic 服务。

代码语言:shell
复制
$ go get github.com/rookie-ninja/rk-boot/v2
$ go get github.com/rookie-ninja/rk-gin/v2

4.配置 boot.yaml

boot.yaml 文件告诉 rk-boot/v2 启动哪些 Gin 配套的服务。

代码语言:yaml
复制
---
gin:
  - name: greeter
    port: 8080
    enabled: true
    prom:
      enabled: true              # 告诉 Gin 启动 Prometheus 客户端
    middleware:
      prom:
        enabled: true            # 告诉 Gin 启动 Prometheus API 监控

5.写个 API

代码语言:go
复制
package main

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/rookie-ninja/rk-boot/v2"
	"github.com/rookie-ninja/rk-entry/v2/cursor"
	"github.com/rookie-ninja/rk-gin/v2/boot"
	"github.com/rookie-ninja/rk-gin/v2/middleware/context"
	"net/http"
)

func main() {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Register handler
	ginEntry := rkgin.GetGinEntry("greeter")
	ginEntry.Router.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.Background())

	// 必要!向 Prometheus Registry 注册 Prometheus Metrics
	ginEntry.PromEntry.Register(rkcursor.SummaryVec())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}

func Greeter(ctx *gin.Context) {
	// 启动此函数的监控,并使用 Release() 函数结束监控
	pointer := rkginctx.GetCursor(ctx).Click()
	defer pointer.Release()

	ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.Query("name")),
	})
}

type GreeterResponse struct {
	Message string
}

6.启动 main.go & 发送请求

代码语言:txt
复制
$ go run main.go
$ curl localhost:8080/v1/greeter

7.查看图形化监控

boot.yaml 文件告诉 Gin 启动 API 监控中间件,所以我们可以监控两个东西。

rk-prom: 自动记录每个 API 的 Metrics,有默认 grafana dashboard

rk-cursor: 代码里嵌入的监控(就是我们写的那两行代码),有默认 grafana dashboard

  • 引入 rk-prom dashboard (代号:15111):

我们可以看到 API 的监控了,不需要任何代码改动。

  • 引入 rk-cursor dashboard (代号:15937):

我们可以看到 Cursor 的监控了

8.解释一下 rk-prom & rk-cursor dashboard

rk-prom 是一个自带的监控中间件,会默认监控所有 API 的运行时间,错误码。包含的监控项有【运行时间】,【API 可用性】,【API 速率】

如果想要看到本地的输出的监控数据,可以查看 localhost:8080/metrics

rk-cursor 是一个 struct,通过 Click() 方法获取一个 Pointer struct,再通过 pointer.Release() 方法结束监控。

会默认监控所有 API 的运行时间,错误码。包含的监控项有【Function 运行时间】,【Function 可用性】,【Function 速率】。

rk-cursor 使用方法

rk-cursor 可以实现如下三个监控。

  • 监控 Function 运行时间
  • 监控 Function 错误
  • 监控 Function 里调用其他 Function 的运行时间(相当于 1层的调用链)

监控 Function 错误

对于上面的代码进行一行改动。

代码语言:go
复制
func Greeter(ctx *gin.Context) {
	// 启动此函数的监控,并使用 Release() 函数结束监控
	pointer := rkginctx.GetCursor(ctx).Click()
	defer pointer.Release()

	// 记录一个 Error,让 Prometheus 标记此次运行是错误的,并且打错误日志
	if time.Now().Second()%2 == 0 {
		pointer.ObserveError(errors.New("manually triggered error"))
	}
	
	ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.Query("name")),
	})
}

再运行一次,发送请求,并观察日志和 grafana dashboard

监控 Function 调用链

rk-cursor 会使用 runtime.Caller() 函数记录上一层调用函数的名字。

举个例子,Greeter() 函数调用 B() 函数和 C() 函数,那么我们可以观察 Greeter() 函数的里,B() 和 C() 花了多尝试时间。

我们稍微修改一下 main.go

代码语言:go
复制
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/rookie-ninja/rk-boot/v2"
	"github.com/rookie-ninja/rk-entry/v2/cursor"
	"github.com/rookie-ninja/rk-gin/v2/boot"
	"net/http"
	"time"
)

// 初始化一个全局 cursor
var gCursor = rkcursor.NewCursor(rkcursor.WithEntryNameAndType("greeter", "GinEntry"))

func main() {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Register handler
	ginEntry := rkgin.GetGinEntry("greeter")
	ginEntry.Router.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.Background())

	// 必要!向 Prometheus Registry 注册 Prometheus Metrics
	ginEntry.PromEntry.Register(rkcursor.SummaryVec())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}

func B() {
	pointer := gCursor.Click()
	defer pointer.Release()

	time.Sleep(1 * time.Millisecond)
}

func C() {
	pointer := gCursor.Click()
	defer pointer.Release()

	time.Sleep(2 * time.Millisecond)
}

func Greeter(ctx *gin.Context) {
	pointer := gCursor.Click()
	defer pointer.Release()

	// 调用 B函数
	B()

	// 调用 C函数
	C()

	ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.Query("name")),
	})
}

type GreeterResponse struct {
	Message string
}

这个例子里,函数的入口是 main.Greeter(),这个函数的 parentOperation 标记为 -

main.Greeter() 调用了 B() 和 C(),在 parentOperation 里选择 main.Greeter(),我们就可以看到,1层的调用链。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 棘手的问题
    • 干净的解决方案
      • 代码干净
        • 现成图表(Grafana)
        • Demo
          • 1.Docker 启动 Prometheus
            • 2.使用 Docker 启动 grafana 并配置 Prometheus
              • 3.下载 rk-boot/v2
                • 4.配置 boot.yaml
                  • 5.写个 API
                    • 6.启动 main.go & 发送请求
                      • 7.查看图形化监控
                        • 8.解释一下 rk-prom & rk-cursor dashboard
                        • rk-cursor 使用方法
                          • 监控 Function 错误
                            • 监控 Function 调用链
                            相关产品与服务
                            Prometheus 监控服务
                            Prometheus 监控服务(TencentCloud Managed Service for Prometheus,TMP)是基于开源 Prometheus 构建的高可用、全托管的服务,与腾讯云容器服务(TKE)高度集成,兼容开源生态丰富多样的应用组件,结合腾讯云可观测平台-告警管理和 Prometheus Alertmanager 能力,为您提供免搭建的高效运维能力,减少开发及运维成本。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档