redis

subtitle

Posted by Kun on April 23, 2025

redis

redis为什么快

  1. 内存:数据存在内存当中进行读取和修改,没有IO瓶颈1,内存的读写速度是磁盘的数十倍
  2. 单线程模型:数据处理的单线程的,避免了多线程之间的上下文切换和加锁的开销,但是向持久化,复制,异步删除大Key是通过后台线程进行处理的
  3. 数据结构:redis对底层的数据结构做了非常多的优化
  4. 高效机制:管道,一次性添加多个命令,减少TCP往返次数 ,批量操作 事务多命令原子操作,但是不支持回滚,编写lua脚本,在服务端执行多个操作
  5. 异步持久化操作:高版本redis引入IO线程,加速持久化写入
  6. 热数据常驻内存+淘汰策略: 配置多种淘汰策略热门数据访问极快(LRU,LFU)
  7. 精简的网络协议 RESP:Redis 使用 RESP(Redis Serialization Protocol) 协议,结构简单,解析快,几乎是定长读取,开销极小。客户端解析和构造都非常高效,几乎没有额外负担。
  8. 集群与分布式支持支持主从复制、哨兵(Sentinel)、Redis Cluster,水平扩展能力强,可支撑大规模高并发系统。

redis底层结构

String

底层实现:动态字符串SDS代替c字符串

img

特性 说明
len 字段 记录实际长度,避免每次调用 strlen 遍历字符串
alloc 字段 记录分配内存长度,支持动态扩容
兼容 C 字符串 尾部保留 \0
空间预分配机制 扩容时额外多分配空间,减少频繁 realloc
1
| len=5 | alloc=8 | "hello" | \0 | 空余空间... |

Redis 会根据内容自动选择最合适的编码方式来存储 String:

编码方式 类型 说明
int 整型 存的是 long 类型数字(< 2^63)
embstr 短字符串(<= 44字节) 连续内存分配,节省内存
raw 普通 SDS 支持动态扩容的长字符串

Redis 在 String 上的优化策略是:能整数就整数、短字符串用 embstr、长字符串用 raw、常用值用共享池、读写用 SDS 结构、命令层原子高效,一切为性能让路。

List

在redis3.2之前,使用的是ZipList和LinkendList

在 Redis 3.2 之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表。

zipList小数据使用的是zipList,连续的内存块,顺序存储 (元素个数 < 512 且每个元素 < 64 字节),插入/删除成本高,会连锁更新数据中的prevlen

img

LinkedList:大数据使用LinkedList 双向链表node 遍历需要跳指针,不连续 ,插入删除的时间复杂度都是O(1),只要修改局部指针指向位置

img

listpack

img

quicklist: ziplist+linkedList组合是将若干个listpack 通过指针相连,可以理解为多个ziplist相连

在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构。

quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数,来规避潜在的连锁更新的风险,但是这并没有完全解决连锁更新的问题。

img

Hash

Hash 类型的底层数据结构是由压缩列表或哈希表实现的:

如果哈希类型元素个数⼩于 512 个(默认值,可由 hash-max-ziplist-entries 配置),所有值 ⼩于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使⽤压缩列表作 为 Hash 类型的底层数据结构;

如果哈希类型元素不满⾜上⾯条件,Redis 会使⽤哈希表作为 Hash 类型的 底层数据结构。

img

Set

内部实现 Set 类型的底层数据结构是由哈希表或整数集合实现的

如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;

1
2
3
4
5
6
7
8
typedef struct intset {
	//编码方式
	uint32_t encoding;
	//集合包含的元素数量
	uint32_t length;
	//保存元素的数组
	int8_t contents[];
} intset;

如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构

Zset

Zset 类型的底层数据结构是由压缩列表或跳表实现的:

如果有序集合的元素个数⼩于 128 个,并且每个元素的值⼩于 64 字节时,Redis 会使⽤压缩列表作为 Zset 类型的底层数据结构;

如果有序集合的元素不满⾜上⾯的条件,Redis 会使⽤跳表作为 Zset 类型的底层数据结构;

img

在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了

不常用

BitMap

布隆过滤器(Bloom Filter)是一种空间效率高、查询速度快的概率型数据结构,主要用于判断一个元素是否在集合中。

使用多个哈希函数将元素映射到一个位数组(bit array)上,将对应的位置置为 1;

