Memcached学习笔记(3)一Memcached分布式之spymemcached实现
一、Java Memcached 客户端
Memcached的客户端目前有3种:
(1)Memcached Client for Java
(2)SpyMemcached
(3)XMemcached
市面上给这三个做了一个比较:
Memcached Client for Java 比 SpyMemcached更稳定、更早、更广泛;
SpyMemcached 比 Memcached Client for Java更高效;
XMemcached 比 SpyMemcache并发效果更好。
当然了,仁者见仁智者见智了。
二、spymemcached用hash一致性算法实现分布式
spymemcached的MemcachedClient类提供了一系列方法如:get,set,add存取对象。这里我就不做过多的阐述了,可以参见官网的API。
上一篇Memcached学习笔记(2)一Memcached分布式介绍过,要实现Memcached的分布式,关键在于客户端分配存储元素到服务器上的算法,比较好的一种算法像“一致性hash算法”,
下面我将用一段简单的代码阐述下此算法的实现思路。
首先:假定我们有五台服务器IP端口如下:
10.10.224.101:11211
10.10.224.102:11211
10.10.224.103:11211
10.10.224.104:11211
10.10.224.102:11211
private List<String> cacheServerAddress;//Memcached服务器地址 private TreeMap<Long, MemcachedClient> memcachedClientMap;//构建MemcachedClient对象map private List<MemcachedClient> memcachedClients; private int numReps = 160;//每个Memcached服务器节点生成的虚拟节点
定义了几个变量,cacheServerAddress存放如上的ip地址信息,
memcachedClientMap为hash一致性算法根据这五台机器构建的
MemcachedClient的treemap
numReps 定义了一个服务器节点生成的虚拟节点数.
第一步:构建由如上五台服务器组成的memcachedClientMap,每个服务器节点产生160个虚拟节点,相当于说由160个key对应同一台memcached服务器。
代码如下:
/** * 设置Memcached客户端 */ private void setMemcachedClients() { try { //根据memcached客户端地址,采用一致性hash算法配置客户端路由,构建一个<key, MemcachedClient>的map if(cacheServerAddress != null && cacheServerAddress.size() > 0) { MemcachedClient client = null; memcachedClients = new ArrayList(); memcachedClientMap = new TreeMap<Long, MemcachedClient>(); for(String address : cacheServerAddress) { client = createMemcachedClient(address); if(client == null) { log.error("setMemcachedClients error address:"+address); return; } memcachedClients.add(client); for(int i=0; i<numReps / 4; i++) { //getKeyForNode方法为这组虚拟结点得到惟一名称 byte[] digest= DefaultHashAlgorithm.computeMd5(getKeyForMemcachedClient(address, i)); /** * Md5是一个16字节长度的数组,将16字节的数组每四个字节一组, 分别对应一个虚拟结点,把虚拟结点四个划分一组的原因*/ for(int h=0; h<4; h++) { Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24) | ((long) (digest[2 + h * 4] & 0xFF) << 16) | ((long) (digest[1 + h * 4] & 0xFF) << 8) | (digest[h * 4] & 0xFF); memcachedClientMap.put(k, client); } } } } }catch (Exception e) { log.error("setMemcachedClients error :"+e); } }
第二步:构建完服务器后,对于需要存储的元素,如何平均分配到以上服务器中。
传一个参数key唯一标示对象的字符串,得到他的hash值,代码如下:
private long hash(String key) { long rv = 0; byte[] bKey = DefaultHashAlgorithm.computeMd5(key); rv = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8) | (bKey[0] & 0xFF); return rv & 0xffffffffL; /* Truncate to 32-bits */ }
返回treemap中大于或等于此hash值的key对应的MemcachedClient,即顺时针方向找到离他最近的节点,代码如下:
private MemcachedClient getMemcachedClientForKey(long hash) { final MemcachedClient client; if (!memcachedClientMap.containsKey(hash)) { //tailMap(key):返回treemap中大于或等于key的对象构建的map SortedMap<Long, MemcachedClient> tailMap = memcachedClientMap.tailMap(hash); if(tailMap == null) { hash = memcachedClientMap.firstKey(); }else { hash = tailMap.firstKey(); } //在JDK1.6中,ceilingKey方法可以返回大于或等于且离它最近的那个key // Long key = memcachedClientMap.ceilingKey(hash); // if(key == null) { // hash = memcachedClientMap.firstKey(); // }else { // hash = key; // } } client = memcachedClientMap.get(hash); return client; }
这样通过hash一致性算法构建的客户端路由可以将对象平均的分配到不同的服务器。添加或删除一个节点服务器,影响都不大。