Redis
redis 支持复杂的数据结构
- string
- hash
- list
- set
- sorted set (去重的同时可以根据分数排序)
redis 的线程模型
redis 内部使用文件事件处理器是单线程的。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE
事件与连接应答处理器关联。
-
客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个
AE_READABLE
事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的AE_READABLE
事件与命令请求处理器关联。 -
假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生
AE_READABLE
事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 AE_READABLE 事件,由于前面 socket01 的AE_READABLE
事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的AE_WRITABLE
事件与命令回复处理器关联。 -
如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个
AE_WRITABLE
事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的AE_WRITABLE
事件与命令回复处理器的关联。
redis 单线程模型,效率也能这么高?
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
过期策略 和 内存淘汰机制
redis 过期策略
redis 过期策略是:惰性删除 + 定期扫描。
-
惰性删除
获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。删除时采用的是异步线程
-
定期扫描
- 默认是每隔 100ms 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;
- 如果过期的 key 比率超过 1/4,那就重复步骤 i;
内存淘汰机制
淘汰机制共 6 种,可以分为 3 类:
-
直接报错,拒绝写入
noeviction
: 新写入操作会报错,读请求可以继续进行
-
全部的 key 范围
allkeys-lru
: 移除最近最少使用的 key。allkeys-random
:随机移除某个 key。
-
设置了过期时间的 key 范围
volatile-lru
:移除最近最少使用的 key。volatile-random
: 随机移除某个 key。volatile-ttl
:有更早过期时间的 key 优先移除。
淘汰算法
近似 LRU 的算法
Redis 使用的是一种近似 LRU
算法,它跟 LRU
算法还不太一样。之所以不使用 LRU 算法,是因为需要消耗大量的额外的内存,需要对现有的数据结构进行较大的改造。
近似 LRU
算法则很简单,在现有数据结构的基础上使用随机采样法来淘汰元素,能达到和 LRU 算法非常近似的效果。Redis 为实现近似 LRU
算法,它给每个 key 增加了一个额外的小字段,就是最后一次被访问的时间戳。
lru 字段存储的是 Redis 时钟 server.lruclock
,Redis 时钟是一个 24bit 的整数,默认是 Unix 时间戳对 2^24 取模的结果,大约 97 天清零一次。
如果 server.lruclock
没有折返 (对 2^24 取模),它就是一直递增的,这意味着对象的 LRU
字段不会超过 server.lruclock
的值。如果超过了,说明 server.lruclock
折返了。通过这个逻辑就可以精准计算出对象多长时间没有被访问(对象的空闲时间)。
LFU
全称是 Least Frequently Used,表示按最近的访问频率进行淘汰,它比 LRU 更加精准地表示了一个 key 被访问的热度。
在 LFU 模式下,lru 字段 24 个 bit 用来存储两个值,分别是 ldt(last decrement time) 和 logc(logistic counter)。
logc 是 8bit 大小,用来存储访问频次,因为 8bit 能表示的最大整数值为 255,存储频次肯定远远不够,所以这 8bit 存储的是频次的对数值,并且这个值还会随时间衰减。如果它的值比较小,那么就很容易被回收。为了确保新创建的对象不被回收,新对象的这 8bit 会初始化为一个大于零的值,默认是 LFU_INIT_VAL=5
。
ldt 是 16bit 大小,用来存储上一次 logc 的更新时间,取的是分钟时间戳对 2^16 进行取模,大约每隔 45 天就会折返。同 LRU
模式一样,我们也可以使用这个逻辑计算出对象的空闲时间,只不过精度是分钟级别的。
ldt 的值和 LRU
模式的 lru 字段不一样的是, ldt 不是在对象被访问时更新的。它在 Redis 的淘汰逻辑进行时进行更新,淘汰逻辑只会在内存达到 maxmemory 的设置时才会触发,在每一个指令的执行之前都会触发。
每次淘汰都是采用随机策略,随机挑选若干个 key,更新这个 key 的「热度」,淘汰掉「热度」最低的。
高可用
Redis 主从架构
- redis 采用异步方式复制数据到 slave 节点.
- 一个 master 是可以配置多个 slave 的;
- slave 也可以连接其他的 slave;
- slave 做复制的时候,不会阻塞 master 的正常工作;
- slave 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
- slave 主要用来进行横向扩容,做读写分离,扩容的 slave 可以提高读的吞吐量。
注意,如果采用了主从架构,那么建议必须开启 master 的持久化,不建议用 slave 作为 master 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave 的数据也丢了。
redis 主从复制
-
全量复制
master 在本地生成一份 rdb 快照文件,这个过程可以内存中直接创建 rdb。
master 将 rdb 快照文件发送给 slave ,如果 rdb 复制时间超过 60 秒,那么 slave 就会认为复制失败
master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave 保存了 rdb 之后,再将新的写命令复制给 slave 。
如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。 -
增量复制
如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 -
异步复制
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
-
heartbeat
主从节点互相都会发送 heartbeat 信息。
master 默认每隔 10 秒 发送一次 heartbeat,slave node 每隔 1 秒 发送一个 heartbeat。 -
过期 key 处理
slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。
redis 持久化的两种方式
- RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
- AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。
RDB 优缺点
-
优点
- RDB 非常适合做冷备
- RDB 对 redis 对外提供的读写服务,影响非常小;
- 相对于 AOF 来说,基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
-
缺点
- 相对于 AOF 来说,可能丢失数据比较多
- 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
AOF 优缺点
-
优点
- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据。
- AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
- AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。
-
缺点
- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件.
RDB 和 AOF 到底该如何选择
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于