京东Java面试被问:系统限流的实现方式

一、限流算法

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 功能全面,生态好 学习成本 微服务架构

实施建议:

  1. 分层限流:网关层→服务层→方法层

  2. 多维度限流:IP、用户、API、参数等多维度

  3. 动态调整:根据系统负载自动调整阈值

  4. 优雅降级:限流后提供合理的用户体验

  5. 监控告警:实时监控限流情况,及时发现问题

  6. 黑白名单:配合黑白名单机制,灵活控制

选择原则:

  • 简单场景:Guava RateLimiter

  • 分布式系统:Redis + Lua

  • 微服务架构:Sentinel或Spring Cloud Gateway

  • 高性能网关:Nginx/OpenResty

  • 复杂业务:多级限流策略组合

相关推荐
晴殇i1 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
孟陬1 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌1 小时前
一站式了解四种限流算法
java·后端·go
绝无仅有1 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
华仔啊2 小时前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
AAA梅狸猫2 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫2 小时前
Handler基本概念
面试
也些宝3 小时前
Java单例模式:饿汉、懒汉、DCL三种实现及最佳实践
java