主要是在消费端往提供端发送数据时,从本地的invoker列表(zookeeper集群下的提供者列表),选择出其中一个Invoker
首选需要确认是哪个集群容错默认是Failover,那么调用FailoverClusterInvoker的父类AbstractClusterInvoker。根据配置获取选择哪个负载均衡LoadBalance
/**
执行
1.获取到集群中对应的所有提供者url
2.获取到负载均衡的方式
3.如果是异步的话,参数加上invocation id
4.执行doInvoke交给对应的容错机制来处理
**/
public Result invoke(final Invocation invocation) throws RpcException {
LoadBalance loadbalance;
//获取到所有的invoker(集群中的所有的提供者url)
List> invokers = list(invocation);
//获取负载均衡
if (invokers != null && invokers.size() > 0) {
//如果是集群,那么就选择第一个url中的参数loadbalance
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).
getExtension(invokers.get(0).getUrl()
.getMethodParameter(
invocation.getMethodName(),"loadbalance", "random"));
} else {
//选择默认random
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class)
.getExtension(Constants."random");
}
//幂等操作:异步操作默认添加invocation id
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
/**
选择哪一个调用者
1. 如果设置了sticky黏性,在第一次选择过提供者后保存下来,以便下次调用,只要提供者没有挂
掉,还是该提供者(保证每次调用的都是同一个提供者)
2. 如果就一个提供者直接返回;有两个提供者,轮询。
3. 使用loadbalance选择invoker.
4. 如果 selected中包含(优先判断) 或者 不可用&&availablecheck=true 则重试.
5. 重试的机制是
先从非select中选,没有的话,再从select中选择
@param availablecheck 如果设置true,在选择的时候先选invoker.available == true
@param selected 已选过的invoker
*/
protected Invoker select(LoadBalance loadbalance, Invocation invocation,
List> invokers, List> selected) {
String methodName = invocation == null ? "" : invocation.getMethodName();
//处理服务的黏性问题,尽可能让客户端总是同一提供者发起调用,
//除非该提供者挂了,再连另一台
boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName,
"sticky", false) ;
{
if ( stickyInvoker != null && !invokers.contains(stickyInvoker) ){
stickyInvoker = null;
}
//如果提供者没有挂掉,那么还是这个提供者
if (sticky && stickyInvoker != null && (selected == null ||
!selected.contains(stickyInvoker))){
if (availablecheck && stickyInvoker.isAvailable()){
return stickyInvoker;
}
}
}
Invoker invoker = doselect(loadbalance, invocation, invokers, selected);
//如果是黏性,那么每次调用者都是使用这个提供者
if (sticky){
stickyInvoker = invoker;
}
return invoker;
}
private Invoker doselect(LoadBalance loadbalance, Invocation invocation,
List> invokers, List> selected) throws RpcException {
//如果只有一个提供者,不是集群,直接返回
if (invokers.size() == 1)
return invokers.get(0);
// 如果只有两个invoker,退化成轮循
if (invokers.size() == 2 && selected != null && selected.size() > 0) {
return selected.get(0) == invokers.get(0) ?
invokers.get(1) : invokers.get(0);
}
//使用负载均衡,来获取到Invoker
Invoker invoker = loadbalance.select(invokers, getUrl(), invocation);
//如果 selected中包含(优先判断) 或者 不可用&&availablecheck=true 则重试.
if( (selected != null && selected.contains(invoker))
||(!invoker.isAvailable() && getUrl()!=null && availablecheck)){
//重新选择invoker
Invoker rinvoker = reselect(loadbalance, invocation, invokers,
selected, availablecheck);
if(rinvoker != null){
invoker = rinvoker;
}else{
//看下第一次选的位置,如果不是最后,选+1位置.
int index = invokers.indexOf(invoker);
//最后在避免碰撞
invoker = index reselect(LoadBalance loadbalance,Invocation invocation,
List> invokers, List> selected ,
boolean availablecheck){
//预先分配一个,这个列表是一定会用到的.
List> reselectInvokers = new ArrayList>(
invokers.size()>1?(invokers.size()-1):invokers.size());
//先从非select中选
if( availablecheck ){
//选isAvailable 的非select
for(Invoker invoker : invokers){
if(invoker.isAvailable()){
if(selected ==null || !selected.contains(invoker)){
reselectInvokers.add(invoker);
}
}
}
}else{ //选全部非select
for(Invoker invoker : invokers){
if(selected ==null || !selected.contains(invoker)){
reselectInvokers.add(invoker);
}
}
}
if(reselectInvokers.size()>0){
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
//最后从select中选可用的.
{
if(selected != null){
for(Invoker invoker : selected){
if((invoker.isAvailable()) //优先选available
&& !reselectInvokers.contains(invoker)){
reselectInvokers.add(invoker);
}
}
}
if(reselectInvokers.size()>0){
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
}
return null;
}
获取集群中的提供者url地址
protected List> list(Invocation invocation) {
List> invokers = directory.list(invocation);
return invokers;
}
根据AbstractDirectory来获取集群的提供者
public abstract class AbstractDirectory implements Directory {
//获取集群中的提供者url
public List> list(Invocation invocation) throws RpcException {
List> invokers = doList(invocation);
List localRouters = this.routers;
if (localRouters != null && localRouters.size() > 0) {
for (Router router: localRouters){
if (router.getUrl() == null || router.getUrl().getParameter(
"runtime", true)) {
invokers = router.route(invokers,
getConsumerUrl(), invocation);
}
}
}
return invokers;
}
}
AbstractDirectory有两个子类RegistryDirectory和StaticDirectory
public class RegistryDirectory extends AbstractDirectory
implements NotifyListener {
/**
获取invoker
从本地缓存中获取对应的invoker列表
当zookeeper上的providers节点发生改变时,会通知消息,那么这时就
会刷新本地的Invoker列表
**/
public List> doList(Invocation invocation) {
if (forbidden) {
throw new RpcException("");
}
List> invokers = null;
// 当zookeeper上的providers节点发生改变时,会通知消息,那么这时就
//会刷新本地的Invoker
Map>> localMethodInvokerMap =
this.methodInvokerMap;
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
//获取方法名称和参数
String methodName = RpcUtils.getMethodName(invocation);
Object[] args = RpcUtils.getArguments(invocation);
if(args != null && args.length > 0 && args[0] != null
&& (args[0] instanceof String || args[0].getClass().isEnum())) {
invokers = localMethodInvokerMap.get(methodName + "."
+ args[0]); // 可根据第一个参数枚举路由
}
if(invokers == null) {
invokers = localMethodInvokerMap.get(methodName);
}
if(invokers == null) {
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
}
if(invokers == null) {
Iterator>> iterator = localMethodInvokerMap.
values().iterator();
if (iterator.hasNext()) {
invokers = iterator.next();
}
}
}
return invokers == null ? new ArrayList>(0) : invokers;
}
}
RpcUtils中,对于异步操作来说,参数加上invocationId的序号
private static final AtomicLong INVOKE_ID = new AtomicLong(0);
/**
* 幂等操作:异步操作默认添加invocation id
* @param url
* @param inv
*/
public static void attachInvocationIdIfAsync(URL url, Invocation inv){
if (isAttachInvocationId(url, inv) && getInvocationId(inv) == null &&
inv instanceof RpcInvocation)
((RpcInvocation)inv).setAttachment("id",
String.valueOf(INVOKE_ID.getAndIncrement()));
}
//判断是否是异步
private static boolean isAttachInvocationId(URL url , Invocation invocation){
String value = url.getMethodParameter(invocation.getMethodName(),
"invocationid.autoattach");
if ( value == null ) {
//异步操作默认添加invocationid
return isAsync(url,invocation) ;
} else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
//设置为添加,则一定添加
return true;
} else {
//value为false时,不添加
return false;
}
}
在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用。可以自行扩展负载均衡策略
AbstractLoadBalance
public abstract class AbstractLoadBalance implements LoadBalance {
/**
如果是一个,直接返回
**/
public Invoker select(List> invokers, URL url,
Invocation invocation) {
if (invokers == null || invokers.size() == 0)
return null;
if (invokers.size() == 1)
return invokers.get(0);
return doSelect(invokers, url, invocation);
}
}
//获取权重
protected int getWeight(Invoker> invoker, Invocation invocation) {
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(),
"weight", 100);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter("timestamp", 0L);
if (timestamp > 0L) {
int uptime = (int) (System.currentTimeMillis() - timestamp);
int warmup = invoker.getUrl().getParameter("warmup", 10 * 60 * 1000);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight;
}
//计算权重
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
int ww = (int) ( (float) uptime / ( (float) warmup / (float) weight ) );
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
protected Invoker doSelect(List> invokers, URL url,
Invocation invocation) {
int length = invokers.size(); // 总个数
int totalWeight = 0; // 总权重
boolean sameWeight = true; // 权重是否都一样
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight; // 累计总权重
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false; // 计算所有权重是否一样
}
}
if (totalWeight > 0 && !sameWeight) {
// 如果权重不相同且权重大于0则按总权重数随机
int offset = random.nextInt(totalWeight);
// 并确定随机值落在哪个片断上
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
// 如果权重相同或权重为0则均等随机
return invokers.get(random.nextInt(length));
}
protected Invoker doSelect(List> invokers, URL url,
Invocation invocation) {
//service+method
String key = invokers.get(0).getUrl().getServiceKey() + "." +
invocation.getMethodName();
int length = invokers.size(); // 总个数
int maxWeight = 0; // 最大权重
int minWeight = Integer.MAX_VALUE; // 最小权重
//取出最大和最小的权重
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
maxWeight = Math.max(maxWeight, weight); // 累计最大权重
minWeight = Math.min(minWeight, weight); // 累计最小权重
}
if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样
AtomicPositiveInteger weightSequence = weightSequences.get(key);
if (weightSequence == null) {
weightSequences.putIfAbsent(key, new AtomicPositiveInteger());
weightSequence = weightSequences.get(key);
}
int currentWeight = weightSequence.getAndIncrement() % maxWeight;
List> weightInvokers = new ArrayList>();
// 筛选权重大于当前权重基数的Invoker
for (Invoker invoker : invokers) {
if (getWeight(invoker, invocation) > currentWeight) {
weightInvokers.add(invoker);
}
}
int weightLength = weightInvokers.size();
if (weightLength == 1) {
return weightInvokers.get(0);
} else if (weightLength > 1) {
invokers = weightInvokers;
length = invokers.size();
}
}
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
// 取模轮循
return invokers.get(sequence.getAndIncrement() % length);
}
protected Invoker doSelect(List> invokers, URL url,
Invocation invocation) {
int length = invokers.size(); // 总个数
int leastActive = -1; // 最小的活跃数
int leastCount = 0; // 相同最小活跃数的个数
int[] leastIndexs = new int[length]; // 相同最小活跃数的下标
int totalWeight = 0; // 总权重
int firstWeight = 0; // 第一个权重,用于于计算是否相同
boolean sameWeight = true; // 是否所有权重相同
for (int i = 0; i < length; i++) {
Invoker invoker = invokers.get(i);
// 活跃数
int active = RpcStatus.getStatus(invoker.getUrl(),
invocation.getMethodName()).getActive();
// 权重
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(),
"weight", 100);
if (leastActive == -1 || active < leastActive) { // 发现更小的活跃数,重新开始
leastActive = active; // 记录最小活跃数
leastCount = 1; // 重新统计相同最小活跃数的个数
leastIndexs[0] = i; // 重新记录最小活跃数下标
totalWeight = weight; // 重新累计总权重
firstWeight = weight; // 记录第一个权重
sameWeight = true; // 还原权重相同标识
} else if (active == leastActive) { // 累计相同最小的活跃数
leastIndexs[leastCount ++] = i; // 累计相同最小活跃数下标
totalWeight += weight; // 累计总权重
// 判断所有权重是否一样
if (sameWeight && i > 0
&& weight != firstWeight) {
sameWeight = false;
}
}
}
if (leastCount == 1) {
// 如果只有一个最小则直接返回
return invokers.get(leastIndexs[0]);
}
if (! sameWeight && totalWeight > 0) {
// 如果权重不相同且权重大于0则按总权重数随机
int offsetWeight = random.nextInt(totalWeight);
// 并确定随机值落在哪个片断上
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// 如果权重相同或权重为0则均等随机
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
protected Invoker doSelect(List> invokers, URL url,
Invocation invocation) {
//service+method
String key = invokers.get(0).getUrl().getServiceKey() + "." +
invocation.getMethodName();
//获取唯一hashCode
int identityHashCode = System.identityHashCode(invokers);
ConsistentHashSelector selector = (ConsistentHashSelector)
selectors.get(key);
//初始化时或者当hashcode不一致时(当提供者增加或者删除时)
if (selector == null ||
selector.getIdentityHashCode() != identityHashCode) {
selectors.put(key, new ConsistentHashSelector(invokers,
invocation.getMethodName(), identityHashCode));
selector = (ConsistentHashSelector) selectors.get(key);
}
return selector.select(invocation);
}
private static final class ConsistentHashSelector {
//虚拟invoker
private final TreeMap> virtualInvokers;
//节点数
private final int replicaNumber;
private final int identityHashCode;
private final int[] argumentIndex;
public ConsistentHashSelector(List> invokers, String methodName,
int identityHashCode) {
this.virtualInvokers = new TreeMap>();
this.identityHashCode = System.identityHashCode(invokers);
URL url = invokers.get(0).getUrl();
this.replicaNumber = url.getMethodParameter(
methodName, "hash.nodes", 160);
String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.
getMethodParameter(methodName, "hash.arguments", "0"));
argumentIndex = new int[index.length];
for (int i = 0; i < index.length; i ++) {
argumentIndex[i] = Integer.parseInt(index[i]);
}
for (Invoker invoker : invokers) {
for (int i = 0; i < replicaNumber / 4; i++) {
byte[] digest = md5(invoker.getUrl().toFullString() + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
}
public int getIdentityHashCode() {
return identityHashCode;
}
public Invoker select(Invocation invocation) {
String key = toKey(invocation.getArguments());
byte[] digest = md5(key);
Invoker invoker = sekectForKey(hash(digest, 0));
return invoker;
}
private String toKey(Object[] args) {
StringBuilder buf = new StringBuilder();
for (int i : argumentIndex) {
if (i >= 0 && i < args.length) {
buf.append(args[i]);
}
}
return buf.toString();
}
private Invoker sekectForKey(long hash) {
Invoker invoker;
Long key = hash;
if (!virtualInvokers.containsKey(key)) {
SortedMap> tailMap = virtualInvokers.tailMap(key);
if (tailMap.isEmpty()) {
key = virtualInvokers.firstKey();
} else {
key = tailMap.firstKey();
}
}
invoker = virtualInvokers.get(key);
return invoker;
}
//计算hash值
private long hash(byte[] digest, int number) {
return (((long) (digest[3 + number * 4] & 0xFF) << 24)
| ((long) (digest[2 + number * 4] & 0xFF) << 16)
| ((long) (digest[1 + number * 4] & 0xFF) << 8)
| (digest[0 + number * 4] & 0xFF))
& 0xFFFFFFFFL;
}
//加密
private byte[] md5(String value) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e.getMessage(), e);
}
md5.reset();
byte[] bytes = null;
try {
bytes = value.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e.getMessage(), e);
}
md5.update(bytes);
return md5.digest();
}
}