rpc之負載均衡


使用集群,比如zk來控制注冊中心,當一個服務有多個請求地址的時候,會返回多個地址。

那么就需要負載均衡來控制我們要請求哪台機器來得到請求。

 

方案一:隨機

傳入key值和key所包含的ip地址值,該地址值存入TreeSet中(有序存儲)

獲得TreeSet的長度,然后隨機得到其索引,挑出隨機的一個。

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

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

 

方案二:輪詢

 TreeSet中的地址值存入一個數組中,並設置一個map集合來記錄該函數調用了幾次,每次調用,就將索引加1,然后返回該索引的地址值。這樣就會按照TreeSet中的順序依次選取請求地址。

private ConcurrentHashMap<String, Integer> routeCountEachJob = new ConcurrentHashMap<String, Integer>();
    private long CACHE_VALID_TIME = 0;
    private int count(String serviceKey) {
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            routeCountEachJob.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 24*60*60*1000;//一天的時間
        }

        // count++
        Integer count = routeCountEachJob.get(serviceKey);
        count = (count==null || count>1000000)?(new Random().nextInt(100)):++count;  // 初始化時主動Random一次,緩解首次壓力
        routeCountEachJob.put(serviceKey, count);
        System.out.println("count:"+count);
        return count;
    }

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

        // round
        int i = count(serviceKey) % addressArr.length;
        System.out.println(i);
        String finalAddress = addressArr[i];
        return finalAddress;
    }

 

方案三: LRU(最近最少使用調度算法)

每次使用了每個節點的時候,就將該節點放置在最后面,這樣就保證每次使用的節點都是最近最久沒有使用過的節點,當節點數大於最大空間的時候,就直接將前面的節點刪掉。

實現:使用LinkedHashMap來實現。它內部有一個雙向鏈表在維護

public String doRoute(String serviceKey, TreeSet<String> addressSet) {

        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLRUMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;//一天
        }

        // init lru
        LinkedHashMap<String, String> 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<String, String>(16, 0.75f, true){
                @Override
                protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                    if(super.size() > 3){
                        return true;
                    }else{
                        return false;
                    }
                }
            };
            jobLRUMap.putIfAbsent(serviceKey, lruItem);
        }

        // put
        for (String address: addressSet) {
            if (!lruItem.containsKey(address)) {
                lruItem.put(address, address);
            }
        }

        // load
        String eldestKey = lruItem.entrySet().iterator().next().getKey();
        String eldestValue = lruItem.get(eldestKey);//LRU算法關鍵體現在這里,實現了固定長度的LRU算法
        return eldestValue;
    }

 

 

方案四:LFU(訪問最頻繁的使用概率也最高),因此,將使用最頻繁的放在最后面使用,保證了使用不頻繁的也能使用上

hashmap的存放是無序的。

public String doRoute(String serviceKey, TreeSet<String> addressSet) {

        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLfuMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }

        // lfu item init
        HashMap<String, Integer> lfuItemMap = jobLfuMap.get(serviceKey);     // Key排序可以用TreeMap+構造入參Compare;Value排序暫時只能通過ArrayList;
        if (lfuItemMap == null) {
            lfuItemMap = new HashMap<String, Integer>();
            jobLfuMap.putIfAbsent(serviceKey, lfuItemMap);   // 避免重復覆蓋
        }
        for (String address: addressSet) {
            if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
                lfuItemMap.put(address, 0);
            }
        }
//        System.out.println(lfuItemMap);

        // load least userd count address
        List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());
        Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
        System.out.println(lfuItemList);

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

        return minAddress;
//        return null;
    }

 

 

方案五:一致性哈希

consistent hashing 是一種 hash 算法,簡單的說,在移除 / 添加一個 cache 時,它能夠盡可能小的改變已存在 key 映射關系,盡可能的滿足單調性的要求。

  1. 每個節點設置5個虛擬節點

  2. 計算serviceKey的hash值

  3. 使用treeMap的tailMap方法返回其鍵大於或等於fromKey的部分視圖

  4. 取視圖的第一個作為服務調用的address

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<String> addressSet) {

        // ------A1------A2-------A3------
        // -----------J1------------------
        TreeMap<Long, String> addressRing = new TreeMap<Long, String>();
        for (String address: addressSet) {
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                long addressHash = hash("SHARD-" + address + "-NODE-" + i);
                addressRing.put(addressHash, address);
            }
        }
        //TreeMap的存放是根據addressHash值排序


        long jobHash = hash(serviceKey);
        SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
        //將addressHash值大於jobHash值的adress都取出來
//        System.out.println(lastRing);
        if (!lastRing.isEmpty()) {
            //如果這個地址不為空,就返回這個的第一個
            return lastRing.get(lastRing.firstKey());
        }
//        System.out.println(lastRing.firstKey());
        //返回沒有減少的地址的第一個
        return addressRing.firstEntry().getValue();
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM