简易的提交限流实现-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查询)
        // ... 保存到数据库
    }
    
    // ... 其他方法 ...
}

代码解析

  1.  关键字

    • 必须使用 static 修饰 currentTokens 等变量。

    • 因为 JFinal 的 Controller 是多例的(或者是原型模式),每次请求都会 new 一个新的 Controller 对象。只有 static 才能让所有请求共享同一个“令牌桶”。

  2. synchronized (lock)

    • 这是为了线程安全

    • 当 100 个请求同时到达 tryAcquire 方法时,必须要排队去拿令牌,防止出现“计算错误”(例如本来只有 1 个令牌,结果两个线程同时读到有余额,都放行了)。

    • 由于这个锁里的逻辑非常简单(只有加减法运算),耗时是纳秒级的,完全不会影响系统性能。

效果

  • 平时:用户慢慢填表单,令牌桶一直是满的,点击提交瞬间通过。

  • 被攻击时:黑客 1 秒发 1000 个请求。

    • 前 5 个请求瞬间通过(消耗掉桶里的存货)。

    • 第 6 个请求进来,发现桶空了,直接被 if (!tryAcquire()) 拦截,返回 JSON 错误。

    • 后续每过 0.5 秒(1秒 / 2个速率),系统会自动生成 1 个新令牌,允许 1 个请求通过。

    • 数据库:每秒最多只承受 2 次查询压力,稳如泰山。

这就是【最简单的分布式拒绝服务防御】(DDoS Protection Lite),不用第三方包,原生 Java 就能搞定!

posted @ 2025-12-06 10:08  人间大爱  阅读(0)  评论(0)    收藏  举报