应用监控的定义与作用

对于工程师们来说,软件某一阶段的开发任务完成就意味着"done"了。然而从软件的生命周期来说,编码的完成只是开始,软件还需要符合预期地运行并试图达到人们对它设定的目标,而监控就是检验这两点的常用可视化方法。

按照监控的对象通常可以将监控分为基础设施监控,中间件监控,应用监控和业务监控,它们各自的监控对象与作用如下表所示:

监控对象 判断软件是否符合预期地运行 判断业务目标是否达到
基础设施监控 服务器、存储等软件的运行环境
中间件监控 数据库、消息队列等公用软件
应用监控 实现具体业务需求的软件
业务监控 业务指标

其中基础设施、中间件和应用层级的监控,由于都在软硬件系统内,它们之中任意一环出现问题都有可能导致软件运行出现异常,实际场景中这些监控通常需要互相配合、关联分析。

而应用级别的监控,由于本身是业务的重要载体,应用监控有时也能直接反应业务指标是否达到,比如应用的吞吐量和时延等指标,当这些指标是业务的侧重点时,应用监控实际上就发挥了业务监控的作用。

应用监控利器 Prometheus

Prometheus是一套开源的监控体系,以指标为度量单位来描述应用的运行情况。

组件及生态


这张图片是Prometheus官网上的架构图,可以看到 Prometheus 除了主要的 Prometheus Server 提供采集和存储指标的时序数据以外,还包括接受推送方式获取指标的 Pushgateway 和管理告警规则的 Alertmanger 等组件。在应用监控中我们主要关注的是 Prometheus Server 和集成到各类应用中负责产生指标的 prometheus client 库。

特性

Prometheus官网中介绍的特性主要有以下几点:

  • 多维度的指标数据模型(prometheus中每条时序数据包括时间戳,指标名称和标签等维度)
  • 指标查询语言PromQL(通过对原始指标数据进行标签筛选,以及取变化率、topN等数据处理函数操作,使得指标的表达更具灵活性)
  • 不依赖于分布式存储,实现单节点自治
  • 基于HTTP协议拉取时序数据(相比于Zabbix中使用json-rpc协议,HTTP协议更符合Web应用中远程调用的主流)

在k8s集群中部署 Prometheus Operator

Prometheus Operator 是在 k8s 集群中部署和维护 prometheus 服务的一种方式,它在 prometheus server 和 alertmanger 等服务端组件的基础上,还把监控对象和告警规则等配置也给"资源化"了,更容易在 k8s 集群中管理。

演示项目 kube-prometheus

Github 中的 kube-prometheus 项目是从 prometheus operator 中分离出来的,主要用来快速搭建一个演示环境。

直接通过 kuebctl apply 命令在k8s集群中部署 prometheus operator:

git clone https://github.com/coreos/kube-prometheus.git
cd kube-prometheus/manifests
kubectl apply -f setup
kubeclt apply -f .

以上命令中的 kubectl apply -f setup 主要创建 monitoring 命名空间,以及prometheus operator 这个控制器还有其他 CRD (自定义资源声明)。

kubectl apply -f .则创建刚才定义好的自定义资源,以及各自的 ServiceAccount 和相关配置。

各自定义资源的拓扑关系如下:

ServiceMonitor的作用

其中跟应用监控关系最密切的就是ServiceMonitor资源,它的yaml文件类似这样:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:labels:k8s-app: alertmanagername: alertmanagernamespace: monitoring
spec:endpoints:- interval: 30sport: webselector:matchLabels:alertmanager: main

ServiceMonitor通过标签筛选需要被监控的对象(k8s service),并且指定从它的哪个端口和url路径来拉取指标,并定义拉取操作的间隔时间。

ServiceMonitor本质是对 prometheus 配置中指标数据来源(endpoint)的抽象,每新建一个 service monitor 资源,prometheus operator 就会在自定义资源 promethues 的配置文件中添加相应的配置,以达到和使用原生 prometheus 相同的效果,这就把原来需要需要手动和统一配置的任务通过 crd 来自动化实现了。

上述例子中几乎所有资源都有对应的 Service Monitor,说明它们都有一个http/https 接口来暴露指标数据,这是因为谷歌还有coreos在设计这些组件时就要求每个组件都要有暴露自身状态的接口,它们在样式上是符合Prometheus 规范的文本信息,类似这样:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0.0008235
go_gc_duration_seconds_sum 0.0008235
go_gc_duration_seconds_count 8

每一个或一组指标都会包含描述信息、指标类型、指标名称和实时数据这些,prometheus在获取http接口中的文本信息后会进一步将它们转化为自己的时序数据模型(加上时间戳维度等)。

Go应用中实现自定义指标

在我们自己开发的应用程序中,也可以通过集成 prometheus 官方提供的client库来对外暴露 prometheus 风格的指标数据。

