一、限流算法
1. 计数器算法(固定窗口)
java
复制
下载
// 最简单的限流算法
public class CounterLimiter {
private final AtomicInteger counter = new AtomicInteger(0);
private final int limit; // 限制次数
private final long interval; // 时间窗口(毫秒)
private long lastResetTime = System.currentTimeMillis();
public CounterLimiter(int limit, long interval) {
this.limit = limit;
this.interval = interval;
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 判断是否进入下一个时间窗口
if (currentTime - lastResetTime > interval) {
counter.set(0);
lastResetTime = currentTime;
}
// 判断是否超过限制
if (counter.get() < limit) {
counter.incrementAndGet();
return true;
}
return false;
}
}
// 问题:窗口边界可能承受2倍流量
// 例如:限制100次/分钟,在59秒~1分01秒可能通过200次
2. 滑动窗口算法
java
复制
下载
// 解决固定窗口的边界问题
public class SlidingWindowLimiter {
// 使用循环队列存储时间戳
private final LinkedList<Long> timestamps = new LinkedList<>();
private final int limit; // 限制次数
private final long windowSize; // 窗口大小(毫秒)
public SlidingWindowLimiter(int limit, long windowSize) {
this.limit = limit;
this.windowSize = windowSize;
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 移除窗口之外的时间戳
while (!timestamps.isEmpty() &&
currentTime - timestamps.getFirst() > windowSize) {
timestamps.removeFirst();
}
// 检查当前窗口内请求数
if (timestamps.size() < limit) {
timestamps.addLast(currentTime);
return true;
}
return false;
}
}
3. 漏桶算法
java
复制
下载
// 以恒定速率处理请求,平滑流量
public class LeakyBucketLimiter {
private final int capacity; // 桶容量
private final long leakRate; // 漏水速率(毫秒/请求)
private int water = 0; // 当前水量
private long lastLeakTime = System.currentTimeMillis();
public LeakyBucketLimiter(int capacity, int ratePerSecond) {
this.capacity = capacity;
this.leakRate = 1000 / ratePerSecond; // 每个请求间隔毫秒数
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 计算漏水:距离上次漏水的时间间隔
long timePassed = currentTime - lastLeakTime;
int leaked = (int) (timePassed / leakRate);
if (leaked > 0) {
water = Math.max(0, water - leaked);
lastLeakTime = currentTime;
}
// 尝试加水
if (water < capacity) {
water++;
return true;
}
return false;
}
}
4. 令牌桶算法(最常用)
java
复制
下载
// 既允许突发流量,又能限制平均速率
public class TokenBucketLimiter {
private final int capacity; // 桶容量
private final long refillRate; // 添加令牌速率(毫秒/令牌)
private int tokens = 0; // 当前令牌数
private long lastRefillTime = System.currentTimeMillis();
public TokenBucketLimiter(int capacity, int tokensPerSecond) {
this.capacity = capacity;
this.refillRate = 1000 / tokensPerSecond;
this.tokens = capacity; // 初始满桶
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 计算应添加的令牌数
long timePassed = currentTime - lastRefillTime;
int refillTokens = (int) (timePassed / refillRate);
if (refillTokens > 0) {
tokens = Math.min(capacity, tokens + refillTokens);
lastRefillTime = currentTime;
}
// 尝试消费令牌
if (tokens > 0) {
tokens--;
return true;
}
return false;
}
}
二、分布式限流实现
1. 基于Redis的分布式限流
java
复制
下载
public class RedisDistributedLimiter {
private final JedisPool jedisPool;
private final String keyPrefix;
private final int limit;
private final int windowInSeconds;
public boolean tryAcquire(String key) {
try (Jedis jedis = jedisPool.getResource()) {
String redisKey = keyPrefix + ":" + key;
long currentTime = System.currentTimeMillis();
// 使用Redis的ZSET实现滑动窗口
// 1. 移除窗口外的记录
jedis.zremrangeByScore(redisKey, 0,
currentTime - windowInSeconds * 1000);
// 2. 获取当前窗口内请求数
long count = jedis.zcard(redisKey);
if (count < limit) {
// 3. 添加当前请求
jedis.zadd(redisKey, currentTime,
UUID.randomUUID().toString());
// 4. 设置过期时间
jedis.expire(redisKey, windowInSeconds + 1);
return true;
}
return false;
}
}
}
2. Redis+Lua脚本(原子操作)
lua
复制
下载
-- limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = tonumber(ARGV[3])
-- 移除窗口外的记录
redis.call('zremrangebyscore', key, 0, current - window * 1000)
-- 获取当前计数
local count = redis.call('zcard', key)
if count < limit then
-- 添加记录并设置过期时间
redis.call('zadd', key, current, current)
redis.call('expire', key, window + 1)
return 1
else
return 0
end
java
复制
下载
public class RedisLuaLimiter {
private static final String LUA_SCRIPT =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local window = tonumber(ARGV[2])\n" +
"local current = tonumber(ARGV[3])\n" +
"redis.call('zremrangebyscore', key, 0, current - window * 1000)\n" +
"local count = redis.call('zcard', key)\n" +
"if count < limit then\n" +
" redis.call('zadd', key, current, current)\n" +
" redis.call('expire', key, window + 1)\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
public boolean tryAcquire(String key, int limit, int windowSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
String sha = jedis.scriptLoad(LUA_SCRIPT);
long currentTime = System.currentTimeMillis();
Object result = jedis.evalsha(sha, 1, key,
String.valueOf(limit),
String.valueOf(windowSeconds),
String.valueOf(currentTime));
return "1".equals(result.toString());
}
}
}
3. Redis+Cell模块(4.0+)
java
复制
下载
// Redis 4.0引入的redis-cell模块
public class RedisCellLimiter {
// CL.THROTTLE命令格式:
// CL.THROTTLE key max_burst count_per_period period quota
public boolean tryAcquire(String key, int maxBurst,
int countPerPeriod, int period) {
try (Jedis jedis = jedisPool.getResource()) {
// 执行限流命令
List<Long> response = (List<Long>) jedis.sendCommand(
Protocol.Command.CL_THROTTLE,
key,
String.valueOf(maxBurst),
String.valueOf(countPerPeriod),
String.valueOf(period),
"1" // 请求的配额
);
// response[0]: 0表示允许,1表示拒绝
// response[1]: 总容量
// response[2]: 剩余容量
// response[3]: 如果被拒绝,需要等待的秒数
// response[4]: 窗口内请求完全重置所需秒数
return response.get(0) == 0;
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、应用级限流实现
1. Guava RateLimiter
java
复制
下载
public class GuavaRateLimiterExample {
// 创建限流器:每秒10个令牌
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
// 平滑突发限流(SmoothBursty)
public void processRequest() {
if (rateLimiter.tryAcquire()) {
// 执行业务逻辑
doBusiness();
} else {
// 限流处理
handleRateLimit();
}
}
// 平滑预热限流(SmoothWarmingUp)
public void processWithWarmup() {
// 预热期5秒,达到稳定速率10/s
RateLimiter warmingLimiter = RateLimiter.create(10.0, 5, TimeUnit.SECONDS);
if (warmingLimiter.tryAcquire()) {
doBusiness();
}
}
}
2. Spring Cloud Gateway限流
yaml
复制
下载
# application.yml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
redis-rate-limiter.requestedTokens: 1 # 每次请求消耗令牌数
key-resolver: "#{@userKeyResolver}"
java
复制
下载
@Configuration
public class RateLimitConfig {
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 按用户限流
String userId = exchange.getRequest()
.getHeaders()
.getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
// 按IP限流
// return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
// 按接口限流
// return Mono.just(exchange.getRequest().getPath().value());
};
}
}
3. Sentinel限流
java
复制
下载
// 1. 定义资源
@SentinelResource(value = "queryUser",
blockHandler = "handleBlock",
fallback = "handleFallback")
public User queryUser(Long id) {
// 业务逻辑
return userService.getById(id);
}
// 2. 限流处理
public User handleBlock(Long id, BlockException ex) {
log.warn("触发限流,用户ID: {}", id);
return new User(); // 返回默认值或抛出异常
}
// 3. 规则配置
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("queryUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS限流
rule.setCount(10); // 阈值:10 QPS
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // 预热
rule.setWarmUpPeriodSec(10); // 预热时间10秒
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
四、网关级限流
1. Nginx限流配置
nginx
复制
下载
# 基于IP的限流
http {
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
server {
location /api/ {
# 使用漏桶算法,burst=20允许突发
limit_req zone=ip_limit burst=20 nodelay;
# 延迟模式(默认),平滑处理
# limit_req zone=ip_limit burst=20;
proxy_pass http://backend;
}
location /static/ {
# 限制并发连接数
limit_conn perip 10;
limit_conn perserver 100;
proxy_pass http://static_backend;
}
# 限流响应状态码
error_page 503 = @limit_handler;
location @limit_handler {
return 429 '{"code":429,"msg":"Too Many Requests"}';
}
}
}
2. OpenResty+Lua限流
lua
复制
下载
-- nginx.conf
location /api {
access_by_lua_block {
local limit = require "resty.limit.req"
-- 创建限流器:10 req/sec, 桶容量20
local lim, err = limit.new("my_limit_req_store", 10, 20)
if not lim then
ngx.exit(500)
end
-- 按IP限流
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
ngx.exit(429) -- Too Many Requests
end
ngx.exit(500)
end
-- 延迟处理
if delay > 0 then
ngx.sleep(delay)
end
}
proxy_pass http://backend;
}
五、多层次限流策略
1. 架构设计
java
复制
下载
// 多级限流:网关层 -> 应用层 -> 方法层
public class MultiLevelRateLimiter {
// 1. 网关层限流(粗粒度)
@Component
public class GatewayFilter implements Filter {
private final RedisDistributedLimiter apiLimiter;
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
String apiPath = req.getRequestURI();
// API级别限流:1000次/分钟
if (!apiLimiter.tryAcquire("api:" + apiPath, 1000, 60)) {
sendRateLimitResponse(response);
return;
}
chain.doFilter(request, response);
}
}
// 2. 应用层限流(用户粒度)
@Service
public class UserService {
private final Map<String, RateLimiter> userLimiters =
new ConcurrentHashMap<>();
public User getUser(String userId) {
// 每个用户单独限流:10次/秒
RateLimiter limiter = userLimiters.computeIfAbsent(
userId, k -> RateLimiter.create(10.0)
);
if (!limiter.tryAcquire()) {
throw new RateLimitException("用户请求过于频繁");
}
return userDao.get(userId);
}
}
// 3. 方法层限流(细粒度)
@Component
public class SensitiveOperationService {
private final RateLimiter operationLimiter =
RateLimiter.create(5.0); // 5次/秒
@RateLimit(key = "resetPassword", limit = 3, period = 3600)
public void resetPassword(String userId) {
// 敏感操作:每小时最多3次
// ...
}
}
}
2. 动态限流配置
java
复制
下载
// 支持动态调整限流规则
@Component
public class DynamicRateLimiter {
private final Map<String, TokenBucketLimiter> limiters =
new ConcurrentHashMap<>();
// 从配置中心动态加载规则
@Scheduled(fixedDelay = 5000)
public void refreshRules() {
List<RateLimitRule> rules = configCenter.getRateLimitRules();
for (RateLimitRule rule : rules) {
String key = rule.getResource();
TokenBucketLimiter limiter = limiters.get(key);
if (limiter == null || !limiter.matches(rule)) {
// 创建或更新限流器
limiters.put(key, createLimiter(rule));
}
}
}
// 注解方式使用
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint joinPoint, RateLimit rateLimit)
throws Throwable {
String key = buildKey(joinPoint, rateLimit);
TokenBucketLimiter limiter = limiters.get(key);
if (limiter != null && !limiter.tryAcquire()) {
throw new RateLimitException("访问频率超限");
}
return joinPoint.proceed();
}
}
六、特殊场景限流
1. 热点数据限流
java
复制
下载
// 防止热点数据被刷
public class HotspotLimiter {
// 热点检测
public boolean isHotspot(String key) {
try (Jedis jedis = jedisPool.getResource()) {
// 使用HyperLogLog统计近似访问量
jedis.pfadd("access:" + key, UUID.randomUUID().toString());
long count = jedis.pfcount("access:" + key);
return count > HOT_THRESHOLD;
}
}
// 热点限流
@RateLimit(key = "#productId", limit = 100, period = 1)
public Product getProductDetail(Long productId) {
// 普通商品:正常逻辑
if (!isHotspot("product:" + productId)) {
return productService.getDetail(productId);
}
// 热点商品:特殊处理
// 1. 使用本地缓存
// 2. 返回精简信息
// 3. 异步加载详情
return getHotProduct(productId);
}
}
2. 灰度发布限流
java
复制
下载
// 新版本逐步放量
public class CanaryReleaseLimiter {
private final RateLimiter canaryLimiter = RateLimiter.create(10.0);
private final RateLimiter stableLimiter = RateLimiter.create(100.0);
private double canaryRatio = 0.1; // 10%流量走新版本
public void processRequest(Request request) {
// 根据用户ID哈希决定走哪个版本
boolean useCanary = Math.abs(request.getUserId().hashCode()) % 100
< canaryRatio * 100;
RateLimiter limiter = useCanary ? canaryLimiter : stableLimiter;
if (limiter.tryAcquire()) {
if (useCanary) {
newVersionService.process(request);
} else {
oldVersionService.process(request);
}
} else {
fallbackService.process(request);
}
}
}
3. 自适应限流
java
复制
下载
// 根据系统负载动态调整限流阈值
public class AdaptiveRateLimiter {
private volatile int currentLimit = 100; // 初始阈值
private final Object lock = new Object();
// 监控系统指标
@Scheduled(fixedDelay = 1000)
public void adjustLimit() {
double cpuUsage = getCpuUsage();
long memoryUsage = getMemoryUsage();
double qps = getCurrentQps();
synchronized (lock) {
if (cpuUsage > 0.8 || memoryUsage > 0.8) {
// 系统负载高,降低限流阈值
currentLimit = (int) (currentLimit * 0.8);
} else if (cpuUsage < 0.3 && memoryUsage < 0.3 && qps < currentLimit * 0.5) {
// 系统负载低,提高限流阈值
currentLimit = (int) (currentLimit * 1.2);
}
// 限制最小和最大值
currentLimit = Math.max(10, Math.min(1000, currentLimit));
}
}
public boolean tryAcquire() {
synchronized (lock) {
// 使用currentLimit进行限流判断
// ...
return true;
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
七、限流策略最佳实践
1. 策略组合
java
复制
下载
// 多种限流策略组合使用
public class CompositeRateLimiter {
private final List<RateLimiter> limiters = new ArrayList<>();
public CompositeRateLimiter() {
// 1. 全局QPS限制
limiters.add(new TokenBucketLimiter(1000, 1000));
// 2. 用户级限制
limiters.add(new UserRateLimiter(100, 60));
// 3. API级限制
limiters.add(new ApiRateLimiter(50, 1));
// 4. 敏感操作限制
limiters.add(new SensitiveOperationLimiter(5, 3600));
}
public boolean allow(Request request) {
for (RateLimiter limiter : limiters) {
if (!limiter.tryAcquire(request)) {
// 记录被哪个限流器拒绝
log.warn("请求被限流: {}, 限流器: {}",
request, limiter.getClass().getSimpleName());
return false;
}
}
return true;
}
}
2. 降级策略
java
复制
下载
// 限流后的降级处理
public enum FallbackStrategy {
RETURN_DEFAULT, // 返回默认值
RETURN_CACHED, // 返回缓存数据
THROW_EXCEPTION, // 抛出异常
QUEUE_AND_RETRY, // 入队等待重试
REDIRECT // 重定向到降级页面
}
@Component
public class RateLimitHandler {
public Object handleRateLimit(String resource,
FallbackStrategy strategy,
Supplier<Object> supplier) {
switch (strategy) {
case RETURN_DEFAULT:
return getDefaultValue(resource);
case RETURN_CACHED:
return getCachedValue(resource);
case THROW_EXCEPTION:
throw new RateLimitException("访问频率超限");
case QUEUE_AND_RETRY:
return queueForRetry(resource, supplier);
case REDIRECT:
redirectToFallbackPage(resource);
return null;
default:
return null;
}
}
}
3. 监控与告警
java
复制
下载
// 限流监控
@Component
public class RateLimitMonitor {
private final MeterRegistry meterRegistry;
@EventListener
public void onRateLimit(RateLimitEvent event) {
// 记录指标
meterRegistry.counter("rate_limit.total",
"resource", event.getResource(),
"type", event.getType().name())
.increment();
// 触发告警
if (event.getRejectCount() > ALERT_THRESHOLD) {
alertService.sendAlert(
"限流告警",
String.format("资源%s在%d秒内被拒绝%d次",
event.getResource(),
event.getWindow(),
event.getRejectCount())
);
}
}
}
八、总结对比
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 计数器 | 实现简单 | 边界问题,不够平滑 | 简单限流需求 |
| 滑动窗口 | 解决边界问题 | 占用内存,实现稍复杂 | 需要精确控制的场景 |
| 漏桶 | 流量绝对平滑 | 无法应对突发流量 | 需要恒定速率处理的场景 |
| 令牌桶 | 允许突发流量 | 实现相对复杂 | 大多数业务场景 |
| Redis分布式 | 支持分布式 | 网络开销,依赖Redis | 分布式系统 |
| Sentinel | 功能全面,生态好 | 学习成本 | 微服务架构 |
实施建议:
-
分层限流:网关层→服务层→方法层
-
多维度限流:IP、用户、API、参数等多维度
-
动态调整:根据系统负载自动调整阈值
-
优雅降级:限流后提供合理的用户体验
-
监控告警:实时监控限流情况,及时发现问题
-
黑白名单:配合黑白名单机制,灵活控制
选择原则:
-
简单场景:Guava RateLimiter
-
分布式系统:Redis + Lua
-
微服务架构:Sentinel或Spring Cloud Gateway
-
高性能网关:Nginx/OpenResty
-
复杂业务:多级限流策略组合