一:负载均衡的概念
在大型的分布式架构中,对于负载较高的服务来说,往往对应着多台机器组成的集群,当请求到来的时候,为了将请求均衡的分配给后端服务器
需要有相应的负载均衡算法来支持,通过相应的负载均衡算法选取一台机器来处理客户端请求,这个过程称为服务的负载均衡。
二:常用的负载均衡算法
常用的负载均衡算法主要有随机法,轮询法,加权随机,加权轮询,最小连接数,一致性hash等,不同的场景所使用的负载均和算法不同,我们应该根据实际情况来选择不同的负载均衡算法。
下面介绍比较常用的加权轮询和一致性hash算法实现。
2.1 负载均衡接口
package com.travelsky.pss.react.cn.fzjh.jk; import java.util.List; import com.travelsky.pss.react.cn.fzjh.vo.ServiceInstance; public interface LoadBalance { //根据请求选择一个服务实例 ServiceInstance chooseServerInstance(); /** * 设置服务的信息 * * @param serviceName 服务名 * @param version 服务版本 */ void setService(String serviceName, String version); /** * 初始化 */ void init(); /** * 获取全部服务列表 * * @return */ List<ServiceInstance> getServiceInstanceList(); /** * 更新服务实例列表 */ void updateServerInstanceList(); /** * 隔离一个服务实例 * * @param server */ void isolateServerInstance(String server); /** * 恢复一个服务实例 * * @param server */ void resumeServerInstance(String server); }
2.2 抽象负载均衡基类
package com.travelsky.pss.react.cn.fzjh.abstrac; import java.util.List; import java.util.Random; import javax.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.travelsky.pss.react.cn.fzjh.jk.LoadBalance; import com.travelsky.pss.react.cn.fzjh.rule.DynamicUploadRule; import com.travelsky.pss.react.cn.fzjh.vo.ServiceInstance; //抽象的负载均衡基类,提供基本的服务信息和相关服务实例列表的管理 public abstract class AbstractLoadBalance implements LoadBalance { protected final Logger logger = LoggerFactory .getLogger(AbstractLoadBalance.class); @Resource(name="dynamicUploadRule") private transient DynamicUploadRule dynamicUploadRule; protected String serviceName; protected String version; // 特定版本的服务的标识 private transient String serviceKey; protected List<ServiceInstance> serviceInstanceList; @Override public void setService(String serviceName, String version) { this.serviceName = serviceName; this.version = version; this.serviceKey = genKey(serviceName, version); } // 随机算法获取可利用的服务列表 @Override public ServiceInstance chooseServerInstance() { List<ServiceInstance> allServiceList = getAllServiceInstanceList(); if (null == allServiceList) { return null; } ServiceInstance serviceInstance = null; int indexOfLoop = 0; Random random = new Random(); if (null != allServiceList && allServiceList.size() > 0) { int serviceCount = allServiceList.size(); while (null == serviceInstance && indexOfLoop < serviceCount * 5) {// 由于是随机选取,不能在serverCount内选出 int index = random.nextInt(serviceCount); serviceInstance = allServiceList.get(index); logger.info("随机选择算法获取可用的服务:" + serviceInstance.getServerName()); if (serviceInstance.isIsolated()) { logger.info("选择的服务暂时不可用:" + serviceInstance.getServerName() + ",重新选择"); indexOfLoop++; serviceInstance = null; } } } return serviceInstance; } @Override public void init() { // 拿到所以的服务器列表 List<ServiceInstance> serviceInstances = getAllServiceInstanceList(); setServiceInstanceList(serviceInstances); } @Override public List<ServiceInstance> getServiceInstanceList() { return serviceInstanceList; } @Override public void updateServerInstanceList() { // 这里实际上应该重新获取注册的服务信息更新,此处默认 List<ServiceInstance> serviceInstanceList = getAllServiceInstanceList(); setServiceInstanceList(serviceInstanceList); } @Override public void isolateServerInstance(String serverName) { for (final ServiceInstance serverInstance : serviceInstanceList) { if (serverName.equals(serverInstance.getServerName())) { serverInstance.setIsolated(true); break; } } } @Override public void resumeServerInstance(String serverName) { for (final ServiceInstance serverInstance : serviceInstanceList) { if (serverName.equals(serverInstance.getServerName())) { serverInstance.setIsolated(false); break; } } } //通过服务名获取服务实例 protected ServiceInstance getServiceInstanceByServiceName(String serviceName) { ServiceInstance serviceInstance = null; List<ServiceInstance> serviceInstances = getAllServiceInstanceList(); if (null == serviceInstances) { return null; } for (final ServiceInstance instance : serviceInstances) { if (instance.getServerName().equals(serviceName)) { serviceInstance = instance; break; } } return serviceInstance; } private String genKey(String serviceName, String version) { return new StringBuffer().append(serviceName).append('#') .append(version).toString(); } private List<ServiceInstance> getAllServiceInstanceList() { // 模拟服务器注册后的服务实例 List<ServiceInstance> serviceInstanceList = dynamicUploadRule.getServiceInstanceRule(); return serviceInstanceList; } protected String getServiceNameByServiceKey(String serviceKey){ int index = serviceKey.indexOf('#'); return serviceKey.substring(0, index); } public String getServiceName() { return serviceName; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getServiceKey() { return serviceKey; } public void setServiceKey(String serviceKey) { this.serviceKey = serviceKey; } public void setServiceInstanceList(List<ServiceInstance> serviceInstanceList) { this.serviceInstanceList = serviceInstanceList; } }
2.3 一致性hash算法
目前在企业里面常用的基于内存的缓存服务memcached默认采用的负载均衡算法就是一致性hash算法,一致性hash算法可用保证对同一参数的请求将会是同一个服务器来处理,一致性hash算法的原理大概如下:我们可以将可利用的服务器列表映射到一个hash环上,当客户端请求过来时,求出key的hash值映射到hash环上,然后顺时针查找,找到的第一台机器就是出来该请求的服务,如果循环完了还是没找到,那么就将请求转给hash环上的第一台机器处理,可以想象的是,如果咱们的集群足够大,可能循环很久才能找到对应的服务器,这就加大了网络IO。
一致性hash算法实现。
package com.travelsky.pss.react.cn.fzjh.loadBalance.rule; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.travelsky.pss.react.cn.fzjh.abstrac.AbstractLoadBalance; import com.travelsky.pss.react.cn.fzjh.consistenthash.ConsistentHash; import com.travelsky.pss.react.cn.fzjh.consistenthash.MurmurHash3; import com.travelsky.pss.react.cn.fzjh.vo.ServiceInstance; @Service("consistentHashLoadBalance") //基于一致性hash算法实现负载均衡 public class ConsistentHashLoadBalance extends AbstractLoadBalance { private static final Logger logger = LoggerFactory .getLogger(ConsistentHashLoadBalance.class); // 虚拟节点总数,用来计算各个服务器的虚拟节点总数 public static final int VITRUAL_NODE_NUMBER = 1000; // 默认的每个服务器的虚拟节点个数 public static final int DEFALUT_NODE_NUMBER = 30; private AtomicReference<ConsistentHash<ServiceInstance>> hashRing = new AtomicReference<ConsistentHash<ServiceInstance>>(); //虚拟节点个数,初始化时根据服务节点数计算,不随服务节点变化而变化 private int numberOfReplicas; public ServiceInstance chooseServerInstance(String serviceKey) { this.serviceName = getServiceNameByServiceKey(serviceKey); //从哈希环中找到对应的节点以及后续的节点 List<ServiceInstance> instances = hashRing.get().getNUniqueBinsFor(serviceName, getServiceInstanceList().size()); ServiceInstance serviceInstance = null; //循环每个阶段,直至找出没有被隔离的服务器 for(ServiceInstance instance: instances){ if(instance.isIsolated()){ logger.info("服务被隔离了,暂时不可用:" + serviceName); } else { //顺时针找到第一个后就返回 serviceInstance = instance; break; } } return serviceInstance; } @Override public void init() { super.init(); numberOfReplicas = getServiceInstanceList().isEmpty()?DEFALUT_NODE_NUMBER:VITRUAL_NODE_NUMBER/getServiceInstanceList().size(); buildHashLoop(); } private void buildHashLoop() { logger.info("开始构建hash环"); hashRing.set(new ConsistentHash<ServiceInstance>(MurmurHash3.getInstance(),numberOfReplicas,serviceInstanceList)); } }
动态配置文件配置的服务器权值如下:
#serviceInstance{serviceName,qzValue,isolated} instance1=127.0.0.1,100,false instance2=127.0.0.2,300,false instance3=127.0.0.3,200,false
其依赖的几个类参考的是开源项目源码修改
package com.travelsky.pss.react.cn.fzjh.consistenthash; import java.nio.charset.Charset; import com.travelsky.pss.react.cn.fzjh.jk.HashFunction; //源代码基于Infinispan项目中的org.infinispan.commons.hash.MurmurHash3修改 //Infinispan项目地址: https://github.com/infinispan/infinispan /** * MurmurHash3 implementation in Java, based on Austin Appleby's <a href= * "https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp" * >original in C</a> * * Only implementing x64 version, because this should always be faster on 64 bit * native processors, even 64 bit being ran with a 32 bit OS; this should also * be as fast or faster than the x86 version on some modern 32 bit processors. * * @author Patrick McFarland * @see <a href="http://sites.google.com/site/murmurhash/">MurmurHash website</a> * @see <a href="http://en.wikipedia.org/wiki/MurmurHash">MurmurHash entry on Wikipedia</a> * @since 5.0 */ public class MurmurHash3 implements HashFunction { private final static MurmurHash3 instance = new MurmurHash3(); public static MurmurHash3 getInstance() { return instance; } private MurmurHash3() { } private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); static class State { long h1; long h2; long k1; long k2; long c1; long c2; } static long getblock(byte[] key, int i) { return ((key[i + 0] & 0x00000000000000FFL)) | ((key[i + 1] & 0x00000000000000FFL) << 8) | ((key[i + 2] & 0x00000000000000FFL) << 16) | ((key[i + 3] & 0x00000000000000FFL) << 24) | ((key[i + 4] & 0x00000000000000FFL) << 32) | ((key[i + 5] & 0x00000000000000FFL) << 40) | ((key[i + 6] & 0x00000000000000FFL) << 48) | ((key[i + 7] & 0x00000000000000FFL) << 56); } static void bmix(State state) { state.k1 *= state.c1; state.k1 = (state.k1 << 23) | (state.k1 >>> 64 - 23); state.k1 *= state.c2; state.h1 ^= state.k1; state.h1 += state.h2; state.h2 = (state.h2 << 41) | (state.h2 >>> 64 - 41); state.k2 *= state.c2; state.k2 = (state.k2 << 23) | (state.k2 >>> 64 - 23); state.k2 *= state.c1; state.h2 ^= state.k2; state.h2 += state.h1; state.h1 = state.h1 * 3 + 0x52dce729; state.h2 = state.h2 * 3 + 0x38495ab5; state.c1 = state.c1 * 5 + 0x7b7d159c; state.c2 = state.c2 * 5 + 0x6bce6396; } static long fmix(long k) { k ^= k >>> 33; k *= 0xff51afd7ed558ccdL; k ^= k >>> 33; k *= 0xc4ceb9fe1a85ec53L; k ^= k >>> 33; return k; } /** * Hash a value using the x64 128 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 128 bit hashed key, in an array containing two longs */ public static long[] MurmurHash3_x64_128(final byte[] key, final int seed) { State state = new State(); state.h1 = 0x9368e53c2f6af274L ^ seed; state.h2 = 0x586dcd208f7cd3fdL ^ seed; state.c1 = 0x87c37b91114253d5L; state.c2 = 0x4cf5ad432745937fL; for (int i = 0; i < key.length / 16; i++) { state.k1 = getblock(key, i * 2 * 8); state.k2 = getblock(key, (i * 2 + 1) * 8); bmix(state); } state.k1 = 0; state.k2 = 0; int tail = (key.length >>> 4) << 4; switch (key.length & 15) { case 15: state.k2 ^= (long) key[tail + 14] << 48; case 14: state.k2 ^= (long) key[tail + 13] << 40; case 13: state.k2 ^= (long) key[tail + 12] << 32; case 12: state.k2 ^= (long) key[tail + 11] << 24; case 11: state.k2 ^= (long) key[tail + 10] << 16; case 10: state.k2 ^= (long) key[tail + 9] << 8; case 9: state.k2 ^= key[tail + 8]; case 8: state.k1 ^= (long) key[tail + 7] << 56; case 7: state.k1 ^= (long) key[tail + 6] << 48; case 6: state.k1 ^= (long) key[tail + 5] << 40; case 5: state.k1 ^= (long) key[tail + 4] << 32; case 4: state.k1 ^= (long) key[tail + 3] << 24; case 3: state.k1 ^= (long) key[tail + 2] << 16; case 2: state.k1 ^= (long) key[tail + 1] << 8; case 1: state.k1 ^= key[tail + 0]; bmix(state); } state.h2 ^= key.length; state.h1 += state.h2; state.h2 += state.h1; state.h1 = fmix(state.h1); state.h2 = fmix(state.h2); state.h1 += state.h2; state.h2 += state.h1; return new long[] { state.h1, state.h2 }; } /** * Hash a value using the x64 64 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 64 bit hashed key */ public static long MurmurHash3_x64_64(final byte[] key, final int seed) { // Exactly the same as MurmurHash3_x64_128, except it only returns state.h1 State state = new State(); state.h1 = 0x9368e53c2f6af274L ^ seed; state.h2 = 0x586dcd208f7cd3fdL ^ seed; state.c1 = 0x87c37b91114253d5L; state.c2 = 0x4cf5ad432745937fL; for (int i = 0; i < key.length / 16; i++) { state.k1 = getblock(key, i * 2 * 8); state.k2 = getblock(key, (i * 2 + 1) * 8); bmix(state); } state.k1 = 0; state.k2 = 0; int tail = (key.length >>> 4) << 4; switch (key.length & 15) { case 15: state.k2 ^= (long) key[tail + 14] << 48; case 14: state.k2 ^= (long) key[tail + 13] << 40; case 13: state.k2 ^= (long) key[tail + 12] << 32; case 12: state.k2 ^= (long) key[tail + 11] << 24; case 11: state.k2 ^= (long) key[tail + 10] << 16; case 10: state.k2 ^= (long) key[tail + 9] << 8; case 9: state.k2 ^= key[tail + 8]; case 8: state.k1 ^= (long) key[tail + 7] << 56; case 7: state.k1 ^= (long) key[tail + 6] << 48; case 6: state.k1 ^= (long) key[tail + 5] << 40; case 5: state.k1 ^= (long) key[tail + 4] << 32; case 4: state.k1 ^= (long) key[tail + 3] << 24; case 3: state.k1 ^= (long) key[tail + 2] << 16; case 2: state.k1 ^= (long) key[tail + 1] << 8; case 1: state.k1 ^= key[tail + 0]; bmix(state); } state.h2 ^= key.length; state.h1 += state.h2; state.h2 += state.h1; state.h1 = fmix(state.h1); state.h2 = fmix(state.h2); state.h1 += state.h2; state.h2 += state.h1; return state.h1; } /** * Hash a value using the x64 32 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 32 bit hashed key */ public static int MurmurHash3_x64_32(final byte[] key, final int seed) { return (int) (MurmurHash3_x64_64(key, seed) >>> 32); } /** * Hash a value using the x64 128 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 128 bit hashed key, in an array containing two longs */ public static long[] MurmurHash3_x64_128(final long[] key, final int seed) { State state = new State(); state.h1 = 0x9368e53c2f6af274L ^ seed; state.h2 = 0x586dcd208f7cd3fdL ^ seed; state.c1 = 0x87c37b91114253d5L; state.c2 = 0x4cf5ad432745937fL; for (int i = 0; i < key.length / 2; i++) { state.k1 = key[i * 2]; state.k2 = key[i * 2 + 1]; bmix(state); } long tail = key[key.length - 1]; // Key length is odd if ((key.length & 1) == 1) { state.k1 ^= tail; bmix(state); } state.h2 ^= key.length * 8; state.h1 += state.h2; state.h2 += state.h1; state.h1 = fmix(state.h1); state.h2 = fmix(state.h2); state.h1 += state.h2; state.h2 += state.h1; return new long[] { state.h1, state.h2 }; } /** * Hash a value using the x64 64 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 64 bit hashed key */ public static long MurmurHash3_x64_64(final long[] key, final int seed) { // Exactly the same as MurmurHash3_x64_128, except it only returns state.h1 State state = new State(); state.h1 = 0x9368e53c2f6af274L ^ seed; state.h2 = 0x586dcd208f7cd3fdL ^ seed; state.c1 = 0x87c37b91114253d5L; state.c2 = 0x4cf5ad432745937fL; for (int i = 0; i < key.length / 2; i++) { state.k1 = key[i * 2]; state.k2 = key[i * 2 + 1]; bmix(state); } long tail = key[key.length - 1]; if (key.length % 2 != 0) { state.k1 ^= tail; bmix(state); } state.h2 ^= key.length * 8; state.h1 += state.h2; state.h2 += state.h1; state.h1 = fmix(state.h1); state.h2 = fmix(state.h2); state.h1 += state.h2; state.h2 += state.h1; return state.h1; } /** * Hash a value using the x64 32 bit variant of MurmurHash3 * * @param key value to hash * @param seed random value * @return 32 bit hashed key */ public static int MurmurHash3_x64_32(final long[] key, final int seed) { return (int) (MurmurHash3_x64_64(key, seed) >>> 32); } @Override public int hash(byte[] payload) { return MurmurHash3_x64_32(payload, 9001); } /** * Hashes a byte array efficiently. * * @param payload a byte array to hash * @return a hash code for the byte array */ public static int hash(long[] payload) { return MurmurHash3_x64_32(payload, 9001); } @Override public int hash(Object o) { if (o instanceof byte[]) return hash((byte[]) o); else if (o instanceof long[]) return hash((long[]) o); else if (o instanceof String) return hash(((String) o).getBytes(ISO_8859_1)); else return hash(o.hashCode()); } @Override public boolean equals(Object other) { return other != null && other.getClass() == getClass(); } @Override public int hashCode() { return 0; } @Override public String toString() { return "MurmurHash3"; } }
package com.travelsky.pss.react.cn.fzjh.consistenthash; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; import com.travelsky.pss.react.cn.fzjh.jk.HashFunction; // 源代码基于Cloudera Flume项目中的com.cloudera.util.consistenthash.ConsistentHash修改 // Cloudera Flume项目地址: https://github.com/cloudera/flume /** * This is an implementation of a consistent hash. T is the type of a bin. * * It is mostly copied from Tom White's implementation found here: * http://www.lexemetech.com/2007/11/consistent-hashing.html * * Blog comments mention that there may be a bug in this implementation -- if * there is a key collision we may lose bins. Probabilistically this is small, * and even smaller with a higher more replication factor. This could be made * even rarer by enlarging the circle by using Long instead of Integer. * * getNBins and getNUniqBins return ordered lists of bins for a particular * object. This is useful for assigning backups if the first bin fails. * * This datastructure is not threadsafe. */ public class ConsistentHash<T> { // when looking for n unique bins, give up after a streak of MAX_DUPES // duplicates public final static int MAX_DUPES = 10; // # of times a bin is replicated in hash circle. (for better load balancing) private final int numberOfReplicas; private final HashFunction hashFunction; private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>(); public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<T> nodes) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; for (T node : nodes) { addBin(node); } } /** * Add a new bin to the consistent hash * * This assumes that the bin's toString method is immutable. * * This is not thread safe. */ public void addBin(T bin) { for (int i = 0; i < numberOfReplicas; i++) { // The string addition forces each replica to have different hash circle.put(hashFunction.hash(bin.toString() + i), bin); } } /** * Remove a bin from the consistent hash * * This assumes that the bin's toString method is immutable. * * This is not thread safe. */ public void removeBin(T bin) { for (int i = 0; i < numberOfReplicas; i++) { // The string addition forces each replica to be different. This needs // to resolve to the same keys as addBin. circle.remove(hashFunction.hash(bin.toString() + i)); } } /** * This returns the closest bin for the object. If the object is the bin it * should be an exact hit, but if it is a value traverse to find closest * subsequent bin. */ public T getBinFor(Object key) { if (circle.isEmpty()) { return null; } int hash = hashFunction.hash(key); T bin = circle.get(hash); if (bin == null) { // inexact match -- find the next value in the circle SortedMap<Integer, T> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); bin = circle.get(hash); } return bin; } /** * This returns the closest n bins in order for the object. There may be * duplicates. */ public List<T> getNBinsFor(Object key, int n) { if (circle.isEmpty()) { return Collections.<T> emptyList(); } List<T> list = new ArrayList<T>(n); int hash = hashFunction.hash(key); for (int i = 0; i < n; i++) { if (!circle.containsKey(hash)) { // go to next element. SortedMap<Integer, T> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } list.add(circle.get(hash)); // was a hit so we increment and loop to find the next bin in the // circle hash++; } return list; } /** * This returns the closest n bins in order for the object. There is extra * code that forces the bin values to be unique. * * This will return a list that has all the bins (and is smaller than n) if n * > number of bins. */ public List<T> getNUniqueBinsFor(Object key, int n) { if (circle.isEmpty()) { return Collections.<T> emptyList(); } List<T> list = new ArrayList<T>(n); int hash = hashFunction.hash(key); int duped = 0; for (int i = 0; i < n; i++) { if (!circle.containsKey(hash)) { // go to next element. SortedMap<Integer, T> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } T candidate = circle.get(hash); if (!list.contains(candidate)) { duped = 0; list.add(candidate); } else { duped++; i--; // try again. if (duped > MAX_DUPES) { i++; // we've been duped too many times, just skip to next, returning // fewer than n } } // find the next element in the circle hash++; } return list; } }
2.4 一致性hash测试效果
applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <context:annotation-config /> <!-- 扫描包 --> <context:component-scan base-package="com.travelsky.pss.react.cn.fzjh" /> <bean id="weightedRoundRoBinLoadBalance" class="com.travelsky.pss.react.cn.fzjh.loadBalance.rule.WeightedRoundRoBinLoadBalance" init-method="init" /> <bean id="consistentHashLoadBalance" class="com.travelsky.pss.react.cn.fzjh.loadBalance.rule.ConsistentHashLoadBalance" init-method="init" /> <!-- 配置动态加载配置文件 --> <bean id="dynamicUploadConfig" class="org.apache.commons.configuration.PropertiesConfiguration" init-method="load"> <property name="file" value="file:config/service.properties" /> <property name="reloadingStrategy"> <bean class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"> <property name="refreshDelay" value="60000"></property> </bean> </property> </bean> </beans>
测试类
package com.travelsky.pss.react_cn_fzjh; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.travelsky.pss.react.cn.fzjh.loadBalance.rule.ConsistentHashLoadBalance; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) public class TestConsistentHashLoadBalance { @Autowired private transient ConsistentHashLoadBalance consistentHashLoadBalance; @Test public void testConsistentLoadBalance() throws InterruptedException{ ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(15, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)); for(int i=1;i<=15;i++){ final int index = i; poolExecutor.execute(new Runnable() { @Override public void run() { System.out.println("当前线程: " + Thread.currentThread().getName() + ",serviceKey:" + "127.0.0." + index + ",选择服务:" + consistentHashLoadBalance.chooseServerInstance("127.0.0." + index + "#1.0.0" ).getServerName()); } }); } } } ////////////////////////////////////多跑几次效果如下//////////////////////////////////////////// 当前线程: pool-1-thread-2,serviceKey:127.0.0.2,选择服务:127.0.0.2 当前线程: pool-1-thread-5,serviceKey:127.0.0.5,选择服务:127.0.0.3 当前线程: pool-1-thread-10,serviceKey:127.0.0.10,选择服务:127.0.0.1 当前线程: pool-1-thread-9,serviceKey:127.0.0.9,选择服务:127.0.0.3 当前线程: pool-1-thread-3,serviceKey:127.0.0.3,选择服务:127.0.0.1 当前线程: pool-1-thread-4,serviceKey:127.0.0.4,选择服务:127.0.0.2 当前线程: pool-1-thread-1,serviceKey:127.0.0.1,选择服务:127.0.0.1 当前线程: pool-1-thread-14,serviceKey:127.0.0.14,选择服务:127.0.0.1 当前线程: pool-1-thread-6,serviceKey:127.0.0.6,选择服务:127.0.0.1 当前线程: pool-1-thread-13,serviceKey:127.0.0.13,选择服务:127.0.0.2 当前线程: pool-1-thread-7,serviceKey:127.0.0.7,选择服务:127.0.0.2 当前线程: pool-1-thread-8,serviceKey:127.0.0.8,选择服务:127.0.0.2 当前线程: pool-1-thread-11,serviceKey:127.0.0.11,选择服务:127.0.0.2 当前线程: pool-1-thread-15,serviceKey:127.0.0.15,选择服务:127.0.0.1 当前线程: pool-1-thread-12,serviceKey:127.0.0.12,选择服务:127.0.0.1 ////////////////////////////////////////////////////////////////// 当前线程: pool-1-thread-1,serviceKey:127.0.0.1,选择服务:127.0.0.1 当前线程: pool-1-thread-2,serviceKey:127.0.0.2,选择服务:127.0.0.2 当前线程: pool-1-thread-10,serviceKey:127.0.0.10,选择服务:127.0.0.1 当前线程: pool-1-thread-5,serviceKey:127.0.0.5,选择服务:127.0.0.3 当前线程: pool-1-thread-14,serviceKey:127.0.0.14,选择服务:127.0.0.1 当前线程: pool-1-thread-6,serviceKey:127.0.0.6,选择服务:127.0.0.1 当前线程: pool-1-thread-3,serviceKey:127.0.0.3,选择服务:127.0.0.1 当前线程: pool-1-thread-7,serviceKey:127.0.0.7,选择服务:127.0.0.2 当前线程: pool-1-thread-11,serviceKey:127.0.0.11,选择服务:127.0.0.2 当前线程: pool-1-thread-15,serviceKey:127.0.0.15,选择服务:127.0.0.1 当前线程: pool-1-thread-9,serviceKey:127.0.0.9,选择服务:127.0.0.3 当前线程: pool-1-thread-13,serviceKey:127.0.0.13,选择服务:127.0.0.2 当前线程: pool-1-thread-4,serviceKey:127.0.0.4,选择服务:127.0.0.2 当前线程: pool-1-thread-8,serviceKey:127.0.0.8,选择服务:127.0.0.2 当前线程: pool-1-thread-12,serviceKey:127.0.0.12,选择服务:127.0.0.1
可以看到,同一个请求经过hash后找到的处理请求的服务器是同一台。
2.5 加权轮询
对一些性能高,负载低的服务器,我们可以给它更高的权值,以便处理更多的请求。
package com.travelsky.pss.react.cn.fzjh.loadBalance.rule; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.travelsky.pss.react.cn.fzjh.abstrac.AbstractLoadBalance; import com.travelsky.pss.react.cn.fzjh.rule.DynamicUploadRule; import com.travelsky.pss.react.cn.fzjh.vo.ServiceInstance; //按照服务器权重的赋值均衡算法 @Service("weightedRoundRoBinLoadBalance") public class WeightedRoundRoBinLoadBalance extends AbstractLoadBalance { protected final Logger logger = LoggerFactory .getLogger(WeightedRoundRoBinLoadBalance.class); @Resource(name="dynamicUploadRule") private DynamicUploadRule dynamicUploadRule; // 所有服务器负载因子的最大公约数 private int gcd; // 负载因子的最大值 private int max; // 轮询周期 private int cycle; // 当前使用的轮询的索引值 private int currentIndex = -1; /** * 一个轮询周期的服务集合,长度为负载因子除以最大公约数相加确定,可重复,AtomicReference保证原子操作 */ private AtomicReference<List<String>> WRRList = new AtomicReference<List<String>>(); @Override public void init() { super.init(); buildWRRList(); } @Override public ServiceInstance chooseServerInstance() { if (!isNotEmpty(WRRList.get())) { logger.info("还未建立起权值轮询服务集合,采用随机算法返回可利用的服务"); return super.chooseServerInstance(); } ServiceInstance serviceInstance = null; synchronized (this) { int index = 0; while (index < cycle && null == serviceInstance) { currentIndex = (currentIndex + 1) % WRRList.get().size(); String serviceName = WRRList.get().get(currentIndex); serviceInstance = getServiceInstanceByServiceName(serviceName); if (null == serviceInstance || serviceInstance.isIsolated()) { index++; } } } return serviceInstance; } /** * 初始化可利用的轮询周期内的服务集合 */ private void buildWRRList() { boolean isGetSucc = false; if (!getServiceInstanceList().isEmpty()) { logger.info("获取的服务列表不为空,开始初始化各个服务器对应的负载因子"); isGetSucc = calcLoadFactors(); } if (isGetSucc) { // 生成轮询的server集合 int total = getServiceInstanceList().size(); // 上一次服务库索引 int i = -1; // 上一次权值 int cw = 0; List<String> newWrrList = new ArrayList<String>(total); // 下面的算法,第一次分配时,把当前权重设置为最大权重,服务器中权重大于等于当前权重的,都会分配负载; // 第一轮分配完后,当前权重减最大公约数,进行第二轮分配; // 如此循环到当前权重为负,则把当前权重重置为最大权重,重新进行循环。一直到轮回周期 for (int j = 0; j < cycle; j++) { while (true) { i = (i + 1) % total;// 服务器下标 if (i == 0) { cw = cw - gcd;// 获得处理的权重 if (cw <= 0) { cw = max; // 如果没有需要分配的服务 if (cw == 0) { newWrrList.add(null); break; } } } ServiceInstance serviceInstance = getServiceInstanceList() .get(i); String serverName = serviceInstance.getServerName(); // 如果被轮询到的server权值满足要求记录servername. if(serviceInstance.getQzValue() >= cw){ newWrrList.add(serverName); break; } } } WRRList.set(newWrrList); } } /** * 初始化最大公约数,最大权值,轮询周期 * @return */ private boolean calcLoadFactors() { // 获取所有服务器列表,根据列表返回一开始设置的各服务器负载因子(这里可以是动态文件,可以是查询DB等方式) /** * 1:获取所有的服务名 2:根据服务名获取不同的服务设置的负载因子 */ // 获取所有服务的负载因子 List<Integer> factors = getDefault(); if (null == factors || factors.size() == 0) { return false; } // 计算最大公约数 eg:10,20的最大公约数是10 gcd = calcMaxGCD(factors); max = calcMaxValue(factors); cycle = calcCycle(factors, gcd); return true; } /** * 计算轮回周期,每个因子/最大公约数之后相加 * eg:100/100 + 200/100 + 300/100 = 6 * @param factors 存储所有的负载因子 * @param gcd 最大公约数 * @return */ private int calcCycle(List<Integer> factors, int gcd) { int cycle = 0; for (int i = 0; i < factors.size(); i++) { cycle += factors.get(i) / gcd; } return cycle; } /** * 计算负载因子最大值 * * @param factors * @return */ private int calcMaxValue(List<Integer> factors) { int max = 0; for (int i = 0; i < factors.size(); i++) { if (factors.get(i) > max) { max = factors.get(i); } } return max; } /** * 计算最大公约数 * @param factors * @return */ private int calcMaxGCD(List<Integer> factors) { int max = 0; for (int i = 0; i < factors.size(); i++) { if (factors.get(i) > 0) { max = divisor(factors.get(i), max); } } return max; } /** * 使用辗转相减法计算 * @param m 第一次参数 * @param n 第二个参数 * @return int最大公约数 */ private int divisor(int m, int n) { if (m < n) { int temp; temp = m; m = n; n = temp; } if (0 == n) { return m; } return divisor(m - n, n); } /** * 获取默认的负载因子 * @param serviceNames 服务名 * @return */ private List<Integer> getDefault() { List<Integer> list = new ArrayList<Integer>(); List<ServiceInstance> instances = dynamicUploadRule.getServiceInstanceRule(); for (final ServiceInstance serviceInstance : instances) { list.add(serviceInstance.getQzValue()); } return list; } /** * 判断非空 * @param list * @return */ private boolean isNotEmpty(List<String> list) { return null != list && list.size() > 0; } }
2.6 加权轮询算法实现效果
package com.travelsky.pss.react_cn_fzjh; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.travelsky.pss.react.cn.fzjh.loadBalance.rule.WeightedRoundRoBinLoadBalance; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) public class TestWeightRoundRoBinLoadBalance { @Autowired private transient WeightedRoundRoBinLoadBalance weightedRoundRoBinLoadBalance; @Test public void testWeightedRoundRoBinLoadBalance(){ ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(15, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)); for(int i=1;i<=15;i++){ poolExecutor.execute(new Runnable() { @Override public void run() { System.out.println("当前线程: " + Thread.currentThread().getName() + "选择服务:" + weightedRoundRoBinLoadBalance.chooseServerInstance().getServerName()); } }); } } } ///////////////////////////////////////效果///////////////////////////////////////// 当前线程: pool-1-thread-2选择服务:127.0.0.2 当前线程: pool-1-thread-1选择服务:127.0.0.2 当前线程: pool-1-thread-5选择服务:127.0.0.3 当前线程: pool-1-thread-6选择服务:127.0.0.1 当前线程: pool-1-thread-3选择服务:127.0.0.2 当前线程: pool-1-thread-9选择服务:127.0.0.3 当前线程: pool-1-thread-10选择服务:127.0.0.2 当前线程: pool-1-thread-13选择服务:127.0.0.2 当前线程: pool-1-thread-11选择服务:127.0.0.1 当前线程: pool-1-thread-7选择服务:127.0.0.3 当前线程: pool-1-thread-15选择服务:127.0.0.2 当前线程: pool-1-thread-4选择服务:127.0.0.3 当前线程: pool-1-thread-8选择服务:127.0.0.2 当前线程: pool-1-thread-12选择服务:127.0.0.2
源代码下载: