Redis知识总结

91 阅读4分钟

Redis知识总结

一、底层结构

参考文章最详细的Redis五种数据结构详解,图片选取参考文章,请以参考文章为准。

1.1 String

  1. 当字符串存储的是可以转化成int类型时,则使用C语言的int存储。
  2. 当字符串字节小于44(每个版本不一样,有的版本时39)时,使用embstr编码存储。因为redis初始化的时候一次分配redisObject时64字节,除了固定的19字节存储固有的数据,剩下的可以存储字符串,达到内存充分利用
  3. 当大于44字节时,使用SDS编码存储。SDS由len,free,char[]组成。

SDS与C字符串的区别:

  • 常数复杂度获取字符串长度

  • 杜绝缓冲区溢出,减少修改字符串时带来的内存重分配次数(空间预分配, 惰性空间释放)

    • 空间预分配

      当追加字符串时,如果len的长度小于1MB,则分配与len一样大小的空间给free,如果大于1MB时,分配1MB给free

    • 惰性空间释放

      减少字符串时,实际上不会释放内存,free不变,等空间预分配时,len=free来实际的释放内存。

  • 二进制安全

参考文章

1.2 List

1.2.1 ziplist

Redis中的列表在3.2之前的版本是使用ziplistlinkedlist进行实现的。在3.2之后的版本就是引入了quicklist

  1. ziplist:

一系列特殊编码的连续内存块组成的顺序存储结构压缩列表并不是以某种压缩算法进行压缩存储数据,而是它表示一组连续的内存空间的使用,节省空间,类似于数组,但是不同之处在于每个节点(entry)所占用的大小可以是不同的

img

压缩列表是列表value和哈希value底层实现的原理之一。压缩列表中每一个节点表示的含义如下所示:

  • zlbytes:4个字节的大小,记录压缩列表占用内存的字节数。
  • zltail:4个字节大小,记录表尾节点距离起始地址的偏移量,用于快速定位到尾节点的地址。
  • zllen:2个字节的大小,记录压缩列表中的节点数。
  • entry:表示列表中的每一个节点。
  • zlend:表示压缩列表的特殊结束符号'0xFF'

再压缩列表中每一个entry节点又有三部分组成,包括previous_entry_ength、encoding、content

  1. previous_entry_ength表示前一个节点entry的长度,可用于计算前一个节点的其实地址,因为他们的地址是连续的。
  2. encoding:这里保存的是content的内容类型和长度。
  3. content:content保存的是每一个节点的内容。

使用场景:当list, hash, set中存储的value为简单整型和字符串时。

1.2.2 双端链表:

img

  • 带有指向前置节点和后置节点的指针,获取这两个节点的复杂度为O(1)
  • 无环:表头节点的prev和表尾节点的next都指向NULL,对链表的访问以NULL结束
  • 链表长度计数器:带有len属性,获取链表长度的复杂度为O(1)
  • 多态:链表节点使用 void*指针保存节点值,可以保存不同类型的值

1.3 Hash

  • 结构

img

如上图,hash中真正存储数据的只有ht[0], 而ht[1]则为之后的扩展做准备。redis中是使用链地址法来解决hash冲突的。

  • rehash

刚开始初始化hash的时候,hash会有一个默认的大小(例如上面的size=2),当新加入的hash值越来越多时,需要进行扩容操作,称之为rehash。

  1. 扩容操作:

    由于ht[0]是一开始用来存储数据的,ht[0]大小无法存储新增的hash数据时, 会将ht[1].size变为ht[0].user的两倍后最接近2的整数幂的值,例如上面ht[0].user=3 扩容后ht[1]的size = 3 * 2=8(刚好是2的整数幂),之后将ht[0]存放的hash数据重新hash,分布在ht[1]当中。然后ht[1]更改下表为ht[0], ht[1]指向null。

  2. 收缩操作

    ht[1] 的size为第一个大于等于 ht[0].used 的 2^n。也就是说,当要收缩时user < size 。

  • 渐进式rehash

回顾上述rehash的过程,当数据量很庞大的时候,rehash的耗时肯定是不小的,此时会造成服务一点时间的不可用。为此,redis设计上采用了分布rehash,让 服务变成可用状态。具体的过程如下:

1. 字典中的rehashidx为-1表示没有进行rehash过程,当要开始rehash时,rehashidx值变为12. redis需要对hash进行CRUD时,保证ht[0]只减不增加,也就是说,查询在ht[0]和ht[1]中查,删除只删除ht[0],更新两者一起更新,增加只在ht[1]中增加。
  1. rehashidx的作用很重要,由于rehash是分批次的,每100个key进行rehash后,rehashidx就+1,这样就能通过key的数量知道当前rehash的进度。
  2. 为什么是100个key呢,因为redis默认100个key的rehash不会超过100ms,不然会break掉这次分配的rehash。

在1.2.1ziplist中,也可以存储hash。

1.4 Sort set

  • 结构skiplist

preview

类似于B+数,但是每层都是链表,方便维护。但这样容易引出另一个问题,就是为什么mysql不用跳表而用B+树呢。详情可以参阅这篇文章

总的来说,B+树一个节点可以存储更多的索引,能使树的深度更低,而跳表一个节点只能存储一个索引,层数更高,查找复杂度更高,但是由于是内存操作,所以redis采用了跳表。

1.5 Set

  • 整数集合

    • intset

      结合对象保存的所有元素都是整数值

      集合对象保存的元素数量不超过512个

  • hashtable

参考1.3Hash

二、持久化机制

  • rdb

    • 快照备份频率
    • fork子线程
    • COW
  • AOF

    • 命令追加
    • 日志瘦身
  • 混合型

master AOF, slave RDB

三、过期策略和淘汰机制

3.1 定期删除+惰性删除

  • 定时随机抽查过期key,进行删除
  • 访问的key若过期,则删除

3.2 淘汰机制

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 a

llkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据,新写入操作会报错

四、redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

五、常见性能问题

5.1 主从问题

1、Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度 rdbSave函数,会阻塞主线 程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务

2、如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一

3、为了主从复制的速 度和连接的稳定性,Master 和 Slave 最好在同一个局域网

4、尽量避免在压力很大的主库上增加从

5、主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1<- Slave2 <- Slave3… 这样的结构方便解决单点故障问题,实现 Slave 对 Master的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

5.2 消息队列

  • 基于List的 LPUSH+BRPOP 的实现
  • PUB/SUB,订阅/发布模式
  • 基于Sorted-Set的实现
  • 基于Stream类型的实现

redis灵魂拷问:如何使用stream实现消息队列 - 云+社区 - 腾讯云 (tencent.com)

5.3 分布式锁

  • 命令
  • 超时时间

5.4 高可用方案

new.qq.com/omn/2022031…