Redis集群(Cluster)
Redis集群(Cluster)
redis集群是一个又多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片的特性。
redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展。
根据官方文档称,可线性扩展到上万个节点(推荐不超过1W个节点)。
redis集群的性能和高可用性均优于哨兵模式,且配置简单。
原理分析
Redis Cluster将所有数据划分为16384个槽位(slots),每个节点负责其中一部分槽位。槽位信息存储在每一个节点中。
当Redis Cluster的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。如此一来当客户端要查找某个key时,可以直接地你个为到目标节点。
而因为槽位的信息可能会存在客户端与服务器不一致的情况,因此还实现了纠正机制来实现槽位信息的校验调整。
槽位定位
Redis Cluster默认对key使用crc16算法进行hash后得到一个整数值,然后用这个整数值对16384个槽位进行取模后获取具体的槽位。
# JedisClusterCRC16 核心源码实现
package redis.clients.util;
public final class JedisClusterCRC16 {
private static final int[] LOOKUP_TABLE = new int[]{0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153, 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757, 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770, 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395, 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752, 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, 16050, 3793, 7920};
private JedisClusterCRC16() {
throw new InstantiationError("Must not instantiate this class");
}
public static int getSlot(String key) {
key = JedisClusterHashTagUtil.getHashTag(key);
return getCRC16(key) & 16383;
}
/**
* 获取key所在的槽位
*/
public static int getSlot(byte[] key) {
int s = -1;
int e = -1;
boolean sFound = false;
for(int i = 0; i < key.length; ++i) {
if (key[i] == 123 && !sFound) {
s = i;
sFound = true;
}
if (key[i] == 125 && sFound) {
e = i;
break;
}
}
return s > -1 && e > -1 && e != s + 1 ? getCRC16(key, s + 1, e) & 16383 : getCRC16(key) & 16383;
}
public static int getCRC16(byte[] bytes, int s, int e) {
int crc = 0;
for(int i = s; i < e; ++i) {
crc = crc << 8 ^ LOOKUP_TABLE[(crc >>> 8 ^ bytes[i] & 255) & 255];
}
return crc & '\uffff';
}
public static int getCRC16(byte[] bytes) {
return getCRC16(bytes, 0, bytes.length);
}
public static int getCRC16(String key) {
byte[] bytesKey = SafeEncoder.encode(key);
return getCRC16(bytesKey, 0, bytesKey.length);
}
}
跳转重定位
当客户端像一个节点发出指令,而该节点发现key所在槽位已经不归自己管理时,它会向客户端发送一个特殊的跳转指令,指令携带了目标操作的节点地址让客户端去该节点进行操作。
客户端收到指令后连接新的节点进行操作,并且会同步更新本地的槽位映射表缓存,后续所有key使用新槽位映射表数据进行操作。
集群节点间通信
维护继集群元数据有两种方式:集中式 和 gossip,而Redis Cluster节点间采用gossip协议进行通信。
集中式
集中式协议的优点就是对元数据的更新和读取时效性好,一旦元数据出现变更立即更新到集中式的存储中,其他节点读取时立即就可以感知获取最新的元数据。 但由于所有的元数据集中在一个地方,会存在数据更新、存储照成一点的影响。
很多中间件在使用集中式存储元数据时,通常借助zookeeper来实现。
gossip
gossip协议的优点在于元数据更新比较分散,更新的请求是陆陆续续到达各节点,相比集中式协议没有更新上的压力。但是分散式更新是陆陆续续到达的,有一定的延迟,元数据更新延迟有可能导致集群的额一些操作有一些滞后。
gossip协议会包含各种信息,例如ping、pong、meet、fail等
- meet:节点发送meet给新加入的节点,包含了集群的元数据信息,新节点读取数据就会加入集群中开始与其他节点进行通信;
- ping:每个节点都会经常经常性的给其他节点发送ping命令,其中包含自己的状态还有自己维护的集群元数据,通过ping互相交换元数据;
- pong:对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于消息广播和更新;
- fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点该节点宕机了;