Apache/brpc中的组合通道技术解析
概述
在现代分布式系统中,服务间的调用模式变得越来越复杂,经常需要并行发起多个RPC调用或进行分层访问。这种复杂性很容易引入多线程编程中的各种棘手问题,这些问题可能难以察觉、调试和复现。Apache/brpc项目提供的组合通道(Combo Channel)技术正是为了解决这类问题而设计的。
组合通道的必要性
传统实现方式存在以下主要问题:
-
同步与异步模式代码不一致:同步模式通常采用异步发起多个RPC然后分别等待的方式,而异步模式则通常使用回调加引用计数的方式实现。这种不一致性增加了代码维护成本。
-
取消操作难以实现:正确取消单个RPC已经不容易,更不用说组合RPC的取消操作了。但在很多场景下,取消无意义的等待是必要的。
-
组合性差:难以将现有实现封装为更大模式的一部分,代码复用性差。
组合通道通过将多个通道组合成一个更大的通道,封装不同的访问模式,为用户提供了一致的同步、异步和取消操作接口。
ParallelChannel详解
ParallelChannel(简称pchan)会并行地向所有内部子通道发送请求并合并响应。用户可以通过CallMapper修改请求,通过ResponseMerger合并响应。
核心特性
- 支持同步和异步访问
- 发起异步操作后可立即销毁
- 支持取消操作
- 支持超时设置
内部结构
如图所示,ParallelChannel可以包含不同类型的子通道,请求和响应可以各不相同。
添加子通道
int AddChannel(brpc::ChannelBase* sub_channel,
ChannelOwnership ownership,
CallMapper* call_mapper,
ResponseMerger* response_merger);
参数说明:
ownership
:控制ParallelChannel是否拥有子通道call_mapper
:请求映射器response_merger
:响应合并器
CallMapper详解
CallMapper负责将ParallelChannel的RPC转换为子通道的RPC。如果call_mapper为NULL,则子通道的请求就是ParallelChannel的请求。
class CallMapper {
public:
virtual ~CallMapper();
virtual SubCall Map(int channel_index,
int channel_count,
const google::protobuf::MethodDescriptor* method,
const google::protobuf::Message* request,
google::protobuf::Message* response) = 0;
};
特殊返回值:
SubCall::Bad()
:立即终止RPCSubCall::Skip()
:跳过当前子通道
ResponseMerger详解
response_merger负责将所有子通道的响应合并为一个。如果为NULL,则使用默认的response->MergeFrom(*sub_response)
实现。
合并结果可能值:
- MERGED:合并成功
- FAIL:合并失败,计入失败计数
- FAIL_ALL:直接终止整个RPC
获取子通道控制器
const Controller* sub(int index) const;
SelectiveChannel详解
SelectiveChannel(简称schan)使用负载均衡算法选择内部的一个子通道进行访问。它主要用于机器组之间的负载均衡。
核心特性
- 支持同步和异步访问
- 发起异步操作后可立即销毁
- 支持取消操作
- 支持超时设置
使用示例
brpc::SelectiveChannel schan;
brpc::ChannelOptions schan_options;
schan_options.timeout_ms = ...;
if (schan.Init(load_balancer, &schan_options) != 0) {
LOG(ERROR) << "Fail to init SelectiveChannel";
return -1;
}
if (schan.AddChannel(sub_channel, NULL) != 0) {
LOG(ERROR) << "Fail to add sub_channel";
return -1;
}
注意事项:
- 可以随时添加子通道
- SelectiveChannel总是拥有子通道
- 会覆盖子通道的超时设置
应用场景:分流到多个命名服务
当需要将流量分流到多个命名服务时,SelectiveChannel是理想选择:
- 服务机器分布在多个命名服务中
- 机器分组,需要先选择组再选择组内机器
PartitionChannel详解
PartitionChannel是ParallelChannel的特化版本,能够基于命名服务中的标签自动添加子通道。
使用步骤
- 实现PartitionParser:
class MyPartitionParser : public brpc::PartitionParser {
public:
bool ParseFromTag(const std::string& tag, brpc::Partition* out) {
// 解析标签格式如"N/M"
}
};
- 初始化PartitionChannel:
brpc::PartitionChannel channel;
brpc::PartitionChannelOptions options;
options.fail_limit = 1;
if (channel.Init(num_partition_kinds, new MyPartitionParser(),
server_address, load_balancer, &options) != 0) {
LOG(ERROR) << "Fail to init PartitionChannel";
return -1;
}
DynamicPartitionChannel
当需要支持多种分区方法或平滑切换分区方案时,可以使用DynamicPartitionChannel。它会为不同的分区方法创建相应的子PartitionChannel,并根据服务器容量分配流量。
总结
Apache/brpc的组合通道技术提供了一套完整的解决方案来处理复杂的RPC调用模式:
- ParallelChannel适合并行发起多个RPC并合并结果的场景
- SelectiveChannel适合在多个子通道间做负载均衡的场景
- PartitionChannel适合基于标签自动分区的场景
- DynamicPartitionChannel支持多种分区方案和平滑迁移
这些组合通道可以相互嵌套使用,构建出更加复杂的调用模式,同时保持接口的一致性和易用性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考