以 Go 语言开发的应用为例,首先在项目中新建一个子目录(包)用来声明和注册需要对外暴露的应用指标:

stat/prometheus.go:

package statimport "github.com/prometheus/client_golang/prometheus"var (testRequestCounter = prometheus.NewCounter(prometheus.CounterOpts{Name: "test_request_total",Help: "Total count of test request",})
)func init() {prometheus.MustRegister(testRequestCounter)
}

示例中声明了一个 Counter 类型的指标,用于统计测试http请求的总数,在 init 函数中对该指标进行注册,之后我们在其他go文件中引入该包时就会自动注册这些指标。

Counter类型是 prometheus 4种指标类型(Counter, Gauge, Histogram, Summary)的一种,用于描述只增不减的数据,比如http服务接收的请求数,具体可以查看 prometheus 的官方文档。

main.go:

package mainimport ("github.com/gin-gonic/gin""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp""github.com/go-app/stat" //刚才写的声明指标的包
)
func main() {r := gin.Default()r.GET("metrics",gin.WrapH(promhttp.Handler()))r.GET("test",func(c *gin.Context) {stat.testRequestCounter.Inc()c.JSON(http.StatusOK, gin.H{"text": "hello world",})})r.Run(":9090")
}

指标声明后还需要在合适的实际触发对指标数据的采集,比如这个例子中在每次访问 /test 请求时在 handle 函数中使请求计数器加1,如果是要统计所有的请求数的话,还可以把采集数据的操作放在中间件中,使任何请求都会触发计数器加1。

实际场景中的应用监控比上述例子复杂得多,因为不同的应用程序可以采集的监控指标不同,即使是同类型的应用,在不同的业务场景下需要采集的指标也会有不同的侧重。但是在谷歌的 SRE 实践中仍然总结出了4个黄金指标,分别是:

  • 延迟 服务处理请求所需要的时间
  • 流量 对系统负载的度量,在http服务中通常是每秒的请求数
  • 错误 请求失败的速率
  • 饱和度 服务容量有多"满",通常是系统中某个最为受限的资源的某个具体指标的度量

这些指标也可以通过 prometheus 的4中基本指标类型去表示,大致的关系是:

Counter ==> 请求量,请求流量
Gauge ==> 系统的饱和度(实时)
Histogram ==> 请求延时在各个区间的分布
Summary ==> 请求延时的中位数,9分位数,95分位数等

根据黄金指标的指导理念,我又设计了一个更复杂一些的示例:
假设有一个固定容量的消息队列,通过http的 /push 和 /pop 请求可以使队列增加或减少一条记录,在队列容量快要满或者快要全空的时候,请求的延时和错误率都会相应增加。

以下是完整的示例代码和最终展示的Grafana图表,仅供参考:
stat/prometheus.go:

package statimport "github.com/prometheus/client_golang/prometheus"var (MqRequestCounter = prometheus.NewCounterVec(prometheus.CounterOpts{Namespace: "mq",Name: "request_total",Help: "Total count of success request",},[]string{"direction"})MqErrRequestCounter = prometheus.NewCounterVec(prometheus.CounterOpts{Namespace: "mq",Name: "err_request_total",Help: "Total count of failed request",},[]string{"direction"})MqRequestDurationHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{Namespace: "mq",Name: "request_duration_distribution",Help: "Distribution state of request duration",Buckets: prometheus.LinearBuckets(110,10,5),},[]string{"direction"})MqRequestDurationSummary = prometheus.NewSummaryVec(prometheus.SummaryOpts{Namespace: "mq",Name: "request_duration_quantiles",Help: "Quantiles of request duration",Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},},[]string{"direction"})MqCapacitySaturation = prometheus.NewGauge(prometheus.GaugeOpts{Namespace: "mq",Name: "capacity_saturation",Help: "Capacity saturation of the message queue",})
)func init() {prometheus.MustRegister(MqRequestCounter,MqErrRequestCounter,MqRequestDurationHistogram,MqRequestDurationSummary,MqCapacitySaturation)
}

main.go:

