Skip to content

Latest commit

 

History

History
236 lines (171 loc) · 11.3 KB

docs-09-metrics.md

File metadata and controls

236 lines (171 loc) · 11.3 KB

English version

09 - 上报Metrics

Metrics(指标)是常用的监控需求,SRPC支持产生与统计Metrics,并通过多种途径上报,其中包括上报到PrometheusOpenTelemetry

秉承着Workflow的风格,所有的上报都是异步任务推送或拉取模式,对RPC的请求和服务不会产生任何性能影响。

本文档会介绍Metrics的概念、结合tutorial-16对接口进行讲解、上报Prometheus的特点、上报OpenTelemetry的特点、以及介绍使用了thread local进行性能提速的Var模块。

1. Metrics概念介绍

Prometheus的数据类型可以参考官方文档:Concepts - MetricsType of Metrics

OpenTelemetry的可以参考数据规范(data specification)以及Metrics的datamodel.md

其中四种基本指标的概念都是一致的,因此SRPC对这四种指标进行了最基本的支持,可分别对应到Prometheus和OpenTelemetry的上报数据中。参考下表:

指标类型 Prometheus OpenTelemetry SRPC中的创建接口 SRPC中的常用操作
单个数值 Gauge Gauge create_gauge(name, help); void increase(); void decrease();
计数器 Counter Sum create_counter(name, help); GaugeVar *add(labels);
直方图 Histogram Histogram create_histogram(name, help, buckets); void observe(data);
采样 Summary Summary create_summary(name, help, quantiles); void observe(data);

四种指标的大致描述:

  1. 单个数值:简单数值的度量,可以简单的增减。
  2. 计数器:累计的度量,可以通过添加若干label去区分不同label下的数值。
  3. 直方图:对观察observer()得到的接口进行分区间累加,因此需要传入buckets告知需要划分的区间;
    比如传入的bucket分桶值是{ 1, 10, 100 }这3个数,则我们可以得到数据分布于{ 0 - 1, 0 - 10, 0 - 100, 0 - +Inf }4个区间的数据累计值,同时也会得到整体的总以及数据个数。
  4. 采样:与直方图类似,但传入的是分位数quantiles,比如{0.5, 0.9}以及它们的精度,主要用于事先不知道数据分布具体数值的场景(相比之下,直方图需要传入具体数值)。
    采样是带有时间窗口的,可以通过接口指定max_age统计窗口时长以及age_bucket内部分桶个数,SRPC中默认的是60秒分5个桶切换时间窗口。 考虑到采样Summary本身的统计复杂性,一般来说建议优先使用直方图Histogram

为了方便用户使用,目前SRPC里这四种指标都使用double类型进行统计。

2. 用法示例

我们结合tutorial-16-server_with_metrics.cc看看基本用法,本示例虽然是server,但client中的用法也是一样的。

(1) 创建插件

我们选择Prometheus作为我们的上报对象,因此需要使用RPCMetricsPull插件。

#include "srpc/rpc_metrics_filter.h" // Metrics插件所在的头文件

int main()
{
    SRPCServer server;
    ExampleServiceImpl impl;
    RPCMetricsPull filter; // 创建一个插件

    filter.init(8080); // 配合Prometheus中填好的收集数据的端口

(2) 添加指标

这个RPCMetricsPull插件本身自带了统计部分常用指标,包括整体请求个数统计、按照service和method作为label的不同维度的请求个数统计以及请求耗时的分位数统计等。

用户可以自行增加想要统计的值,这里我们增加一个用于统计请求大小的直方图histogram,名字为"echo_request_size",上报时的指标描述信息是"Echo request size",数据bucket划分是 { 1, 10, 100 } 。

    filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100});

说明:

添加指标的时机
指标是可以随时添加的,即使server/client跑起来之后也可以。但必须在操作这个指标之前添加,否则获取指标的时候会获取到空指针,无法进行统计操作。

指标的名字
指标的名字是全局唯一的(无论是四种基本类型中的哪种),且只能包含大小写字母和下划线,即a-z, A-Z, _ 。如果使用已经存在的名字创建指标,则会创建失败并返回NULL。
一旦创建成功,我们之后都会使用这同一个名字去操作这个指标。

创建其他指标的接口
可以查看刚才include的头文件rpc_metrics_filter.h中的:class RPCMetricsFilter

(3) 把插件添加到server/client中

由于我们需要操作指标时,是需要调用这个插件上的接口的,因此我们在service中保留一下这个指针。这只是示例程序的用法,用户可以使用自己习惯的方式:

class ExampleServiceImpl : public Example::Service
{
public:
	void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override;
	void set_filter(RPCMetricsPull *filter) { this->filter = filter; }

private:
	RPCMetricsPull *filter; // 保留了插件的指针,并实现接口设置进去,这并非SRPC框架的接口
};

main函数中继续这样写:

int main()
{
    ...
    impl.set_filter(&filter); // 设置到我们刚才为service留的接口中

    server.add_filter(&filter); // client也一样可以调用add_filter()
    server.add_service(&impl);

    filter.deinit();
    return 0;
}

