由于实验室在一个项目中用到了Memcached分布式缓存,自己这段时间也对分布式缓存深入学习了一下,本文就总结一下自己的收获,还是从Memcached是什么谈起吧。
Memcached是什么
Memcached是一款高性能的分布式内存对象缓存系统,使用它可以减少应用系统对数据库的直接访问,减轻了数据库负载,并且提升了应用程序的响应速度。可以将Memcached比作一个巨大的、存储了很多
LRU算法
操作系统的页面置换算法中也包括该算法,这种算法非常适合于缓存系统。它基于一种假设:过去一段时间使用频率最低的数据,将来也会很少使用。下面是我自己实现的一个基于LRU算法的缓存,它实现了基本的缓存功能,不过需要改进的一点是该缓存应该是单例的。
package colin.cache;
/**
*
* @author Colin Wang
* Created on Apr 18, 2015
*/
public class LRUCache {
private static final int DEFAULT_MAX_SIZE = 10;
private Object[] elements;
private int size;
public LRUCache(int maxSize) {
elements = new Object[maxSize];
}
public LRUCache() {
this(DEFAULT_MAX_SIZE);
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
public boolean isFull() {
return size == elements.length;
}
/**
* 返回元素在数组中的位置
*
* @param element
* @return
*/
public int indexOf(Object element) {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) {
return i;
}
}
return -1;
}
public Object push(Object element) {
int index = -1;
if (!isFull() && indexOf(element) == -1) {
// 缓存未满,并且其中不含有待插入的元素
elements[size++] = element;
} else if (isFull() && indexOf(element) == -1) {
// 缓存已满,并且其中不含有待插入的元素
for (int i = 0; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
elements[size - 1] = element;
} else {
index = indexOf(element);
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
elements[size - 1] = element;
}
return index == -1 ? null : elements[index];
}
public void show() {
for (int i = 0; i < size; i++) {
System.out.print(elements[i].toString() + " ");
}
}
}
一致性哈希算法
该算法在分布式缓存系统中应用十分广泛,它的关键之处在于使用环形的哈希空间。首先我们先考虑一下JDK中HashMap的实现,我们知道,HashMap有一个初始容量和装载因子,当HashMap中元素的数量达到了初始容量*装载因子这么多时,HashMap就会进行扩容(变为原来的2倍)然后再哈希,当元素很多时,这种再哈希操作实际上是很消耗资源的。尽管HashMap使用了一种比较优化的方式,比如HashMap的大小总是为2的n次幂(默认大小为16),这样既减少了哈希碰撞的几率(因为2的n次幂-1转化为二进制时所有位均为1,这样就避免了某些位不能发挥作用),又增加了HashMap在定位元素时的效率,比如方法:
static int indexFor(int h, int length) {
return h & (length-1);
}
当length总是2的n次方时,h & (length - 1)运算等价于对length取模。但是这些仍然不能完全弥补所有元素必须全部重新哈希的弱点。比如我们拥有N台缓存服务器,所有的热数据已经通过Hash算法映射到了这N台服务器上,这时候当其中一台缓存服务器挂掉,或者我们又新添了几台缓存服务器进来时,所有已经映射好的数据都将失效,而我们的后端数据库将会承受这一切!所以,一致性Hash算法就派上用场了,上面已经提到,一致性哈希算法使用的是环形的哈希空间,如下图所示:
vcq9o6y1scbk1tDSu7j2u7q05rf+zvHG973htePKp9CnyrGjrLK7u+G1vNbCy/nT0LXEu7q05sr9vt22vMqn0KejrNKyvfa99tDo0qqw0dStwLSxu7u6tOa1vbjDveG148nPtcTK/b7dvMzQ+Lu6tOa1vc/C0ru49suzyrHV67e9z/LA68v81+69/LXEveG148nPvLS/yaGjtbHQwtT20ru49r3htePKsaOszazR+dKy1rvQ6NKqttS63MnZtcTSu7K/t9bK/b7dvfjQ0NTZuf7Po7Ki07PJ5LW90MK1xL3htePJz7y0v8mho86qwcux3MPis/bP1sr9vt231sXksru++dTItcTH6b/2o6yxyMjnvvi087bgyv21xMr9vt22vLG7u7q05rW9wcvSu7j2veG148nPo6y2+Mbky/u1xL3htePU8ru6tOa63MnZtcTK/b7do6zSu9bC0NS5/s+jy+O3qLu5zOGz9sHLPHN0cm9uZz7Q6cTiveG14zwvc3Ryb25nPrXEuMXE7qGjyOfPws28y/nKvqO6PC9wPgoKPHA+PGltZyBzcmM9"https://www.cppentry.com/upload_files/article/57/1_0qlto__.jpg" alt="加入虚拟结点" title="">
A1和A2为结点A的虚拟结点,虚拟结点的hash值计算可以采用对应结点的IP地址加数字后缀的方式,如“192.168.1.1#1”。这样就可以将被散列到A1和A2的数据都缓存进结点A中,从而避免了数据分布不平衡的现象。