TiDB 使用开源时序数据库 Prometheus 作为监控和性能指标信息存储方案,使用 Grafana 作为可视化组件进行展示。
Prometheus 狭义是上软件本身,即 prometheus server,广义上是基于 prometheus server 为核心的各类软件工具的生态。除 prometheus server 和 grafana 外,Prometheus 生态常用的组件还有 alertmanager、pushgateway 和非常丰富的各类 exporters。
prometheus server 自身是一个时序数据库,相比使用 MySQL 做为底层存储的 zabbix 监控,拥有非常高效的插入和查询性能,同时数据存储占用的空间也非常小。另外不同于 zabbix,prometheus server 中的数据是从各种数据源主动拉过来的,而不是客户端主动推送。如果使用 prometheus server 要接收推送的信息,数据源和 prometheus server 中间需要使用 pushgateway。
Prometheus 监控生态非常完善,能监控对象非常丰富。详细的 exporter 支持对象可参考官方介绍 exporters列表 。
Prometheus 可以监控的对象远不止官方 exporters 列表中的产品,有此产品原生支持不在上面列表,如 TiDB; 有些可以通过标准的 exporter 来监控一类产品,如 snmp_exporter; 还有些可以通过自己写个简单的脚本往 pushgateway 推送;如果有一定开发能力,还可以通过自己写 exporter 来解决。同时有些产品随着版本的更新,不需要上面列表中的 exporter 就可以支持,比如 ceph。
随着容器和 kurbernetes 的不断落地,以及更多的软件原生支持 Prometheus,相信很 Prometheus 会成为监控领域的领军产品。
Prometheus 的架构图如下:
Prometheus 生态中 prometheus server 软件用于监控数据库的存储、检索,以及告警消息的推送,是 Prometheus 生态最核心的部分。
Alertmanger 负责接收 prometheus 软件推送的告警,并将告警经过分组、去重、等处理后,按告警标签内容路由后,通过邮件、短信、企业微信、钉钉、webhook 等发送给接收者。
大部分软件监控时还需要部署一个 exporter 做为 agent 来采集数据,但是有部分软件原生支持 Prometheus,比如 TiDB 的部分组件,在不用部署 exporter 的情况下就可以直接采集监控数据。
Prometheus 数据查询的语言 PromQL, 可以通过 prometheus server 的 web UI,在浏览器上直接编写 PromQL 检索,也可以将 PromQL 固化到 grafana 的报表中做动态的展示,还可以通过 API 接口做更丰富的自定义功能。
Prometheus 除了可以采集静态的 exporters 之外,还可要通过 Service discover 的方式监控各种动态的目标,如 kubernetes 的 node,pod,service 等。
除 exporter 和 service discovery 之外,还可以写脚本做一些自定义的采集,然后通过 push 的方式推送到 pushgateway,pushgateway 对于 prometheus server 来说就是一个特殊的 exporter,prometheus server 可以像抓取其他 exporters 一样抓取 pushgateway 的信息。
Prometheus 可以运行在 kubernetes 中,也可以运行中虚拟机中。Prometheus 的大部分组件都已经有编译好的二进制文件和 docker 镜像。对于二进制文件,从官方网站下载解压后就可以启动运行,命令如下:
prometheus --config.file=conf/prometheus.yml
建议将二进制文件做成 systemd 的一个服务,这部分可以参考 TiDB 上运行 prometheus 的方式 。
prometheus server 的配置文件是 yaml 格式,由参数 --config.file 去指定需要使用的配置文件。配置文件一般命名为 prometheus.yml
常用配置
配置文件示例:
global:
scrape_interval: 15s
scrape_timeout: 10s
external_labels:
monitor: 'codelab-monitor'
rule_files:
- rules/centos7.rules.yml
- rules/mariadb.rules.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- 21.129.127.3:9093
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 5s
static_configs:
- targets: ['localhost:9090']
- job_name: node
file_sd_configs:
- files:
- conf.d/centos.yml
配置文件说明:
- global: 指的是全局变更
- scrape_interval: 抓取目标监控信息的间隔,默认 15 秒
- scrape_timeout: 抓取时的超时时间,默认 10 秒
- external_labels: 额外添加的标签,这个标签可以在多个外部系统流转,如:federation, remote storage, Alertmanager
- rule_files: 这个写的是生成告警规则的配置文件,具体写法会在后 alertmanger 章节介绍
- alerting: 这个是用来配置 alertmanager 地址,可以写多个 alertmanager 的地址
- scrape_configs: 从这开始,后面是采集对象的配置
- job_name:可以定义多个 job,每个 job 里有一类的采集对象
- static_configs: 后面可以写一些静态的监控对象
- targets: 要抓取的具体对象(instance)
- file_sd_configs: 如果监控对象过多,可使用这种方式写到独立的文件中
告警规则配置
告警规划配置文件示例:
groups:
- name: alert.rules
rules:
- alert: InstanceDown
expr: up == 0
for: 1s
labels:
level: emergency
annotations:
summary: "该实例抓取数据超时"
description: "项目:{{ $labels.project }} , service: {{ $labels.service}}" 当前值{{ $value }}
说明:
- groups: 标记当前所有的告警规划为同一组
- name: 这告警组的自定义名称
- alert:告警规则的名称
- expr:告警的表达式
- for: 问题发生后保持多长时间再推送给
- alertmanager,调低时间可以增加告警的敏感度,调高会减少告警毛刺。
- labels: 可以加一些自定义的键值对标签
- annotations: 可以加一些描述信息
TiDB 已经原生支持 Prometheus, 在 TiDB 旧版本中,TiDB 的监控信息是由各 TiDB 的各个组件主动上报给 pushgateway,再由 prometheus server 去 pushgateway 上主动抓取监控信息。
从 TiDB 2.1 版本开始由主动上报变成主动暴露 Metrics 接口 ,由 prometheus server 主动抓取信息, 这样的架构更符合 Prometheus 的设计思想,整个数据采集路径少一层 pushgateway。 数据采集完成后由于 grafana 做报表展示,同时告警信息主动推动 alertmanager,再 altermanager 将告警推送到不同的消息渠道。
PromQL (Prometheus Query Language)是 Promehteus 提供的函数查询语言,可以进行实时查询,也可以通过函数做聚合运算。
Promethes 中的数据类型分 4 类:
- Instant vector - 一个时间点的时序数据;
- Range vector - 一个时间段的时序数据;
- Scalar** -数字,浮点值;
- String** - 字符串,当前还没有用。
下面看看 prometheus 里存储的记录是什么样的,示例如下,这是使用 web UI ( http://prometheus-server:9090/graph) 查询出的结果以 Table 格式展示的内容:
up{alert_lev="0",,instance="21.129.14.103:2998",job="hadoop",project="dtl",service="hadoop"} 1
说明:
up: 是一条具体的时序记录名字,同时 up 又是一条特殊的时序名称,他是 Prometheus 对每个监控对象自动生成的,指示该对象一起停状态,1 是可连通,0 是不可能连通(注意,0 不一定是服务挂了,也有可能是获取记录的时候超时了)。
instance,job,project,service,alert_lev 都是该条的记录的标签,相对于关系型数据库中的字段。其中 instance 和 job 是基于 prometheus.yaml 中的内容自动生成的,project,service,alert_lev 是用户自定义的标签。instance 一般是 prometheus 里的 target,但是也可以在标签里重写。
最后的 1 是这条记录在查询的时间的结果
这种查询直接写时序名称就可查出数据,
http_requests_total
同时还可以在{}中加一些标签做为条件
http_requests_total{job="prometheus",group="canary"}
一个标签还可以匹配多个值
http_requests_total{job=~"prometheus|node",group="canary"}
还可以使用匹配不需要的
http_requests_total{job!~"prometheus|node",group!="canary"}
可以匹配正则表达式
up{mysql=~".+"} #匹配所以包含 mysql 的 up 时序数据
还可以使用算术运算和比较运算进一步过滤结果
Hadoop_DataNode_BytesWritten{job="hadoop"}/1024/1024 > 500
Range vector 查询类似于 instance vector 查询 ,但是加通过[]加上限定时间范围,时间单位有以下级别:
· s - seconds
· m - minutes
· h - hours
· d - days
· w - weeks
· y - years
示例如下
Hadoop_DataNode_BlocksRead{ instance="21.129.14.104:2998"}[1m]
通常时间范围查询时会和函数一起使用,比如 rate() 函数
rate(node_cpu_seconds_total{mode='user',instance='21.129.20.161:9100'}[5m])
使用的 offset 查询的结果是显示的时序往前偏移的值,示例如下
sum((tidb_server_query_total{result="OK"} offset 1d))
rate() 和 irate()
这两个函数用于计数器(counter)类型的数据,这类数据会一直增加,使用这两个函数后,将展示一定时间范围内的变化情况,但它俩的计算方式有差异,irate()基于时间范围内连续的两个时间点。而 rate()是基于时间范围内的所有时间点,所以 irate()展示的数据更为精确些,做图毛刺会更明显,示例如下:
irate(node_cpu_seconds_total{mode='user',instance='21.129.20.161:9100'}[5m])
increase()
将会计算出指定时间范围内的变化量,用法如下:
increase(http_requests_total{job="api-server"}[5m])
histogram_quantile()
累积直方图百分位数, 用法 histogram_quantile(φ float, b instant-vector),百分位是介于0和1之间。这个函数计算的结果是直方图中指定百分比的最大值。比如0.95的百分位的结果是200,说明所有数据中,小于200的占总数据的比例为95%。使用示例如下:
histogram_quantile(0.95, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le, instance))
sum(),avg()
聚合函数,使用示例
sum(tikv_store_size_bytes{instance=~"$instance", type="available"}) by (instance)
显示 1 分钟范围内 IO 使用率,由于 node_disk_io_time_ms 是个 counter 类型的,所以使用 rate()函数
rate(node_disk_io_time_ms[1m]) / 1000
以 bypte 为聚合条件,显示 1 分钟内 Failed Query OPM 总数
sum(increase(tidb_server_execute_error_total[1m])) by (type)
监控 duration 的百分位,百分们是 0.99,直方图的桶是以 le,instance 组合为单位。
histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le, instance))
显示 cpu 的使用率,计算方式是先求 idle 空间 CPU 的,然后再同 100 求差异,聚合单们是 instance 标签。
100 - avg by (instance) (irate(node_cpu{mode="idle"}[1m]) ) * 100
本节介绍了 TiDB 组件的报警项。根据严重级别,报警项可分为三类,按照严重程度由高到低依次为:紧急级别、重要级别、警告级别。
紧急级别的报警通常由于服务停止或节点故障导致,此时需要马上进行人工干预操作。告警规则里的标签 level: emergency,告警示例:
TiDB_schema_error
报警规则:
increase(tidb_session_schema_lease_error_total{type="outdated"}[15m]) > 0
规则描述:
TiDB 在一个 Lease 时间内没有重载到最新的 Schema 信息。如果 TiDB 无法继续对外提供服务,则报警。
处理方法:
该问题通常由于 TiKV Region 不可用或超时导致,需要看 TiKV 的监控指标定位问题。
对于重要级别的报警,需要密切关注异常指标。告警规则里的标签 level: critical,告警示例:
TiDB_server_panic_total
报警规则:
increase(tidb_server_panic_total[10m]) > 0
规则描述:
发生崩溃的 TiDB 线程的数量。当出现崩溃的时候会报警。该线程通常会被恢复,否则 TiDB 会频繁重启。
处理方法:
收集 panic 日志,定位原因。
警告级别的报警是对某一问题或错误的提醒。告警规则里的标签 level: warning,
告警示例:
TiDB_memory_abnormal
报警规则:
go_memstats_heap_inuse_bytes{job="tidb"} > 1e+10
规则描述:
对 TiDB 内存使用量的监控。如果内存使用大于 10 G,则报警。
处理方法:
通过 HTTP API 来排查 goroutine 泄露的问题。
更多关于 TiDB 报警规划,以及 TiDB 详细告警的处理方法,请参考 官网介绍 。
由于往外发送告警需要邮箱、短信、企业微信等外部消息通道打通,一般企业内部都有各自不同的安全要求和操作规范。另外像短信接口并不是统一标准的,大部分也不是原生支持 prometheus 的,所以需要用户自己编写适配脚本,以 webhook 的方式与 alertmanger 适配。
建议使用 TiDB 时,用户自己创建一个独立的 alertmanager,用于接收来自不同 prometheus server 的告警,统一集中路由发送,即可以有效安全管理,也可以减少用户自己的部署操作。
alertmanager 配置 TiDB 路由示例
路由部署配置:
- match:
env: test-cluster
level: emergency
receiver: tidb-emergency
group_by: [alertname, cluster, service]
说明:
- match: 是一条路由规划
- env: test-cluster,level: emergency: 这是从 Prometheus 过来的记录所携带的标签,如果能够匹配该标签,则符合当前这条路由规则
- receiver: 接收人,和后面接收部分的 name 一致
- group_by: 告警做分组聚合的标签,后面括号内的是 prometheus 记录所携带的标签。
接收部分示例
- name: 'tidb-emergency'
webhook_configs:
- url: 'xxxx'
wechat_configs:
- corp_id: 'xxxxx'
to_party: 'xxx'
agent_id: 'xxxx'
api_url: 'https://qyapi.weixin.qq.com/cgi-bin/'
api_secret: 'xxxxxx'
说明:
- name: 和上面路由规则的 receiver 对应一致
- webhook_configs: 以 webhook 的方式发送
- wechat_configs: 以企业微信的方式发送,具体要求参考 企业微信 文档;
- 由于默认的告警发送的内容过多,包含注释等信息,影响可读性。建议用户自己写 webhook 的方式发送告警。