(4) 操作指标

我们在每次收到请求的时候,都把EchoRequest的大小统计到刚才创建的直方图指标上:

class ExampleServiceImpl : public Example::Service
{
public:
	void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override
	{
		resp->set_message("Hi back");
		this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong());
	}

可以看到,我们通过filter上的histogram()接口,就可以带着刚才的名字去到指标的指针,并且通过observe()填入数据大小。

四种基本类型的获取接口如下:

class RpcMetricsFilter : public RPCFilter
{
    GaugeVar *gauge(const std::string& name);                                   
    CounterVar *counter(const std::string& name);                               
    HistogramVar *histogram(const std::string& name);                           
    SummaryVar *summary(const std::string& name);

如果找到,会返回四种基本指标类型的指针,可以如示例进行下一步操作比如histogram的统计接口observe(),注意:不存在这个名字则会返回空指针,因此要保证我们拿到的变量一定是成功创建过的

四种类型常用的操作接口如上文表格所示,具体可以参考rpc_var.h

值得说明一下Counter类型的接口:

class CounterVar : public RPCVar
{
    GaugeVar *add(const std::map<std::string, std::string>& labels);

接口可以对Counter指标添加某个维度的Gauge值,各维度的统计是分开的,且labels为一个map,即可以通过多组label指定一个维度,比如:

    filter->counter("service_method_count")->add({"service", "Example"}, {"method", "Echo"}})->increase();

就可以获得一个针对{service="Example",method="Echo"}统计出来的数值。

(5) 自动上报

SRPC的插件都是自动上报的,因此无需用户调用任何接口。我们尝试调用client发送请求产生一些统计数据,然后看看上报出来的数据是什么。

./srpc_pb_client 

message: "Hi back"

message: "Hi back"

由于Prometheus是使用Pull模式拉取,即会通过我们注册到Prometheus的端口和/metrics进行拉取,也就是我们刚才初始化上报插件需要配对上端口的原因。通过与Prometheus相同的方式,我们可以本地访问一下,会看到这样的一些数据:

curl localhost:8080/metrics

# HELP total_request_count total request count
# TYPE total_request_count gauge
total_request_count 2.000000
# HELP total_request_method request method statistics
# TYPE total_request_method counter
total_request_method{method="Echo",service="Example"} 2.000000
# HELP total_request_latency request latency nano seconds
# TYPE total_request_latency summary
total_request_latency{quantile="0.500000"} 645078.500000
total_request_latency{quantile="0.900000"} 645078.500000
total_request_latency_sum 1290157.000000
total_request_latency_count 2
# HELP echo_request_size Echo request size
# TYPE echo_request_size histogram
echo_request_size_bucket{le="1.000000"}0
echo_request_size_bucket{le="10.000000"}0
echo_request_size_bucket{le="100.000000"}2
echo_request_size_bucket{le="+Inf"} 2
echo_request_size_sum 40.000000
echo_request_size_count 2

可以看到,我们针对tutorial-02的client产生的两个请求,分别获得的四种基本指标的统计数据。其中histogram是我们创建的,而gauge、counter、summary都是插件中自带的。插件中默认统计的数据还会陆续添加,以方便开发者。

3. 上报Prometheus的特点

上报Prometheus主要特点刚才已经大概描述过:

  1. 使用Pull模式,定期被收集;
  2. 需要指定我们被收集数据的端口;
  3. 通过/metrics返回具体数据内容;
  4. 数据内容为Prometheus所约定的string格式;

界面参考如下: prometheus_img

4. 上报OpenTelemetry的特点

上报OpenTelemetry的主要特点:

  1. 使用推送模式,定期发送http请求;
  2. 插件需要填入要上报的URL;
  3. 内部默认通过/v1/metrics上报数据;
  4. 数据内容为OpenTelemetry所约定的protobuf

基本接口参考:

class RPCMetricsOTel : public RPCMetricsFilter
{
public:
    RPCMetricsOTel(const std::string& url);
    RPCMetricsOTel(const std::string& url, unsigned int redirect_max,           
                   unsigned int retry_max, size_t report_threshold,
                   size_t report_interval); 

用户可以指定累计多少个请求上报一次或者累计多久上报一次,默认为累计100个请求或1秒上报一次。

5. Var模块

上面可以看到,我们每个请求都会对全局唯一名称指定的变量进行操作,那么多线程调用时,对复杂的指标类型(比如直方图或者采样)操作会成为性能瓶颈吗?

答案是不会的,因为通过filter获取对应指标的接口是thread local的。

SRPC内部引入了线程安全的var结构,每次获取var时调用的接口,拿到的都是thread local的指标指针,每次统计也都是分别收集到本线程,因此多线程情况下的统计不会造成对全局的争抢。而上报是异步上报的,在上报被触发的时候,全局会通过expose()挨个把每个线程中相应的指标reduce()到一起,最后通过具体模块需要的格式进行上报。