在上次的zipmap分析完之后,事实上关于redis源码结构体部分的内容事实上已经所有结束了。由于以下还有几个和结构体相关的操作类,就页把他们归并到struct包下了。这类的文件有:t_hash.c,z_list,z_set.c,t_string.c,t_zset.c,这些文件的功能事实上都差点儿相同,就是用来实现Client和Server之间的命令处理的操作类,通过robj的形式,把dict,ziplist等存入robj中,进行各个转换。实现命令操作。避开了结构体原先的复杂结构,相当于是封装了结构体的操作类。今天我所讲的是t_hash,是dict哈希字典,ziplist压缩列表与robj之间的转换。

统称hashType类型。

由于此文件无头文件。仅仅有.c文件,所以为了方便学习。我把方法拉了出来。

/* 以下是方法的归类 */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) /* 当hashType为ziplist时,推断对象长度是否超出了服务端可接受的ziplist最大长度,超过则转成哈希字典类型*/
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) /* 当robj用的是字典的编码方式的时候,则经过编码转换 */
int hashTypeGetFromZiplist(robj *o, robj *field,unsigned char **vstr,unsigned int *vlen,long long *vll) /* 获取ziplist压缩列表中的某个索引位置上的值 */
int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) /* 获取哈希字典中的某个值 */
robj *hashTypeGetObject(robj *o, robj *field) /* 获取某个key相应的对象类型 */
int hashTypeExists(robj *o, robj *field) /* hastType类型推断某个键是否存在 */
int hashTypeSet(robj *o, robj *field, robj *value) /* hashType设置操作,分2种情况,ziplist,和字典hashtable */
int hashTypeDelete(robj *o, robj *field) /* hashType删除操作。分为ziplist的删除操作,和hashtable的删除操作 */
unsigned long hashTypeLength(robj *o) /* hashType求长度操作 */
hashTypeIterator *hashTypeInitIterator(robj *subject) /* 获取hashType迭代器 */
void hashTypeReleaseIterator(hashTypeIterator *hi) /* 释放hashType迭代器 */
int hashTypeNext(hashTypeIterator *hi) /* 通过hashType迭代器获取下一个元素 */
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,unsigned char **vstr,unsigned int *vlen,long long *vll) /* 依据当前迭代器的位置,获取当前ziplist的所在位置的key位置,或value该位置上的值 */
void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) /* 依据当前迭代器的位置,获取当前dict的所在位置的key位置,或value该位置上的值 */
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) /* 依据当前迭代器的位置。获取当前key对象 */
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) /* 依据cclient对象,找到key是否存在,创建或实现加入操作 */
void hashTypeConvertZiplist(robj *o, int enc) /* 从ziplist压缩表到hashtable的转换 */
void hashTypeConvert(robj *o, int enc) /* 对象转换操作,比如从ziplist到dict的转换 */

hashType的相关操作命令类。事实上就是对上面方法的结合调用:

/* 哈希命令类型 */
void hsetCommand(redisClient *c) /* client设置指令 */
void hsetnxCommand(redisClient *c) /* client设置下一个位置指令 */
void hmsetCommand(redisClient *c) /* 客户单设置命令,假设没有key,还有兴许操作 */
void hincrbyCommand(redisClient *c) /* client加入value值操作 */
void hincrbyfloatCommand(redisClient *c) /* client加入float类型value值操作 */
static void addHashFieldToReply(redisClient *c, robj *o, robj *field) /* */
void hgetCommand(redisClient *c) /* client获取操作,假设没找到,直接不做不论什么操作 */
void hmgetCommand(redisClient *c) /* client获取key操作。假设为空。会返回一些了NULL值 */
void hdelCommand(redisClient *c) /* client删除操作 */
void hlenCommand(redisClient *c) /* client求长度命令 */
static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) /* client加入hashType迭代器操作 */
void genericHgetallCommand(redisClient *c, int flags) /* client获取操作原始方法。能够加入flag參数 */
void hkeysCommand(redisClient *c) /* client获取key值命令 */
void hvalsCommand(redisClient *c) /* client获取val值命令 */
void hgetallCommand(redisClient *c) /* client获取key;value 2个值都获取 */
void hexistsCommand(redisClient *c) /* client推断记录是否存在操作 */
void hscanCommand(redisClient *c) /* client扫描操作 */

robj的操作实现转换的原理非常easy,rob通过里面的ptr指针。存的就是真实的ziplist或者dict哈希总类。然后后面的操作都是基于此进行的,比方说以下的方法:

