使用集群,比如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(); }