当前位置: 首页 > 后端技术 > PHP

跟随大斌阅读源码-Redis6-对象与数据类型(下)

时间:2023-03-29 16:40:09 PHP

继续阅读我们的对象与数据类型。在上一节中,我们一起认识了字符串和列表,然后还有散列、集合和有序集合。1哈希对象哈希对象可选的编码有:ziplist和hashtable。1.1ziplist-encodedhashobjectsziplist-encodedhashobjects使用压缩列表作为底层实现。每当有新的键值对加入哈希对象时,程序会先将存放key的压缩列表节点推到表尾,再将存放值的压缩列表节点推到表尾桌子。因此:保存键值对的两个节点总是靠得很近,保存键的节点在前,保存值的节点在后;先添加到hash对象的键值对会被复制到压缩列表的表头方向,后面添加的键值对会放在压缩列表尾部的方向.执行以下HSET命令,服务器会创建一个如图9所示的list对象作为profilekey的值:127.0.0.1:6379>HSETprofilename"Tom"(integer)1127.0.0.1:6379>HSETprofileage"25"(integer)1127.0.0.1:6379>HSETprofilecareer"Programer"(integer)1127.0.0.1:6379>OBJECTENCODINGprofile"ziplist"对象使用的压缩列表如图10:1.2hashtableencodedhashtable编码的哈希对象使用字典作为底层实现。hash对象中的每个键值对使用字典键值对存储:字典中的每个键都是一个字符串对象,键值对的key存储在对象中;字典中的每个值都是一个字符串对象,它存储的是键值对的值。如果前面的profilekey使用的是hashtable编码的hash对象,那么hash对象应该如图11所示:1.3编码转换当hash对象同时满足以下两个条件时,会使用ziplist编码:hashobject在所有保存的键值对中,key和value的字符串长度小于64字节;hash对象中保存的键值对个数小于512。以上条件中的临界值对应redis.conf文件中的配置:hash-max-ziplist-value和hash-max-ziplist-条目。在3.2版本中,添加哈希键值对时,实际上总是先创建一个ziplist编码的哈希对象,然后再进行转换检查。关于何时进行编码转换,有两种情况:更新或添加键值对时,如果value的字节数大于hash-max-ziplist-value,则从ziplist编码转换为hashtable编码;当添加一个新的key-value时,如果hash中的key-value对的个数大于hash-max-ziplist-entries,就会从ziplist编码转为hashtable编码。需要注意的是,上面的转换中不会发生从hashtable到ziplist的转换,即使满足条件。hash码转换的功能可以参考t_hash.c/hashTypeConvert,源码如下:#o为原始对象,enc为目标码。voidhashTypeConvert(robj*o,intenc){if(o->encoding==OBJ_ENCODING_ZIPLIST){//仅当原始编码为OBJ_ENCODING_ZIPLIST时转换hashTypeConvertZiplist(o,enc);}elseif(o->encoding==OBJ_ENCODING_HT){serverPanic("未实现");}else{serverPanic("未知的哈希编码");}}2集合对象集合对象的可选编码有:intset和hashtable。2.1Intset编码集合对象intset编码集合对象使用整数集合作为底层实现,集合对象包含的所有元素都存储在整数集合中。执行以下SADD命令将创建一个intset编码的集合对象,如图12所示:127.0.0.1:6379>SADDnumbers135(integer)3127.0.0.1:6379>OBJECTENCODINGnumbers"intset"2.2hashtableencodedCollectionobject哈希表编码的集合对象使用字典作为底层实现。字典的每个键都是一个字符串对象,每个字符串对象包含一个集合元素,而字典的值都设置为NULL。执行以下SADD命令创建哈希表编码的集合对象,如图13所示:127.0.0.1:6379>SADDfruits"apple""banana""cherry"(integer)3127.0.0.1:6379>OBJECTENCODINGfruits"hashtable"2.3编码转换当集合对象同时满足以下两个条件时,对象使用intset编码:集合对象中存储的所有元素都是可以用longdouble表示的整数值;collection对象存储的元素个数不超过512个。以上条件中的临界值对应redis.conf文件中的配置:set-max-intset-entries。对于集合对象,当添加第一个键值对时,会检查键值对中的值。如果是符合条件的整数值,则创建一个intset编码的集合对象,否则,将创建一个hashtable编码的集合对象。关于何时进行编码转换,有两种情况:当更新或添加一个键值对时,如果该值不能用longdouble表示,则将其从intset编码转换为hashtable编码;添加键值对时,如果集合中的值的键值对数量大于set-max-intset-entries,则由intset编码转为hashtable编码。同样,即使满足条件,也不会在上述转换中发生从hashtable到intset的转换。hash码转换的功能可以参考t_set.c/setTypeConvert,源码如下:#setobj为原始对象,enc为目标码。hvoidsetTypeConvert(robj*setobj,intenc){setTypeIterator*si;serverAssertWithInfo(NULL,setobj,setobj->type==OBJ_SET&&setobj->encoding==OBJ_ENCODING_INTSET);if(enc==OBJ_ENCODING_HT){//只能转成OBJ_ENCODING_HT编码int64_tintele;dict*d=dictCreate(&setDictType,NULL);robj*元素;/*预先调整字典大小以避免重新散列*/dictExpand(d,intsetLen(setobj->ptr));/*为了添加元素,我们提取整数并创建redis对象*/si=setTypeInitIterator(setobj);while(setTypeNext(si,&element,&intele)!=-1){element=createStringObjectFromLongLong(intele);serverAssertWithInfo(NULL,元素,dictAdd(d,元素,NULL)==DICT_OK);}setTypeReleaseIterator(si);setobj->encoding=OBJ_ENCODING_HT;zfree(setobj->ptr);setobj->ptr=d;}别的{serverPanic("不支持集合转换");}}3Sortedsetobject有序集合对象的可选编码为:ziplist和skiplist3.1ziplist编码的有序集合对象和intset编码的集合对象使用compressedlist作为底层实现。每个集合元素都使用两个相邻的打包列表节点进行存储。第一个节点保存元素的成员,第二个成员保存元素的分数。压缩列表中的集合元素按照分数从小到大排序。分数较小的元素被放置在列表头部的方向,而分数较大的元素被放置在靠近列表末尾的方向。执行以下SADD命令将创建一个ziplist编码的集合对象,如图14所示:127.0.0.1:6379>ZADDprice8.5apple5.0banana6.0cherry(integer)3127.0.0.1:6379>OBJECTENCODINGprice"ziplist"bottomlayerziplist的结构如图15所示:3.2Skiplist-encodedcollectionobjectskiplist-encodedcollectionobject使用zset作为底层实现。zset结构包含字典和跳跃列表。该结构的源代码如下:#server.htypedefstructzset{dict*dict;zskiplist*zsl;}zset;zset结构中的zslskiplist保存了所有set元素从小到大,每个skiplist节点保存一个Collection元素。跳表节点的object属性保存元素的成员,跳表节点的score属性保存元素的分支。**程序通过这个跳表对有序集合进行范围操作。例如ZRANK、ZRANGE等命令就是基于跳表API实现的。此外,zset结构中的dict字典为排序集创建了从成员到分数的映射。字典中的每个键值对保存一个集合元素:字典中的键保存元素的成员,字典的值保存元素的分数。使用这个字典,程序以O(1)的复杂度查找给定成员的分数。有序集合的每个元素的成员是一个字符串对象,每个元素的分数是一个double类型的浮点数。值得一提的是,zset结构虽然同时使用了跳表和字典来存储有序集合的元素,但是这两个数据结构会通过指针共享相同元素的成员和分数,所以不会出现重复成员和点,没有额外的内存被浪费。如果前面的pricekey创建了一个由skiplist而不是ziplist编码的有序集合对象,那么这个有序集合对象将如图16所示,对象使用的zset结果如图17所示:在17中,对于为了显示的方便,重复显示每个元素的成员和分数。实际上,它们是共享元素的成员和分数。3.3编码转换当有序集合对象同时满足以下两个条件时,对象使用ziplist编码:有序集合对象中存储的元素个数不超过128个。有序集合中持有的所有元素成员均小于长度为64个字节。以上条件中的临界值对应redis.conf文件中的配置:zset-max-ziplist-entries和zset-max-ziplist-value。对于集合对象,添加键值对时,会检查集合元素和键值对中的值。如果满足条件,将创建一个ziplist编码的集合对象;否则,将创建一个skiplist编码的集合。目的。对应的源码如下:#t_zset.c/zaddGenericCommand...zobj=lookupKeyWrite(c->db,key);if(zobj==NULL){if(xx)gotoreply_to_client;/*无键+XX选项:无事可做。*/if(server.zset_max_ziplist_entries==0||server.zset_max_ziplist_valueargv[scoreidx+1]->ptr)){#对象元素个数为0,即zobj=createZsetObject();}else{zobj=createZsetZiplistObject();}dbAdd(c->db,key,zobj);}else{if(zobj->type!=OBJ_ZSET){addReply(c,shared.wrongtypeerr);转到清理;}}SummaryHash对象有ziplist和hashtable编码。Set对象有intset和hashtable编码。已排序的集合对象具有ziplist和skiplist编码。