Redis分布式缓存理论知识
Redis分布式缓存理论
需要解决的问题:
- 缓存的快速命中
- 分布式系统的可扩展性
数据分布理论
一. 节点取余
实现思路:使用特点的数据,如Redis的键或用户ID,再根据节点数量N使用公式:** % N ** 计算出哈希值,用来决定数据映射到哪一个节点上。
示例:HashMap
优点:简单
缺点:扩容困难(每次扩容都需要将 键 重新进行 取余 ),所以扩容时采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况( HashMap 就是采用翻倍扩容的方式:如下)
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
观察上面的代码:
if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >=DEFAULT_INITIAL_CAPACITY)
其中,通过移位就把HashMap中Table的长度扩容到原来的两倍,观察上面数据迁移算法可以看出,迁移的逻辑变得很方便。一些键的映射值完全不会改变,另一些键的映射值加上oldCap之后就是新的映射值
二. 一致性哈希分区
实现思路:为系统中的每个节点分配一个token,范围一般在 0 ~ ,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个打印出等于该哈希值的token节点
优点:加入和删除节点只影响哈希环中相邻的节点,对其它节点没有影响
存在的问题:
- 加减节点造成哈希环中的部分数据无法名字
- 节点较少时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量节点的分布式方案
- 普通的一致性哈希分区增减节点时需要增加一倍或减去一半节点才能保证数据和负载均衡
实例:Dynamo (是亚马逊key-value模式的存储平台)
<img src='https://wang_ya_nan.gitee.io/pages/image/2018/06/01/zxjyrq.svg'></svg>
三. 虚拟槽分区
实现思路:使用了哈希分区,使用分散度良好的哈希函数把所有的数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。槽是集群内数据管理和迁移的基本单位。
特点: 降低了节点扩容和收缩难度
Redis虚拟槽的分配(假设有5个节点):
<img src='https://wang_ya_nan.gitee.io/pages/image/2018/06/01/lspimu.svg'></svg>
我的个人博客,有空来坐坐