相信很多人应该都知道Redis有五种数据类型:string、list、hash、set和orderedset。但是这五种数据类型是什么意思呢?Redis数据是如何存储的?今天,我们就来了解一下Redis的五种数据结构的含义及其底层实现。首先要明确的是,Redis并不是直接使用这五种数据结构来实现键值对数据库,而是基于这些数据结构创建了一套对象系统。我们常说的数据类型,准确的说是Redis对象系统的类型。1Object对于Redis来说,所有键值对的存储都是在object结构中存储数据。不同之处在于,键始终是字符串对象,而值可以是任何类型的对象。对象源代码结构如下:typedefstructredisObject{unsignedtype:4;//对象类型无符号编码:4;//对象编码unsignedlru:LRU_BITS;//LRUint引用计数;//参考统计数据void*ptr;//指向底层实现数据结构的指针}robj;type字段:对象类型,也就是我们常说的。字符串、列表、散列、集合、zset。编码:对象编码。也就是我们上面提到的底层数据结构。LRU:键值对的LRU。refcount:键值对对象的引用统计。当这个值为0时,对象被回收。*ptr:指向底层实现数据结构的指针。它是数据实际存储的地址。1.2对象类型对象有五种数据类型,也就是我们上面提到的:字符串类型、列表类型、哈希类型、集合类型、有序集合类型,结合我们上面提到的键值对存储类型的区别,我们可以明白了,我们常说的“一个listkeyorahashkey”本质上是指一个key对应的value是一个list对象或者hash对象。对于type字段,我们可以使用TYPE命令查看指定key对应的value的对象类型。1.3对象编码按理来说,类型已经有了,为什么还需要编码呢?想一想,通过编码属性,我们是否使用了不同编码的对象?这种使用方式可以根据不同的使用场景为一个对象设置不同的编码,从而优化特定场景下的效率,大大提高Redis的灵活性和效率。例如,当列表对象包含的元素较少时,Redis使用压缩列表作为列表对象的底层实现:压缩列表比快速链表更节省内存,当元素数量较少时,会报错内存中连续的块压缩列表可以比快速列表更快地加载到缓存中;随着列表对象包含的元素越来越多,当使用压缩列表存储元素的优势消失时,对象会将底层实现从压缩列表改为函数更强,更适合容纳大量元素的快速链表元素。后面介绍完编码类型,我们再详细学习不同类型对应的编码方式。编码属性有以下值:OBJ_ENCODING_RAWOBJ_ENCODING_INTOBJ_ENCODING_HTOBJ_ENCODING_QUICKLISTOBJ_ENCODING_ZIPLISTOBJ_ENCODING_INTSETOBJ_ENCODING_SKIPLISTOBJ_ENCODING_EMBSTR对象的编码类型可以通过OBJECTENCODING命令获得。OBJECTENCODING命令对应的源码如下:#src/object.cchar*strEncoding(intencoding){switch(encoding){caseOBJ_ENCODING_RAW:return"raw";案例OBJ_ENCODING_INT:返回“int”;caseOBJ_ENCODING_HT:返回“哈希表”;caseOBJ_ENCODING_QUICKLIST:返回“quicklist”;caseOBJ_ENCODING_ZIPLIST:返回“ziplist”;案例OBJ_ENCODING_INTSET:返回“intset”;caseOBJ_ENCODING_SKIPLIST:返回“跳过列表”;案例OBJ_ENCODING_EMBSTR:返回“embstr”;默认值:返回“未知”;}}OBJECTENCODING命令的输出值与encoding属性值的对应关系如下:对象使用的底层数据结构编码常量OBJECTENCODING输出一个简单的动态字符串OBJ_ENCODING_RAW"raw"integerOBJ_ENCODING_INT"int"embstrencodedsimpledynamicstringOBJ_ENCODING_EMBSTR"embstr"字典OBJ_ENCODING_HT"hashtable"压缩列表OBJ_ENCODING_ZIPLIST"ziplist"快速列表OBJ_ENCODING_QUICKLIST"quicklist"整数集OBJ_ENCODING_INTSET"intset"跳过表OBJ_ENCODING_SKIPLIST"skiplist"总结,如下图十一不同:编码对象是:使用双端或快速列表实现使用压缩列表实现的列表对象使用字典实现的列表对象使用压缩列表实现的散列对象使用字典实现的集合对象使用整数集合实现的集合对象有序集合对象Implemented使用压缩列表实现排序的集合对象使用普通SDS实现的字符串对象使用embstr编码的SDS实现的字符串对象使用整数值实现的字符串对象接下来我们将对以上十一个对象一一介绍,然后一一了解对象编码。2字符串对象的可选编码有:int、raw或embstr。2.1Int编码的字符串对象如果一个字符串对象存储了一个整数值,而这个整数值可以用long类型来表示,那么字符串对象会把这个整数值存储在字符串对象结构体的ptr属性中,而字符的字符串对象的编码设置为int。我们执行下面的SET命令,服务器会创建一个int编码的字符串对象作为numkey的值,如下所示:#redis-cli127.0.0.1:6380>setnum12345OK127.0.0.1:6380>OBJECTENCODINGnum"int"2.2原始编码的字符串对象如果字符串对象持有一个字符串值,并且字符串值的长度大于44字节(根据版本的不同,这个值会有所不同。参见object.OBJ_ENCODING_EMBSTR_SIZE_LIMIT中的常量c文件),则字符串对象将使用**简单动态字符串(SDS)来保存此字符串值,并将对象的编码设置为raw。我们执行下面的SET命令,服务器会创建一个如图7所示的原始编码字符串对象作为k1键的值(45字节):127.0.0.1:7379>setstory'k01234567890123456789012345678901234567890123'OK127.0.0.1:7379>OBJECTENCODINGk4"raw"2.3embstrencodedstringobject如果字符串中持有一个字符串值,并且字符串值的长度小于等于44字节(根据版本不同,值会有所不同。见OBJ_ENCODING_EMBSTR_SIZE_LIMITconstantintheobject.cfilefordetails),则字符串对象将使用embstr编码保存字符串。Embstr编码是一种专门用于保存段字符串的优化编码方式。这种编码和原始编码一样,使用redisObject和sdshdr结构来表示字符串对象。但它不同于raw-encoded的字符串对象:raw-encoded会调用两次内存分配函数,分别创建redisObject和sdshdr结构。embstr编码通过一个内存分配函数分配一个连续的空间,依次包含redisObject和sdsHdr。结构。相应的,在释放内存时,embstr编码的对象只需要调用一次内存释放函数即可。因此,使用embstr编码的字符串对象存储短字符串值有以下好处:创建字符串对象时,内存分配次数从两次减少到一次。当释放一个embstr编码的字符串对象时,内存释放函数的调用次数从2次减少到1次。更好地利用缓存。embstr编码的字符串对象的所有数据都存储在一块连续的内存中,比raw编码的字符串对象更能发挥缓存的优势。以下命令创建一个embstr编码的字符串对象作为msg键的值。值对象结构如图8所示。127.0.0.1:6380>SETmsghelloOK127.0.0.1:6380>OBJECTENCODINGmsg"embstr"2.4浮点数编码在Redis中,也存储了longdouble浮点数作为字符串值。我们想把一个浮点数保存到一个字符串对象中,程序会先把浮点数转换成字符串值,然后保存转换后的字符串值。执行以下代码会创建一个包含3.14的字符串对象,代表“3.14”:127.0.0.1:6380>SETpi3.14OK127.0.0.1:6380>OBJECTENCODINGpi"embstr"必要时,程序会将存储在将字符串对象转换为浮点值,进行一些操作,然后将得到的浮点值转换回字符串值并继续存储在字符串对象中。例如我们对pikey进行如下操作:127.0.0.1:6380>INCRBYFLOATpi2.0"5.14"127.0.0.1:6380>OBJECTENCODINGpi"embstr"在执行INCRBYFLOAT命令的过程中,字符串和浮点数数字实际上会出现值相互转换的情况。2.5编码转换int编码的字符串对象和embstr编码的字符串对象在满足一定条件时会被转换为raw编码的字符串对象。对于一个int编码的字符串对象,如果在执行命令后,对象保存的是整数值而不是字符串,那么字符串对象就会从int变为raw。比如APPEND命令等。另外,对于embstr编码的字符串,由于Redis没有为它们写任何相应的修饰符,所以embstr编码的字符串对象实际上是只读的。当我们对一个embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换为raw。也就是说embstr编码的字符串一旦被修改,就必须转为raw编码的字符串对象。2.6values和codes的对应对于string对象的各个code,总结如下:valuecode可以用long表示,整型值int可以用longdouble存储,浮点数raw或embstr不能由long或longdouble表示。Fractionalvalueraworstringwithembstr>44bytesrawstringwithlessthanorequalto44bytesembstr3listobject列表对象的可选编码有:quicklist(3.2版本之前的ziplist和linkedlist)。3.1Quicklist编码列表对象3.2版本引入quicklist编码,结合了ziplist和linkedlist,采用双向链表的形式,在每个节点上存储一个ziplist,每个ziplist可以存储多个键值对。也就是说,quicklist的每个节点上存储的不是一条数据,而是一条数据。执行以下命令,服务器会创建一个list对象,quicklist结构如图8所示:127.0.0.1:7379>RPUSHanimal'dog''cat''pig'(integer)3(5.12s)127.0.0.1:7379>OBJECTENCODINGanimal"quicklist"总结Redis实现了一套对象系统来实现所有的功能。对象具有对象类型和对象代码。对象类型对应五种类型:string、list、hash、set、orderedset。对象编码对应跳表、压缩列表、集合、动态字符串等八种底层数据结构。
