菜鸟笔记
提升您的技术认知

brpc 笔记

bthread(一) 前言bthread(二) 线程模型及bthreadbthread(三) bthread数据结构bthread(四) bthread用户接口和代码执行路径bthread(五) 无锁队列rq的代码实现bthread(六) 小结brpc的精华bthread源码剖析brpc介绍、编译与使用brpc源码解析(一)—— rpc服务添加以及服务器启动主要过程brpc源码解析(二)—— brpc收到请求的处理过程brpc源码解析(三)—— 请求其他服务器以及往socket写数据的机制brpc源码解析(四)—— Bthread机制brpc源码解析(五)—— 基础类resource pool详解brpc源码解析(六)—— 基础类socket详解brpc源码解析(七)—— worker基于ParkingLot的bthread调度brpc源码解析(八)—— 基础类EventDispatcher详解brpc源码解析(九)—— 基础类WorkStealingQueue详解brpc源码解析(十)—— 核心组件bvar详解(1)简介和整体架构brpc源码解析(十一)—— Reducer类和Adder类解析brpc源码解析(十二)—— 核心组件bvar详解 AgentGroup类详解brpc源码解析(十三)—— 核心组件bvar详解(4)combiner详解brpc源码解析(十四)—— 核心组件bvar详解 sampler详解brpc源码解析(十五)—— bthread栈创建和切换详解brpc源码解析(十六)—— 作为client的连接建立和处理详解brpc源码解析(十七)—— bthread上的类futex同步组件butex详解brpc源码解析(十八)—— MPSC队列ExecutionQueue详解brpc源码解析(十九)—— 双buffer数据结构DoublyBufferedData详解brpc源码解析(二十)—— 用于访问下游的Channel类详解

brpc源码解析(十三)—— 核心组件bvar详解(4)combiner详解

阅读 : 389

前面介绍了agent,本篇我们来聊聊combiner,这也是bvar的一个特别重要的基础组件,整个combiner相关的都在combiner.h源文件里,由GlobalValue、ElementContainer、AgentCombiner三大类构成,AgentCombiner里还有个很重要的子struct Agent,这也是上次介绍过的agentgroup里保存的数据类型。下面就分别介绍下这几个类的作用和实现。

1. GlobalValue

GlobalValue,全局变量辅助类,保存了agent和combiner的指针,主要是提供了一些同步机制,用于把agent的值往combiner的全局结果里合并。

2. ElementContainer

正如字面含义,ElementContainer是实际元素的容器,也就是对实际类型的封装,之所以需要这么一个封装是因为需要保证多线程操作情况下的同步正确,bvar在本地的tls存储除了会被当前线程使用,还会在聚合读取等场景下被其他线程使用,ElementContainer是一个模板类,而且进行了特化,通用模板定义如下:

可以看到,提供了读、写、交换、编辑(根据传入的op和value修改本地value),并且使用了锁来保证同步。模板参数有两个,分别是元素类型T和使能器Enabler,这个Enabler比较有意思,也是特化版本所用到的重要部分。
除了这个通用版本,还有一个针对原子类型变量的特化版本,加锁的开销相对是比较大的,对于int float等本身就带原子性的类型,通过原子操作就能保证同步,bvar作为一个重视性能的计数器,自然不会放过这种优化点,于是就有了下面这种特化:

这里将第二个模板参数进行了特化,使用了typename butil::enable_if::type作为参数,这里用到了enable_if和SFINAE,有兴趣可以转门找相关文档学习,简单来说就是如果条件满足,这个类型就是定义的,否则就是未定义的,具体到这里,就是如果T是原子类型,那么第二个参数是有定义的,从而能够匹配上这个特化模板,否则只能匹配前面那个更通用的模板。C++里模板的匹配规则是最特化匹配,会优先选择特化程度最高的。

这个特化模板里都是通过原子操作来实现各种操作的,由于不需要额外的数据同步,因此所有的memory_order都是relaxed。

这两个模板都实现了load store exchange和modify,通用模板里有一个独有函数merge,而原子变量模板有一个compare_exchange_weak是独有的,这和实际用途有关系。

3. AgentCombiner

