当前位置: 首页 > news >正文

lua脚本实战—— Redis并发原子性陷阱

需求分析

对于内容类网站,比如用户浏览题目的答案,需要先登录才能追溯,那么可以统计用户访问频率来限制数据的爬取。

可采用分级反爬虫策略,先告警、再采取强制措施:

  1. 如果每分钟超过 10 道题,给管理员发送警告
  2. 如果每分钟超过 20 道题,直接踢下线,进行封号操作

解决方案

统计访问频率 - 基于 Redis 统计(分布式)

分布式存储 Redis 的 string 结构支持 incr 累加操作,可以对每个用户分钟(或其他时间精度)级别的访问次数进行累加统计。

1)设计 Redis 键值对

要能区分出用户和时间窗,示例 key 为:user:access:{userId}:{timestamp_in_minutes}

  1. {userId} 是用户 ID。
  2. {timestamp_in_minutes}是当前的分钟级时间戳,即将当前时间戳转化为分钟,这样每分钟的访问都会被统计到一个 key 中。

每个 key 的value,就是该用户在这分钟内的访问次数。

2)Redis 操作逻辑

对 Redis 的操作包括设置 key、给 key 增加计数、给 key 设置过期时间。

如下代码:

//假设使用 jedis 客户端
// 使用 Redis 的 INCR 操作增加当前秒的访问次数
jedis.incr(redisKey);// 设置过期时间(TTL),例如只保存60秒的数据
jedis.expire(redisKey, 60);  // 60秒后自动过期

然而,这种方法存在潜在的设计陷阱:incr 和 expire 是两个独立的操作。如果你在高并发情况下调用 incr() 之后发生上下文切换(比如另一个线程执行操作),可能会导致两个问题:

  1. 过期时间重置:如果在高并发场景下,多次调用 incr() 后又多次调用 expire(),可能会不断重置该 key 的过期时间,导致这个key 永远不会过期。(当前场景不会,但是连续性访问的场景就有可能发生)
  2. 非原子操作:由于 incr() 和 expire()是独立操作,在并发情况下,两个线程都可能先判断 key 不存在,然后各自执行 set 操作,导致计数逻辑出错。

所以我们要确保计数和过期时间的操作是原子性的,可以使用 Redis 的 Lua 脚本来完成。如果 key 不存在,则初始化并设置过期时间,否则只进行计数。

高并发场景的例子

假设你限制 1 分钟访问次数最多为 100 次,有很多用户在第 59 秒和第 60 秒猛戳接口。

这时你的 key user:count 已经存在,比如它只剩 1 秒 TTL:

TTL(user:count) = 1

这时候来了个请求,它调用:

jedis.incr("user:count");   // 现在 value = 88
jedis.expire("user:count", 60);  //  TTL 又被设置回了 60 秒!!!

现在这个 key 原本应该在 1 秒后过期,但被重新设置为 60 秒了。

正确做法如下:

String luaScript = "if redis.call('exists', KEYS[1]) == 1 then " +"  return redis.call('incr', KEYS[1]); " +"else " +"  redis.call('set', KEYS[1], 1); " +"  redis.call('expire', KEYS[1], 180); " +  // 设置 180 秒过期时间"  return 1; " +"end";

这里一定要给 Redis key 设置过期时间!因为统计超过一分钟,之前的数据就没什么用了。

为什么不用Sentinel或HotKey?

对于该项目,主要的目的是反爬虫,而不是应对高并发大流量的请求,所以不需要结合 Sentinel 或 Hotkey 去精确统计流量。自主实现固定时间窗口(1 分钟)的访问频率统计就足够了。为了便于项目扩展为分布式,使用 Redis 方案来实现。

相关文章:

  • 文章记单词 | 第102篇(六级)
  • 深度剖析并发I/O模型select、poll、epoll与IOCP核心机制
  • ”故茗”茶文化网站
  • 【【嵌入式开发 Linux 常用命令系列 19 -- linux top 命令的交互使用介绍】
  • 谢飞机的Java面试奇遇:AIO、BIO、NIO与Netty深度解析
  • 【心海资源】【原创开发】TG,飞机,电报,协议号及直登号转API
  • RK常见系统属性设置/获取命令使用
  • 推进可解释人工智能迈向类人智能讨论总结分享
  • canal实现mysql数据同步
  • python web flask专题-Flask入门指南:从安装到核心功能详解
  • 人脸识别技术合规备案最新政策详解
  • NC IntellisysIQ QP、QPA和QPD QP3 Slave buried slave ON RS232 等通讯接口针脚定义
  • 多语种多场景的的分页详解
  • Linux下MySQL的安装与使用
  • Linux C/C++编程 —— 线程技术总结
  • NGINX如何处理静态文件?你会如何优化静态文件的传输?
  • 1538. 【中山市第十一届信息学邀请赛决赛】互质(different)
  • 从制造到智造:猎板PCB的技术实践与产业价值重构
  • 文章记单词 | 第115篇(六级)
  • 飞书知识问答深度体验:企业AI应用落地的典范产品
  • 做网站有意思吗?/关于搜索引擎的搜索技巧
  • 用dw如何做网站链接/新冠疫情最新情况最新消息
  • 渭南做网站费用/怎么做百度网页
  • 河南省住房城乡建设厅官方网站/谷歌chrome官网
  • 网站建设的实训技术总结/十大搜索引擎神器
  • 微信网站结构/怎么让百度收录我的网站