目录
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能,各个memcached不会互相通信以共享数据,这完全取决memcached api的客户端所使用的路由算法。
路由算法
方式一:缓存服务器全部缓存
特征:当一个key-value新增时,将数据加入全部memcached服务器中,然后查找时,可以根据负载均衡算法分配到不同的memcached服务器上。
缺点:每台memcached都缓存了全部数据,数据冗余
String address1 = "127.0.0.1:11211";
String address2 = "127.0.0.1:11212";
String address3 = "127.0.0.1:11213";
MemcachedClientBuilder builder1 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address1));
MemcachedClientBuilder builder2 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address2));
MemcachedClientBuilder builder3 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address3));
MemcachedClient client1 = builder1.build();
MemcachedClient client2 = builder2.build();
MemcachedClient client3 = builder3.build();
MemcachedClient[] clients = new MemcachedClient[] { client1, client2, client3 };
// 轮询算法
int k = 0;
String key = "hello";
String value = "world";
// 将缓存增加到全部的缓存服务器上
for (int i = 0; i < clients.length; i++) {
clients[i].add(key, 0, value);
}
// 采用轮询,实现负载均衡
// A请求到了0号服务器,B请求就去1号服务器,下一个请求去2号服务器,然后在重新来0号服务器
k = (k + 1) % clients.length;
clients[k].get(key);
// 每台服务器都缓存了全部的数据,冗余,还得轮询保证到每台缓存服务器的数量大致相同
方式二:hash取模算法
特点:将key的hash值对服务器的数量进行取余,余数为几就去编号为此余数的缓存服务器。
优点:每台服务器只缓存key取余的为自己编号的值
缺点:当新增或者删除服务器时,导致服务器全部的缓存都不能命中,需要清除全部服务器的数据
String address1 = "127.0.0.1:11211";
String address2 = "127.0.0.1:11212";
String address3 = "127.0.0.1:11213";
MemcachedClientBuilder builder1 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address1));
MemcachedClientBuilder builder2 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address2));
MemcachedClientBuilder builder3 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address3));
MemcachedClient client1 = builder1.build();
MemcachedClient client2 = builder2.build();
MemcachedClient client3 = builder3.build();
MemcachedClient[] clients = new MemcachedClient[] { client1, client2, client3 };
// hash取余算法
String key = "hello";
String value = "world";
// 存一个key时,先将key按照hash算法算出hash值,然后对缓存服务器的台数进行取余
int hash = key.hashCode() % clients.length;
clients[hash].add(key, 0, value);
// 查询也一样,对key的hash值进行取余,到对应的memcached服务器去数据
clients[hash].get(key);
// 当一台服务器挂了之后只影响那一台的缓存服务器的缓存,其他机器不影响
// 由于key的分布不均与,可能导致有些服务器存储太多的数据,需要扩容,但是新增,减少服务器的数量会导致缓存命中不了,
方式三:一致性hash算法
一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形)整个空间按顺时针方向组织。0和232-1在零点中方向重合。下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位址哈希置,接下来使用如下算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
优点:解决了新增或者删除服务器时,导致全部缓存不命中问题,当新增或者删除时,只影响自己的前面一台服务器的缓存服务器。一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。有可能导致数据倾斜,一台缓存服务器存储大量的数据,其他服务器只存储少量的数据,优化,增加虚拟节点,将服务器的名称,或者对象进行取余,然后也放入环中,尽量使数据均匀的分布在各台服务器
class MemcachedMapper {
private Integer hash;
private MemcachedClient client;
public MemcachedMapper() {
}
public MemcachedMapper(Integer hash, MemcachedClient client) {
this.hash = hash;
this.client = client;
}
public Integer getHash() {
return hash;
}
public void setHash(Integer hash) {
this.hash = hash;
}
public MemcachedClient getClient() {
return client;
}
public void setClient(MemcachedClient client) {
this.client = client;
}
}
String address1 = "127.0.0.1:11211";
String address2 = "127.0.0.1:11212";
String address3 = "127.0.0.1:11213";
MemcachedClientBuilder builder1 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address1));
MemcachedClientBuilder builder2 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address2));
MemcachedClientBuilder builder3 = new XMemcachedClientBuilder(AddrUtil.getAddresses(address3));
MemcachedClient client1 = builder1.build();
MemcachedClient client2 = builder2.build();
MemcachedClient client3 = builder3.build();
MemcachedClient[] clients = new MemcachedClient[] { client1, client2, client3 };
// 一致性hash算法
String key = "hello";
String value = "world";
List<MemcachedMapper> memcachedMappers = new ArrayList<MemcachedMapper>();
memcachedMappers.add(new MemcachedMapper(address1.hashCode(), client1));
memcachedMappers.add(new MemcachedMapper(address2.hashCode(), client2));
memcachedMappers.add(new MemcachedMapper(address3.hashCode(), client3));
memcachedMappers.sort(new Comparator<MemcachedMapper>() {
public int compare(MemcachedMapper o1, MemcachedMapper o2) {
return o1.getHash() - o2.getHash();
}
});
int keyhash = key.hashCode();
int i = 0;
for(;i < memcachedMappers.size();i++) {
if(keyhash < memcachedMappers.get(i).getHash()) {
break;
}
}
//形成闭环
if(i == memcachedMappers.size()) {
i = 0;
}
//插入
memcachedMappers.get(i).getClient().add(key, 0, value);
i = 0;
for(;i < memcachedMappers.size();i++) {
if(keyhash < memcachedMappers.get(i).getHash()) {
break;
}
}
//形成闭环
if(i == memcachedMappers.size()) {
i = 0;
}
//查询也一样
memcachedMappers.get(i).getClient().get(key);
}