package mainimport ("fmt""github.com/gin-gonic/gin""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp""github.com/go-app/stat""math/rand""net/http""strings""time"
)type queueConfig struct {length intmaxErrorRate float64maxDuration time.Duration
}type messageQueue struct {qc queueConfigqueue []string
}var pushErrCount, popErrCount intfunc (m *messageQueue) push() (ok bool,duration time.Duration){startTime := time.Now()stat.MqRequestCounter.With(prometheus.Labels{"direction":"push"}).Inc()factor := float64(len(m.queue))/float64(m.qc.length)fixedDuration := time.Duration(float64(m.qc.maxDuration)*factor) + time.Millisecond*100time.Sleep(fixedDuration)errorRate := m.qc.maxErrorRate * factorif rand.Intn(100) < int(errorRate*100) {ok = falsepushErrCount += 1stat.MqErrRequestCounter.With(prometheus.Labels{"direction":"push"}).Inc()} else {ok = truem.queue = append(m.queue,"#")}duration = time.Now().Sub(startTime)durationMs := float64(duration/time.Millisecond)stat.MqRequestDurationHistogram.With(prometheus.Labels{"direction":"push"}).Observe(durationMs)stat.MqRequestDurationSummary.With(prometheus.Labels{"direction":"push"}).Observe(durationMs)fmt.Printf("%v",strings.Join(m.queue,""))fmt.Printf("\t Factor: %v Success:%v Duration:%v PushErrCount:%v\n",factor,ok,duration,pushErrCount)return
}func (m *messageQueue) pop() (ok bool,duration time.Duration){startTime := time.Now()stat.MqRequestCounter.With(prometheus.Labels{"direction":"pop"}).Inc()factor := float64(m.qc.length-len(m.queue))/float64(m.qc.length)fixedDuration := time.Duration(float64(m.qc.maxDuration)*factor) + time.Millisecond*100time.Sleep(fixedDuration)errorRate := m.qc.maxErrorRate * factorif rand.Intn(100) < int(errorRate*100) {ok = falsepopErrCount += 1stat.MqErrRequestCounter.With(prometheus.Labels{"direction":"pop"}).Inc()} else {ok = truem.queue = m.queue[:len(m.queue)-1]}duration = time.Now().Sub(startTime)durationMs := float64(duration/time.Millisecond)stat.MqRequestDurationHistogram.With(prometheus.Labels{"direction":"pop"}).Observe(durationMs)stat.MqRequestDurationSummary.With(prometheus.Labels{"direction":"pop"}).Observe(durationMs)fmt.Printf("%v",strings.Join(m.queue,""))fmt.Printf("\t Factor: %v Success:%v Duration:%v PopErrCount:%v\n",factor,ok,duration,popErrCount)return
}func main() {r := gin.Default()r.GET("metrics",gin.WrapH(promhttp.Handler()))api := r.Group("api")qc := queueConfig{length: 100,maxErrorRate: 0.2,maxDuration: 50*time.Millisecond,}mq := messageQueue{qc: qc,queue: make([]string,0,qc.length),}rand.Seed(time.Now().UnixNano())api.POST("push",func(c *gin.Context) {ok, duration := mq.push()c.JSON(http.StatusOK,gin.H{"success": ok,"duration": duration,"length": len(mq.queue),})})api.POST("pop",func(c *gin.Context) {ok, duration := mq.pop()c.JSON(http.StatusOK,gin.H{"success": ok,"duration": duration,"length": len(mq.queue),})})go func() {for {saturation := float64(len(mq.queue))/float64(mq.qc.length)stat.MqCapacitySaturation.Set(saturation)time.Sleep(time.Second*5)}}()r.Run(":9090")
}

client.go (用于持续对应用发起请求):

package mainimport ("encoding/json""fmt""io/ioutil""net/http""os""time"
)type Body struct {Success  bool          `json:"success"`Duration time.Duration `json:"duration"`Length   int           `json:"length"`
}func main() {pushFlag := truevar baseUrl stringif len(os.Args[1:]) == 0 {baseUrl = "http://localhost:9090"} else {baseUrl = os.Args[1:][0]}pushUrl := baseUrl + "/api/push"popUrl := baseUrl + "/api/pop"for {if pushFlag {resp, err := http.Post(pushUrl, "application/json", nil)if err != nil {fmt.Fprint(os.Stderr, err)}bodyStr, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Fprint(os.Stderr, err)}body := &Body{}err = json.Unmarshal(bodyStr, body)if err != nil {fmt.Fprint(os.Stderr, err)}fmt.Printf("%v\n", body)resp.Body.Close()if body.Length == 100 {pushFlag = false}} else {resp, err := http.Post(popUrl, "application/json", nil)if err != nil {fmt.Fprint(os.Stderr, err)}bodyStr, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Fprint(os.Stderr, err)}body := &Body{}err = json.Unmarshal(bodyStr, body)if err != nil {fmt.Fprint(os.Stderr, err)}fmt.Printf("%v\n", body)resp.Body.Close()if body.Length == 0 {pushFlag = true}}}
}

Grafana图表:

