redis集群实现(四) 数据的和槽位的分配
class Node{
private long time; // 创建时间
private String name; // 名称
private int flag; // 标示主从,或者在线状态
private String ip;
private int port;
private ClusterState clusterState; // 集群信息
}
class ClusterState{
private Node myself;
private int state;
private int size; // 知道包含一个槽的节点数量
private Dictionary nodes; // 集群节点名单
}
127.0.0.1:7000> set key value
-> Redirected to slot [12539] located at 192.168.39.153:7002
OK
192.168.39.153:7002> get key
"value"
192.168.39.153:7002>struct redisCommand redisCommandTable[] = {
————————————————————————
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
————————————————————————
可以看出,set命令会执行setCommand函数进行解析,继续进入setCommand函数查看
void setCommand(redisClient *c) {
int j;
robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = REDIS_SET_NO_FLAGS;
————————————————————————
// 对value编码
c->argv[2] = tryObjectEncoding(c->argv[2]);
//真正执行set命令的地方
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
继续进入setGenericCommand函数
void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
//参数检查和过期时间的检查
————————————————————————
//在数据库里设置key value
setKey(c->db,key,val);
//设置完成以后的时间通知
————————————————————————
}
接着看数据库的setKey函数
void setKey(redisDb *db, robj *key, robj *val) {
// 添加或覆写数据库中的键值对
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
} else {
dbOverwrite(db,key,val);
}
------------------------------------------------------------------------------------------
当没有在数据库中发现key的时候,我们需要执行dbAdd函数把key-value添加到数据库里。
void dbAdd(redisDb *db, robj *key, robj *val) {
// 赋值key的名字
sds copy = sdsdup(key->ptr);
// 添加键值对到字典中
int retval = dictAdd(db->dict, copy, val);
// 如果键已经存在,那么停止
redisAssertWithInfo(NULL,key,retval == REDIS_OK);
// 如果开启了集群模式,就把键保存到槽里面
if (server.cluster_enabled) slotToKeyAdd(key);
}
继续进入slotToKeyAdd函数
//把键key添加到槽里边
void slotToKeyAdd(robj *key) {
// 通过字符串key计算出键对应的槽
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
// 将槽 slot 作为分数,键作为成员,添加到 slots_to_keys 跳跃表里面
zslInsert(server.cluster->slots_to_keys,hashslot,key);
incrRefCount(key);
}
keyHashSlot是一个哈希函数,通过key映射到一个0-16384的整数,我们来看一下实现
unsigned int keyHashSlot(char *key, int keylen) {
//start 和end
int s, e;计算key字符串对应的映射值,redis采用了crc16函数然后与0x3FFF取低16位的方法。crc16以及md5都是比较常用的根据key均匀的分配的函数,就这样,用户传入的一个key我
们就映射到一个槽上,然后经过gossip协议,周期性的和集群中的其他节点交换信息,最终整个集群都会知道key在哪一个槽上。
(1) 绝大部分请求是纯粹的内存操作(非常快速)
(2) 采用单线程,避免了不必要的上下文切换和竞争条件
(3) 非阻塞IO - IO多路复用
1)单线程的问题
无法发挥多核CPU性能,单进程单线程只能跑满一个CPU核
可以通过在单机开多个Redis实例来完善
可以通过数据分片来增加吞吐量,问题(不支持批量操作、扩缩容复杂等)
2)多线程的问题
多线程处理可能涉及到锁
多线程处理会涉及到线程切换而消耗CPU