简易的提交限流实现-java
public class MessageController {
// ================== 1. 限流配置区域 (纯Java实现) ==================
// 最大容量 (允许瞬间并发多少个)
private static final double MAX_TOKENS = 5.0;
// 生成速率 (每秒允许通过多少个请求,这里设为 2 个/秒)
private static final double RATE_PER_SECOND = 2.0;
// 当前剩余令牌数 (初始填满)
private static double currentTokens = MAX_TOKENS;
// 上次补充令牌的时间
private static long lastRefillTime = System.currentTimeMillis();
// 线程锁对象 (保证多线程安全)
private static final Object lock = new Object();
/**
* 尝试获取令牌 (核心算法)
* @return true=获取成功(放行), false=获取失败(拦截)
*/
private boolean tryAcquire() {
synchronized (lock) {
long now = System.currentTimeMillis();
// A. 计算由于时间流逝,应该补充多少令牌
// (当前时间 - 上次时间) / 1000秒 * 每秒速率
double generatedTokens = (now - lastRefillTime) / 1000.0 * RATE_PER_SECOND;
// B. 补充令牌,但不能超过最大容量
if (generatedTokens > 0) {
currentTokens = Math.min(MAX_TOKENS, currentTokens + generatedTokens);
lastRefillTime = now; // 更新补充时间
}
// C. 尝试拿走 1 个令牌
if (currentTokens >= 1.0) {
currentTokens -= 1.0;
return true; // 放行
} else {
return false; // 桶空了,拦截
}
}
}
// ================== 限流配置结束 ==================
/**
* 接收表单提交
*/
public void save() {
// 【第 4 道防线:全局熔断限流】
// 如果拿不到令牌,直接返回繁忙,根本不执行后面的数据库查询
if (!tryAcquire()) {
renderJson(Ret.fail("msg", "提交人数过多,请稍后重试"));
return;
}
// ... 下面是你的业务代码 ...
// 1. 验证码校验
// 2. 判空
// 3. 手机号去重 (DB查询)
// 4. IP 限流 (Cache查询)
// ... 保存到数据库
}
// ... 其他方法 ...
}
代码解析
-
关键字:
-
必须使用 static 修饰 currentTokens 等变量。
-
因为 JFinal 的 Controller 是多例的(或者是原型模式),每次请求都会 new 一个新的 Controller 对象。只有 static 才能让所有请求共享同一个“令牌桶”。
-
-
synchronized (lock):
-
这是为了线程安全。
-
当 100 个请求同时到达 tryAcquire 方法时,必须要排队去拿令牌,防止出现“计算错误”(例如本来只有 1 个令牌,结果两个线程同时读到有余额,都放行了)。
-
由于这个锁里的逻辑非常简单(只有加减法运算),耗时是纳秒级的,完全不会影响系统性能。
-
效果
-
平时:用户慢慢填表单,令牌桶一直是满的,点击提交瞬间通过。
-
被攻击时:黑客 1 秒发 1000 个请求。
-
前 5 个请求瞬间通过(消耗掉桶里的存货)。
-
第 6 个请求进来,发现桶空了,直接被 if (!tryAcquire()) 拦截,返回 JSON 错误。
-
后续每过 0.5 秒(1秒 / 2个速率),系统会自动生成 1 个新令牌,允许 1 个请求通过。
-
数据库:每秒最多只承受 2 次查询压力,稳如泰山。
-
这就是【最简单的分布式拒绝服务防御】(DDoS Protection Lite),不用第三方包,原生 Java 就能搞定!
浙公网安备 33010602011771号