深度解析xxl-rpc之负载均衡算法

介绍

在xxl-rpc中提供了5中负载均衡的算法,包括轮询、随机、LRU、LFU、一致性HASH可供用户选择。
包位置:
深度解析xxl-rpc之负载均衡算法_第1张图片

源码解读

1.负载均衡抽象类
/**
 * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器;
 *      a、virtual node:解决不均衡问题
 *      b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围
 *
 * @author xuxueli 2018-12-04
 */
public abstract class XxlRpcLoadBalance {
	// addressSet  是 serviceKey 的地址集合
    public abstract String route(String serviceKey, TreeSet addressSet);

}

2.随机

随机算法很简单,使用了随机类 Random生成随机数。

public class XxlRpcLoadBalanceRandomStrategy extends XxlRpcLoadBalance {

    private Random random = new Random();

    @Override
    public String route(String serviceKey, TreeSet addressSet) {
        // arr  将set 转成数组
        String[] addressArr = addressSet.toArray(new String[addressSet.size()]);

        // random  random 长度
        String finalAddress = addressArr[random.nextInt(addressSet.size())];
        return finalAddress;
    }

}
3.轮询

轮询的意思就是,轮着来。使用次数% 服务提供者的数量,而且使用次数count是加加操作,这样就得到的结果就是挨着的,比如说有1,2,3个服务提供者,这个算法就能够保证这次是1,下次是2,再下次是3。

/**
 * round
 *
 * @author xuxueli 2018-12-04
 */
public class XxlRpcLoadBalanceRoundStrategy extends XxlRpcLoadBalance {
    // 用于记录某个服务 次数的 通过这个次数 % 服务提供者数量  得到 选择哪个位置的机器
    private ConcurrentMap routeCountEachJob = new ConcurrentHashMap();
    private long CACHE_VALID_TIME = 0;
    private int count(String serviceKey) {
        // cache clear  过段时间就要清一下 map
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            routeCountEachJob.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 24*60*60*1000;
        }

        // count++
        Integer count = routeCountEachJob.get(serviceKey);
        //  超过 1000000就要 初始化  ,初始化的时候使用随机数,防止第一次老往第一台机器上扔
        count = (count==null || count>1000000)?(new Random().nextInt(100)):++count;  // 初始化时主动Random一次,缓解首次压力
        routeCountEachJob.put(serviceKey, count);
        return count;
    }

    @Override
    public String route(String serviceKey, TreeSet addressSet) {
        // arr
        String[] addressArr = addressSet.toArray(new String[addressSet.size()]);

        // round
        String finalAddress = addressArr[count(serviceKey)%addressArr.length];
        return finalAddress;
    }

}
4.LRU

LRU意思是最近使用最少,xxl作者是通过java LinkedHashMap 来实现的LRU,当在创建对象的时候将LinkedHashMap将accessOrder 值设置为true就可以了。

/**
 * lru
 *
 * @author xuxueli 2018-12-04
 */
public class XxlRpcLoadBalanceLRUStrategy extends XxlRpcLoadBalance {
    //  存储 serviceKey 与 address
    private ConcurrentMap> jobLRUMap = new ConcurrentHashMap>();
    private long CACHE_VALID_TIME = 0;

    public String doRoute(String serviceKey, TreeSet addressSet) {

        // cache clear 过段时间清空 map
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLRUMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }

        // init lru
        LinkedHashMap lruItem = jobLRUMap.get(serviceKey);
        if (lruItem == null) {  /// 初始化
            /**
             * LinkedHashMap
             *      a、accessOrder:ture=访问顺序排序(get/put时排序)/ACCESS-LAST;false=插入顺序排期/FIFO;
             *      b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;
             */
            lruItem = new LinkedHashMap(16, 0.75f, true){
                @Override
                protected boolean removeEldestEntry(Map.Entry eldest) {
                    if(super.size() > 1000){   // size 超过1000
                        return true;
                    }else{
                        return false;
                    }
                }
            };
            jobLRUMap.putIfAbsent(serviceKey, lruItem);
        }

        // put new
        for (String address: addressSet) {  // 添加新的
            if (!lruItem.containsKey(address)) {
                lruItem.put(address, address);
            }
        }
        // remove old 将不存在记录
        List delKeys = new ArrayList<>();
        for (String existKey: lruItem.keySet()) {
            if (!addressSet.contains(existKey)) {
                delKeys.add(existKey);
            }
        }

        //  移除不存在的
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lruItem.remove(delKey);
            }
        }

        // load
        String eldestKey = lruItem.entrySet().iterator().next().getKey();
        String eldestValue = lruItem.get(eldestKey);
        return eldestValue;
    }

    @Override
    public String route(String serviceKey, TreeSet addressSet) {
        String finalAddress = doRoute(serviceKey, addressSet);
        return finalAddress;
    }

}
5.一致性Hash

原理就是将所有提供该服务的机器按照hash值(节点)从小到大的顺序排列到 hash环上(这个环就是想象中的,你把一个线性表收尾连在一起不都可以当作一个环),然后计算出这个服务key的hash值,也按照循序放到hash环上,然后顺时针取挨着你最近的那个节点就行了 ,这样每次这个服务都去找这个节点。

