sofa-rpc轮询算法总结

sofa轮询算法总结

类型 算法名称 描述
RandomLoadBalancer 负载均衡随机算法
LocalPreferenceLoadBalancer 本地优先随机算法
ConsistentHashLoadBalancer 一致性hash算法
RoundRobinLoadBalancer 轮询算法
WeightRoundRobinLoadBalancer 轮询算法

负载均衡随机算法

public class RandomLoadBalancer extends AbstractLoadBalancer {
    private final Random random = new Random();

    public RandomLoadBalancer(ConsumerBootstrap consumerBootstrap) {
        super(consumerBootstrap);
    }

    public ProviderInfo doSelect(SofaRequest invocation, List providerInfos) {
        ProviderInfo providerInfo = null;
        int size = providerInfos.size();
        int totalWeight = 0;
        boolean isWeightSame = true;

        int offset;
        int i;
        for(offset = 0; offset < size; ++offset) {
            i = this.getWeight((ProviderInfo)providerInfos.get(offset));
            totalWeight += i;
            if (isWeightSame && offset > 0 && i != this.getWeight((ProviderInfo)providerInfos.get(offset - 1))) {
                isWeightSame = false;
            }
        }

        if (totalWeight > 0 && !isWeightSame) {
            offset = this.random.nextInt(totalWeight);

            for(i = 0; i < size; ++i) {
                offset -= this.getWeight((ProviderInfo)providerInfos.get(i));
                if (offset < 0) {
                    providerInfo = (ProviderInfo)providerInfos.get(i);
                    break;
                }
            }
        } else {
            providerInfo = (ProviderInfo)providerInfos.get(this.random.nextInt(size));
        }

        return providerInfo;
    }
}
  1. 权重值计算方式值得总结,根据总的权重值获取随机权重值。
  2. 然后遍历通道,随机权重值offset减去当前权重值。
  3. 以前根据权重值数量放到一个重复通道放到List中,随机ArrayList的index,太笨重。
  4. 以前短信通道还按照权重区间,定义通道fromOffset,toOffset,有点像上面的这个算法。offset也是List的0通道累加

轮询算法

@Extension("roundRobin")
public class RoundRobinLoadBalancer extends AbstractLoadBalancer {
    private final ConcurrentMap sequences = new ConcurrentHashMap();

    public RoundRobinLoadBalancer(ConsumerBootstrap consumerBootstrap) {
        super(consumerBootstrap);
    }

    public ProviderInfo doSelect(SofaRequest request, List providerInfos) {
        String key = this.getServiceKey(request);
        int length = providerInfos.size();
        PositiveAtomicCounter sequence = (PositiveAtomicCounter)this.sequences.get(key);
        if (sequence == null) {
            this.sequences.putIfAbsent(key, new PositiveAtomicCounter());
            sequence = (PositiveAtomicCounter)this.sequences.get(key);
        }

        return (ProviderInfo)providerInfos.get(sequence.getAndIncrement() % length);
    }

    private String getServiceKey(SofaRequest request) {
        StringBuilder builder = new StringBuilder();
        builder.append(request.getTargetAppName()).append("#").append(request.getMethodName());
        return builder.toString();
    }
}


public class PositiveAtomicCounter {
    private static final int MASK = 2147483647;
    private final AtomicInteger atom = new AtomicInteger(0);

    public PositiveAtomicCounter() {
    }

    public final int incrementAndGet() {
        return this.atom.incrementAndGet() & 2147483647;
    }

    public final int getAndIncrement() {
        return this.atom.getAndIncrement() & 2147483647;
    }

    public int get() {
        return this.atom.get() & 2147483647;
    }
}

  1. 轮询的核心思想就是AtomicInteger曾增长,然后与通道size取余。
  2. PositiveAtomicCounter加入了与最大致的与运算,与运算的目的是不超过Max值。

本地优先随机算法


@Extension("localPref")
public class LocalPreferenceLoadBalancer extends RandomLoadBalancer {
    public LocalPreferenceLoadBalancer(ConsumerBootstrap consumerBootstrap) {
        super(consumerBootstrap);
    }

    public ProviderInfo doSelect(SofaRequest invocation, List providerInfos) {
        String localhost = SystemInfo.getLocalHost();
        if (StringUtils.isEmpty(localhost)) {
            return super.doSelect(invocation, providerInfos);
        } else {
            List localProviderInfo = new ArrayList();
            Iterator i$ = providerInfos.iterator();

            while(i$.hasNext()) {
                ProviderInfo providerInfo = (ProviderInfo)i$.next();
                if (localhost.equals(providerInfo.getHost())) {
                    localProviderInfo.add(providerInfo);
                }
            }

            if (CommonUtils.isNotEmpty(localProviderInfo)) {
                return super.doSelect(invocation, localProviderInfo);
            } else {
                return super.doSelect(invocation, providerInfos);
            }
        }
    }
}

