Redis 开发随笔
Redis 内存管理
如何查看 Redis 的内存使用情况
使用 Redis 的 INFO 命令,可以获取有关 Redis 服务器的内存信息,命令如下:
1 | info memory |
其中与内存使用情况相关的输出信息如下:
used_memory:已使用的内存
如何查看 Redis 的最大占用内存
第一种查看方式
要查看 Redis 的最大占用内存,可以使用 Redis 的 CONFIG 命令来获取 Redis 服务器的内存配置信息,命令如下:
1 | config get maxmemory |
第二种查看方式
要查看 Redis 的最大占用内存,可以使用 Redis 的 INFO 命令来获取 Redis 服务器的内存信息,命令如下:
1 | info memory |
其中与最大占用内存相关的输出信息如下:
maxmemory:可使用的最大内存(最大占用内存)
Redis 默认有多大的内存可以使用
如果 Redis 不设置最大内存大小,或者设置最大内存大小为 0,那么在 64 位操作系统下不限制内存大小,在 32 位操作系统下最多可以使用 3GB 内存。因为 32 位系统最大只支持 4GB 的内存,而系统本身就需要一定的内存资源来支持运行,所以 Redis 限制 32 位系统最大有 3GB 内存可以使用。
生产环境如何修改 Redis 的内存大小
在生产环境中,一般推荐设置 Redis 的内存大小为最大物理内存的四分之三。Redis 内存大小的修改,有以下两种方式:
- 第一种方式,通过配置文件永久修改
- 打开 Redis 的配置文件(通常是
redis.conf),找到并设置maxmemory参数的值,该参数是bytes字节类型,需要注意单位的转换。 - 重新启动 Redis 服务,以使新的内存配置生效。
- 打开 Redis 的配置文件(通常是
- 第二种方式,通过命令临时修改
- 使用
config set maxmemory xxxx命令修改 Redis 的内存大小,单位是字节。 - 特别注意,在 Redis 服务器重启之后,这个修改会失效,也就是说使用命令这种修改方式不会永久保存到配置文件中。
- 使用
Redis 内存使用超出设置的最大值会怎样
当 Redis 的内存使用超出了设置的最大内存限制(maxmemory)时,通常会导致以下几种情况之一:
- 数据丢失: 如果 Redis 的内存超出了设置的最大值,并且未启用持久化(如 RDB 快照或 AOF 日志),那么一旦 Redis 进程重新启动,超出内存限制的数据可能会丢失。
- 操作失败: 当 Redis 超出内存限制时,它可能会拒绝执行新的写操作(如 SET、LPUSH 等),并返回错误给客户端。这种情况下,客户端可能会收到类似于
OOM command not allowed when used memory > 'maxmemory'的错误信息。 - 触发操作系统的交换: 当 Redis 使用的内存超出物理内存限制时,操作系统可能会将一部分内存数据交换到磁盘上的交换空间(Swap Space)中。这会导致系统性能急剧下降,因为磁盘访问速度远远低于内存访问速度。这种情况下,Redis 的响应时间会显著增加,而且可能会出现超时或请求失败的情况。
为了避免这些问题,可以考虑以下解决方案:
- 监控 Redis 的内存使用情况,及时发现异常并采取措施。
- 合理设置 Redis 的最大内存限制,确保不会超出系统的物理内存。
- 使用合适的数据持久化方案(如 RDB 快照或 AOF 日志)以防止数据丢失。
- 如果条件允许,考虑使用集群模式或分片来分散数据,以减少单个实例的内存使用量。
Redis 删除数据
Redis 对过期 Key 的删除策略
如果一个键是过期的,那它到了过期时间之后,是不是马上就从内存中被删除呢?答案肯定是否定的,因为 Redis 不可能时时刻刻遍历所有被设置了过期时间的 Key,来检测数据是否已经到达了过期时间,然后对它进行删除。Redis 针对过期 Key,提供了三种不同的删除策略,分别是:定时删除、惰性删除、定期删除。
定时删除
- 定时删除能保证内存中数据的最大新鲜度,因为它可以保证过期 Key 会在过期后马上被删除,其所占用的内存也会随之释放,可以理解为 “立即删除”。
- 因为删除操作会占用 CPU 资源,如果刚好碰上了 CPU 很忙的时候,比如正在做交集或排序等计算的时候,就会给 CPU 造成额外的压力。这会产生大量的性能消耗,同时也会影响数据的读取操作。因此定时删除策略的缺点是:它对 CPU 是最不友好的。
- 简而言之,定时删除对 CPU 不友好,但对内存友好,相当于用处理器性能换取存储空间(即时间换空间)。
惰性删除
- 惰性删除的策略刚好和定时删除相反,惰性删除在数据到达过期时间后不做任何处理,等下次访问该数据时发现已过期,并将其删除,并返回不存在。
- 使用惰性删除访问数据的特点:访问一个数据,如果发现其在过期时间之内,则返回该数据;如果发现已经过了过期时间,则将其删除,并返回不存在。
- 如果一个键已经过期,而这个键又仍然保留在内存中,那么只要这个过期 Key 不被删除,它所占用的内存就不会释放。因此惰性删除策略的缺点是:它对内存是最不友好的。
- 在使用惰性删除策略时,如果内存中有非常多的过期 Key,而这些过期 Key 又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行 FLUSHDB),甚至可以将这种情况看作是一种内存泄漏 —— 无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的 Redis 服务器来说,肯定不是一个好消息。
- 简而言之,惰性删除对内存不友好,但对 CPU 友好,相当于用存储空间换取处理器性能(即空间换时间)。
定期删除
- 定期删除策略是前两种策略的折中:定期删除策略每隔一段时间执行一次删除过期 Key 操作,并通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。其做法为:周期性轮询 Redis 中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频率。
- 定期删除策略的特点:CPU 性能占用设置有峰值,检测频率可自定义设置。内存压力不是很大,长期占用内存的冷数据会被持续清理。
- 定期删除策略的难点是确定删除操作执行的时长和频率。Redis 不可能时时刻刻遍历所有被设置了过期时间的 Key,来检测数据是否已经到达了过期时间,然后对它进行删除。如果删除操作执行得太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将 CPU 时间过多地消耗在删除过期 Key 上面。如果删除操作执行得太少,或者执行的时间太短,定期删除策略又会和惰性删除策略一样,出现浪费内存的情况。因此,如果采用定期删除策略,Redis 服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。
- 定期删除的举例:Redis 默认每间隔 100ms 检查是否有过期的 Key,如果有过期 Key 则删除。特别注意,Redis 不是每隔 100ms 将所有的 Key 检查一次,而是随机抽取进行检查。因此,如果只采用定期删除策略,会导致很多 Key 到达过期时间了也没有被删除。
- 简而言之,定期删除可以周期性抽查内存空间(随机抽查,重点抽查),然后将无用的内存空间释放掉。
总结
惰性删除和定期删除都存在数据没有被抽到的情况,如果这些数据已经到了过期时间,不会被删除掉,这会导致大量过期的 Key 堆积在内存中,最终使 Redis 内存空间紧张或者很快耗尽。因此必须要有一个更好的兜底方案,那就是使用 Redis 的内存淘汰策略。
Redis 的内存淘汰策略有哪些
在 Redis 中,允许用户通过 maxmemory 参数设置最大使用内存的大小 ,这在内存有限的情况下是很有用的。当 Redis 内存数据集大小上升到一定大小的时候,就会执行内存淘汰策略。其中 LRU 和 LFU 都是用于缓存淘汰策略的算法,它们的作用是在缓存空间不足时确定哪些数据应该被删除掉。
LRU (Least Recently Used):根据最近使用的顺序来淘汰数据,即淘汰最长时间未被访问的数据。当有新的数据被访问时,LRU 会将其移到最近使用的位置。LFU (Least Frequently Used):根据数据被访问的频率来淘汰数据,即淘汰访问频率最低的数据。当缓存空间不足时,LFU 会选择访问次数最少的数据进行删除。
Redis 4.0 之前一共实现了 6 种内存淘汰策略,但是在 4.0 之后,又增加了 2 种策略。截止目前为止(Redis 6.0.8 版本),Redis 定义了 8 种内存淘汰策略来处理内存空间不足的情况。
noeviction:不会淘汰任何数据,当使用的内存空间超过maxmemory值时,返回错误。volatile-ttl:从已设置过期时间的数据集中,挑选将要过期的数据进行淘汰。volatile-random:从已设置过期时间的数据集中,随机挑选一个数据进行淘汰。volatile-lru:从已设置过期时间的数据集中,挑选最长时间未被访问(LRU)的数据进行淘汰。volatile-lfu:从已设置过期时间的数据集中,挑选访问频率最低(LFU)的数据进行淘汰。allkeys-random:从所有数据集中,随机选择一个数据进行淘汰。allkeys-lru:从所有数据集中,挑选最长时间未被访问(LRU)的数据进行淘汰。allkeys-lfu:从所有数据集中,挑选访问频率最低(LFU)的数据进行淘汰。
这些内存淘汰策略,涵盖了两个维度:过期键和所有键,涉及四个方面:TTL、Radom、LRU、LFU。在实际工作中,使用得最多的内存淘汰策略是 allkeys-lru,即从所有数据集中,挑选最长时间未被访问(LRU)的数据进行淘汰。
如何查看 Redis 的内存淘汰策略
第一种查看方式
要查看 Redis 的内存淘汰策略,可以使用 Redis 的 CONFIG 命令来获取 Redis 服务器的内存配置信息,命令如下:
1 | config get maxmemory-policy |
第二种查看方式
要查看 Redis 的内存淘汰策略,可以使用 Redis 的 INFO 命令来获取 Redis 服务器的内存信息,命令如下:
1 | info memory |
其中与内存淘汰策略相关的输出信息如下:
maxmemory-policy:内存淘汰策略
如何修改 Redis 的内存淘汰策略
- 第一种方式,通过配置文件永久修改
- 打开 Redis 的配置文件(通常是
redis.conf),找到并设置maxmemory-policy参数的值,比如设置为allkeys-lru。 - 重新启动 Redis 服务,以使新的内存配置生效。
- 打开 Redis 的配置文件(通常是
- 第二种方式,通过命令临时修改
- 使用
config set maxmemory-policy xxx命令修改内存淘汰策略。 - 特别注意,在 Redis 服务器重启之后,这个修改会失效,也就是说使用命令这种修改方式不会永久保存到配置文件中。
- 使用
Redis 分布式锁
分布式锁都有哪些实现方案
- 基于 MySQL 实现分布式锁。
- 基于 Redis 实现分布式锁,常用的 Redis 客户端是 Redisson。
- 基于 ZooKeeper 实现分布式锁,常用的 ZooKeeper 客户端是 Curator。
提示
- JVM 层面的加锁,都是单机版的锁,比如 Synchronized、ReentrantLock 等。
- 分布式微服务架构中,拆分后各个微服务之间为了解决资源竞争而加入的锁,是分布式锁。
分布式锁使用的注意事项有哪些
- Redis 的分布式锁是基于
SETNX命令。 - 如果出异常的话,可能无法释放锁,所以必须在
finally代码块中释放锁。 - 如果应用服务宕机了,在代码层面根本没有走到
finally代码块,也就没办法可以保证解锁,因此需要设置锁的过期时间,防止出现死锁现象。 - 除了设置锁的过期时间之外,还必须保证
SETNX操作和设置锁过期时间的操作是原子性操作(不可分割)。 - 规定只能删除自己的锁,不能误删别人的锁,可以使用 Lua 脚本或者 Redis 事务来实现这一规则。
- 判断锁归属的操作和删除锁的操作必须是原子性操作(不可分割)。
Redis、ZooKeeper 实现分布式锁的区别
CAP 理论指出,在分布式系统中,Consistency(一致性)、Availability(可用性)和 Partition tolerance(分区容忍性)这三个特性无法同时实现,只能在其中两个特性之间进行权衡。针对 CAP 理论,Redis 与 ZooKeeper 实现分布式锁的区别如下:
Redis 集群
- Redis 集群满足 CAP 理论中的 AP,可以保证可用性和分区容忍性。
- Redis 集群不保证一致性,它的主从复制属于异步复制,而异步复制可能会造成锁的丢失。
- 比如:主节点没来得及将刚刚插入进来的数据复制给从节点就宕机了,那么主节点和从节点的数据就不一致。此时如果是在集群模式下,就得使用 Redisson 来解决这方面的问题。
ZooKeeper 集群
- ZooKeeper 集群满足 CAP 理论中的 CP,可以保证一致性和分区容忍性。
- ZooKeeper 集群可以保证强一致性,对于集群中所有节点来说,要么全部更新成功,要么全部更新失败,因此使用 ZooKeeper 集群并不存在主从节点数据不一致的问题,也就不会造成锁的丢失。
- ZooKeeper 集群不保证服务可用性,在 Leader 选举期间集群是不可用的,虽然服务最终能够恢复。由于选举 Leader 的时间一般比较长,可能需要花费 30s ~ 120s,漫长的选举时间导致的服务长期不可用,这往往是不能容忍的。
Redis 底层原理
Redis 是单线程的为什么还可以支撑高并发
这是一道高频的 BAT 面试题,其答案如下。
面试题剖析
Redis 基于 Reactor 模式开发了网络事件处理器,这个处理器称为文件事件处理器(File Event Handler)。这个文件事件处理器是单线程的,所以 Redis 才叫做单线程模型。它是 Redis 单线程架构的核心组件,采用 I/O 多路复用机制同时监听多个 socket,并根据 socket 上的事件来选择对应的事件处理器来处理这个事件,以实现高性能的网络通信。
文件事件处理器
- 工作原理:
- 当被监听的 socket 准备好执行如
accept、read、write、close等操作时,Redis 会将这些操作抽象为不同的 “文件事件”。此时,文件事件处理器会调用预先注册好的具体事件处理器来响应这些事件。 - 文件事件处理器本身是单线程运行的,但通过 I/O 多路复用机制(如
select、poll、epoll、kqueue等)可以高效地监听多个 socket。这样既保持了 Redis 内部线程模型的简洁性,也保证了良好的并发处理性能。
- 当被监听的 socket 准备好执行如
- 结构组成:
- 文件事件处理器由以下四个核心组件构成:
- (1) 多个 Socket:与客户端的连接。
- (2) I/O 多路复用器:负责监听所有 socket 上的 I/O 事件。
- (3) 文件事件分派器:将就绪的 socket 事件分派给对应的事件处理器。
- (4) 具体事件处理器:包括连接应答处理器、命令请求处理器、命令回复处理器等
- 如果是客户端要连接 Redis,那么会为 socket 关联连接应答处理器。
- 如果是客户端要写数据到 Redis,那么会为 socket 关联命令请求处理器。
- 如果是客户端要从 Redis 读数据,那么会为 socket 关联命令回复处理器。
- 处理流程:
- (1) 多个 socket 上可能同时发生各种 I/O 事件(如读、写、连接、断开等)。
- (2) I/O 多路复用器会监听这些 socket 上的事件,并将就绪的 “socket + 事件类型”(即文件事件)放入事件队列。
- (3) 文件事件分派器从事件队列中取出就绪事件,根据对应的 socket 和事件类型,选择合适的事件处理器进行处理。
- (4) 当前事件处理完成后,继续处理队列中的下一个就绪事件。
- 总结说明:
- 这种模型实现了并发监听 + 串行处理的效果,避免了线程切换带来的开销,同时又具备处理大量连接的能力,是 Redis 高性能的重要保障之一。
- 工作原理:
文件事件
- 当一个 socket 变得可读(例如客户端向 Redis 发送数据或关闭连接),或者有新的连接请求到来时,会触发一个
AE_READABLE事件。 - 当 socket 可写(例如 Redis 需要向客户端发送响应数据)时,会触发一个
AE_WRITABLE事件。 - I/O 多路复用程序可以同时监听
AE_READABLE和AE_WRITABLE两种事件。 - 如果同一个 socket 同时触发这两种事件,文件事件分派器会优先处理
AE_READABLE,再处理AE_WRITABLE,以避免读数据延迟或粘包等问题。
- 当一个 socket 变得可读(例如客户端向 Redis 发送数据或关闭连接),或者有新的连接请求到来时,会触发一个

- 客户端与 Redis 通信的一次完整流程
- (1) 在 Redis 启动初始化时,Redis 会将连接应答处理器与
AE_READABLE事件绑定。 - (2) 当客户端向 Redis 发起连接请求时,监听的 socket 会触发
AE_READABLE事件,此时由连接应答处理器处理连接请求,建立连接并创建一个对应的客户端 socket。随后,Redis 会将该客户端 socket 的AE_READABLE事件与命令请求处理器关联,用于处理后续的命令请求。 - (3) 当客户端发送命令请求(无论是读还是写),客户端 socket 上会再次触发
AE_READABLE事件,命令请求处理器被调用,从 socket 中读取请求数据,并进行命令解析与执行。 - (4) 当 Redis 执行完命令并准备好响应数据后,会将该 socket 的
AE_WRITABLE事件与命令回复处理器关联。 - (5) 当该 socket 变为可写(即内核缓冲区有空间)时,会触发
AE_WRITABLE事件,由命令回复处理器将响应数据写入 socket,供客户端读取。 - (6) 响应数据写入完成后,Redis 会解除该 socket 与
AE_WRITABLE事件及命令回复处理器的绑定,避免持续监听写事件,提高资源利用效率。
- (1) 在 Redis 启动初始化时,Redis 会将连接应答处理器与
提示
poll、epoll等 I/O 多路复用机制本身是系统调用接口,它们的底层实现由 Linux 操作系统内核负责管理。
面试题答案
- 1. 纯内存操作,访问速度极快
- Redis 是基于纯内存的数据存储系统,所有的读写操作都是在内存中完成的,不涉及磁盘 I/O,其数据访问速度远超传统基于磁盘的数据库。即使是单线程,也能在内存中完成每秒数万甚至十万级别的 QPS。
- 2. 采用高效的 I/O 多路复用机制(Reactor 模型)
- Redis 使用了非阻塞的 I/O 多路复用机制(如
poll、epoll、kqueue等)来监听多个 socket,通过单线程配合事件驱动模型,可以同时监听成千上万个连接请求,串行快速处理事件,实现高效处理能力。
- Redis 使用了非阻塞的 I/O 多路复用机制(如
- 3. 单线程反而避免了多线程的锁竞争与上下文切换开销
- 多线程编程中常见的问题包括:锁竞争、死锁、线程上下文切换、共享资源同步等。Redis 采用单线程模型,避免了这些问题,降低了系统复杂度,提高了执行效率与可控性。
- Redis 在处理请求时,每次只处理一个连接的事件,不会出现资源竞争,因此执行路径更短、CPU 利用率更高。
- 4. 内部采用高效的数据结构和优化的实现方式
- Redis 内部使用高度优化的 C 语言实现,所有核心操作都在极短时间内完成。同时使用了诸如哈希表、跳表、压缩列表、整数集合等高效的数据结构,加速了数据访问和存储效率。
- 5. 请求通常很轻量,执行耗时极短
- 大部分 Redis 请求(如
GET、SET、INCR等)都非常简单,执行时间通常在微秒级,即使单线程处理,也足以满足大量请求的快速响应。
- 大部分 Redis 请求(如
Redis 使用误区
往 Redis 里写的数据怎么没了
有同学问我:” 老师啊,我往 Redis 里写的数据怎么没了?我们生产环境的 Redis 经常会丢一些数据,明明写进去了,过一会儿就不见了。” 我说,同学啊,你问出这个问题就说明你对 Redis 的理解还不到位。Redis 是缓存,不是数据库,你把缓存当存储用了,当然会出问题。什么是缓存?缓存就是用内存来加速数据访问的中间层,内存访问速度快,但代价是容量有限,不能像磁盘一样存很多数据。比如一台机器可能只有几十个 G 的内存,但却有几个 T 的磁盘空间。Redis 就是用内存来做高性能、高并发读写的,所以 Redis 本质上是一个缓存系统。那你说内存有限,比如你配置 Redis 最多用 10 个 G 的数据,你却往里面写了 20 个 G 的数据,会发生什么?Redis 肯定只能保留 10 个 G 的数据,多出来的 10 个 G 数据会被清理掉。那清理哪些数据?当然是清理那些不常用的,保留常用的,这就叫淘汰策略。还有一种情况是你自己给某些键设置了过期时间,时间到了也会自动删除。所以 Redis 丢数据这件事,不是 Bug,而是它的本质设计决定的。总结一下:Redis 是缓存,缓存的数据是会丢的,要么是你自己设置了过期时间,要么是 Redis 内存满了触发了内存淘汰机制。想存持久数据,就用数据库,别让缓存干数据库的活。
数据明明都过期了,怎么还占用着内存
有一种情况是你设置了 Key 的过期时间,但你知道 Redis 是什么时候真正把这些过期 Key 删除掉的吗?如果你不知道,那就容易踩坑。之前有个同学就问过我:为啥我明明设置了很多 Key 1 小时后过期,但过了一小时再看,Redis 的内存占用还是很高?这就是你不清楚 Redis 是怎么处理过期 Key 导致的。比如你设置 Redis 最大内存是 10GB,现在写入了 5GB 数据,并且都设置了 1 小时后过期,结果一小时后你再看 Redis,发现内存还是占用了 5GB,感觉数据没被清除。你用命令去查那些 Key,确实查不到了,说明逻辑上确实过期了;但 Redis 的内存没立即释放,这是因为 Redis 并不会在过期时间一到就立刻删除这些 Key,它采用的是 “惰性删除 + 定期删除” 的策略,只有访问这些 Key 时或被后台任务随机扫描到才会被真正清除。如果没有访问,也没被扫描到,过期 Key 就会暂时留在内存里,占用内存空间。这种机制是为了性能优化,不然为每个 Key 设置定时器成本太高。所以 Redis 中设置了过期时间的 Key,不代表到期立刻释放内存,只有在真正被删除时,内存才会释放掉。
特别注意
惰性删除策略和定期删除策略都存在数据没有被抽到的情况,如果这些数据已经到了过期时间,不会被删除掉,这会导致大量过期的 Key 堆积在内存中,最终使 Redis 内存空间紧张或者很快耗尽。因此必须要有一个更好的兜底方案,那就是使用 Redis 的内存淘汰策略。
主节点持久化对于主从架构的安全保障意义
在采用 Redis 主从架构的场景下,强烈建议开启 Master 节点的持久化机制(RDB 和 / 或 AOF),这是确保数据安全的关键手段。很多人误以为有了一个或多个 Slave 节点就等于做了数据备份,其实这是极其错误且危险的想法。如果关闭了 Master 节点的持久化机制,仅将数据保存在内存中,一旦 Master 节点宕机重启,可能导致灾难性后果(比如缓存数据丢失)。
- 为什么不能仅依赖 Slave 节点作为 Master 节点的 “热备”
- 当 Master 节点关闭了 RDB 和 AOF 持久化,只将数据保存在内存中;
- 在某一时刻,Master 节点宕机了;
- Master 节点重启后,由于本地没有任何持久化文件,Master 节点会认为自己的数据集为空;
- Master 节点启动后会将空的数据集通过复制同步给所有 Slave 节点;
- 所有 Slave 节点中的数据随即都被清空;
- 最终导致 100% 的缓存数据丢失,从而造成缓存雪崩,导致大量请求打到数据库。
- 即使部署了 Redis Sentinel(哨兵) 或 Redis Cluster(集群),实现了高可用机制,也无法完全避免数据清空风险
- 如果 Sentinel(哨兵)还没有检测到 Master 节点出现故障,Master 节点就自动重启了;
- 此时 Master 节点没有持久化数据,重启后认为数据集为空;
- 然后将空的数据集通过复制同步给所有 Slave 节点,造成所有 Slave 节点的数据被清空。
- 生产环境中,Master 节点正确的做法
- Master 节点必须启用持久化(建议同时开启 RDB 和 AOF)
- 避免 Master 节点宕机重启后出现 “数据空同步”;
- 保证 Master 节点具备自恢复能力。
- 建议定期备份 Master 节点的持久化文件(RDB/AOF)
- 一旦本地数据文件损坏或丢失,可以从备份文件中恢复;
- 启动时加载备份文件,确保 Master 节点具备有效数据。
- Master 节点必须启用持久化(建议同时开启 RDB 和 AOF)
