在秒杀系统中,流量控制是至关重要的一环。为了防止瞬时的请求激增导致系统崩溃,我们可以采用计数器算法和令牌桶算法来限制用户的请求频率。本文将结合 Spring Boot,通过具体的代码示例介绍这两种算法,并使用生动的比喻来解释其原理。
计数器算法是一种简单直观的流量控制方法。想象一下,我们有一个包裹计数器,每当用户发起秒杀请求,计数器就加一。当计数器超过设定的阈值时,我们暂时停止接收请求,以避免系统过载。
@RestController
public class SeckillController {
private static final int REQUEST_THRESHOLD = 100; // 设定的请求阈值
private static int requestCounter = 0; // 请求计数器
@GetMapping("/seckill")
public ResponseEntity<String> seckill() {
if (requestCounter < REQUEST_THRESHOLD) {
// 处理秒杀逻辑
requestCounter++;
return ResponseEntity.ok("秒杀成功!");
} else {
// 请求超过阈值,返回限流提示
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁,请稍后重试。");
}
}
}
在这个例子中,SeckillController
中的 /seckill
接口使用计数器算法来控制请求频率。当请求计数未超过阈值时,允许秒杀请求;当计数超过阈值时,返回请求过于频繁的提示。
令牌桶算法是一种更为灵活的流量控制方式。我们可以将其比喻为一个装有令牌的桶,系统以一定的速率不断往桶中放入令牌。用户请求时,需要从桶中获取令牌,如果桶中没有足够的令牌,则拒绝请求。
import java.util.concurrent.TimeUnit;
@RestController
public class SeckillController {
private static final int REQUEST_RATE = 10; // 令牌桶速率(每秒放入的令牌数)
private static final int BUCKET_CAPACITY = 20; // 令牌桶容量
private static int availableTokens = BUCKET_CAPACITY; // 当前可用的令牌数量
private static long lastRefillTime = System.currentTimeMillis(); // 上次令牌补充时间
@GetMapping("/seckill")
public ResponseEntity<String> seckill() {
refillTokens(); // 补充令牌
if (tryConsumeToken()) {
// 处理秒杀逻辑
return ResponseEntity.ok("秒杀成功!");
} else {
// 令牌不足,返回限流提示
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁,请稍后重试。");
}
}
private synchronized void refillTokens() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastRefillTime;
int tokensToAdd = (int) (elapsedTime * (REQUEST_RATE / 1000.0));
availableTokens = Math.min(BUCKET_CAPACITY, availableTokens + tokensToAdd);
lastRefillTime = currentTime;
}
private synchronized boolean tryConsumeToken() {
if (availableTokens >= 1) {
availableTokens--;
return true;
} else {
return false;
}
}
}
在这个例子中,SeckillController
中的 /seckill
接口使用令牌桶算法来控制请求频率。通过 refillTokens
方法定期补充令牌,通过 tryConsumeToken
方法尝试获取令牌,从而控制秒杀请求的处理。