bvar是brpc使用的多线程环境下的计数器类库,作为一个完善的rpc框架,在实际生产环境中统计诸如qps、连接数等各种数值是必须的,也是服务监控的很重要的一部分,但在多线程环境下,计数器被多线程访问,容易出现cache bouncing影响性能,bvar最核心的思想就是利用thread local变量来减少cache bouncing,本质上是将写的竞争转移到了读,但在诸如监控这种场景下,通常读是远远小于写的,因此这种转移的正向效果是显著的。
bvar的官方文档对于bvar的使用介绍已经很详细,我这边打算接下来用几篇文章从源码层面入手介绍这个优秀的数值统计类库,学习下优秀的设计思想。本篇主要会根据源码先整体分析bvar的组织和实现方式,后续将会深入各个类的实现。
bvar内部的核心类,从功能作用上来看大致可以分为三种:
1.1 提供基础功能的组件:
这些基础类属于bvar机制的基础,是外部直接调用功能的基础支撑。
(1)variable:variable是所有bvar的基类,主要提供全局注册,列举,查询等功能。如果用默认参数新建bvar,并不会注册到任何全局结构,此时bvar纯粹是一个更快的计数器。把bvar注册到全局表(varmap)中的行为叫”曝光“(expose),可通过显示调用expose函数或者通过带名字的构造参数实现曝光,曝光后就可以通过ip:port/vars查看,也就是说曝光后的bvar可以通过内置服务直接查看,不需要额外的监控服务。
(2)agentgroup:用来实际管理统计值数据存储的类,也就是负责tls空间的分配。
(3)agentcombiner:前面说过了bvar的核心思想是利用tls来减少写的竞争,那么在读的时候就少不了将tls数据combine的过程,这个类就提供了这个功能,里面提供了方法来对各个thread的tls数据进行聚合得到global值,统计值类型的bvar创建的时候会同时创建对应的combiner。combiner依赖agentgroup来满足统计值类型的bvar对tls数据的写入和从各tls数据汇总成global值。
(4)sampler:字面意思就是采样器,用于定时采样,对于统计值类型的bvar,初始化的时候是没有也不需要sampler的,被window或者persecond追踪后,才有定时采样的需求,才会新建对应的sampler并进行定时调度。
1.2 统计值类型:
bvar面向用户的核心部分,各种统计需要直接操作的类型,有以下几类:
(1)reducer:reducer继承自variable,用二元运算符把多个值合并为一个值,运算符需满足结合律,交换律,没有副作用。比如常用的累加计数器、最大值、最小值adder,miner,maxer都属于reducer。
(2)intrecorder:用于计算平均值。
(3)status:记录和显示很少修改或者定期修改的值。
(4)passivestatus:和status类似,之所以叫passive是因为实际需要用时才调用回调函数来得到。
(5)latencyrecorder:专用于计算latency和qps的计数器。只需输入latency,就能获得latency / max_latency / qps / count。和其他基本统计值类型不一样,latencyrecorder没有继承自由variable,而是由多个其他类型的bvar组合实现。
(6)gflag:读取并曝光关心的gflags用于监控。
1.3 时间窗口类型:
除了直接读写统计值类型,还有很重要的一类就是时间窗口,这也是我们监控服务的常见需求,此类bvar不能独立存在,必须依赖已有的计数器,也就是追踪采集已有的计数器,每隔一秒自动采集。时间窗口类型有window和persecond。
(1)window:获得之前一段时间内的统计值。具体到实现上就是后台线程定期去采集值。
(2)persecond :获得之前一段时间内平均每秒的统计值,除了返回值会除以时间窗口之外,和window基本相同。
各核心类关系可以大概总结为下图:
由于画图的局限性,以上只是大概,比如不是所有的值类型的都用到了sampler,reducer等作为window和persecond的依赖也没有体现。
为了便于理解,这里贴几个官方文档的使用示例:
(1)adder的使用
bvar::adder value;
value << 1 << 2 << 3 << -4;
check_eq(2, value.get_value());
(2)maxer加上window的使用
bvar::maxer max_value;
value << 1 << 2 << 3 << -4;
check_eq(3, value.get_value());
bvar::window > max_value_per_second(&max_value, 1);
(3)passivestatus的使用
static void get_username(std::ostream& os, void*) {
char buf[32];
if (getlogin_r(buf, sizeof(buf)) == 0) {
buf[sizeof(buf)-1] = '\0';
os << buf;
} else {
os << "unknown";
}
}
passivestatus g_username("process_username", get_username, null);
这篇算是一个引子,介绍了了下bvar的基本功能和整体架构组织,后续将会对bvar比较核心的各机制分别进行源码层面的解析。
参考: