先回顾下DefaultRouteManager#route的实现,在pigeon中,会通过调用这个方法,获取唯一的服务提供方连接实例,来完成RPC远程调用:
public Client route(List<Client> clientList, InvokerConfig<?> invokerConfig, InvocationRequest request) {
if (logger.isDebugEnabled()) {
for (Client client : clientList) {
if (client != null) {
logger.debug("available service provider:\t" + client.getAddress());
}
}
}
// 获取所有可能的连接
List<Client> availableClients = getAvailableClients(clientList, invokerConfig, request);
// 通过客户端负载均衡策略选取合适的唯一客户端连接
Client selectedClient = select(availableClients, invokerConfig, request);
// 如果不是活跃的连接,重新选取
while (!selectedClient.isActive()) {
logger.info("[route] remove client:" + selectedClient);
// 去除不活跃的
availableClients.remove(selectedClient);
if (availableClients.isEmpty()) {
// 为空退出,避免死循环
break;
}
selectedClient = select(availableClients, invokerConfig, request);
}
// 没有获取到满足要求的活跃的连接
if (!selectedClient.isActive()) {
throw new ServiceUnavailableException("no available server exists for service[" + invokerConfig + "], env:"
+ ConfigManagerLoader.getConfigManager().getEnv());
}
return selectedClient;
}
在经过路由策略获取到所有可能的连接,下一步就是通过客户端负载均衡策略选取合适的唯一客户端这一步在DefaultRouteManager#select中完成。
在DefaultRouteManager#select应用真正的负载均衡策略之前,会先做两步配置:
- 方法级别配置,如配置:
pigeon.loadbalance.dynamictype={"com.dianping.arch.test.benchmark.service.EchoService#echo" : "random"}
则会对特定服务方法优先使用random策略- 服务级别配置,通过xml、api等方式配置invokerConfig的loadbalance属性
- 应用默认配置,如指定pigeon.loadbalance.defaulttype 来配置默认负载均衡策略
下面来看代码的具体实现:
private Client select(List<Client> availableClients, InvokerConfig<?> invokerConfig, InvocationRequest request) {
LoadBalance loadBalance = null;
if (loadBalance == null) {
// 获取具体的负载均衡实例
loadBalance = LoadBalanceManager.getLoadBalance(invokerConfig, request.getCallType());
}
if (loadBalance == null) {
// 如果没有配置,默认为WeightedAutoawareLoadBalance
loadBalance = WeightedAutoawareLoadBalance.instance;
if (request.getCallType() == Constants.CALLTYPE_NOREPLY) {
// 如果调用无需返回,则用随机负载均衡策略
loadBalance = RandomLoadBalance.instance;
}
}
// 判断是否有针对特定服务方法级别动态配置的负载均衡策略配置,看下一个函数代码实现
LoadBalance dynamicLoadBalance = getDynamicLoadBalance(request);
if (dynamicLoadBalance != null) {
loadBalance = dynamicLoadBalance;
}
// 判断是否有优先的服务地址配置,如果有,先过滤指定地址的服务提供方连接实例
List<Client> preferClients = null;
if (enablePreferAddresses) {
if (availableClients != null && availableClients.size() > 1 && !CollectionUtils.isEmpty(preferAddresses)) {
preferClients = new ArrayList<Client>();
for (String addr : preferAddresses) {
for (Client client : availableClients) {
if (client.getHost().startsWith(addr)) {
preferClients.add(client);
}
}
// 找到优先的立即结束
if (preferClients.size() > 0) {
break;
}
}
}
}
if (preferClients == null || preferClients.size() == 0) {
preferClients = availableClients;
}
// 应用具体的负载均衡策略
Client selectedClient = loadBalance.select(preferClients, invokerConfig, request);
checkClientNotNull(selectedClient, invokerConfig);
return selectedClient;
}
在最后,通过调用LoadBalance#select方法,来应用负载均衡策略找出唯一合适的服务提供方连接。
在pigeon中,提供了4种负载均衡策略,如果用户有自定义的负载均衡策略,可以实现LoadBalance接口,并实现内部的select方法,在入参的连接实例列表种选择返回唯一的连接实例,而后调用LoadBalanceManager#register方法将自定义负载均衡策略注册到负载均衡管理器中。
在pigeon默认实现中,有4种负载均衡策略,包括:
AbstractLoadBalance是LoadBanlance接口的抽象实现,定义了所有负载均衡器的公共逻辑部分。具体实现包括两个方法:
先跳过权重的计算原理,看看在获取到每个示例连接的权重后,具体的负载均衡策略如何实现doSelect完成负载均衡调用
对所有连接实例的权重进行累加,生成区间在0到权重和的随机数,按权重对连接实例分段,看随机数落在哪个连接实例权重区间,返回对应的连接实例。如有client1=2,client2=3,client3=3,则权重和为8,且按权重分段有client1=[0,2),client2=[2,5),cleint3=[5,8)。生成区间[0,8)的随机数n,看随机数落在哪个连接实例权重区间。
具体源码实现:
public Client doSelect(List<Client> clients, InvokerConfig<?> invokerConfig, InvocationRequest request, int[] weights) {
assert (clients != null && clients.size() >= 1);
// 连接实例唯一直接返回
if (clients.size() == 1) {
return clients.get(0);
}
int clientSize = clients.size();
// 权重和
int totalWeight = 0;
// 是否全部权重相同
boolean weightAllSame = true;
for (int i = 0; i < clientSize; i++) {
totalWeight += weights[i];
if (weightAllSame && i > 0 && weights[i] != weights[i - 1]) {
// 存在权重不一样的情况
weightAllSame = false;
}
}
if (!weightAllSame) {
// 生成区间在0到权重和的随机数,按权重对连接实例分段,看随机数落在哪个连接实例权重区间,返回对应的连接实例
int weightPoint = random.nextInt(totalWeight);
for (int i = 0; i < clientSize; i++) {
Client client = clients.get(i);
weightPoint -= weights[i];
if (weightPoint < 0) {
return client;
}
}
}
// 权重都相同,随机返回实例列表的一个
Client client = clients.get(random.nextInt(clientSize));
if