霸王餐CPS系统中Java实现接口限流的多种算法与落地技巧

在“霸王餐”CPS系统中,第三方回调、佣金查询、活动配置等接口常面临恶意刷量或突发流量冲击。若无有效限流机制,将导致数据库压力激增、服务雪崩。本文结合计数器、滑动窗口、漏桶、令牌桶四种算法,提供基于Guava、Redis及Sentinel的可落地限流方案。

1. 固定窗口计数器(简单但存在临界突刺)

适用于低精度场景,如每日回调次数限制:

package baodanbao.com.cn.cps.ratelimit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class FixedWindowRateLimiter {

    private final LoadingCache<String, AtomicInteger> counters;
    private final int limit;
    private final long windowSeconds;

    public FixedWindowRateLimiter(int limit, long windowSeconds) {
        this.limit = limit;
        this.windowSeconds = windowSeconds;
        this.counters = CacheBuilder.newBuilder()
            .expireAfterWrite(windowSeconds, TimeUnit.SECONDS)
            .build(new CacheLoader<>() {
                @Override
                public AtomicInteger load(String key) {
                    return new AtomicInteger(0);
                }
            });
    }

    public boolean tryAcquire(String key) {
        AtomicInteger counter = counters.getUnchecked(key);
        int current = counter.incrementAndGet();
        return current <= limit;
    }
}

使用示例:

FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 60); // 每分钟100次
if (!limiter.tryAcquire("callback:" + merchantId)) {
    throw new RuntimeException("Too many requests");
}

2. 滑动日志窗口(高精度但内存开销大)

记录每次请求时间戳,动态计算窗口内请求数:

public class SlidingLogRateLimiter {

    private final LoadingCache<String, List<Long>> requestLogs;
    private final int limit;
    private final long windowMillis;

    public SlidingLogRateLimiter(int limit, long windowSeconds) {
        this.limit = limit;
        this.windowMillis = windowSeconds * 1000;
        this.requestLogs = CacheBuilder.newBuilder()
            .expireAfterAccess(windowSeconds, TimeUnit.SECONDS)
            .build(new CacheLoader<>() {
                @Override
                public List<Long> load(String key) {
                    return new ArrayList<>();
                }
            });
    }

    public synchronized boolean tryAcquire(String key) {
        long now = System.currentTimeMillis();
        List<Long> logs = requestLogs.getUnchecked(key);
        // 清除窗口外记录
        logs.removeIf(ts -> now - ts > windowMillis);
        if (logs.size() >= limit) {
            return false;
        }
        logs.add(now);
        return true;
    }
}

在这里插入图片描述

3. 令牌桶算法(支持突发流量)

使用Guava RateLimiter实现单机限流:

@Component
public class TokenBucketRateLimiter {

    private final Map<String, com.google.common.util.concurrent.RateLimiter> limiters = new ConcurrentHashMap<>();

    public RateLimiter getOrCreate(String key, double permitsPerSecond) {
        return limiters.computeIfAbsent(key, k -> 
            com.google.common.util.concurrent.RateLimiter.create(permitsPerSecond));
    }

    public boolean tryAcquire(String key, double permitsPerSecond) {
        return getOrCreate(key, permitsPerSecond).tryAcquire();
    }
}

// Controller中使用
@Autowired
private TokenBucketRateLimiter tokenLimiter;

@PostMapping("/commission/query")
public Object queryCommission(@RequestBody QueryDTO dto) {
    if (!tokenLimiter.tryAcquire("query:" + dto.getUserId(), 10.0)) {
        throw new RuntimeException("Request too fast");
    }
    // 业务逻辑
}

4. Redis+Lua实现分布式令牌桶

适用于集群环境,保证全局一致性:

@Component
public class DistributedTokenBucket {

    private final StringRedisTemplate redisTemplate;

    public DistributedTokenBucket(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private static final String LUA_SCRIPT =
        "local key = KEYS[1]\n" +
        "local rate = tonumber(ARGV[1])\n" +
        "local capacity = tonumber(ARGV[2])\n" +
        "local now = tonumber(ARGV[3])\n" +
        "local requested = tonumber(ARGV[4])\n" +
        "local tokens = redis.call('GET', key)\n" +
        "if tokens == false then\n" +
        "  tokens = capacity\n" +
        "else\n" +
        "  local last_time = redis.call('GET', key .. ':ts')\n" +
        "  if last_time == false then last_time = now end\n" +
        "  tokens = math.min(capacity, tonumber(tokens) + (now - tonumber(last_time)) * rate)\n" +
        "end\n" +
        "if tokens >= requested then\n" +
        "  redis.call('SET', key, tokens - requested)\n" +
        "  redis.call('SET', key .. ':ts', now)\n" +
        "  return 1\n" +
        "else\n" +
        "  return 0\n" +
        "end";

    public boolean tryAcquire(String key, double rate, int capacity, int permits) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script,
            Collections.singletonList("tb:" + key),
            String.valueOf(rate),
            String.valueOf(capacity),
            String.valueOf(System.currentTimeMillis()),
            String.valueOf(permits)
        );
        return result != null && result == 1;
    }
}

调用示例:

boolean allowed = distributedTokenBucket.tryAcquire(
    "callback:meituan", 
    50.0,   // 每秒50个令牌
    100,    // 桶容量100
    1       // 每次请求1个令牌
);
if (!allowed) throw new RuntimeException("Rate limited");

5. Sentinel集成(生产级方案)

通过注解实现细粒度限流:

@PostConstruct
public void initRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule("commission_callback_api")
        .setGrade(RuleConstant.FLOW_GRADE_QPS)
        .setCount(200) // 单机QPS上限200
        .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) // 匀速排队
        .setMaxQueueingTimeMs(500);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

@SentinelResource(value = "commission_callback_api", blockHandler = "handleBlocked")
@PostMapping("/callback")
public ResponseEntity<?> handleCallback(@RequestBody CallbackDTO dto) {
    return baodanbao.com.cn.cps.service.CallbackService.process(dto);
}

public ResponseEntity<?> handleBlocked(BlockException ex) {
    return ResponseEntity.status(429).body("Too many requests");
}

本文著作权归 俱美开放平台 ,转载请注明出处!

更多推荐