使用 Prometheus 进行应用监控相关推荐

  1. 在微服务架构下基于 Prometheus 构建一体化监控平台的最佳实践

    欢迎关注方志朋的博客,回复"666"获面试宝典 随着 Prometheus 逐渐成为云原生时代的可观测事实标准,那么今天为大家带来在微服务架构下基于 Prometheus 构建一体 ...

  2. prometheus之docker监控与告警系列(二)

    序 本系列主要介绍prometheus+cadvisor+alertmanager打造docker监控,主要监控指定docker容器是否挂掉 本节主要熟悉prometheus+Alertmanager ...

  3. prometheus连续查询_Grafana + Prometheus快速搭建监控平台

    随着业务的越发复杂,软件系统也越来越庞大,对软件系统的要求越来越高,意味着我们需要随时掌控系统的运行情况.因此,系统的实时监控以及可视化展示,就显得非常重要. 今天来介绍下使用Grafana + Pr ...

  4. Prometheus+Grafana PG监控部署以及自定义监控指标

    点击上方"蓝字" 关注我们,享更多干货! 1.前言 Prometheus:是从云原生计算基金会(CNCF)毕业的项目.Prometheus是Google监控系统BorgMon类似实 ...

  5. 开源OceanBase如何与Prometheus与Grafana监控结合

    一.OceanBase 数据库简介 OceanBase 数据库是一个原生的分布式关系数据库,它是完全由阿里巴巴和蚂蚁集团自主研发的项目,近期成立单独的商业公司北京奥星贝斯进行运营,并于2021年6月1 ...

  6. Jmeter+Prometheus+Grafana性能监控平台:将JMeter压测数据输出到Prometheus

    前言 1.小编之前写过一篇文章详细讲解了如何搭建一个HTTP接口性能实时监控测试平台,是用Grafana+Influxdb+Jmeter组合实现的,可以参考我写的这篇博客https://editor. ...

  7. 视频教程-Prometheus+Grafana企业级监控实战(运维篇)2020年视频教程-Linux

    Prometheus+Grafana企业级监控实战(运维篇)2020年视频教程 资深DevOps工程师,曾经在华为,乐逗游戏工作,目前就职于知名物流公司工作 希望结合工作实践 给大家带来很多干货 周龙 ...

  8. 作者领读 | Prometheus云原生监控

    撰文:朱政科 01 作者导读 昨天收到书,用了两天时间,我也亲自把这本书读完了一遍.今天写这篇文章的目的是带读者用正确的方式读这本书. <Prometheus云原生监控:运维与开发实战> ...

  9. Prometheus Operator + blackbox_exporter 监控Web页面

    背景 目前生产环境使用Zabbix自带的web监控模块对所有子优鸟页面进行监控,由于目前Zabbix服务器为单节点,经常出现取不到web监控数据的情况.现将web监控迁移到Prometheus上. 但 ...

  10. Prometheus api 查询监控数据导出 CSVExcel

    Prometheus api 获取监控数据导出 CSV 1. 发送给企业微信机器人 #  upload_file 是为了生成 media_id, 供消息使用 # -*- encoding: utf-8 ...

最新文章

  1. 设计模式之“代理模式”
  2. 第九周项目二-我的数组类
  3. puppet开源的软件自动化配置和部署工具——本质就是CS,服务端统一管理配置...
  4. E: 您必须在 sources.list 中指定代码源(deb-src) URI 解决办法
  5. 简单了解Vue的异步请求,axios-0.18.0.js插件实现异步
  6. MTK 驱动(79)---如何调整CPU corenum, freq, policy
  7. IBM TPM2.0 模拟器
  8. Oracle版本区别[转载]
  9. 心法利器[57] | 文本多分类问题经验
  10. pytorch visdom可视化工具学习—1—详细使用-3-Generic Plots和Others
  11. 如何从头开始使用Python实现堆栈泛化(Stacking)
  12. PHP人民币金额数字转中文大写的函数
  13. web逻辑思维题目_经典的逻辑思维训练题
  14. H3C 无线控制器+瘦ap 配置
  15. GoAhead学习之GoForms
  16. 计算机中缺少d3dx11_43.dll,韩博士传授win10系统运行软件提示计算机丢失d3dx11_43.dll的处理对策...
  17. Qt开发经验小技巧196-200
  18. 大数据分析师需要掌握哪些技能
  19. 方大九钢携手图扑软件:数字孪生智慧钢厂
  20. 小度智能音箱Pro全新登场,百度软硬件结合如何青出于蓝而胜于蓝?

热门文章

  1. DELL inspiron n5010外接耳机有声,外放喇叭没声解决方法
  2. 敏捷软件开发方法论_什么是敏捷方法论? 现代软件开发讲解
  3. 校花干得土得掉渣的事
  4. HHSTU1050型货车转向系及前轴设计(说明书+任务书+CAD图纸)
  5. 仿哔哩哔哩应用客户端Android版源码项目
  6. python画花朵代码_python画花朵代码分享
  7. 丹佛斯与西门子变频器FC报文控制解析
  8. 2022年天津最新建筑八大员(标准员)模拟考试试题及答案
  9. 2022-1-4 类
  10. 电源滤波器的作用及原理