redis中hash表的使用

网友投稿 550 2022-09-25

redis中hash表的使用

redis中hash表的使用

假定我们有一个hashmap的逻辑结构,用户编号的为15的人,name是dlf,school是xdu

当我们在redic-cli命令行下敲下 hset id:15 name dlf时

redis里面都发生了什么事情呢?

任何一个使用过redis的用户,即使没有看过redis的源码,想一下这个过程,那么肯定都包含下面这几步

1 socket连接

2 redis-server收到命令信息

3 redis-server解析命令信息(找到对应的命令,及附带的参数)

4 调用对应的命令

5 返回结果

当然在redis-cli发送命令之前,redis-server首先启动,然后加载各种配置,初始化服务器等等。

在这篇博客里,我们只介绍第四点,就是找到redis内部的命令后,并且也已经分析出了参数,如何调用的过程。

dict的图示:

另外,我默认大家都了解redis内部dict的数据结构。

/* * 哈希表节点 */typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,形成链表 struct dictEntry *next;} dictEntry;/* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. *//* * 哈希表 * * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。 */typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used;} dictht;/* * 字典 */typedef struct dict { // 类型特定函数 dictType *type; // 私有数据 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 当 rehash 不在进行时,值为 -1 初始化的时候 就为-1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 目前正在运行的安全迭代器的数量 int iterators; /* number of iterators currently running */} dict;

在redis中不管是列表还是map,数据结构就是list和dictht,但是真正的底层却是redisObject

typedef struct redisObject { // 类型 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ // 引用计数 int refcount; // 指向实际值的指针 void *ptr;} robj;

其中type指得是这个obj里是stirng还是hash,是list还是set

encoding值的是编码格式

OK,在看一张图

下面是我们执行

hset id:14 name zhangsan

hset id:14 school xdu

之后数据在内存中的分配图:

图整体分为3部分,分别是aaa,bbb,ccc大图如下:

好了现在正式开始一步一步调试:

void hsetCommand(redisClient *c) { int update; robj *o; // 取出或新创建哈希对象 //hset id:15 name zhangsan //那么c->argv[1] 指向的就是id:15 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; // 如果需要的话,转换哈希对象的编码 //hashmap底层可以使用dict存储也可以使用list存储 hashTypeTryConversion(o,c->argv,2,3); // 编码 field 和 value 对象以节约空间 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); // 设置 field 和 value 到 hash //将name zhangsan这个两个域写到id:15的value //这个o是空的argv[2][3] 里面放的就是name和zhangsan //新建立entry的value指向这个robj update = hashTypeSet(o,c->argv[2],c->argv[3]); // 返回状态:显示 field-value 对是新添加还是更新 addReply(c, update ? shared.czero : shared.cone); // 发送键修改信号 signalModifiedKey(c->db,c->argv[1]); // 发送事件通知 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id); // 将服务器设为脏 server.dirty++;}robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { //对于hset userId:14 name zhangsan //这个命令来说,key就是user:14 robj *o = lookupKeyWrite(c->db,key); // 对象不存在,创建新的 if (o == NULL) { //第一次写入hset userId:14 ~~ 库里肯定没有user:14对应的robj o = createHashObject(); dbAdd(c->db,key,o); // 对象存在,检查类型 } else { if (o->type != REDIS_HASH) { addReply(c,shared.wrongtypeerr); return NULL; } } // 返回对象 return o;}

请告诉我,在lookupKeyWrite方法里最终返回的o,在我给的内存分布图里是哪块内容?

是下面这个:

当第一次调用hset userid:15 name zhangsan这个命令的时候,lookupKeyWrite返回的是null。时序图中,第五步的dbAdd最终会到达

int dictAdd(dict *d, void *key, void *val){ //hset userId : 14 name zhangsan //对于上面的命令,key是usrid:14 //val是新建的 // 尝试添加键到字典,并返回包含了这个键的新哈希节点 // T = O(N) dictEntry *entry = dictAddRaw(d,key); // 键已存在,添加失败 if (!entry) return DICT_ERR; // 键不存在,设置节点的值 // T = O(1) dictSetVal(d, entry, val); // 添加成功 return DICT_OK;}

dictAddRaw会生成,并且绑定dictentry的key与robj

dictSetVal(d, entry, val)会绑定dictentry的value与robj

请记住,在aaa中,先有下面那个robj,再有上面的dictentry下面就是时序图中,第六步了(我假定,hash表的底层实现是hash)

/* Add an element, discard the old if the key already exists. * Return 0 on insert and 1 on update. * * 将给定的 field-value 对添加到 hash 中, * 如果 field 已经存在,那么删除旧的值,并关联新值。 * * This function will take care of incrementing the reference count of the * retained fields and value objects. * * 这个函数负责对 field 和 value 参数进行引用计数自增。 * * 返回 0 表示元素已经存在,这次函数调用执行的是更新操作。 * * 返回 1 则表示函数执行的是新添加操作。 */int hashTypeSet(robj *o, robj *field, robj *value) { //hset userid:15 name zhangsan //将name zhangsan这个两个域写到id:15的value //这个o是空的 //新建立entry(userid:15)的value指向这个robj int update = 0; // 添加到 ziplist if (o->encoding == REDIS_ENCODING_ZIPLIST) { //省略部分代码 // 添加到字典 } else if (o->encoding == REDIS_ENCODING_HT) { // 添加或替换键值对到字典 // 添加返回 1 ,替换返回 0 if (dictReplace(o->ptr, field, value)) { /* Insert */ incrRefCount(field); } else { /* Update */ update = 1; } incrRefCount(value); } else { redisPanic("Unknown hash encoding"); } // 更新/添加指示变量 return update;}

/* Add an element, discarding the old if the key already exists. * * 将给定的键值对添加到字典中,如果键已经存在,那么删除旧有的键值对。 * * Return 1 if the key was added from scratch, 0 if there was already an * element with such key and dictReplace() just performed a value update * operation. * * 如果键值对为全新添加,那么返回 1 。 * 如果键值对是通过对原有的键值对更新得来的,那么返回 0 。 * * T = O(N) */int dictReplace(dict *d, void *key, void *val){ dictEntry *entry, auxentry; /* Try to add the element. If the key * does not exists dictAdd will suceed. */ // 尝试直接将键值对添加到字典 // 如果键 key 不存在的话,添加会成功 // T = O(N) if (dictAdd(d, key, val) == DICT_OK) return 1; /* It already exists, get the entry */ // 运行到这里,说明键 key 已经存在,那么找出包含这个 key 的节点 // T = O(1) entry = dictFind(d, key); /* Set the new value and free the old one. Note that it is important * to do that in this order, as the value may just be exactly the same * as the previous one. In this context, think to reference counting, * you want to increment (set), and then decrement (free), and not the * reverse. */ // 先保存原有的值的指针 auxentry = *entry; // 然后设置新的值 // T = O(1) dictSetVal(d, entry, val); // 然后释放旧值 // T = O(1) dictFreeVal(d, &auxentry); return 0;}

上面的dictadd是干什么的?

等等,谁能告诉我dictAdd的参数d是什么?

是它:

如果第一次执行hset user:id name zhangsan

在dictadd里直接就会保存entry的两个robj

换句话说,如果第一次执行hset user:id name zhangsan

dictadd会返回ok,然后跳出dictreplace

至于第一次调用hset userid:14 school xdu

与hset userid:14 name lisi

会怎么样,大家自己想吧

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:首席营销官:6万人砍价不成功?拼多多公关塌了!
下一篇:你的代码写的很臭
相关文章

 发表评论

暂时没有评论,来抢沙发吧~