霸王餐CPS系统中Java实现接口限流的多种算法与落地技巧
·
霸王餐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");
}
本文著作权归 俱美开放平台 ,转载请注明出处!
更多推荐

所有评论(0)