面试官:小明,redis有多少种数据结构?小明:8种采访者:那你说说有什么区别?小明:raw,int,ht,zipmap,linkedlist,ziplist,intset,skiplist,embstr面试官:嗯,你在说什么?小明:我在回答你的问题。我以前研究过这个问题,我不会错的。采访者:嗯,今天的采访先到这里。您可以返回并等待通知。小明:……上面的对话,是面试官有问题还是小明有问题?其实是有问题的。面试官的问题不准确,小明的回答也不准确。但是可以看出面试官的水平一般,因为他听到了这些术语,并不知道小明说的是redis的底层编码类型,从而错失了挖掘小明技术潜力的机会。而小明也有点聪明,忽略了面试官要考察的知识点,炫耀自己最近看的一些皮毛,导致误会。基于上面的介绍,本文就来聊一聊redis中的数据结构。redis源码选用版本:3.0.0本文目标:了解redis编码类型的概念,理解为什么要根据源码层级的深浅设置不同的编码类型,但不会展开各种底层datastructurestoomuchredisobjecttype和encodingtype的细节redisobjecttype就是在面试中经常测试的redis数据类型是什么的问题中被问到的确切陈述。对于我们这些只会面试不会开发的程序员来说很熟悉,就是string、hash、list、set、orderedset。这个可以在redis源码中找到:redis.c/*Objecttypes*/#defineREDIS_STRING0#defineREDIS_LIST1#defineREDIS_SET2#defineREDIS_ZSET3#defineREDIS_HASH4很多人对redis数据结构的理解可能到此为止,但其实这只是一个抽象的结构由redis暴露,其底层实现依赖于其编码类型来确定编码类型对应的数据结构。如果一个对象类型只有一种底层数据结构的实现,那么这种编码类型就完全多余了。早期的redis没有这个概念。但是后来为了优化性能,一种对象类型可能对应多种不同的编码实现,所以关于redis底层数据结构的知识点开始变得复杂起来。编码类型在redis源码中也有准确定义:redis.c/*Objectsencoding.SomekindofobjectslikeStringsandHashescanbe*internallyrepresentedinmultipleways.The'encoding'fieldoftheobject*issettooneofthisfieldsforthisobject.*/#defineREDIS_ENCODING_RAW0/*Rawrepresentation*/#defineREDIS_ENCODING_INT1/*Encodedasinteger*/#defineREDIS_ENCODING_HT2/*Encodedashashtable*/#defineREDIS_ENCODING_ZIPMAP3/*Encodedaszipmap*/#defineREDIS_ENCODING_LINKEDLIST4/*Encodedasregularlinkedlist*/#defineREDIS_ENCODING_ZIPLIST5/*Encodedasziplist*/#defineREDIS_ENCODING_INTSET6/*Encodedasintset*/#defineREDIS_ENCODING_SKIPLIST7/*Encodedasskiplist*/#defineREDIS_ENCODING_EMBSTR8/*Embeddedsdsstringencoding*/其实我们不用寻找任何额外解释编码类型的作用,看源码中的英文注释即可。对象编码(编码类型):一些对象类型,例如字符串和哈希,可以有多种内部实现。一个redis对象的encoding字段可以设置如下值表示该对象的底层编码类型是同一个对象类型,可以有不同的编码类型作为底层实现。同一编码类型也可以在上层支持多种对象类型。它们的关系如下:redis对象类型和编码类型看完这篇你肯定至少有三个疑问:为什么一个对象类型对应多种编码类型,它要解决什么问题?redis怎么知道什么时候用这个编码类型,什么时候用那个编码类型,编码类型可以随时改变吗?各种编码类型的实现原理是什么?(本章不划重点,通篇介绍一些基本思想,具体实现在其他章节讲解)别着急,这部分只是让你知道redis暴露给用户的是什么只是一个抽象的数据结构。不代表其底层的具体实现。接下来,我就带大家慢慢来。为什么一种对象类型对应多种编码类型?写redis的大牛也是程序员。他总不能给自己增加代码的复杂度,对性能的提升也没有帮助吧?毕竟像redis这样的中间组件,在性能上肯定胜过同类产品。是的,它是为了提高性能。直观感受不同的编码类型首先,我们直观感受一下同一个物体对应不同编码类型的场景。这里使用redis命令对象encodingxxx查看某个key的值对象使用的编码类型127.0.0.1:6379>setnumber100OK127.0.0.1:6379>objectencodingnumber"int"127.0.0.1:6379>setnumber"100"OK127.0.0.1:6379>objectencodingnumber"int"127.0.0.1:6379>setnumberabcOK127.0.0.1:6379>objectencodingembstr"127.0.0.1:6379>setnumberaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaOK127.0.0.1:6379>objectencodingnumber"raw"127.0.0.1:6379>setnumber9999999999999999999999999OK127.0.0.1:6379>objectencodingnumber"embstr"127.0.0.1:6379>setnumber99999999999999999999999999999999999999999999999999999999999999OK127.0.0.1:6379>objectencodingnumber"raw"Wetestedwithourmostcommonlyusedstring,andobservedthatitsencodingtype随我设置的值变化,我整理了下表来表示上面的测试结果valueencodingtype100int"100"intabcembstraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaraw9999999999999999999999999embstr99999999999999999999999999999999999999999raw当然,我是因为知道字符串的编码类型的条件,踩专门选取了这些有代表性的值进行测试,我们可以总结出一个规律不论是100还是"100",theencodingtypeisint,whichmeansthatwhenredisjudgeswhethertheencodingtypeofintegercanbeusedtorepresenttheobject,itjustcheckswhetherthevaluecanbeconvertedintoastringabc,whichisarelativelyshortinteger,isencodedasembstr,andthelongerstringThestringaaaaaaa..aisencodedasraw,indicatingthattheencodingtypesoflongandshortstringsaredifferent.Fromthis,itcanbeguessedthatredismayhaveoptimizedthestoragestrategyforshortstrings(ofcourse,itisonlyareasonableguessatpresent,anditispossibleItissomekindofoptimizationforlongstrings)integers999...9andlongerintegers9999999...9arealsoconvertedintocorrespondingtypesrepresentingstrings,indicatingvalues??thatcanberepresentedbyintegerencodingtypes,isarediswithacertainsizelimit.Analysisoftheoptimizationofstringencodingtypes.Fromtheaboveexperiments,welearnedthatthereareindeedthreeencodingtypesofstringobjects:int,raw,andembstrint.Itcanbestoredinintegers,trytouseintegersforstorage,itmustbemorespace-savingthanthestringmethod.Next,let'sanalyzetheencodingtypesoflongstringsandshortstrings.Why?Notonlystringtypes,butalsoobjecttypessuchashashandlistarerepresentedbyaunifiedstructureredisObject.他的结构如下:redis.htypedefstructredisObject{unsignedtype:4;//对象类型unsignedencoding:4;//编码类型void*ptr;//值指针...(省略一些不重要的字段)}robj;占4位的type表示对象类型(5种),同样占4位的encoding域表示编码类型(8种),指针域ptr表示实际值的内存地址。如果对象的编码类型是整数(encoding=REDIS_ENCODING_INT),那么这个ptr会指向一个long类型的变量。util.cif(!string2ll(s,slen,&llval))return0;...*lval=(long)llval;return1;object.c...o->ptr=(void*)value;如果对象的编码类型是raw或者embstr,那么这个ptr会指向一个sdshdr结构体的变量指向相同的结构,那么它是如何优化的呢?那么就要进入下面两个方法,看object.crobj*createStringObject(char*ptr,size_tlen){if(len<=39)returncreateEmbeddedStringObject(ptr,len);elsereturncreateRawStringObject(ptr,len);}你看,这段代码很清楚。当字符串的长度<=39时,创建一个embstr类型的字符串对象,否则创建一个raw类型的字符串对象。那么这两种创建方式的区别一定隐藏在这两种方法中,我们点进去吧!embstr类型robj*createEmbeddedStringObject(char*ptr,size_tlen){robj*o=zmalloc(sizeof(robj)+sizeof(structsdshdr)+len+1);structsdshdr*sh=(void*)(o+1);o->type=REDIS_STRING;o->encoding=REDIS_ENCODING_EMBSTR;o->ptr=sh+1;...(一些赋值操作)returno;}原始类型robj*createRawStringObject(char*ptr,size_tlen){returncreateObject(REDIS_STRING,sdsnewlen(ptr,len));}sdssdsnewlen(constvoid*init,size_tinitlen){...structsdshdr*sh=zmalloc(sizeof(structsdshdr)+initlen+1);...}robj*createObject(inttype,void*ptr){robj*o=zmalloc(sizeof(*o));o->typetype=type;o->encoding=REDIS_ENCODING_RAW;o->ptrptr=ptr;...(一些赋值操作)returno;}用于阅读源代码比较很多同学可能会立刻注意到它们之间的区别。其实很简单,就是raw类型为redisObject和sdshdr结构体申请内存空间,而embstr只申请一次内存空间,然后把两个结构体挨着放。除此之外没有其他区别。可视化示意图如下:看到这里,一切就说明了。很简单,只是申请内存这一步的不同而已。但是对于我们这些想把简单的东西包装成高级演讲技巧的程序员来说,还是得想办法装。我们总结过使用embstr编码相比于raw编码的优势:embstr只申请一次内存,而raw需要申请两次,所以节省了申请一次内存的消耗。releaseembstr只需要释放一次内存,而raw需要两次,所以省去了一次释放内存的消耗。RedisObject和embstr的sdshdr是放在一个连续的内存中的,那么能不能利用缓存带来的优势呢?源码级别的理解,再加上让面试官着迷的结束语,已经够有意思了。不同编码类型的情况上一节我们通过字符串观察了不同的编码类型,也理解了为什么会有不同的编码类型。接下来,我们总结其他对象和编码类型。原理就不深入源码分析了,基本思路和字符串一样。字符串的编码类型int:8字节长整型embstr:小于等于39字节的字符串raw:大于39字节的字符串哈希编码类型ziplist:元素个数小于512,所有值均小于64byteshashtable:除上述条件外,list的编码类型ziplist:元素个数小于512,所有值小于64byteshashtable:除上述条件外,setintset的编码类型:元素个数小于512,且所有值为整数hashtable:除上述条件外,有序集合ziplist的编码类型:元素个数小于128,且所有值为lessthan64byteshashtable:除了上面的情况,既然没有解释,我觉得是纯内存就用最干净的方式给大家描述一下,没有多余的部分。具体数据结构的细节,我会用其他文章来解释。这时,经过一番培训,小明又遇到了专业的面试官。专业面试官:小明,redis有几种数据结构……进化了小明:面试官面试官,你的问题分为两部分。第一种情况,redis的对象类型一共有5种,也就是我们常说的对外暴露的数据类型,即....底层对应的编码类型有8种3.0.0源码,里面有....专业面试官:谁让你急着回答的?进化小明:……专业面试官:好了,今天的面试先到这里,你回去等通知进化小明:……
