dot Redis 8 发布了——它是开源的

了解更多

Redis 4.0 中鲜为人知但能加速您应用程序的特性

Redis 4.0 为 Redis 生态系统带来了令人惊叹的特性:Modules(模块)。Modules(模块) 是 Redis 的一个巨大转变——突然间,它成为了一个开放的领域,可以在 Redis 内部实现自定义数据类型和全速计算。但是,尽管这次发布的大部分宣传都集中在 Modules 上,新版本还引入了一个同样具有颠覆性、非常重要的命令:UNLINK

要确定您是否可以使用 UNLINK 命令,请从 redis-cli 运行 INFO 命令。响应将告诉您关于服务器的所有信息。在第一部分(#Server)中应该有一行叫做 redis_version。如果这个值大于 4.0,那么您就可以使用 UNLINK 命令了。所有版本的 Redis Enterprise 5.0+ 和所有新的 Redis Enterprise Cloud 订阅都应该能够使用 UNLINK 命令。并非所有 Redis 提供商都能保持最新,因此在更改任何代码之前最好检查一下版本。

让我们回顾一下 Redis 的一个关键架构特性:单线程。Redis 在很大程度上是一个单线程应用程序。它一次只做一件事,并且做得非常快。多线程很复杂,会引入锁和其他难以捉摸的问题,这些问题反而可能会减慢应用程序的速度。尽管 Redis(直到 4.0 版本)在少数情况下使用了多线程,但它通常会在完成一个命令后才开始执行下一个命令。

删除键(使用 DEL)通常是您可能不太在意的一个命令。高速写入和读取是值得夸耀的事情,但在许多情况下,删除数据同样重要。像 Redis 中的大多数其他命令一样,DEL 命令在单个线程中运行。如果您的键的值只有几千字节,这没什么大不了的——可能只需要远少于一毫秒的时间。但是当您的键的值是几兆字节、100 兆字节或 500 兆字节时会发生什么?哈希、有序集合、列表或集合通常是通过随着时间的推移添加项目构建起来的,这可能导致一个几千兆字节大小的键。当您使用 DEL 删除这些大键中的一个时会发生什么?由于 Redis 是单线程的,您的整个服务器会被占用…嗯,相当长一段时间。更糟糕的是,这些键中保存的数据可能是通过成千上万或数百万个微小请求构建起来的,因此应用程序或操作员可能无法真正了解删除这些数据需要多长时间。

理智会告诉我们不要在一个包含一百万个成员的有序集合上运行这样的命令:

> ZRANGE some-zset 0 -1

然而,对 some-zset 执行 DEL 也需要类似的时间——虽然没有传输开销,但内存释放操作会累积起来,而且在此期间,您的 CPU 会被占用,导致服务器无法响应。在 UNLINK 之前,您可能不得不采取非原子性的方法,结合使用 SCAN 进行小批量删除,以避免这种内存释放的噩梦。无论哪种方式,都令人不快!

正如您可能猜到的,UNLINK 应运而生!UNLINK 在语法上与 DEL 相同,但提供了一个更理想的解决方案。首先,它将键从整个键空间中移除。然后,在另一个线程中开始回收内存。从多线程的角度来看,这是一个安全的操作,因为它(在主线程中)将项目从键空间中移除,从而使其无法通过任何 Redis 命令访问。

如果您的值很大,速度提升是巨大的——UNLINK 是一个 O(1) 操作(每个键;在主线程中),无论键中存储的值有多大。而使用 DEL 删除一个大值可能需要几百毫秒或更长时间,UNLINK 则会在不到一毫秒内完成(包括网络往返时间)。当然,您的服务器仍然需要在另一个线程中花费 CPU 周期重新分配值的内存(其中工作是 O(N),其中 N 是被删除值的分配次数),但主线程的性能不太可能受到另一个线程中操作的严重影响。

那么,您应该将代码中所有的 DEL 都替换成 UNLINK 吗?很可能。在少数情况下,DEL 才是您真正需要的。这里我可以想出两种情况

  • 在 MULTI/EXEC 或管道中,当添加和删除大值时,DEL 是理想的选择。在这种情况下,UNLINK 不会立即释放空间,在流量大的场景中,如果内存达到上限,可能会遇到问题。
  • 当能够写入而不触发驱逐(eviction)比快速响应更关键时。

在一个没有极端内存限制的全新环境中,很难想象您会不需要 UNLINK 的情况。UNLINK 将提供更一致的行为和整体更好的性能,而且它需要的代码改动非常小(如果您可以在客户端中重命名命令,甚至无需改动)。如果 UNLINK 适合您的应用程序,那就动手将您的 DEL 都改为 UNLINK,然后看看效果吧。