使用集群,比如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 映射關系,盡可能的滿足單調性的要求。
-
每個節點設置5個虛擬節點
-
計算serviceKey的hash值
-
-
取視圖的第一個作為服務調用的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(); }
