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 服务,以使新的内存配置生效。
  • 第二种方式,通过命令临时修改
    • 使用 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 服务,以使新的内存配置生效。
  • 第二种方式,通过命令临时修改
    • 使用 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,漫长的选举时间导致的服务长期不可用,这往往是不能容忍的。