AgentCombiner也是整个combine的主体,是整个combiner最核心的部分

3.1 定义

AgentCombiner基本定义如下:

AgentCombiner同样是一个模板,模板参数为结果类型,元素类型和二元操作符, ResultTp和ElementTp分别用于聚合结果和单个tls元素,对于adder这种普通reducer类型的bvar,二者是完全一样的:


对于其他复杂一点的类型,比如Percentile,这二者是有区别的:

三个typedef是为了简化后面的一些定义,申明GlobalValue为友元类是因为GlobalValue要访问combiner的私有变量。

3.2 内部结构体Agent

下面我们先看下AgentCombiner一个重要的子结构体:

这也是实际存储在AgentGroup里的类型,刚开始看到struct Agent : public butil::LinkNode这种写法的时候有点懵逼,学习了下才知道原来这是奇异递归模板模式(Curiously Recurring Template Pattern),简称CRTP,这里这么使用的好处是可以提高运行效率,具体的可以找相关资料学习,这里不多说。

butil::Linknode是一种双向链表节点类型,提供了向前取向后取向前插入向后插入取值赋值等等基本操作,这里Agent通过继承的方式基于链表来进行agent的数据的保存和处理,

combiner指针指向当前agent所属bvar的combiner,初始值为Null,可以据此判断此agent是否已经分配。element为实际保存数据的变量。

提供了reset函数对combiner和 element进行重置,merge_global函数用于将当前agent里的tls值merge到combiner的global result里,部分bvar类型需要用到,比如percentile。

析构函数里调用combiner的commit_and_erase来提交现有数据并去除该agent(从combiber维护的agent链表里移除)。

3.3 成员变量

成员变量如下:

(1)_id:由agentgroup分配的对应当前bvar变量的一个id,用于去tls block里寻址.
(2)_op:此bvar的操作符。
(3)_lock:用于操作全局汇总结果的锁。
(4)_global_result:用来保存汇总结果。
(5)_result_identity和_element_identity:比较特殊,本质上分别是ResultTp和ElementTp类型的初始化过后不经其他修改的变量,被各种reset性质的函数用来清空变量。
(6)_agents保存了当前bvar所有agent的链表。

3.3 构造函数和析构函数

(1)构造函数:

给各个成员变量赋值,_id的赋值调用的是AgentGroup的create_new_agent()函数,分配了一个当前bvar独有的id。

(2)析构函数:

清除agent数据然后归还id。

3.3 主要功能函数

(1)combine_agents()

汇聚所有agent的值返回,这也是外部最常调用的一个函数,比如reducer的get_value调用的就是这个函数,遍历agent链表汇总所有agent的tls值和_global_result保存的值返回。

(2)reset_all_agents()

重置所有的agent,包括combiner内部的_global_result,返回汇总值,和combine_agents很类似,区别就是会额外重置agent值和 _global_result值。

(3)clear_all_agents()

清除所有agents,combiner的析构函数会调用次函数,因为agent有可能被重用,所以对每个agent都要reset一下

(4)commit_and_erase(Agent *agent)

提交tls值并且移除agent,Agent的析构函数会调用这个,也就是某个线程退出的时候会调用这个函数实现提交完数据后将自己的agent从bvar的agent list里移除。

(5)commit_and_clear

提交tls值并且利用_element_identity将tls置为初值,有些bvar需要用到这个功能。

(6)get_or_create_tls_agent()

这个函数前面的文章提到过,获取或者创建本线程对应的agent,因为需要尽可能快,所以首先调用更快的AgentGroup::get_tls_agent,如果是null才去调用AgentGroup::get_or_create_tls_agent,由于只有线程第一次访问没有agent的时候才会是null,所以大部分情况都能比较快地得到agent,中间判断如果agent->combiner非null,说明是先前建立的agent,直接返回即可,否则需要把当前combiner赋给它并且将agent挂到_agents链表里面。

4. 总结

在bvar里,combiner的角色无疑是非常重要的,它负责协调各个agent,提供汇总等针对各个agent的操作,直接和AgentGroup交互,给各个特定线程提供agent。同时根据源码也能看出为了追求高性能确实下了很多功夫。