Redis的五大数据类型
String数据类型
string 是Redis的最基本的数据类型,可以理解为与 Memcached 一模一样的类型,一个key 对应一个 value。string 类型是二进制安全的,意思是 Redis 的 string 可以包含任何数据,比如图片或者序列化的对象,一个 redis 中字符串 value 最多可以是 512M。
①、相关命令介绍
string 数据类型在 Redis 中的相关命令:
表格图片引用:http://www.cnblogs.com/xrq730/p/8944539.html(下同)


①、上面的 ttl 命令是返回 key 的剩余过期时间,单位为秒。
②、mset和mget这种批量处理命令,能够极大的提高操作效率。因为一次命令执行所需要的时间=1次网络传输时间+1次命令执行时间,n个命令耗时=n次网络传输时间+n次命令执行时间,而批量处理命令会将n次网络时间缩减为1次网络时间,也就是1次网络传输时间+n次命令处理时间。
但是需要注意的是,Redis是单线程的,如果一次批量处理命令过多,会造成Redis阻塞或网络拥塞(传输数据量大)。
③、setnx可以用于实现分布式锁,具体实现方式后面会介绍。
上面是 string 类型的基本命令,下面介绍几个自增自减操作,这在实际工作中还是特别有用的(分布式环境中统计系统的在线人数,利用Redis的高性能读写,在Redis中完成秒杀,而不是直接操作数据库。)。


②、典型使用场景
一、计数
由于Redis单线程的特点,我们不用考虑并发造成计数不准的问题,通过 incrby 命令,我们可以正确的得到我们想要的结果。
二、限制次数
比如登录次数校验,错误超过三次5分钟内就不让登录了,每次登录设置key自增一次,并设置该key的过期时间为5分钟后,每次登录检查一下该key的值来进行限制登录。
hash数据类型
hash 是一个键值对集合,是一个 string 类型的 key和 value 的映射表,key 还是key,但是value是一个键值对(key-value)。类比于 Java里面的 Map<String,Map<String,Object>> 集合。
①、相关命令介绍

演示如下:

②、典型使用场景
查询的时间复杂度是O(1),用于缓存一些信息。
List数据类型
list 列表,它是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),它的底层实际上是个链表。
列表有两个特点:
一、有序
二、可以重复
这两个特点要注意和后面介绍的集合和有序集合相对比。
①、相关命令介绍



②、典型使用场景
一、栈
通过命令 lpush+lpop
二、队列
命令 lpush+rpop
三、有限集合
命令 lpush+ltrim
四、消息队列
命令 lpush+brpop
Set数据类型
Redis 的 set 是 string 类型的无序集合。
相对于列表,集合也有两个特点:
一、无序
二、不可重复
①、相关命令介绍



②、典型使用场景
利用集合的交并集特性,比如在社交领域,我们可以很方便的求出多个用户的共同好友,共同感兴趣的领域等。
zset数据结构
zset(sorted set 有序集合),和上面的set 数据类型一样,也是 string 类型元素的集合,但是它是有序的。
①、相关命令介绍


②、典型使用场景
和set数据结构一样,zset也可以用于社交领域的相关业务,并且还可以利用zset 的有序特性,还可以做类似排行榜的业务。
Redis基本数据结构
简单动态字符串
Redis中的字符串使用“简单动态字符串”(SDS)表示,无论是字符串值还是键底层都采用“简单动态字符串”。

free:未使用空间大小;len:字符串长度;buf:以空字符结尾的char数组。
为了减少内存重新分配次数,SDS做出了以下优化:
- 空间预分配:额外分配的未使用空间数量由以下公式决定:
- 如果对SDS进行修改之后,SDS的长小于1MB,那么程序分配和
len属性同样大小的未使用空间, - 如果对SDS进行修改之后,SDS的长度将大于等千1MB, 那么程序会分配 1MB 的未使用空间。
- 如果对SDS进行修改之后,SDS的长小于1MB,那么程序分配和
- 惰性空间释放:程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用
free属性将这些字节的数量记录起来,并等待将来使用。
链表
链表是Redis列表键实现之一,也是很多其他功能实现的基础,链表节点定义如下:

链表的完整结构体定义如下

