常用的负载均衡算法实现

一:负载均衡的概念
      在大型的分布式架构中,对于负载较高的服务来说,往往对应着多台机器组成的集群,当请求到来的时候,为了将请求均衡的分配给后端服务器
  需要有相应的负载均衡算法来支持,通过相应的负载均衡算法选取一台机器来处理客户端请求,这个过程称为服务的负载均衡。
二:常用的负载均衡算法
       常用的负载均衡算法主要有随机法,轮询法,加权随机,加权轮询,最小连接数,一致性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

  源代码下载:

 

你可能感兴趣的:(负载均衡,一致性hash,加权轮询)