模拟客户端分布式的一致性hash

本文探讨了一种使用一致性Hash实现的客户端分布式缓存应用,包括数据存储、获取及故障转移策略。通过引入虚拟节点增强哈希均匀性,确保数据在多个服务器间高效分配。并提出了基于消息队列和Zookeeper的解决方案来通知分布式客户端缓存服务器的状态变化,确保系统的高可用性和一致性。

模拟了一个利用一致性hash的客户端分布式缓存的应用场景,实现了put操作,其实get操作也是一样:


<span style="font-size:18px;">public class ConHashShard { 

	private TreeMap<Long, Server> servers; 			// 增加虚拟节点后的hash值和server hash值的映射
	private final int VIRTUAL_NOde = 100; 			// 每个server节点的虚拟节点个数

	public ConHashShard(List<Server> serverlist) {
		init(serverlist);
	}

	/**
	 * 这里可以把server当做缓存服务器
	 * @param args
	 */
	public static void main(String[] args) {
		Server s1 = new Server("server1", "192.168.1.1:80");
		Server s2 = new Server("server2", "192.168.1.2:80");
		Server s3 = new Server("server3", "192.168.1.3:80");
		Server s4 = new Server("server4", "192.168.1.4:80");
		List<Server> servers = new ArrayList<Server>(); // 真实机器节点
		servers.add(s1);
		servers.add(s2);
		servers.add(s3);
		servers.add(s4);		//一共有四个server
		ConHashShard sh = new ConHashShard(servers);
		System.out.println("sharding 成功,总共有4个server,每个server有100个虚拟节点");
		
		//通过一致性hash算法,像server发送数据
		sh.put("dong11","value1");
		sh.put("东","value2");
		sh.put("key33","value3");
		sh.put("key44","value4");
		sh.put("key55","value5");
		sh.put("key66","value6");
		sh.put("key77","value7");
		sh.put("key88","value8");
		
		//模拟有一个server down了
		sh.delServer(s1);
		System.out.println("删除server1");
		sh.put("key55","value5");
		sh.put("key66","value6");
		sh.put("key77","value7");
		
		//server 重新启动
		sh.addServer(s1);
		System.out.println("增加server1:  ");
		
		sh.put("key55","value5");
		sh.put("key66","value6");
		sh.put("key77","value7");
		
	}

	private void delServer(Server server) {
		for (int n = 0; n < VIRTUAL_NOde; n++) {
			servers.remove(hash("SHARD-" + server.getName() + "-NODE-" + n));
		}
	}

	private void init(List<Server> serverlist) {		 				// 初始化一致性hash环
		servers = new TreeMap<Long, Server>();
		for (Server server : serverlist) { 		// 每个真实机器节点都需要关联虚拟节点
			for (int n = 0; n < VIRTUAL_NOde; n++)
				//hash("SHARD-" + shardInfo.name + "-NODE-" + n)把不同的hash尽量均匀
				servers.put(hash("SHARD-" + server.getName() + "-NODE-" + n), server);
		}
	}

	private void addServer(Server s) {
		for (int n = 0; n < VIRTUAL_NOde; n++)
			servers.put(hash("SHARD-" + s.getName() + "-NODE-" + n), s);

	}



	public void put(String key,String value) {
		//方法用于返回此映射,其键大于或等于fromKey的部分视图。
		//返回的映射受此映射支持,因此改变返回映射反映在此映射中,反之亦然。
		/**
		 * http://www.yiibai.com/java/util/treemap_tailmap.html
		 * 
		 * public class TreeMapDemo {
   				public static void main(String[] args) {
      				TreeMap<Integer, String> treemap = new TreeMap<Integer, String>();
      				SortedMap<Integer, String> treemapincl = new TreeMap<Integer, String>();
            
      	`			treemap.put(2, "two");
      				treemap.put(1, "one");
      				treemap.put(3, "three");
      				treemap.put(6, "six");
      				treemap.put(5, "five");      
      
      				System.out.println("Getting tail map");
      				treemapincl=treemap.tailMap(3);
      				System.out.println("Tail map values: "+treemapincl);      
   				}    
			}
			上面的代码执行入下:
			Getting tail map
			Tail map values: {3=three, 5=five, 6=six}
		 */
		SortedMap<Long, Server> tail = servers.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点
		if (tail.size() == 0) {
			return;
		}
		Server server = tail.get(tail.firstKey());
		server.put(key, value);
		System.out.println("把key: " + key + "   vlaue:  " + value + "  存入了: " + tail.get(tail.firstKey()));
		System.out.println("数据的hash值为:  " +  hash(key) + "  server的hash值为:  " + 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;
	}
}</span>

<span style="font-size:18px;">package com.dong.istudy.consistenthash;

import java.util.HashMap;
import java.util.Map;

</span>
<span style="font-size:18px;">//缓存服务器</span>
<span style="font-size:18px;">public class Server {

	private String name;
	private String ip;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	private Map<String,String> map = new HashMap<String,String>();

	public Server(String name, String ip) {
		this.name = name;
		this.ip = ip;
	}

	@Override
	public String toString() {
		return name + "-" + ip;
	}
	
	public void put(String key,String value) {
		map.put(key, value);
	}
	
	public String get(String key) {
		return map.get(key);
	}
}
</span>

上面的程序模拟了利用客户端进行分布式的缓存方案。但是有一个问题,如果客户端也是分布式的怎么办,也就是说如果有一个缓存server down掉了,如何确保每一个客户端都得到了通知?

解决方案如下:

1,把用到了所有的缓存server的配置信息存储到消息队列比如Activemq,客户端采用发布订阅的模式监听server配置信息的变化;

2,解决方案一看似解决了问题,但是Activemq这个时候也形成了单点怎么办?可以把Activemq做成分布式;

3,那么如果保证Activemq的中配置信息数据的强一致性呢?比如说,有缓存server down掉,或者是主动的增加,移除一个缓存server 所造成的数据一致性的问题。因此这个时候可以采用zookeeper,zookeeper正可以保证强一致性和高可用性(zookeeper集群),而存储配置信息也正是zookeeper的一个典型的应用场景。



参考:

http://blog.youkuaiyun.com/haitao111313/article/details/7537799

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值