/**
 * consustent hash
 *
 * 单个JOB对应的每个执行器,使用频率最低的优先被选举
 *      a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数
 *      b、LRU(Least Recently Used):最近最久未使用,时间
 *
 * @author xuxueli 2018-12-04
 */
public class XxlRpcLoadBalanceConsistentHashStrategy extends XxlRpcLoadBalance {

    private int VIRTUAL_NODE_NUM = 5;

    /**
     * get hash code on 2^32 ring (md5散列的方式计算hash值)
     * @param key
     * @return
     */
    private long hash(String key) {

        // md5 byte
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 not supported", e);
        }
        md5.reset();
        byte[] keyBytes = null;
        try {
            keyBytes = key.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unknown string :" + key, e);
        }

        md5.update(keyBytes);
        byte[] digest = md5.digest();

        // hash code, Truncate to 32-bits
        long hashCode = ((long) (digest[3] & 0xFF) << 24)
                | ((long) (digest[2] & 0xFF) << 16)
                | ((long) (digest[1] & 0xFF) << 8)
                | (digest[0] & 0xFF);

        long truncateHashCode = hashCode & 0xffffffffL;
        return truncateHashCode;
    }

    public String doRoute(String serviceKey, TreeSet addressSet) {

        // ------A1------A2-------A3------
        // -----------J1------------------
        TreeMap addressRing = new TreeMap();
        for (String address: addressSet) {//  添加虚拟的节点 ,防止 节点少的时候发生故障,然后造成雪崩事故。
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                long addressHash = hash("SHARD-" + address + "-NODE-" + i);
                addressRing.put(addressHash, address);
            }
        }
        // 获得serviceKey的hash值
        long jobHash = hash(serviceKey);
        // tailMap 方法就是获取 key 大于 参数的所有值
        SortedMap lastRing = addressRing.tailMap(jobHash);
        if (!lastRing.isEmpty()) {
            // 找到了就取lastRing 挨着 它最近的那个, 也就是第一个  ,正好符合一致性hash 顺时针取挨着最近的要求的 要求
            return lastRing.get(lastRing.firstKey());
        }
        
        // 没有获取到,说明这个hash值是最大的了,后面没有了 ,就取最小的那个。也就是整个hash 环最小的那个
        return addressRing.firstEntry().getValue();
    }

    @Override
    public String route(String serviceKey, TreeSet addressSet) {
        String finalAddress = doRoute(serviceKey, addressSet);
        return finalAddress;
    }

}
6.LFU

LFU(Least Frequently Used)的意思就是在一段时间内根据数据历史访问频率来淘汰数据,数据访问频率越低就容易被淘汰,也就是说数据访问频率越高,就越不容易被淘汰。
这里作者按照访问频率来取出访问最低的那个,要是都是从0 开始,没有中途加入的话,效果跟轮询算法一样,但是你要是中途有加入的话,就会一直访问 新加入的那个,知道追赶上频率倒数第二名,然后两个再交替访问,其实最终会实现轮询效果。

/**
 * lru
 *
 * @author xuxueli 2018-12-04
 */
public class XxlRpcLoadBalanceLFUStrategy extends XxlRpcLoadBalance {
    //  key : 服务key   value: 机器:使用次数
    private ConcurrentMap> jobLfuMap = new ConcurrentHashMap>();
    private long CACHE_VALID_TIME = 0;

    public String doRoute(String serviceKey, TreeSet addressSet) {

        // cache clear  过段时间就清空
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLfuMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }

        // lfu item init
        HashMap lfuItemMap = jobLfuMap.get(serviceKey);     // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;
        if (lfuItemMap == null) {
            lfuItemMap = new HashMap();
            jobLfuMap.putIfAbsent(serviceKey, lfuItemMap);   // 避免重复覆盖
        }

        // put new
        for (String address: addressSet) {

            // 之前没有过 或者是调用次数 大于1000000 就进行初始化
            if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
                lfuItemMap.put(address, 0);
            }
        }

        // remove old   移除那种不存在的
        List delKeys = new ArrayList<>();
        for (String existKey: lfuItemMap.keySet()) {
            if (!addressSet.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lfuItemMap.remove(delKey);
            }
        }

        // load least userd count address   按照次数进行排序
        List> lfuItemList = new ArrayList>(lfuItemMap.entrySet());
        Collections.sort(lfuItemList, new Comparator>() {  // 根据次数 从小到大排序
            @Override
            public int compare(Map.Entry o1, Map.Entry o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });

        Map.Entry addressItem = lfuItemList.get(0);
        String minAddress = addressItem.getKey();
        addressItem.setValue(addressItem.getValue() + 1);

        return minAddress;
    }

    @Override
    public String route(String serviceKey, TreeSet addressSet) {
        String finalAddress = doRoute(serviceKey, addressSet);
        return finalAddress;
    }
}

你可能感兴趣的:(rpc,java,深度解析xxl-rpc)