上游一般通过命名服务发现所有的下游节点,并通过多种负载均衡方法把流量分配给下游节点。当下游节点出现问题时,它可能会被隔离以提高负载均衡的效率。被隔离的节点会定期被健康检查,成功后重新加入正常节点。理解「命名服务 + 负载均衡 + 健康检查」这套组合,是理解 brpc 客户端流量分配的第一步。
命名服务:NamingService
在 brpc 中,NamingService 用于获得服务名对应的所有节点。一个直观的做法是定期调用一个函数以获取最新的节点列表,但这会带来一定的延时(定期调用的周期一般在若干秒左右),作为通用接口不太合适;特别是当命名服务提供事件通知时(比如 zk),这个特性没有被利用。所以我们反转了控制权:不是我们调用用户函数,而是用户在获得列表后调用我们的接口,对应 NamingServiceActions。当然我们还是得启动进行这一过程的函数,对应 NamingService::RunNamingService。下面以三个实现解释这套方式。
如果用户需要建立这些对象仍然是不够方便的,因为总是需要一些工厂代码根据配置项建立不同的对象。鉴于此,我们把工厂类做进了框架,并且是非常方便的形式:
"protocol://service-name"
e.g.
bns://<node-name> # baidu naming service
file://<file-path> # load addresses from the file
list://addr1,addr2,... # use the addresses separated by comma
http://<url> # Domain Naming Service, aka DNS.
这套方式是可扩展的,实现了新的 NamingService 后在 global.cpp 中依葫芦画瓢注册一下就行了,如下图所示:

看到这些熟悉的字符串格式,容易联想到 ftp://、zk://、galileo:// 等等都是可以支持的。用户在新建 Channel 时传入这类 NamingService 描述,并能把这些描述写在各类配置文件中。
负载均衡:LoadBalancer
brpc 中,LoadBalancer 从多个服务节点中选择一个节点,目前的实现包括 rr、wrr、random、la、c_murmurhash、c_md5、_dynpart 等。
LoadBalancer 最重要的是如何让不同线程中的负载均衡不互斥,解决这个问题的技术是 DoublyBufferedData。
和 NamingService 类似,我们使用字符串来指代一个 load balancer,在 global.cpp 中注册:

健康检查
对于那些无法连接却仍在 NamingService 中的节点,brpc 会定期连接它们,成功后对应的 Socket 将被“复活”,并可能被 LoadBalancer 选择上,这个过程就是健康检查。
注意:被健康检查或在 LoadBalancer 中的节点一定在 NamingService 中。换句话说,只要一个节点不从 NamingService 删除,它要么是正常的(会被 LoadBalancer 选上),要么在做健康检查。
传统的做法是使用一个线程做所有连接的健康检查,brpc 简化了这个过程:为需要的连接动态创建一个 bthread 专门做健康检查(Socket::HealthCheckThread)。这个线程的生命周期被对应连接管理。具体来说,当 Socket 被 SetFailed 后,健康检查线程就可能启动(如果 SocketOptions.health_check_interval 为正数的话)。