1、redis的数据结构
(1)动态字符串(SDS)
redis自身构建了一个简单动态字符串的抽象类型,SDS,在redis里,包含字符串的键值对在底层都是由SDS来实现的。
除了用来保存数据库的字符串值之外,SDS还被用来作缓冲区:AOF模块中的AOF缓冲区、客户端输入缓冲区。
SDS的结构:free,当free属性值为0时,表示SDS没有分配未使用空间;len,SDS保存的字符串长度;buf[],char数组类型,将保存的字符串值拆分保存,最后跟一个空字符 ,空字符不计算到len属性里。
SDS的特性:len属性,在获取字符串长度时时间复杂会降低,仅为O(1),避免了获取字符串长度成为redis性能瓶颈,
通过len属性杜绝缓冲区溢出
通过空间预分配和惰性空间分配来减少修改字符串时带来的内存重分配次数,空间预分配,当SDS中保存一个长度为5的字符串,此时SDS的属性len=5,free=0,在其后添加一个长度为5的字符串,再加上一个空字符,此时长度为len=5+5+1 =11而SDS会在分配一个长度为11的空闲空间,SDS属性len=11,free=11;惰性空间,SDS属性len=11,free=0,减少字符串3位,此时SDS属性len=8,free=3,SDS不会把空余空间收回,而是交给free进行记录,以供下次使用
可以保存文本和二进制数据
(2)链表
链表可以提供高效的节点重排能力、顺序性节点访问、并且可以通过增删节点来灵活的调节链表长度,因此redis中对链表应用很广泛,例如列表键、发布/订阅、慢查询、监视器、构建客户区缓冲区等
每个链表节点都由一个ListNode结构表示,都有执向前节点和后节点的指针以及链表长度等信息,redis中实现的是双向无环链表,根据不同类型的特定函数,可以保存不同类型的值
(3)字典(symbol table)
用于保存键值对的抽象数据结构,字典中一个键可以和一个值进行关联,redis中数据库底层就是通过自身构建的字典来实现的,对数据库的增删改查也都是在对字典进行操作
Redis中字典的底层是由哈希表实现的,一个哈希表有多个哈希节点,每个节点保存了字典的一个键值对,字典的结构包含类型特定函数 type属性;私有数据privdata;哈希表ht[2]每个字典携带两个哈希表,一个存储数据,一个rehash时使用;rehash索引 trehashidx,当rehash不再进行时,值为-1
Redis中字典所使用的哈希表中主要包含哈希表数组 table、哈希表大小size、哈希表大小掩码值sizemask用于计算索引值、哈希表已有节点数量used
哈希表数组table使用dictEntry结构,dictEntry结构都保存着一个键值对和一个指向下一个节点的指针,形成一个链表,这个指针还可以将多个哈希值相同的键值对连接在一起,避免发生键冲突的问题
当要将一个新的键值对添加到字典里时,需要先根据键值对的键计算出哈希值和索引值,redis中使用MurmurHash2算法来计算哈希值,然后根据索引值,将包含新键值对的哈希表放到字典中哈希数组指定的索引上。
当哈希表中保存的键值对过多或过少时,需要对哈希表的大小进行处理,此时可以通过重新散列(rehash)来处理
重新散列的过程:根据ht[0]哈希表的used属性定义ht[1]的大小,扩展操作中ht[1]的大小是第一个大于等于ht[0].used*2的2^n,如果是收缩操作,ht[1]的大小是第一个大于等于ht[0].used的2^n,例如:扩展:ht[0].used =4,4*2=8,8=2^3,那么ht[1]的大小就是8,收缩:若ht[0].used=6,第一个大于等于6的2的n次方是2^3,那么ht[1]的大小就是8
将ht[0]中的键值对重新计算哈希值和索引值,然后保存到ht[1]指定位置上
释放ht[0],将ht[1]设置为ht[0],并在ht[1]新建一个空白的哈希表,以供下一次rehash使用
(4)跳跃表(skiplist)
一种有序的数据结构,通过在每个节点维持多个指向其他节点的指针,实现快速访问,Redis中有序集合键(set)底层由跳跃表来实现以及集群节点中跳跃表作内部数据结构。
跳跃表由zskiplistNode结构和zskiplist结构组成,zskiplistNode表示跳跃表节点,zskiplist用于存储跳跃表节点相关信息,包含跳跃表的表头节点header属性、跳跃表的表尾节点tail属性、记录跳跃表层数最大的节点数level属性、跳跃表长度length属性
(5)压缩列表(ziplist)
redis中列表和哈希的底层实现之一,当要保存少量的整数值、小数值或者较短的字符串,redis就会使用压缩列表作为底层实现
压缩列表的出现是为了redis 节约内存,它是由一系列的特殊编码的连续内存块组成顺序型结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或一个整数值
(6)整数集合(intset)
Redis中集合的底层实现之一,当集合中都是整数时,redis默认使用整数集合作为集合的底层实现。intset结构包含三个属性,encoding编码格式、length数量、contents[]保存数据的数组,从小到大排序,是整数集合的底层实现,数组保存的数据类型由encoding来确定,程序会根据新添加的数据类型来改变数组的类型
2、redis的对象
redis中基于数据结构来创建了一个对象系统,系统中包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象,通过这些对象来实现数据库、键值对,redis队象系统还通过引用计数技术实现内存回收机制和对象共享机制
redis使用对象来表示数据库中的键和值,每当我们在数据库中新建一个键值对时,我们都会创建至少两个对象,键值对的键和键值对的值
redis中每个对象都是由redisobject结构表示,包含type属性、encoding(编码类型属性)、ptr(指向底层实现数据结构的,数据结构有对象的encoding决定)
每种不同类型的对象都至少使用两种编码
(1)字符串对象
字符串编码的对象可以是int、raw、embstr
字符串对象保存的是整数值并且这个整数值可以用long类型表示,那么字符串对象会把整数值保存在ptr属性里并设置编码为int
字符串对象保存的是字符串值,若长度大于32字节,那么字符串对象会把字符串值保存在SDS里,并设置编码为raw,若小于32字节,那么字符串对象会设置编码为embstr
字符串对象保存的是long double 类型的值,那么字符串对象编码设置为embstr或raw
(2)列表对象
列表对象的编码是zipList(压缩列表)或者linkedlist(双端链表)
linkedList编码的列表对象在底层双端链表中包含了多个字符串对象
字符串对象是redis对象中唯一一个被其他四种类型嵌套的对象
列表对象保存的字符串长度<64字节,数量小于<512个,使用zipList,反之使用linkedlist,但是这两个值在配置文件中可以修改,list-max-ziplist-value和list-max-ziplist-entries
(3)哈希对象
哈希对象编码是ziplist(底层压缩列表)和hashtable(底层字典,每一个键、值都是字符串对象)
哈希对象对象保存的字符串长度<64字节,数量小于<512个,使用zipList,反之使用hashtable,但是这两个值在配置文件中可以修改,hash-max-ziplist-value和hash-max-ziplist-entries
(4)集合对象
集合对象编码是intset(底层整数集合)和hashtable(底层字典,每一个键、值都是字符串对象)
集合对象保存的值都是整数值且数量不超过512个,使用inset,反之使用hashtable,数量值在配置文件中可以修改,set-max-inset-value
(5)有序集合对象
有序集合对象编码是ziplist(底层压缩列表,从小到大排序)和skiplist(底层zset,一个zset包含一个跳跃表和一个字典,从小到大排序)
有序集合同时使用跳跃表和字典实现的原因:通过保留字典和跳跃表的优点,提高有序集合的查找和范围性操作的性能和效率
有序集合对象保存的值数量小于128,所有成员长度<64字节,使用ziplist,反之使用skiplist,但是这两个值在配置文件中可以修改,zset-max-ziplist-value和zset-max-ziplist-entries
(6)内存回收
redis在自己的对象系统中构建一个引用计数技术实现的内存回收机制,程序跟踪对象的引用计数信息,适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息由redisObject结构的refcount属性记录。
创建一个新对象时,初始化计数值为1,当对象被一个新程序使用时,引用计数加一,不再被程序使用时,引用计数减一,当引用计数为0时,对象占用的内存会被释放。
(7)对象共享
redis对象共享通过对象的计数属性实现,将数据库键的指针值指向同一个现有的值对象,被共享的值对象的引用计数增加一。
但是共享对象不包含字符串对象,若是字符串对象共享,验证时间会大大提升,验证int类型,复杂度为O(1),而验证字符串类型复杂度为O(N),多个对象复杂度为O(N^2)
(8)其他
SET、GET、APPEND、STRLEN等命令只能对字符串键执行
HDEL、HSET、HGET、HLEN等命令只能对哈希键执行
RPUSH、LPOP、LINSERT、LLEN等命令只能对列表键执行
SADD、SPOP、SINTER、SCARD等命令只能对集合键执行
ZADD、ZCARD、ZRANK、ZSCORE等命令只能对有序集合键执行