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的数据结构。

[cpp] view plain copy

  • /* 

  • * 哈希表节点 

  • */  

  • 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

    [java] view plain copy

  • 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大图如下:




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

    [java] view plain copy

  • 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最终会到达

    [cpp] view plain copy

  • 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)

    [cpp] view plain copy

  • /* 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;  

  • }  

    [cpp] view plain copy

  • /* 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

    会怎么样,大家自己想吧




    [java] view plain copy

  • [java] view plain copy










  • 阅读全文

喜欢阅读
  • 失忆冷妻

    失忆冷妻

    她,紫晓微,一个有着绝色容貌的女子。像是被上天所眷恋,出身在一个不错的家庭之中,被所有男人所追逐,她几乎是没有一丝的烦恼。一场的灾难,让她在一夜之间失去一切。一场意外,更是导致她失去记忆。她,什么都没有,除了一张容颜。高贵的她,从天堂坠下地狱,从公主成为了灰姑娘。生活的一切被改得天翻地覆,唯一没有变化的,便是那些爱她的男人。“既然被上天放弃,那便是开创自己的未来。”握起拳头,她决定创造属于自己的人生。

  • 神勇狂兵

    神勇狂兵

    他是兵王太岁,是无数人的噩梦,因伤修养,却不知道自己已经有了未婚妻,身怀灭门惨案,一边调查线索,一边又是桃花运来袭.....

  • 爱你如初误情殇

    爱你如初误情殇

    爱入骨,才有恨入骨,当深爱换来无尽的折磨,她才知,爱上他是一个错。

  • 婚情不予两生契

    婚情不予两生契

    一场意外,苏桐意外产子;一桩交易,她成了席嘉洛的契约情人;本以为,一个拿钱一个办事,两不相欠,但为何她却总也摆脱不掉这个高冷男人的纠缠?真相揭开,原来,他竟然是孩子的……

  • 贤妻之洗净铅华

    贤妻之洗净铅华

    上一世她是被亲生父母满嘴谎言蒙蔽了心智。他们拿着养父母的血汗钱却来告诉自己是他们给了自己上学的机会;他们烧了自己的大学录取通知书只为了让她早点嫁人好拿到高额的彩礼;他们哄着自己拿出丈夫的转业金给娘家盖房子却在动迁分房的时候分文不吐;她被婆婆扫地出门的时候,他们在研究如何将这个没钱没房的女人嫁给一个有钱的老头子。重活一世,张翠莲要远离只认钱不认人的亲生父母踢开一心想把亲生姐姐卖个好价钱的弟弟。最重要的是,她要报答养父养母一片护犊之情,找到那个甘愿自毁前程理解她包容她迁就她的男人。这一辈子,她发誓要做一个好媳妇!

  • 爱你誓言如初

    爱你誓言如初

    从大学领了毕业证之后,我先去白凝墓前放了一束白菊,然后就用剩下的钱买了一张去江城的火车票。在江城,有我的一个无论如何也要报复的仇人,他有钱有势,从正面我没有一丝与他抗争的实力,所以我不惜舍弃一切尊严,一切前程,只要能他伏罪认错!

  • 鬼缘未了

    鬼缘未了

    阴阳师娶了触犯律规被贬人间的阎王长女为妻。阎王寿辰时长女被提前召回,夫妻情深,情缘何了?长公主却将魂魄依附在人间好闺蜜的身上,叮咛夫君,与闺蜜行夫妻之礼,长公主亦能感受夫妻之实。同样触犯律规的妹妹,被罚永驻人间,人不人,鬼不鬼。长公主嘱咐夫君关照,要么娶了妹妹,让其灵魂安息,享受人间天伦。阴阳师何去何从,一边是伉俪情深,一边是妻妹漂泊不定的魂灵,一时难为。刘家村鬼魅祸害,人们却无所适从,绝望至极,上千年的刘家村大难临头,恐遭灭绝......

  • 医道神术

    医道神术

    一代小神医勇闯都市,各色粉嫩妹纸疯狂倒贴。手有医道神术,都市任我行。专治一切病状,更治各种不服。脚踩都市天骄,坐拥千万美女。

  • 猜你喜欢
    热门推荐
  • 荷兰乳牛克价格
  • 联合金麒麟厂家广告语
  • 这就是铁甲骑士王
  • 无主之地前传威廉 流派
  • 海马七座suv价格图片
  • vmware esxi与vsphere
  • 手机广告贴
  • ironpython vs
  • 巫师3三女巫杀不杀
  • vhp消毒
  • 双色球七加二是啥意思
  • 男裤尺码94是多少
  • have less supportive
  • blunder 口语
  • nba勇士坠机
  • 缘丰料理加盟
  • 成都 医院 验光
  • 宏博昌荣传播
  • 血源诅咒官方攻略本
  • 约会吧怎么免费回复
  • All Right Reserved 强大网