/* Get the value from a hash table encoded hash, identified by field.
* Returns -1 when the field cannot be found. */
/* 获取哈希字典中的某个值 */
int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
dictEntry *de; redisAssert(o->encoding == REDIS_ENCODING_HT); //通过robj->ptr里面存的dict总类或ziplist类開始寻找
de = dictFind(o->ptr, field);
if (de == NULL) return -1;
//获取当中的value值
*value = dictGetVal(de);
return 0;
}

全部关于robj的相关结构体操作都会分成为2种情况处理,ZIPLIST和HASH类型就是dict类型,并且操作ziplist类型的时候要进行转码处理,当然在进行ziplist存入robj的时候要进行编码操作。可见,设计者在考虑到命令传输的时候想得还是非常周到了。也考虑了安全的问题。

/* Add an element, discard the old if the key already exists.
* Return 0 on insert and 1 on update.
* This function will take care of incrementing the reference count of the
* retained fields and value objects. */
/* hashType设置操作。分2种情况,ziplist,和字典hashtable */
int hashTypeSet(robj *o, robj *field, robj *value) {
int update = 0; if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr; //首先对field和value进行解码
field = getDecodedObject(field);
value = getDecodedObject(value); zl = o->ptr;
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
if (fptr != NULL) {
/* Grab pointer to the value (fptr points to the field) */
vptr = ziplistNext(zl, fptr);
redisAssert(vptr != NULL);
update = 1; //设置的操作,事实上先删除。再插入语一个新值
/* Delete value */
zl = ziplistDelete(zl, &vptr); /* Insert new value */
zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
}
} if (!update) {
/* Push new field/value pair onto the tail of the ziplist */
zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
}
o->ptr = zl;
//用完之后。引用计数递减
decrRefCount(field);
decrRefCount(value); /* Check if the ziplist needs to be converted to a hash table */
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
hashTypeConvert(o, REDIS_ENCODING_HT);
} else if (o->encoding == REDIS_ENCODING_HT) {
//假设是字典,直接替换
if (dictReplace(o->ptr, field, value)) { /* Insert */
incrRefCount(field);
} else { /* Update */
update = 1;
}
//用完之后,引用计数递减
incrRefCount(value);
} else {
redisPanic("Unknown hash encoding");
}
return update;
}

在这个过程中。redis代码中还用到了一个引用计数的东西,应该是为了合理的内存释放控制。在非常多地方能够看到这种操作;

/* Higher level function of hashTypeGet*() that always returns a Redis
* object (either new or with refcount incremented), so that the caller
* can retain a reference or call decrRefCount after the usage.
*
* The lower level function can prevent copy on write so it is
* the preferred way of doing read operations. */
/* 获取某个key的对象 */
robj *hashTypeGetObject(robj *o, robj *field) {
robj *value = NULL; if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX; if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
//在ziplist中获取值
if (vstr) {
value = createStringObject((char*)vstr, vlen);
} else {
value = createStringObjectFromLongLong(vll);
}
} } else if (o->encoding == REDIS_ENCODING_HT) {
robj *aux; if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
//对象被引用了。计数递增
incrRefCount(aux);
value = aux;
}
} else {
redisPanic("Unknown hash encoding");
}
return value;
}

client的命令操作事实上是基于一个叫redisClient的对象,这个事实上也就是robj对象,命令传输时,这个robj->ptr存着,详细的数据,robj->args[]存放了各种參数,后面就是调用前面的方法了,唯一不一样的是,命令调用后要有回复和更新通知操作。,以下是一个设置的命令;

/* hashType处理client的命令请求 */
void hsetCommand(redisClient *c) {
int update;
robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,3);
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
//命令的操作都是通过。client中的对象,和存在于里面的命令參数组成
update = hashTypeSet(o,c->argv[2],c->argv[3]);
//操作完加入回复
addReply(c, update ? shared.czero : shared.cone);
//发送通知表示命令运行完成。预測者会触发窗体上的显示
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id); //client命令运行成功。由于客户单此时的数据时最新的,服务端的脏数据就自然多了一个。
server.dirty++;
}

其它命令与此类似,就不说了。可以看见,如今慢慢的可以稍微向逻辑层的代码靠近了,后面的代码也一定很精彩。

