关于dubbo的负载均衡策略已经有太多blog介绍了,这里说一下我在使用dubbo一致性哈希负载均衡策略时候遇到的一个坑。

在使用dubbo2.8.4版本consistenthash策略的时候,我们根据用户uid做哈希,理论上同一个用户的访问应该打到同一个provider节点上, 但实际上我们发现不同consumer节点过来的同一个uid的请求并不能打到同一个provider节点上。这是因为dubbo2.8.4版本 的一致哈希策略存在问题。如何解决?

  1. dubbo2.8.4实际上是2015年的一个老版本,对应的是当当的开源项目dubbox,我们可以将其升级为dubbo2.6.x(看着是版本号减小啊, 实际上是升级,感兴趣的可以研究一下dubbo版本号~),dubbo2.6.x已经修复了这个问题,但是问题是 实际升级并不会如想象般简单,由于dubbo服务众多,而且版本的不兼容(比如序列化/反序列化的不兼容),升级可能会困难重重。

  2. 另一种方法是我们实现dubbo的LoadBalance接口,提供一个新的一致哈希实现,我们先看一下dubbo2.8.4的一致性哈希的实现问题在哪。

    public ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
     this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
     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<T> 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);
             }
         }
     }
    }
    

    上面代码中通过md5计算digest的时候使用的invoker的url实际包含了和provider相关的参数”&pid=”以及和时间相关的参数”&timestamp=”参数, 所以fix这个问题最直接的方法就是去掉这些参数影响,只要稍作修改即可。

    byte[] digest = md5(invoker.getUrl().getAddress() + i);
    

源码中的ConsistentHashLoadBalance全部实现保持不变,但我们将类名修改为ConsistentHashLoadBalance2

public class ConsistentHashLoadBalance2 extends AbstractLoadBalance {

    ...
    
}

然后我们在resources/META-INF/dubbo中定义com.alibaba.dubbo.rpc.cluster.LoadBalance文件,内容为

consistenthash2=xxx=com.xxx.ConsistentHashLoadBalance2

这样只要在consumer侧引入相应的修改,配置loadblance=”consistenthash2”即可。