查询时,对同样的哈希函数取值,如果所有对应位都是 1,则可能存在;只要有一个为 0,则一定不存在。

Geospatial

HyperLogLog

Stream

####

redis持久化

redis持久化有两种方式

一种是rdb这也是redis默认的持久化方式,原理是在写入redis时将数据的快照保存到磁盘上,因此可以设置保存频率的阈值在redis.conf配置文件中设置save 时间 频次,在某个时间内进行了某次写操作

第二种是AOF这是需要在配置文件中手动开启,aof只记录每次操作的指令,这样的好处是几乎不会丢失数据,但是如果进行频繁插入修改,会造成aof文件中冗余数据过多,而且在redis每次进行操作时都会进行一次aof的记录,性能大大受到影响,因此有fgync可以设置为每秒进行一次写入,还有rewrite设置aof文件大小到达多少是进行一次重写,然后每超过100%就会进行一次重写

这两种持久化的方式各有优缺点

当服务器宕机时,aof最多只会丢失1秒的数据,rdb丢失的数据就可能是最后一次保存之后的数据

如果redis数据操作失败时aof可以轻松的查看aof文件,可以进行修改,再重启redis服务,就可以轻松恢复之前的数据,

rdb是一堆二进制文件,不方便进行修改操作

aof非常吃性能

Redis的持久化机制有两种主要方式:RDB(快照)和AOF(追加文件)。

RDB 是 Redis 的默认持久化方式。其原理是在 Redis 写入操作时,将数据的快照保存到磁盘上。RDB 文件是一个二进制文件,包含了某一时刻 Redis 内存中所有数据的完整拷贝。你可以在 redis.conf 配置文件中设置 save 指令来定义持久化的频率,比如每隔某段时间进行一次保存。虽然 RDB 可以减少磁盘写入次数,提高性能,但在发生故障时,可能会丢失最近一次保存之后的数据。

AOF(追加文件)则记录了 Redis 服务器接收到的每一条写操作命令。这种方式几乎能保证数据不会丢失,因为它能记录下每一次的写操作。然而,AOF 会导致文件变得非常庞大,特别是当操作频繁时。AOF 的性能受影响较大,每次写操作都会追加到 AOF 文件中。为了优化性能,Redis 提供了 appendfsync 设置,可以将 AOF 文件的同步频率调整为每秒一次。AOF 文件也会进行重写,以避免文件过大,重写的触发条件是文件大小超出设定的阈值。

总结

  • RDB:操作简单,性能较好,但在服务器宕机时可能会丢失最近一次快照之后的数据。快照文件是二进制格式,不便于修改。
  • AOF:数据持久化更可靠,最多只会丢失1秒的数据,但性能开销较大,文件可能会变得非常庞大。可以通过查看和修改 AOF 文件来恢复数据。

主从复制

master slave

master 每段时间会给slave发送一个请求查看slave是否掉线,slave会回应这个请求,这就是心跳机制

slave复制master时master可能会进行全量复制和增量复制

判断逻辑时master的偏移量

1. 心跳机制

Master(主库)会定期发送心跳请求给 Slave(从库)以确认从库的连接状态。Slave 收到心跳请求后会立即回应,告知主库其正常运行。这种机制确保 Master 能够及时发现 Slave 是否掉线或失去响应,以便进行相应的处理。

2. 复制方式
  • 全量复制:在初次复制或 Slave 数据严重滞后于 Master 时,Master 会将整个数据库的内容发送给 Slave。这种情况下,Slave 会先清空自己的数据,然后接收 Master 传输的所有数据并写入自己的数据库。
  • 增量复制:当 Slave 已经拥有较为完整的数据时,Master 会基于其偏移量(即已经传输的日志位置)发送增量数据给 Slave。这些增量数据通常是 Master 最近的变更日志,通过这些日志,Slave 可以更新自己以保持与 Master 的同步。
3. 偏移量判断

Master 和 Slave 在复制过程中会使用偏移量来判断数据同步的进度。偏移量是指主从复制过程中,Master 已经发送给 Slave 的日志位置。通过对比 Master 的当前偏移量和 Slave 的偏移量,Master 能够判断 Slave 是否需要进行全量复制或增量复制。当 Slave 的偏移量落后于 Master 时,Master 会根据具体情况选择全量或增量方式进行数据同步。

这个流程保证了主从复制的效率和数据一致性,是分布式数据库中实现高可用和容灾的重要手段。