一致性hash算法(值的学习)

  1. 当然,万事不可能十全十美,一致性Hash算法比普通的余数Hash算法更具有伸缩性,但是同时其算法实现也更为复杂,本文就来研究一下,如何利用Java代码实现一致性Hash算法。在开始之前,先对一致性Hash算法中的几个核心问题进行一些探究。

  2. 算法的具体原理这里再次贴上:

     先构造一个长度为2^32的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2^32-1])将服务器节点放置在这个Hash环上,
     然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),
     接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
    
  • 理解所有node的hash值为[0, 2^32-1]内,为保持一个环状,hash必须有序数据结构存储,形成hash值与NodeObject的映射。我们选择TreeHash,使用红黑树,可以使得查找的时间复杂度降低为O(logN),比上面两种解决方案,效率大大提升。
  • Hash的计算是分布均匀在环上的必要条件。 采用比如FNV1_32_HASH算法的计算效率就会高一些。
  • 增加虚拟节点来提高HashServer的分布。


/**
 * 一致性hash算法,同样的请求(第一参数)会打到同样的节点
 *
 * @author GengZhang
 */
@Extension("consistentHash")
public class ConsistentHashLoadBalancer extends AbstractLoadBalancer {

    /**
     * {interface#method : selector}
     */
    private ConcurrentMap<String, Selector> selectorCache = new ConcurrentHashMap<String, Selector>();

    /**
     * 构造函数
     *
     * @param consumerBootstrap 服务消费者配置
     */
    public ConsistentHashLoadBalancer(ConsumerBootstrap consumerBootstrap) {
        super(consumerBootstrap);
    }

    @Override
    public ProviderInfo doSelect(SofaRequest request, List<ProviderInfo> providerInfos) {
        String interfaceId = request.getInterfaceName();
        String method = request.getMethodName();
        String key = interfaceId + "#" + method;
        int hashcode = providerInfos.hashCode(); // 判断是否同样的服务列表
        Selector selector = selectorCache.get(key);
        if (selector == null // 原来没有
            ||
            selector.getHashCode() != hashcode) { // 或者服务列表已经变化
            selector = new Selector(interfaceId, method, providerInfos, hashcode);
            selectorCache.put(key, selector);
        }
        return selector.select(request);
    }

    /**
     * 选择器
     */
    private static class Selector {

        /**
         * The Hashcode.
         */
        private final int                         hashcode;

        /**
         * The Interface id.
         */
        private final String                      interfaceId;

        /**
         * The Method name.
         */
        private final String                      method;

        /**
         * 虚拟节点
         */
        private final TreeMap<Long, ProviderInfo> virtualNodes;

        /**
         * Instantiates a new Selector.
         *
         * @param interfaceId the interface id
         * @param method      the method
         * @param actualNodes the actual nodes
         */
        public Selector(String interfaceId, String method, List<ProviderInfo> actualNodes) {
            this(interfaceId, method, actualNodes, actualNodes.hashCode());
        }

        /**
         * Instantiates a new Selector.
         *
         * @param interfaceId the interface id
         * @param method      the method
         * @param actualNodes the actual nodes
         * @param hashcode    the hashcode
         */
        public Selector(String interfaceId, String method, List<ProviderInfo> actualNodes, int hashcode) {
            this.interfaceId = interfaceId;
            this.method = method;
            this.hashcode = hashcode;
            // 创建虚拟节点环 (默认一个provider共创建128个虚拟节点,较多比较均匀)
            this.virtualNodes = new TreeMap<Long, ProviderInfo>();
            int num = 128;
            for (ProviderInfo providerInfo : actualNodes) {
                for (int i = 0; i < num / 4; i++) {
                    byte[] digest = HashUtils.messageDigest(providerInfo.getHost() + providerInfo.getPort() + i);
                    for (int h = 0; h < 4; h++) {
                        long m = HashUtils.hash(digest, h);
                        virtualNodes.put(m, providerInfo);
                    }
                }
            }
        }

        /**
         * Select provider.
         *
         * @param request the request
         * @return the provider
         */
        public ProviderInfo select(SofaRequest request) {
            String key = buildKeyOfHash(request.getMethodArgs());
            byte[] digest = HashUtils.messageDigest(key);
            return selectForKey(HashUtils.hash(digest, 0));
        }

        /**
         * 获取第一参数作为hash的key
         *
         * @param args the args
         * @return the string
         */
        private String buildKeyOfHash(Object[] args) {
            if (CommonUtils.isEmpty(args)) {
                return StringUtils.EMPTY;
            } else {
                return StringUtils.toString(args[0]);
            }
        }

        /**
         * Select for key.
         *
         * @param hash the hash
         * @return the provider
         */
        private ProviderInfo selectForKey(long hash) {
            Map.Entry<Long, ProviderInfo> entry = virtualNodes.ceilingEntry(hash);
            if (entry == null) {
                entry = virtualNodes.firstEntry();
            }
            return entry.getValue();
        }

        /**
         * Gets hash code.
         *
         * @return the hash code
         */
        public int getHashCode() {
            return hashcode;
        }
    }
}



  • Selector构造方法就是添加ServerNode及virtualNode方法,128个node参数。
  • selectForKey方法获取大于当前key的Map,取第一个。

应用场景

  1. 问题
  • 它是负载路由算法,当ServerNode伸缩时候,会引起一切请求路由变化,所以不适合分库持久化数据查询。
  1. 适合请求路由,查询cache等设计场景。
  2. 参考https://blog.csdn.net/u010412301/article/details/52441400

你可能感兴趣的:(sofa-rpc)