`
haitaoandroid
  • 浏览: 27482 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

应用于负载均衡的一致性哈希及java实现

 
阅读更多

这几天看了几遍一致性哈希的文章,但是都没有比较完整的实现,因此试着实现了一下,这里我就不讲一致性哈希的原理了,网上很多,以一致性哈希用在负载均衡的实例来说,一致性哈希就是先把主机ip从小大到全部放到一个环内,然后客户端ip来连接的时候,把客户端ip连接到大小最接近客户端ip且大于客户端ip的主机。当然,这里的ip一般都是要先hash一下的。我的程序运行结果如下:

添加客户端,一开始有4个主机,分别为s1,s2,s3,s4,每个主机有100个虚拟主机:
101客户端(hash:-3872430075274208315)连接到主机->s2-192.168.1.2
102客户端(hash:-6461488502093916753)连接到主机->s1-192.168.1.1
103客户端(hash:-3272337528088901176)连接到主机->s3-192.168.1.3
104客户端(hash:7274050343425899995)连接到主机->s2-192.168.1.2
105客户端(hash:6218187750346216421)连接到主机->s1-192.168.1.1
106客户端(hash:-8497989778066313989)连接到主机->s2-192.168.1.2
107客户端(hash:2219601794372203979)连接到主机->s3-192.168.1.3
108客户端(hash:1903054837754071260)连接到主机->s3-192.168.1.3
109客户端(hash:-2425484502654523425)连接到主机->s1-192.168.1.1
删除主机s2-192.168.1.2的变化:
hash(-8497989778066313989)改变到->s4-192.168.1.4
hash(7274050343425899995)改变到->s2-192.168.1.2
hash(-3872430075274208315)改变到->s4-192.168.1.4
hash(7274050343425899995)改变到->s1-192.168.1.1
增加主机s5-192.168.1.5的变化:
hash(1903054837754071260)改变到->s5-192.168.1.5
hash(1903054837754071260)改变到->s5-192.168.1.5
hash(-3272337528088901176)改变到->s5-192.168.1.5
最后的客户端到主机的映射为:
hash(-8497989778066313989)连接到主机->s4-192.168.1.4
hash(-6461488502093916753)连接到主机->s1-192.168.1.1
hash(-3872430075274208315)连接到主机->s4-192.168.1.4
hash(-3272337528088901176)连接到主机->s5-192.168.1.5
hash(-2425484502654523425)连接到主机->s1-192.168.1.1
hash(1903054837754071260)连接到主机->s5-192.168.1.5
hash(2219601794372203979)连接到主机->s3-192.168.1.3
hash(6218187750346216421)连接到主机->s1-192.168.1.1
hash(7274050343425899995)连接到主机->s1-192.168.1.1

看结果可知:一开始添加到9个客户端,连接到主机s1,s2,s3,s4的客户端分别有3,3,3,0个,经过删除主机s2,添加主机s5,最后9个客户端分别连接到主机s1,s2,s3,s4,s5的个数为4,0,1,2,2.这里要说明一下删除主机s2的情况,hash尾号为9995的客户端先连接到s2,再连接到s1,为什么会出现这种情况呢?因为每一个真实主机有n个虚拟主机,删除s2却打印“hash(7274050343425899995)改变到->s2-192.168.1.2”是因为删除了s2的其中一个虚拟主机,跳转到另一个虚拟主机,但还是在s2上,当然,这里是打印中间情况,以便了解,真实的环境是删除了s2后,所有他的虚拟节点都会马上被删除,虚拟节点上的连接也会重新连接到另一个主机的虚拟节点,不会存在这种中间情况。

以下给出所有的实现代码,大家共同学习:

public class Shard<Node> { // S类封装了机器节点的信息 ,如name、password、ip、port等

	static private TreeMap<Long, Node> nodes; // 虚拟节点到真实节点的映射
	static private TreeMap<Long,Node> treeKey; //key到真实节点的映射
	static private List<Node> shards = new ArrayList<Node>(); // 真实机器节点
	private final int NODE_NUM = 100; // 每个机器节点关联的虚拟节点个数
	boolean flag = false;
	
	public Shard(List<Node> shards) {
		super();
		this.shards = shards;
		init();
	}

	public static void main(String[] args) {
//		System.out.println(hash("w222o1d"));
//		System.out.println(Long.MIN_VALUE);
//		System.out.println(Long.MAX_VALUE);
		Node s1 = new Node("s1", "192.168.1.1");
		Node s2 = new Node("s2", "192.168.1.2");
		Node s3 = new Node("s3", "192.168.1.3");
		Node s4 = new Node("s4", "192.168.1.4");
		Node s5 = new Node("s5","192.168.1.5");
		shards.add(s1);
		shards.add(s2);
		shards.add(s3);
		shards.add(s4);
		Shard<Node> sh = new Shard<Shard.Node>(shards);
		System.out.println("添加客户端,一开始有4个主机,分别为s1,s2,s3,s4,每个主机有100个虚拟主机:");
		sh.keyToNode("101客户端");
		sh.keyToNode("102客户端");
		sh.keyToNode("103客户端");
		sh.keyToNode("104客户端");
		sh.keyToNode("105客户端");
		sh.keyToNode("106客户端");
		sh.keyToNode("107客户端");
		sh.keyToNode("108客户端");
		sh.keyToNode("109客户端");
		
		sh.deleteS(s2);
		
		
		sh.addS(s5);
		
		System.out.println("最后的客户端到主机的映射为:");
		printKeyTree();
	}
	public static void printKeyTree(){
		for(Iterator<Long> it = treeKey.keySet().iterator();it.hasNext();){
			Long lo = it.next();
			System.out.println("hash("+lo+")连接到主机->"+treeKey.get(lo));
		}
		
	}
	
	private void init() { // 初始化一致性hash环
		nodes = new TreeMap<Long, Node>();
		treeKey = new TreeMap<Long, Node>();
		for (int i = 0; i != shards.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点
			final Node shardInfo = shards.get(i);

			for (int n = 0; n < NODE_NUM; n++)
				// 一个真实机器节点关联NODE_NUM个虚拟节点
				nodes.put(hash("SHARD-" + shardInfo.name + "-NODE-" + n), shardInfo);
		}
	}
	//增加一个主机
	private void addS(Node s) {
		System.out.println("增加主机"+s+"的变化:");
		for (int n = 0; n < NODE_NUM; n++)
			addS(hash("SHARD-" + s.name + "-NODE-" + n), s);

	}
	
	//添加一个虚拟节点进环形结构,lg为虚拟节点的hash值
	public void addS(Long lg,Node s){
		SortedMap<Long, Node> tail = nodes.tailMap(lg);
		SortedMap<Long,Node>  head = nodes.headMap(lg);
		Long begin = 0L;
		Long end = 0L;
		SortedMap<Long, Node> between;
		if(head.size()==0){
			between = treeKey.tailMap(nodes.lastKey());
			flag = true;
		}else{
			begin = head.lastKey();
			between = treeKey.subMap(begin, lg);
			flag = false;
		}
		nodes.put(lg, s);
		for(Iterator<Long> it=between.keySet().iterator();it.hasNext();){
			Long lo = it.next();
			if(flag){
				treeKey.put(lo, nodes.get(lg));
				System.out.println("hash("+lo+")改变到->"+tail.get(tail.firstKey()));
			}else{
				treeKey.put(lo, nodes.get(lg));
				System.out.println("hash("+lo+")改变到->"+tail.get(tail.firstKey()));
			}
		}
	}
	
	//删除真实节点是s
	public void deleteS(Node s){
		if(s==null){
			return;
		}
		System.out.println("删除主机"+s+"的变化:");	
		for(int i=0;i<NODE_NUM;i++){
			//定位s节点的第i的虚拟节点的位置
			SortedMap<Long, Node> tail = nodes.tailMap(hash("SHARD-" + s.name + "-NODE-" + i));
			SortedMap<Long,Node>  head = nodes.headMap(hash("SHARD-" + s.name + "-NODE-" + i));
			Long begin = 0L;
			Long end = 0L;
			
			SortedMap<Long, Node> between;
			if(head.size()==0){
				between = treeKey.tailMap(nodes.lastKey());
				end = tail.firstKey();
				tail.remove(tail.firstKey());
				nodes.remove(tail.firstKey());//从nodes中删除s节点的第i个虚拟节点
				flag = true;
			}else{
				begin = head.lastKey();
				end = tail.firstKey();
				tail.remove(tail.firstKey());
				between = treeKey.subMap(begin, end);//在s节点的第i个虚拟节点的所有key的集合
				flag = false;
			}
			for(Iterator<Long> it = between.keySet().iterator();it.hasNext();){
				Long lo  = it.next();
				if(flag){
					treeKey.put(lo, tail.get(tail.firstKey()));
					System.out.println("hash("+lo+")改变到->"+tail.get(tail.firstKey()));
				}else{
					treeKey.put(lo, tail.get(tail.firstKey()));
					System.out.println("hash("+lo+")改变到->"+tail.get(tail.firstKey()));
				}
			}
		}
		
	}

	//映射key到真实节点
	public void keyToNode(String key){
		SortedMap<Long, Node> tail = nodes.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点
		if (tail.size() == 0) {
			return;
		}
		treeKey.put(hash(key), tail.get(tail.firstKey()));
		System.out.println(key+"(hash:"+hash(key)+")连接到主机->"+tail.get(tail.firstKey()));
	}
	
	/**
	 *  MurMurHash算法,是非加密HASH算法,性能很高,
	 *  比传统的CRC32,MD5,SHA-1(这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免)
	 *  等HASH算法要快很多,而且据说这个算法的碰撞率很低.
	 *  http://murmurhash.googlepages.com/
	 */
	private static Long hash(String key) {
		
		ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
		int seed = 0x1234ABCD;
		
		ByteOrder byteOrder = buf.order();
        buf.order(ByteOrder.LITTLE_ENDIAN);

        long m = 0xc6a4a7935bd1e995L;
        int r = 47;

        long h = seed ^ (buf.remaining() * m);

        long k;
        while (buf.remaining() >= 8) {
            k = buf.getLong();

            k *= m;
            k ^= k >>> r;
            k *= m;

            h ^= k;
            h *= m;
        }

        if (buf.remaining() > 0) {
            ByteBuffer finish = ByteBuffer.allocate(8).order(
                    ByteOrder.LITTLE_ENDIAN);
            // for big-endian version, do this first:
            // finish.position(8-buf.remaining());
            finish.put(buf).rewind();
            h ^= finish.getLong();
            h *= m;
        }

        h ^= h >>> r;
        h *= m;
        h ^= h >>> r;

        buf.order(byteOrder);
        return h;
	}
	
	static class Node{
		String name;
		String ip;
		public Node(String name,String ip) {
			this.name = name;
			this.ip = ip;
		}
		@Override
		public String toString() {
			return this.name+"-"+this.ip;
		}
	}

}

参考:http://blog.csdn.net/wuhuan_wp/article/details/7010071



分享到:
评论

相关推荐

    一致性hashjava实现

    一致性哈希(Consistent Hashing)是一种分布式哈希算法,主要应用于分布式缓存、负载均衡等领域,以解决在分布式环境中动态添加或删除节点时,尽可能少地改变已有的哈希映射关系。在这个Java实现中,我们看到的是...

    带虚拟节点的一致性哈希

    一致性哈希(Consistent Hashing)是一种分布式哈希算法,主要应用于分布式缓存、负载均衡等领域,例如Memcached和Redis等系统。它解决了在分布式环境中数据分片与节点动态增减时,尽量减少数据迁移的问题。带虚拟...

    一致性哈希算法演示.rar

    一致性哈希算法是一种分布式哈希表(DHT)中用于解决数据分片和负载均衡问题的算法。在大型分布式系统中,例如缓存系统、分布式数据库等,一致性哈希能够确保当节点加入或离开时,尽可能少的数据需要迁移,从而保持...

    Java集群与负载均衡

    4. **Java中的负载均衡**:Java应用可以通过使用内置的负载均衡库(如Jetty的Continuation)或集成外部负载均衡解决方案(如Nginx、HAProxy)实现负载均衡。 5. **云服务负载均衡**:云提供商如AWS的ELB(Elastic ...

    一致性哈希与Chord1

    【一致性哈希与Chord1】是一篇关于分布式哈希算法的文章,主要讨论了一致性哈希和普通哈希的区别,以及如何通过引入...在Java等编程语言中,一致性哈希可以用于实现高效、灵活的分布式服务,如缓存系统、负载均衡器等。

    大型网站架构系列:负载均衡详解

    1. 数据一致性:在分布式系统中,保持数据一致性是个挑战,需要采用一致性哈希、分布式锁等机制。 2. 会话保持(Session Persistence):确保特定用户的会话信息在同一次会话中始终存储在同一台服务器上。 3. 监控与...

    一致性Hash简单实现

    一致性哈希(Consistent Hashing)是一种分布式哈希表(DHT)的算法,它主要应用于分布式缓存、负载均衡等场景,旨在解决在动态扩展或收缩系统规模时,尽量减少数据迁移的问题。在这个简单的实现中,我们将探讨如何...

    Memcached负载均衡Jar包

    Xmemcached是一个功能强大的Java客户端,提供了线程安全、高性能的连接池管理,以及基于一致性哈希的负载均衡算法。一致性哈希是一种分布式哈希表算法,它的主要优点在于当新的服务器加入或离开集群时,只有较少的...

    基于NIO-EPOOL模型netty实现的具备一致性哈希算法的NAT端口映射器

    一致性哈希在NAT端口映射器中的应用主要体现在动态负载均衡上。当有多台服务器作为端口映射服务节点时,一致性哈希可以保证新添加或移除节点时,已有连接的影响最小。它通过计算哈希值,将请求分配到特定节点,使得...

    定时器实现负载均衡

    6. **负载均衡策略**:除了基于服务器负载的简单策略,还可以采用更复杂的策略,如轮询、最少连接数、哈希一致性等,以适应不同场景的需求。 7. **扩展性**:如果需要在更多服务器上扩展,定时器和负载均衡策略应...

    负载均衡案例附mysql数据库

    这包括但不限于:选择合适的负载均衡算法(如轮询、最少连接数、IP哈希等)、数据库的读写分离、主从复制配置、数据库的水平或垂直分区、以及如何通过监控和日志来分析和优化系统性能。此外,还会涉及如何处理...

    nginx+redis负载均衡、session共享

    总的来说,"nginx+redis负载均衡、session共享"的架构提高了Web服务的可扩展性和可靠性,确保了用户在多台服务器间的会话一致性。在实际部署时,还需要考虑如容错处理、性能监控、安全策略等更多细节,以构建更健壮...

    Java 轻量级的集群负载均衡设计

    5. **哈希一致性**:通过哈希函数确保相同请求始终被发送到同一服务器,适用于需要会话持久化的场景。 在"load-balancer-server-master"这个项目中,可能会包含以下组件: 1. **负载均衡器**:核心组件,负责接收...

    Dubbo负载均衡策略.docx

    5. **一致性哈希负载均衡(Consistent Hash Load Balance)** 这种策略考虑到了请求的关联性。一致性哈希会在服务提供者列表上创建一个虚拟环形空间,每个服务提供者对应环上的多个点。请求根据其特定的键值映射到...

    Nginx+tomcat 实现负载均衡session共享demo

    总结,通过`Nginx`实现`Tomcat`集群的负载均衡和`session`共享,不仅可以提高服务的可用性和性能,还能为用户提供一致性的体验。在这个过程中,配置`Nginx`的负载均衡策略和选择合适的`session`共享方案是关键。希望...

    Nginx+Tomcat+Redis实现负载均衡过程中session所需架包

    Nginx支持多种负载均衡策略,如轮询、最少连接数、IP哈希等。例如,可以使用以下配置实现轮询策略: ```nginx http { upstream backend { server server1.example.com; server server2.example.com; # 可以添加...

    实现基于nginx的tomcat负载均衡和集群配置

    本篇文章将详细讲解如何使用Nginx作为反向代理服务器来实现对Tomcat应用服务器的负载均衡和集群配置。 首先,我们需要理解Nginx和Tomcat的角色。Nginx是一款高性能的HTTP和反向代理服务器,常用于处理静态内容和...

    Nginx+Tomcat+memcached实现集群部署、负载均衡session共享.rar

    这个方案结合了Nginx作为反向代理和负载均衡器,Tomcat作为Java应用服务器,以及memcached作为分布式缓存系统,以实现高可用性和性能优化。 首先,Nginx是一款轻量级的Web服务器,常用于反向代理和负载均衡。它的...

    apache,tomcat负载均衡和session复制

    当我们谈论"Apache,tomcat负载均衡和session复制"时,这意味着我们要探讨如何在多台服务器之间分配负载,并确保用户会话的无缝迁移和一致性。 **负载均衡**是解决高并发、高可用性问题的重要策略。它通过将来自...

Global site tag (gtag.js) - Google Analytics