京东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

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

相关推荐
Sheep Shaun2 小时前
STL中的map和set:红黑树的优雅应用
开发语言·数据结构·c++·后端·c#
宁大小白2 小时前
pythonstudy Day45
开发语言·python·深度学习
宁晓2 小时前
单表配置多级类型,按名称模糊筛选
java·后端
Yu_iChan3 小时前
Day03 公共字段填充与菜品管理
java·开发语言
独自破碎E3 小时前
如何防止接口被恶意刷量?
java·开发语言
期待のcode3 小时前
Java的单例模式
java·开发语言·单例模式
断春风3 小时前
从 JDK 8 到 JDK 21:企业级 Java 版本选择的架构思考
java·架构·jdk
h7ml3 小时前
构建可扩展的企业微信消息推送服务:事件驱动架构在Java中的应用*
java·架构·企业微信
heartbeat..3 小时前
JavaWeb 核心:HttpServletRequest 请求行、请求头、请求参数完整梳理
java·网络·web·request