Redis源代码分析(八)--- t_hash哈希转换的更多相关文章

  1. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

  2. redis 源代码分析(一) 内存管理

    一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...

  3. Redis源代码分析(十一年)--- memtest内存测试

    今天,我们继续redis源代码test下测试在封装中的其它文件.今天读数memtest档,翻译了,那是,memory test 存储器测试工具..可是里面的提及了非常多东西,也给我涨了非常多见识,网上 ...

  4. Redis源代码分析(23)--- CRC循环冗余算法RAND随机数的算法

    他今天就开始学习Redis源代码的一些工具来实现,在任何一种语言工具.算法实现的原理应该是相同的,一些比較经典的算法.比方说我今天看的Crc循环冗余校验算法和rand随机数产生算法. CRC算法全称循 ...

  5. Redis源代码分析(三)---dict哈希结构

    昨天分析完adlist的Redis代码.今天立即马不停蹄的继续学习Redis代码中的哈希部分的结构学习,只是在这里他不叫什么hashMap,而是叫dict.并且是一种全新设计的一种哈希结构,他仅仅是通 ...

  6. Redis源代码分析(二十八)--- object创建和释放redisObject物

    今天的学习更有效率.该Rio分析过,学习之间的另一种方式RedisObject文件,只想说RedisObject有些生成和转换.都是很类似的.列出里面长长的API列表: /* ------------ ...

  7. Redis源代码分析-内存数据结构intset

    这次研究了一下intset.研究的过程中,一度看不下过去,可是还是咬牙挺过来了.看懂了也就是那么回事.静下心来,切莫浮躁 Redis为了追求高效,在存储下做了非常多的优化,像intset就是作者为了节 ...

  8. Redis源代码分析(二十四)--- tool工具类(2)

    在上篇文章中初步的分析了一下,Redis工具类文件里的一些使用方法,包含2个随机算法和循环冗余校验算法,今天,继续学习Redis中的其它的一些辅助工具类的使用方法.包含里面的大小端转换算法,sha算法 ...

  9. Redis源代码分析(六)--- ziplist压缩列表

    ziplist和之前我解析过的adlist列表名字看上去的非常像.可是作用却全然不同.之前的adlist主要针对的是普通的数据链表操作. 而今天的ziplist指的是压缩链表.为什么叫压缩链表呢.由于 ...

随机推荐

  1. 【Redis发布订阅】

    Redis通过PUBLISH.SUBSCRIBE等命令实现发布与订阅模式. 举例:QQ群的公告,单个发布者,多个收听着. *** 发布/订阅 PUBLISH 频道 消息 将消息发布到指定的频道. . ...

  2. Django transaction 误用之后遇到的一个问题与解决方法

    今天在调试项目开发好的一个模块的时候,发现了一个很诡异的现象,最后追踪发现是因为在项目中事务处理有误所致.这个问题坑了我好一会,所以记录一下,以免再踩坑.下面开始详述. 我们都知道 Django 框架 ...

  3. debian8平滑升级到debian9

    本文在Creative Commons许可证下发布. 首先,在升级时可以查看一下自己的版本号: uname -a ##查看内核信息 cat /etc/issue ##查看发行版本号   方法1:利用网 ...

  4. [Python] Use Python Classes

    Object oriented classes work much like classes in other languages. Learn how to create them and use ...

  5. 关于getinstalledpackages參数的分析。

    此blog不写API的使用方法仅仅分析此參数的知识点. 今天学习安卓突然学习到了getinstalledpackages()的方法获取到安装应用信息 ,他接收一个int flags的值.然后在网上查询 ...

  6. localtime死锁——多线程下fork子进程

    近期測试我们自己改进的redis,发如今做rdb时,子进程会一直hang住.gdb attach上.堆栈例如以下: (gdb) bt #0 0x0000003f6d4f805e in __lll_lo ...

  7. mybatis自己主动生成mapper,dao,映射文件

    一.先创建数据脚本,这里用的mysql数据脚本 drop table VOTE_ITEM; drop table VOTE_OPTION; drop table VOTE_SUBJECT; drop ...

  8. jquery--延迟对象

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  9. 荣获CCF(中国计算机学会)高级会员代表资格

    详细地址:http://www.ccf.org.cn/sites/ccf/xjhydb.jsp?contentId=2624287722908 650) this.width=650;" b ...

  10. deep-in-es6(五)

    解构 Destructuring: 解构赋值允许使用类似数组或对象字面量的语法将数组和对象的属性赋值给给中变量. 一般情况访问数组中的前三个元素: var first = arr[0]; var se ...