sentinel哨兵模式

监控:用于同步各个节点的状态信息,

通知:sentinel在通知阶段要不断的去获取master/slave的信息,然后在各个sentinel之间进行共享

故障转移:当master宕机时,如何没有哨兵连接上就会却认为主节点掉线,哨兵们会进行投票,选出一个话事人(可以深入讲讲如何选出话事人),然后再由话事人发起投票,选哪一个从节点为主节点然后其他的从节点开始通过全量复制新主节点数据,如果所有的从节点在同一时间都进行全量复制,新主节点的带宽可能扛不住,因此有一个配置可以设置同时只给一个从节点全量复制

Sentinel(哨兵)模式是一个用于监控和管理数据库主从复制的高可用性解决方案。它包含以下几个关键部分:

1. 监控

Sentinel 会持续监控 Master 和 Slave 节点的状态,包括它们的健康状况和连接状态。它定期检查节点的响应,以确保这些节点能够正常运行。如果 Sentinel 发现某个节点存在问题,它会记录并报告这些问题。

2. 通知

在通知阶段,Sentinel 不断收集各个节点的状态信息,并将这些信息在 Sentinel 集群中进行共享。这样,集群中的所有 Sentinel 实例都可以对数据库系统的整体状态有一个一致的了解,并能在必要时采取适当的行动。

3. 故障转移

当 Master 节点宕机或无法响应时,Sentinel 会启动故障转移过程:

  • 检测主节点宕机:如果 Sentinel 无法与 Master 节点建立连接,它会开始认为 Master 节点可能已经宕机。为了确认这一点,Sentinel 会通过监控其他 Sentinel 实例来进一步验证 Master 的状态。
  • 投票选举:如果 Sentinel 确认 Master 节点宕机,它们会启动一个投票过程以选举出一个新的 Master 节点。这个过程涉及到所有 Sentinel 实例之间的协商,以确保选择一个新的主节点。
  • 选举话事人:在投票过程中,一个 Sentinel 会被选为话事人(Leader),负责组织和协调故障转移过程。话事人会发起对 Slave 节点的投票,以确定哪个 Slave 节点将成为新的 Master 节点。
  • 选举新 Master:在选举阶段,话事人会选择一个 Slave 节点作为新的 Master 节点。选中的 Slave 节点会开始成为新的 Master,并通过全量复制将数据同步到其他从节点。
  • 全量复制:为了确保数据一致性,新选出的 Master 节点需要将其数据完全同步到其他从节点。在这个过程中,可能会存在带宽问题,因为所有从节点同时进行全量复制会对网络带来较大的压力。为此,系统允许配置限制,同时只让一个从节点进行全量复制,以减少带宽负载。

这个过程确保了数据库系统的高可用性和数据一致性,即使在主节点出现故障的情况下,也能够快速恢复服务。

cluster集群

主节点有多个横向分布,一台主机内存16g那么四台并排分布就有64g, cluster集群会给四台机器分块分为16384块,当要存一个key时,对key进行crc16算法将key均匀的存储在四台主机中

1. 基本概念

  • 节点:Redis Cluster 由多个 Redis 节点组成,每个节点可以是主节点或从节点。主节点负责存储数据,从节点则是主节点的备份,用于容错和负载均衡。
  • 分片:数据被划分到不同的节点上,每个节点负责一部分数据。Redis Cluster 使用 16384 个哈希槽来实现数据分片。
  • 哈希槽:每个键通过哈希函数映射到一个哈希槽,然后该槽对应一个或多个 Redis 节点。哈希槽是 Redis Cluster 分片的核心。

2. 数据分片

  • 槽分配:集群中的每个节点负责一定范围的哈希槽。例如,一个节点可能负责 0-8191 号槽,另一个节点负责 8192-16383 号槽。
  • 哈希算法:Redis 使用 CRC16 哈希算法将键映射到 16384 个槽中的一个,然后将该槽分配给集群中的某个节点。

3. 优缺点

  • 优点
    • 横向扩展:通过添加更多节点,可以轻松扩展集群的容量和性能。
    • 高可用性:通过主从复制和故障转移,确保数据的高可用性。
    • 负载均衡:数据分片机制使得负载在集群中均匀分布。
  • 缺点
    • 复杂性:集群的管理和配置比单节点 Redis 更加复杂。
    • 网络延迟:节点之间的网络通信可能带来一定的延迟。