当前位置:首页 > 问答 > 正文

Redis里怎么快点清理Set里的东西,别慢吞吞的那种操作

DELUNLINK

当你需要清理的不仅仅是Set里的几个成员,而是整个Set键都想扔掉的时候,这是最快的方法。

  • DEL命令:这是最直接的删除命令,你告诉Redis删除一个键,它会立刻执行,并且在这个操作完成之前,其他所有命令都得等着(也就是会“阻塞”其他操作),如果这个Set非常大,这个“等待”的时间就会很明显,可能会让你的应用感觉到卡顿。

    • 什么时候用:当你非常确定这个Set不大(比如就几百几千个成员),或者即使有点大,但你的业务可以接受极短暂的卡顿(比如在凌晨低峰期执行)时,可以用它,简单粗暴,见效快。
  • UNLINK命令(首选):这是Redis 4.0版本之后提供的“非阻塞”删除命令,你让它删除一个键,它不会立刻去辛苦地释放内存,而是先把键名从数据库里摘出来,让你感觉上像是立刻删除了,真正释放内存这个累活,Redis会找个“闲工夫”在后台慢慢做,这样就不会阻塞其他请求。

    Redis里怎么快点清理Set里的东西,别慢吞吞的那种操作

    • 什么时候用几乎在所有你需要删除整个Set的情况下,都应该首选UNLINK,这才是“快点清理”的精髓——让你的应用感觉不到卡顿,快速响应,除非你的Redis版本太老不支持,否则无脑用UNLINK代替DEL就对了。

示例:

# 删除(可能阻塞)名为 'my_large_set' 的整个集合
DEL my_large_set
# 非阻塞删除(推荐)名为 'my_large_set' 的整个集合
UNLINK my_large_set

清理Set的一部分:精准打击,而非逐个击毙

更多的时候,你不想删整个Set,只是想删掉里面符合条件的一部分成员,这时候最容易掉进的坑就是使用SREM命令一个个地删。

  • SREM key member1 member2 ...:这个命令本身没问题,问题是你怎么提供这些成员,如果你先用一个SMEMBERS命令把整个Set的所有成员都取到客户端,然后再一个个或一批批地用SREM塞回去删,那将是灾难性的。SMEMBERS在数据量大时会非常耗内存和网络资源,而且整个过程很慢。

正确的“快”法是什么呢?

Redis里怎么快点清理Set里的东西,别慢吞吞的那种操作

  1. 使用SPOP命令(随机删除):如果你只是想快速减少Set的大小,并不关心具体删除的是哪些成员,SPOP是你的最佳选择,它可以随机地从Set中弹出(删除并返回)一个或多个成员,这个操作的时间复杂度是常数级的,非常快。

    • 什么时候用:适用于需要随机抽样删除的场景,比如清理缓存、限制集合大小等。
    • 示例SPOP my_set 50 # 随机删除并返回50个成员。
  2. 利用SSCAN + SREM(管道化)进行模式化删除:如果你必须要根据一定的模式或条件来删除成员(删除所有以"temp:"开头的成员),SSCAN是唯一的出路。

    • 为什么不能用SMEMBERS:前面说了,大Set用SMEMBERS等于自杀。

      Redis里怎么快点清理Set里的东西,别慢吞吞的那种操作

    • SSCAN怎么用SSCAN命令允许你增量式地遍历整个Set,它每次只返回一小部分成员,不会阻塞Redis,也不会撑爆你的客户端内存。

    • 如何“快”:关键在于,不要每次SSCAN拿到一小批成员就立刻发起一个SREM请求,网络来回的开销太大了,你应该使用 Redis 管道(Pipeline) 技术,具体步骤是:

      • 使用SSCAN迭代遍历Set,将需要删除的成员先收集在你的应用程序的内存中(比如一个列表里)。
      • 当收集到的成员数量达到一个合理的批次大小时(比如1000个或2000个),通过一个管道,一次性提交一个包含多个SREM命令的请求给Redis。
      • 这样,将多次网络往返减少到一次,速度得到极大提升。
    • 示例(伪代码逻辑)

      cursor = 0
      delete_list = []
      loop:
          cursor, members = SSCAN my_set cursor MATCH "temp:*" COUNT 500
          for each member in members:
              if member matches condition: # 这里可以加更复杂的判断
                  add member to delete_list
          if size of delete_list >= 1000:
              send a Pipeline of SREM my_set [all members in delete_list]
              clear delete_list
          if cursor == 0: break
      # 清理最后一批
      if delete_list not empty:
          send a Pipeline of SREM my_set [all members in delete_list]

终极武器:Lua脚本

如果你觉得上面管道的方式还需要在客户端处理逻辑有点麻烦,想要更“原子性”的操作,可以考虑使用Lua脚本。

  • 原理:Lua脚本在Redis服务器端原子性执行,你可以在一个脚本里写一个循环,使用SCAN命令的脚本版本(注意,在Lua里使用redis.replicate_commands()来让SCAN正确工作),边遍历边删除,这样只需要一次网络通信,所有逻辑都在服务器端完成,既避免了阻塞,又保证了操作的原子性。
  • 优点:高效,原子性,减轻客户端负担。
  • 缺点:脚本编写和调试相对复杂,而且一个写得不好的Lua脚本同样可能长时间阻塞Redis(所以一定要用SCAN而不是全局操作),对于大多数应用场景,上面的管道方法已经足够好。

总结一下怎么选:

  • 删整个Set:用 UNLINK,别用 DEL
  • 随机删一部分,不管是谁:用 SPOP,最快最省事。
  • 按条件删一部分,数据量不大:可以直接用 SREM 接多个成员名。
  • 按条件删一部分,数据量巨大:必须用 SSCAN 迭代找出目标,然后用 管道(Pipeline) 批量 SREM,高手可以考虑用 Lua脚本

在Redis里追求速度,就是要最大限度地减少网络往返次数,避免长时间阻塞服务,以及避免在客户端和服务端之间传输过大的数据包,只要你遵循这几个原则,清理Set就能“快起来”。