Redis初识
- 基于键值对的存储服务系统
- 支持多种数据结构:字符串、哈希、列表、集合、有序集合
- 高性能,功能丰富
特性
- 速度快(单线程,数据存储在内存,使用c语言编写,贴近操作系统)
- 持久化(断电不丢失数据,在内存中的数据异步更新到磁盘上)
- 支持多种数据结构
- 支持多种编程语言
- 功能丰富:Lua脚本,事务,发布订阅,pipeline
- 主从复制
- 高可用、分布式
典型使用场景
- 缓存系统
- 计数器:统计计数业务,接口限流等,使用incr原子命令
- 消息队列系统:
- 排行榜:
- 社交网络
- 实时系统:
简单动态字符串sds
sds相比c字符串的优势:
链表list
- 应用广泛,如列表键、发布与订阅、慢查询、监视器、客户端输出缓冲区等。
字典dict
- 哈希表结构定义:
- 字典结构:
- 哈希算法:MurmurHash算法
- 解决键冲突:链地址法,在表头添加。
- rehash:
- 渐进式rehash:
rehash动作不是一次性、集中式地完成的,而是分多次、渐进式地完成的。
总结
- 字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,一个仅在进行rehash时使用
- 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表
- 在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对rehash到新哈希表中,并且这个过程不是一次性完成的,而是渐进式地完成的
跳跃表skiplist
跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问结点的目的。
支持平均O(logN),最坏O(N)复杂度的结点查找。
结构:
- 前进指针、后退指针用于遍历。
- 跨度用于记录两个节点之间的距离,用来计算排位(rank):在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。
- 分值 double类型。
- 每个跳跃表节点的层高都是1至32之间的随机数(幂次定律,即越大的数生成的概率越小,随机生成值),层的数量越多,访问其他节点的速度就越快。
- 节点对象必须唯一、但分值可以相同:分值相同的节点按照成员对象在字典序中的大小来进行排序,小靠表头,大靠表尾。
整数集合intset
自动升级底层数组:
- 过程:扩容 -> 为新元素分配空间 -> 变更原有元素的类型并重新分配空间 -> 添加新元素
- 因为每次向整数集合添加新元素都可能会引起升级,而每次升级都需要对底层数组中已有的所有元素进行类型转换,所以向整数集合添加新元素的时间复杂度为O(N)
升级的好处:
- 提升灵活性:自动升级底层数组,避免类型错误
- 节约内存:整数集合既可以让集合同时保存三种不同类型的值,又可以确保升级操作在有需要的时候进行,这可以尽量节省内存
降级:
- 整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。
压缩列表ziplist
当一个列表键只包含少量列表项,并且每个列表项值是小整数值或是长度比较短的字符串,Redis就会使用压缩列表来做列表建的底层实现。压缩列表为了节省内存而开发,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者是一个整数值。
- 结构:
- 压缩列表节点构成:
连锁更新(cascade update):增删节点情况下产生的连续多次空间扩展操作
- 例子:在一个压缩列表中,有多个连续的、长度介于250~253字节之间的结点e1至eN。此时如果将一个长度大于等于254字节的新结点设置为压缩列表的表头结点,此时将引发连锁更新操作:
-
最坏情况下需要对压缩列表执行N次空间重分配操作,每次空间重分配的最坏复杂度为O(N),故连锁更新的最坏复杂度为O(N^2)
-
触发连锁更新条件概率低,增删等命令的平均复杂度仅为O(N)
-
因为ziplistPush、ziplistInsert、ziplistDelete、ziplistDeleteRange四个函数都有可能会引起连锁更新,所以最坏复杂度都是O(N^2)