专业的JAVA编程教程与资源

网站首页 > java教程 正文

限流算法面面观:Sentinel与Gateway的差异揭秘

temp10 2025-09-19 02:44:01 java教程 2 ℃ 0 评论

在分布式系统和高并发场景中,限流是保证系统稳定性和高可用性的关键技术之一。本期咱们一起探讨Sentinel和Spring Cloud Gateway的限流实现差异,并通过代码示例帮助你更好地理解和应用这些技术。

三大经典限流算法Java实现

1. 滑动时间窗口算法实现

public class SlidingWindow {
    private final long windowSizeInMs;
    private final int maxRequests;
    private final LinkedList<Long> requests = new LinkedList<>();
    
    public SlidingWindow(long windowSizeInMs, int maxRequests) {
        this.windowSizeInMs = windowSizeInMs;
        this.maxRequests = maxRequests;
    }
    
    public synchronized boolean allowRequest() {
        long currentTime = System.currentTimeMillis();
        // 移除过期请求
        while (!requests.isEmpty() && 
               requests.getFirst() < currentTime - windowSizeInMs) {
            requests.removeFirst();
        }
        
        if (requests.size() < maxRequests) {
            requests.addLast(currentTime);
            return true;
        }
        return false;
    }
}

// 使用示例
public class SlidingWindowExample {
    public static void main(String[] args) {
        // 限制1秒内最多10个请求
        SlidingWindow window = new SlidingWindow(1000, 10);
        
        for (int i = 0; i < 15; i++) {
            if (window.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 被限流");
            }
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

2. 令牌桶算法实现

public class TokenBucket {
    private final int capacity;
    private final double refillRate; // 令牌/毫秒
    private double tokens;
    private long lastRefillTime;
    
    public TokenBucket(int capacity, int refillTokensPerSecond) {
        this.capacity = capacity;
        this.refillRate = refillTokensPerSecond / 1000.0;
        this.tokens = capacity;
        this.lastRefillTime = System.currentTimeMillis();
    }
    
    public synchronized boolean allowRequest() {
        refillTokens();
        if (tokens >= 1) {
            tokens -= 1;
            return true;
        }
        return false;
    }
    
    private void refillTokens() {
        long currentTime = System.currentTimeMillis();
        double timePassed = currentTime - lastRefillTime;
        tokens = Math.min(capacity, tokens + timePassed * refillRate);
        lastRefillTime = currentTime;
    }
}

// 使用示例
public class TokenBucketExample {
    public static void main(String[] args) {
        // 容量10个令牌,每秒补充5个
        TokenBucket bucket = new TokenBucket(10, 5);
        
        for (int i = 0; i < 20; i++) {
            if (bucket.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 被限流");
            }
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

Sentinel 限流实战

3. Sentinel 基础使用示例

// 添加Maven依赖
// <dependency>
//     <groupId>com.alibaba.csp</groupId>
//     <artifactId>sentinel-core</artifactId>
//     <version>1.8.6</version>
// </dependency>

public class SentinelExample {
    
    // 定义资源
    private static final String RESOURCE_NAME = "orderApi";
    
    static {
        // 配置流控规则:QPS不超过10
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource(RESOURCE_NAME);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(10);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
    
    public void createOrder() {
        Entry entry = null;
        try {
            entry = SphU.entry(RESOURCE_NAME);
            // 执行业务逻辑
            System.out.println("创建订单成功");
        } catch (BlockException e) {
            // 被限流
            System.out.println("创建订单被限流");
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        SentinelExample example = new SentinelExample();
        // 模拟高并发请求
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                example.createOrder();
            }).start();
            Thread.sleep(10);
        }
    }
}

4. Sentinel 热点参数限流

public class SentinelParamFlowExample {
    
    private static final String RESOURCE_NAME = "queryUserInfo";
    
    static {
        // 热点参数限流规则
        ParamFlowRule rule = new ParamFlowRule(RESOURCE_NAME)
            .setParamIdx(0) // 第一个参数为热点参数
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            .setCount(5);   // 每个参数值每秒最多5次
            
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
    }
    
    public String queryUserInfo(Long userId) {
        Entry entry = null;
        try {
            // 使用热点参数
            entry = SphU.entry(RESOURCE_NAME, EntryType.IN, 1, userId);
            return "用户信息: " + userId;
        } catch (BlockException e) {
            return "请求过于频繁,请稍后重试";
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
}

Spring Cloud Gateway 限流实战

5. Gateway + Redis 限流配置

# application.yml
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8080
          predicates:
            - Path=/user/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10   # 每秒令牌数
                redis-rate-limiter.burstCapacity: 20   # 令牌桶容量
                key-resolver: "#{@userKeyResolver}"

6. Gateway 限流Java配置

@Configuration
public class RateLimitConfig {
    
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            // 根据用户ID限流
            String userId = exchange.getRequest()
                .getQueryParams()
                .getFirst("userId");
            return Mono.just(Objects.requireNonNullElse(userId, "anonymous"));
        };
    }
    
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(10, 20);
    }
}

// 自定义限流处理器
@Component
public class CustomRateLimiter implements RateLimiter {
    
    @Override
    public Mono<Response> isAllowed(String routeId, String id) {
        // 自定义限流逻辑
        return Mono.just(new Response(true, -1));
    }
    
    public static class Response {
        private final boolean allowed;
        private final long tokensRemaining;
        
        public Response(boolean allowed, long tokensRemaining) {
            this.allowed = allowed;
            this.tokensRemaining = tokensRemaining;
        }
    }
}

性能测试对比

7. 限流算法性能测试

public class RateLimiterBenchmark {
    
    public static void main(String[] args) {
        testSlidingWindow();
        testTokenBucket();
        testSentinel();
    }
    
    private static void testSlidingWindow() {
        SlidingWindow limiter = new SlidingWindow(1000, 1000);
        long start = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++) {
            if (limiter.allowRequest()) {
                count++;
            }
        }
        long time = System.currentTimeMillis() - start;
        System.out.println("滑动窗口: " + time + "ms, 通过请求: " + count);
    }
    
    private static void testTokenBucket() {
        TokenBucket limiter = new TokenBucket(1000, 1000);
        long start = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++) {
            if (limiter.allowRequest()) {
                count++;
            }
        }
        long time = System.currentTimeMillis() - start;
        System.out.println("令牌桶: " + time + "ms, 通过请求: " + count);
    }
    
    private static void testSentinel() {
        // 初始化Sentinel规则
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("test");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1000);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
        
        long start = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++) {
            Entry entry = null;
            try {
                entry = SphU.entry("test");
                count++;
            } catch (BlockException e) {
                // 被限流
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
        long time = System.currentTimeMillis() - start;
        System.out.println("Sentinel: " + time + "ms, 通过请求: " + count);
    }
}

实际应用建议

  1. 网关层限流:使用Gateway的令牌桶算法,适合处理突发流量
  2. 服务层限流:使用Sentinel的滑动窗口,实现更精细的控制
  3. 热点数据保护:使用Sentinel的热点参数限流,保护关键资源

通过以上代码示例,咱们可以看到不同限流算法的具体实现方式和应用场景。在实际项目中:

限流算法面面观:Sentinel与Gateway的差异揭秘

  • 滑动时间窗口适合需要精确控制QPS的场景
  • 令牌桶算法适合允许突发流量的场景
  • 漏桶算法适合需要平滑输出流量的场景

Sentinel和Gateway分别针对不同的使用场景提供了优秀的限流解决方案。小编认为理解它们的底层原理和实现方式,能够帮助咱在实际项目中做出更合理的技术选型。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表