head为表头指针;tail为表尾指针;len为链表长度计数器;dup为函数指针,用于复制链表节点所保存的值;free为函数指针,用于释放链表节点所保存的值;match为函数指针,则用于对比链表节点所保存的值和另一个输入值是否相等。
Redis链表特性:
①、双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
②、无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
③、带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
④、多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。
字典
字典将键和值进行关联,当哈希键中的键值对数量比较多,或者键值对中的元素比较大的时候,采用字典作为底层实现。字节的数据结构如下


哈希表结构dict中,table属性是一个数组,每个元素都是指向dictEntry结构的指针,size属性记录了哈希表的大小,sizemask属性的值总是等于size-1,而used属性则记录了哈希表目前已有节点(键值对)的数量。
字典结构dictType中有两个哈希表ht[0]和ht[1],ht[l]哈希表只会在对 ht[0]哈希表进行rehash时使用,rehashidx它记录了rehash目前的进度。type属性是一个指向dictType结构的指针,dictType结构保存了一簇用于操作特定类型键值对的函数,例如计算哈希值、复制键、复制值、对比键、销毁键和销毁值的函数。而privdata属性则保存了需要传给那些类型特定函数的可选参数。
为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。
- 如果执行的是扩展操作,那么ht[l]的大小为第一个大于等于
ht[0].used*2的[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kpI3zItc-1613741221622)(https://juejin.im/equation?tex=2%5Em)]; - 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于
ht[O].used的[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Cq3zasV-1613741221626)(https://juejin.im/equation?tex=2%5Em)]。
字典采用渐进式rehash,好处在千它采取分而治之的方式,将 rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上。
**①、哈希算法:**Redis计算哈希值和索引值方法如下:
#1、使用字典设置的哈希函数,计算键 key 的哈希值``hash = dict->type->hashFunction(key);``#2、使用哈希表的sizemask属性和第一步得到的哈希值,计算索引值``index = hash & dict->ht[x].sizemask;
**②、解决哈希冲突:**这个问题上面我们介绍了,方法是链地址法。通过字典里面的 *next 指针指向下一个具有相同索引值的哈希表节点。
**③、扩容和收缩:**当哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩。具体步骤:
1、如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。
2、重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。
3、所有键值对都迁徙完毕后,释放原哈希表的内存空间。
④、触发扩容的条件:
1、服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于1。
2、服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于5。
ps:负载因子 = 哈希表已保存节点数量 / 哈希表大小。
⑤、渐近式 rehash
什么叫渐进式 rehash?也就是说扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。如果保存在Redis中的键值对只有几个几十个,那么 rehash 操作可以瞬间完成,但是如果键值对有几百万,几千万甚至几亿,那么要一次性的进行 rehash,势必会造成Redis一段时间内不能进行别的操作。所以Redis采用渐进式 rehash,这样在进行渐进式rehash期间,字典的删除查找更新等操作可能会在两个哈希表上进行,第一个哈希表没有找到,就会去第二个哈希表上进行查找。但是进行 增加操作,一定是在新的哈希表上进行的。
跳跃表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质:
1、由很多层结构组成;
2、每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
3、最底层的链表包含了所有的元素;
4、如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
5、链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

Redis中跳跃表节点定义如下:
header: 指向跳跃表的表头节点。tail: 指向跳跃表的表尾节点。level: 记录目前跳跃表内,层数最大的那个节点的层数。length: 记录跳跃表的长度。
zskiplistNode 结构,该结构包含以下属性:
- 层 (
level) : 每个层都带有两个属性:前进指针和跨度。前进指针用于 访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的 距离。 - 后退 (
backward) 指针:指向位于当前节点的前一个节点。 - 分值 (
score): 节点按各自所保存的分值从小到大排列。 - 成员对象 (
obj): 节点所保存的成员对象。

①、搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
②、插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
③、删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。
压缩列表
压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。

压缩列表的每个节点构成如下:

①、previous_entry_ength:记录压缩列表前一个字节的长度。previous_entry_ength的长度可能是1个字节或者是5个字节,如果上一个节点的长度小于254,则该节点只需要一个字节就可以表示前一个节点的长度了,如果前一个节点的长度大于等于254,则previous length的第一个字节为254,后面用四个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费。
②、encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。
③、content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。
作者:zhenghaoz
链接:https://juejin.cn/post/6844904069127995400
来源:掘金
作者:IT可乐
么做很有效地减少了内存的浪费。
②、encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。
③、content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。
作者:zhenghaoz
链接:https://juejin.cn/post/6844904069127995400
来源:掘金
作者:IT可乐
出处:http://www.cnblogs.com/ysocean/