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

用户黑名单怎么用Redis搞定,性能和管理都方便不少

用户黑名单是很多系统都需要的基础功能,比如防止恶意用户刷帖、限制违规用户发言、拦截垃圾注册等,用传统的关系型数据库(比如MySQL)来做,虽然简单直接,但每次检查都需要去查数据库,当用户量巨大、请求非常频繁时(比如春运抢票、明星发微博后的评论场景),数据库很容易成为瓶颈,导致系统卡顿甚至崩溃。

Redis因为几个天生的优势,非常适合搞定黑名单场景,它的数据都放在内存里,读写速度极快,能达到微秒级别,这保证了检查黑名单这个高频操作不会拖慢整个系统,Redis提供了丰富的数据结构,让我们可以用最合适的方式来管理黑名单,既高效又灵活。

用户黑名单怎么用Redis搞定,性能和管理都方便不少

核心思路:用Redis的Set和Sorted Set

最常用的两种数据结构是Set(集合)和Sorted Set(有序集合),它们都能存储多个不重复的元素,非常适合用来存用户ID。

用户黑名单怎么用Redis搞定,性能和管理都方便不少

  1. 使用Set实现永久黑名单或需精确匹配的黑名单 Set的最大特点就是里面的元素是无序且唯一的,这正好符合黑名单的基本要求:一个用户要么在黑名单里,要么不在,不需要重复添加。

    • 操作命令非常简单:
      • 添加用户到黑名单: SADD blacklist:user_id 123456 (意思是向名为 blacklist:user_id 的集合里添加用户ID 123456
      • 检查用户是否在黑名单: SISMEMBER blacklist:user_id 123456 ,Redis会直接返回1(存在)或0(不存在),这个操作的速度是常数时间的,非常快。
      • 将用户移出黑名单: SREM blacklist:user_id 123456
      • 查看黑名单所有用户: SMEMBERS blacklist:user_id (谨慎使用,如果黑名单很大,可能会影响性能,一般用于管理后台查看)
    • 适用场景:
      • 永久封禁: 对于一些严重违规的用户,直接永久封禁,他们的ID就永远放在这个Set里。
      • 需要精确匹配的场景: 比如黑名单是基于用户ID的,用Set非常合适。
  2. 使用Sorted Set实现带过期时间的黑名单 这是Redis解决黑名单问题的一个“杀手级”应用,Sorted Set除了存储成员(member),还会为每个成员关联一个分数(score),我们可以巧妙地把这个“分数”当成是用户黑名单的过期时间戳

    用户黑名单怎么用Redis搞定,性能和管理都方便不少

    • 工作原理: 我们把要拉黑的用户ID作为成员(member),把他的解封时间点(比如当前时间戳 + 7天的秒数)作为分数(score)存入Sorted Set。
    • 操作命令:
      • 添加用户到黑名单(带7天过期时间): ZADD blacklist:ttl 1717584000 123456 (假设 1717584000 是7天后的一个具体时间戳)
      • 检查用户是否在有效黑名单内: 这个检查需要两步走,但依然高效。
        1. 先获取该用户的分数(即过期时间):ZSCORE blacklist:ttl 123456
        2. 如果返回空(nil),说明不在黑名单,如果返回了一个时间戳,就拿这个时间戳和当前时间比较,如果时间戳大于当前时间,说明黑名单还在有效期内;如果小于当前时间,说明已经过期了。
      • 自动化清理过期黑名单: 这是Sorted Set最方便的地方,我们可以利用它根据分数范围排序的特性,定期执行一个任务:ZREMRANGEBYSCORE blacklist:ttl 0 1717497600 (这个命令会删除所有分数在0到 1717497600(当前时间戳)之间的成员),因为过期的用户其分数(过期时间)肯定小于当前时间,这个命令一下,所有到期的用户就被自动清理出黑名单集合了,保证了集合不会无限膨胀,这个清理任务可以写个脚本每分钟跑一次,成本极低。
    • 适用场景:
      • 临时封禁: 禁言7天”、“限制登录3天”等,这是最常见的需求。

实际应用中的管理和优化技巧

光有数据结构还不够,在实际项目中这样用会让管理和性能更好:

  • 命名规范: 像上面的例子一样,给Key起个清晰的名字,blacklist:user_id 用于永久黑名单,blacklist:ttl 用于临时黑名单,如果业务复杂,还可以加上前缀,如 live_comment_blacklist:ttl(直播评论黑名单)。
  • 封装检查逻辑: 不应该在业务代码里到处写Redis命令,应该统一封装一个服务或函数,比如叫 BlacklistService.checkUserStatus(userId),这个函数内部去处理是用Set还是Sorted Set,以及判断时间逻辑,这样以后想修改黑名单策略(比如从永久改为临时),只需要改这一个地方。
  • 控制黑名单大小: 对于临时黑名单,一定要有上面提到的自动清理机制,对于永久黑名单,如果数据量真的巨大(上亿),可以考虑按用户ID的范围进行分片,存到多个Set里,blacklist:shard_1, blacklist:shard_2,来分散单个Key的压力。
  • 与数据库配合: 黑名单的源数据(谁、何时、为何被拉黑、解封时间)还是会存在MySQL这类数据库里,用于后台查询和审计,Redis只是作为一个超高速的缓存层,当管理员在后台操作拉黑时,程序应该同时向MySQL和Redis写入数据。

总结一下

用Redis做黑名单,核心就是利用其内存速度和高频数据结构。Set适合简单直接的封禁,Sorted Set通过将过期时间作为分数,完美解决了临时黑名单的自动管理问题。 这种方法相比直接查数据库,性能提升是数量级的,能让你的应用在高并发下依然稳定,通过简单的封装和定时任务,管理起来也非常方便,避免了手动清理的麻烦。

(参考文献:Redis官方文档中对Set和Sorted Set数据结构的说明;《Redis实战》一书中关于使用Redis实现社交网站功能的案例,其中提到了类似黑名单的实现思路。)