Prometheus 是一款时序(time serier)数据库,但它的功能却并不止于 TSDB,而是一款设计用于进行目标(Target)监控的关键组件;集合生态系统内的其他组件,例如 pushGateway、Altermanager 和 Grafana 等,可构成一个完整的 IT 监控系统。
时序数据,是在一段时间内通过重复测量而获得的观察值的集合;将这些观测值绘制于图形之上,有一个数据轴和时间轴。我们服务器的指标数据、应用程序的性能检测数据、网络数据等也都是时序数据。
Prometheus 支持通过三种类型的途径从目标上抓取(scrape)指标数据;管理告警,主要是负责实现报警功能。在 Prometheus Server 中支持基于 PromQL 创建告警规则,如果满足 PromQL 定义的规则,则会产生一条告警,而告警的后续处理流程则由 AlertManager 进行管理。
Prometheus 同其他时序数据库相比有一个非常典型的特性:它是主动从各 TARGET(客户端)上拉取(pull)数据,而非等待监控端的推送(push)
pull 的优势在于,集中控制有利于配置集在 Prometheus server 上完成,包括指标和采集的速率。
Prometheus 的目标是收集在 TARGET 上预先完成聚合的聚合性数据,而非事件性驱动的存储系统。
(服务的发起方,拉模型)
Prometheus 组件中的核心部分,收集和存储时间序列数据,提供 PromQL 查询语言的支持。内置的 Express Browser UI,通过这个 UI 可以直接通过 PromQL 实现数据的查询以及可视化。
将监控数据采集的端点通过 HTTP 服务的形式暴露给 Prometheus Server,Prometheus Server 通过访问该 Exporter 提供的 Endpoint 端点,即可以获取到需要采集的监控数据。
主要是实现接收由 Client push 过来的指标数据,在指定的时间间隔,由主程序来抓取。由于 Prometheus 数据采集基于 Pull 模型进行设计,因此在网络环境的配置上必须要让 Prometheus Server 能够直接与 Exporter 进行通信。当这种网络需求无法直接满足时,就可以利用 PushGateway 来进行中转。可以通过 PushGateway 将内部网络的监控数据主动 Push 到 Gateway 当中。而 Prometheus Server 则可以采用同样 Pull 的方式从 PushGateway 中获取到监控数据。
管理告警,主要是负责实现报警功能。在 Prometheus Server 中支持基于 PromQL 创建告警规则,如果满足 PromQL 定义的规则,则会产生一条告警,而告警的后续处理流程则由 AlertManager 进行管理。在 AlertManager 中我们可以与邮件,Slack 等等内置的通知方式进行集成,也可以通过 Webhook 自定义告警处理方式。AlertManager 即 Prometheus 体系中的告警处理中心。
Prometheus 整个工作流程大概是这样的:
Exporters 相当于 targets 的代理层,例如 mysql 本身不能提供监控服务, prometheus 通过 exporters 来 pull 到想要的 metrics
实例可以简单的理解为就是一个 target,网络客户端,实际上在多核心的服务器上,一个 instance 就代表一个 cpu 核心;
node_cpu_seconds_total{cpu="0",instance="192.168.0.61:9100",job="prometheus",mode="idle"} 12880841.65
node_cpu_seconds_total{cpu="1",instance="192.168.0.61:9100",job="prometheus",mode="idle"} 12808726.3
node_cpu_seconds_total{cpu="2",instance="192.168.0.61:9100",job="prometheus",mode="idle"} 12857636.16
node_cpu_seconds_total{cpu="3",instance="192.168.0.61:9100",job="prometheus",mode="idle"} 12781748.83
通常,具有类似功能的 Instance 的集合称为一个 job。例如一个 nginx 集群中所有的 nginx 进程。
抓取到异常值后,Prometheus 支持通过报警(alert)机制向用户发送反馈,以便用户能够及时采取应对措施。
Prometheus server 仅负责生成报警指示,具体的报警行为由另一个独立的应用程序 AlertManager 负责。
https://github.com/prometheus/prometheus
- relabel:根据配置文件中的 relabel 对指标的 label 重置处理
- pool:字节池
- timestamp:时间戳
- rulefmt:rule 格式的验证
- runtime:获取运行时信息在程序启动时打印
main 函数解析命令行参数,并读取配置文件信息(由 --config.file 参数提供)。Prometheus 特别区分了命令行参数配置(flag-based configuration)和文件配置(file-based configuration)。前者用于简单的设置,并且不支持热更新,修改需要启停 Prometheus Server 一次;后者支持热更新。
main 函数会完成初始化、启动所有的组件。这些组件包括:Termination Handler、Service Discovery Manager、Web Handler 等。各组件是独立的 Go Routine 在运行,之间又通过各种方式相互协调,包括使用 Synchronization Channel、引用对象 Reference、传递 Context
go mod tidy // 配置 go.mod
直接启动会报错,因为没有 prometheus.yml 配置文件,初始配置文件默认放在 documentation/remote_storage 目录下,将其拷贝至根目录下。默认监控的是 prometheus 本身,可以通过配置文件自定义监控。
npm install
npm start
# 启动受监控应用
# 启动 prometheus 服务
prometheus 2.14.0 版本后,UI 界面渲染通过 React app 方式来生成,go build 直接编译生成的二进制文件缺少 react app 相关的依赖包。可以通过 http://localhost:3000 转发代理的方式访问 http://localhost:9090 打开 promethues 原生 UI 界面。
云原生、容器场景下按需的资源使用方式对于监控系统而言就意味着没有了一个固定的监控目标,所有的监控对象(基础设施、应用、服务)都在动态的变化,这对基于 Push 模式传统监控软件带来挑战。
对于 Prometheus 这一类基于 Pull 模式的监控系统,显然也无法继续使用的 static_configs 的方式静态的定义监控目标。而对于 Prometheus 而言其解决方案就是引入一个中间的代理人(服务注册中心),这个代理人掌握着当前所有监控目标的访问信息,Prometheus 只需要向这个代理人询问有哪些监控目标即可, 这种模式被称为服务发现。
通过服务发现的方式,管理员可以在不重启 Prometheus 服务的情况下动态的发现需要监控的 Target 实例信息。Prometheus 每个被控目标暴露一个 endpoint 供 server 抓取,要获知这些 endpoint 有多种方式,最简单的是在配置文件里静态配置,还有基于 k8s、consul、dns 等多种方式,基于文件的服务发现是比较灵活普遍的一种方式。
后期,随着我们的用户数渐渐变多,单台服务器的压力扛不住的时候,我们就要用到负载均衡技术,增加多台服务器来抗压,后端的数据库也可以用主从的方式来增加并发量(web2.0),模型如下图所示:
那么啥时候才需要用到服务注册和发现呢?答案是分布式微服务时代。
在微服务时代,我们所有的服务都被劲量拆分成最小的粒度,原先所有的服务都在混在 1 个 server 里,现在就被按照功能或者对象拆分成 N 个服务模块,这样做的好处是深度解耦,1 个模块只负责自己的事情就好,能够实现快速的迭代更新。坏处就是服务的管理和控制变得异常的复杂和繁琐,人工维护难度变大。还有排查问题和性能变差(服务调用时的网络开销)
各个微服务相互独立,每个微服务,由多台机器或者单机器不同的实例组成,各个微服务之间错综复杂的相互关联调用。
比如上面的图中,我们将原先 1 个 server 的服务进行了拆分,拆出了 User 服务,Order 服务,Goods 服务,Search 服务等等。
在不用服务注册时,我们只能手动更改配置
每一个服务对应的机器或者实例在启动运行的时候,都去向名字服务集群注册自己,比如图中,User 服务有 6 个 docker 实例,那么每个 docker 实例,启动后,都去把自己的信息注册到名字服务模块上去
//给 User 服务申请 1 个独有的专属名字
UserNameServer = NameServer->apply('User');
//User 服务下的 6 台 docker 实例启动后,都去注册自己
UserServer1 = {ip: 192.178.1.1, port: 3445}
UserNameServer->register(UserServer1);
把每个服务的机器实例注册到了名字服务器上之后,接下来,我们如何去发现我们需要调用的服务的信息呢?这就是服务发现了。
//服务发现,获取 User 服务的列表
list = NameServer->getAllServer('User');
//list 的内容
[
{
"ip": "192.178.1.1",
"port": 3445
},
{
"ip": "192.178.1.2",
"port": 3445
},
......
{
"ip": "192.178.1.6",
"port": 3445
}
]
目前市面上已经有了服务注册和服务发现的解决方案,代表作是:zookeeper
和 consul
以及 etcd
,他们功能强大,安全稳定,高并发高可用,强一致性,目前市面上都是用这几个来实现自己的服务注册和发现的。
其中,consul 是后起之秀,源于它安装简单,功能强大,提供健康检查,web 管理后台,支持多数据中心,暴露了方便的 HTTP 接口,使得它被更多的人所使用,唯一不足的是它不支持 sub/pub 订阅机制,所以服务发现,得使用者自己去 HTTP 轮训发现变更。
1、部署集群。首先需要有一个正常的 Consul 集群,有 Server,有 Leader。这里在服务器 Server1、Server2、Server3 上分别部署了 Consul Server。
2、选举 Leader 节点。假设他们选举了 Server2 上的 Consul Server 节点为 Leader。这些服务器上最好只部署 Consul 程序,以尽量维护 Consul Server 的稳定。
3、注册服务。然后在服务器 Server4 和 Server5 上通过 Consul Client 分别注册 Service A、B、C,这里每个 Service 分别部署在了两个服务器上,这样可以避免 Service 的单点问题。服务注册到 Consul 可以通过 HTTP API(8500 端口)的方式,也可以通过 Consul 配置文件的方式。
4、Consul client 转发注册消息。Consul Client 可以认为是无状态的,它将注册信息通过 RPC 转发到 Consul Server,服务信息保存在 Server 的各个节点中,并且通过 Raft 实现了强一致性。
5、服务发起通信请求。最后在服务器 Server6 中 Program D 需要访问 Service B,这时候 Program D 首先访问本机 Consul Client 提供的 HTTP API,本机 Client 会将请求转发到 Consul Server。
6、Consul Server 查询到 Service B 当前的信息返回,最终 Program D 拿到了 Service B 的所有部署的 IP 和端口,然后就可以选择 Service B 的其中一个部署并向其发起请求了。
//服务注册
NameServer->register(newServer);
//服务发现
NameServer->getAllServer();
scrape 组件只包含三个文件 manager.go、scrape.go、target.go,提供了单实例采集百万指标的能力
官方仓库地址: https://github.com/prometheus/mysqld_exporter
mysqld_exporter 主要由三个部分组成:
Prometheus MySQL Exporter源码阅读与分析 - Jiajun的编程随想
收集器并发收集所有指标,每个具体指标都会实现 Scraper 这个接口:
// Scraper is minimal interface that let's you add new prometheus metrics to mysqld_exporter.
type Scraper interface {
// Name of the Scraper. Should be unique.
Name() string
// Help describes the role of the Scraper.
// Example: "Collect from SHOW ENGINE INNODB STATUS"
Help() string
// Version of MySQL from which scraper is available.
Version() float64
// Scrape collects data from database connection and sends it over channel as prometheus metric.
Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error
}