5-3 熔断限流

3 阅读4分钟

5-3 熔断限流

概念解析

核心概念

概念说明
熔断快速失败,防止故障扩散
限流控制请求速率,保护系统
降级服务不可用时返回兜底数据
隔离资源隔离,防止雪崩

限流算法

算法特点适用场景
计数器简单,实现容易固定限流
滑动窗口精度高精细控制
漏桶流量整形流量均匀
令牌桶突发流量允许突发

代码示例

1. Sentinel 限流

pom.xml

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2023.0.1.0</version>
</dependency>

application.yml

spring:
  cloud:
    sentinel:
      server-addr: localhost:8718  # Sentinel 控制台端口
      transport:
        dashboard: localhost:8080
      eager:
        enabled: true  # 启动即注册

注解方式

@RestController
public class SentinelController {

    @GetMapping("/api/resource")
    @SentinelResource(value = "apiResource",
        blockHandler = "handleBlock",
        fallback = "handleFallback")
    public Result<String> getResource() {
        return Result.success("Success");
    }

    // 限流处理
    public Result<String> handleBlock(BlockException e) {
        return Result.error(429, "请求过于频繁,请稍后重试");
    }

    // 降级处理
    public Result<String> handleFallback(Throwable e) {
        return Result.error("服务繁忙,请稍后重试");
    }
}

2. Sentinel 规则配置

@Configuration
public class SentinelRuleConfig {

    @PostConstruct
    public void initRules() {
        // 定义限流规则
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("apiResource");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(100);  // QPS 100
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(rule);

        FlowRuleManager.loadRules(rules);

        // 定义熔断规则
        List<CircuitBreakerRule> breakerRules = new ArrayList<>();
        CircuitBreakerRule breakerRule = new CircuitBreakerRule();
        breakerRule.setResource("apiResource");
        breakerRule.setGrade(CircuitBreakerRule.SLOW_RATIO_RULE);
        breakerRule.setCount(10);  // 慢调用比例阈值
        breakerRule.setSlowRatioThreshold(0.5);  // 50% 慢调用
        breakerRule.setStatIntervalMs(10000);  // 统计窗口 10s
        breakerRule.setMinRequestAmount(5);  // 最小请求数
        breakerRule.setRecoveryTimeoutMs(30_000);  // 熔断时长
        breakerRules.add(breakerRule);

        CircuitBreakerRuleManager.loadRules(breakerRules);
    }
}

3. Sentinel + OpenFeign

feign:
  sentinel:
    enabled: true  # 启用 Sentinel
// 定义降级类
@Component
public class UserServiceFallback implements UserClient {

    @Override
    public Result<User> getUser(Long id) {
        return Result.error("用户服务暂时不可用");
    }
}

// 使用
@FeignClient(
    name = "user-service",
    fallback = UserServiceFallback.class
)
public interface UserClient { }

4. 自定义限流注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
    int value() default 100;  // 限流阈值
    int timeout() default 0;  // 等待超时
}

// 切面实现
@Aspect
@Component
public class RateLimiterAspect {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint joinPoint,
                        RateLimiter rateLimiter) throws Throwable {

        String key = getKey(joinPoint);
        int limit = rateLimiter.value();

        // 令牌桶算法
        String script = """
            local key = KEYS[1]
            local limit = tonumber(ARGV[1])
            local current = tonumber(redis.call('get', key) or 0)
            if current < limit then
                redis.call('incr', key)
                return 1
            else
                return 0
            end
            """;

        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(key),
            String.valueOf(limit)
        );

        if (result == 0) {
            throw new BizException(429, "请求过于频繁");
        }

        return joinPoint.proceed();
    }
}

// 使用
@Service
public class OrderService {

    @RateLimiter(value = 100, timeout = 0)
    public void createOrder(Order order) {
        // 创建订单
    }
}

5. Resilience4j 熔断

pom.xml

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>

配置

resilience4j:
  circuitbreaker:
    instances:
      userService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true
        waitDurationInOpenState: 30s
        failureRateThreshold: 50
        eventConsumerBufferSize: 10
  timelimiter:
    instances:
      userService:
        timeoutDuration: 3s
        cancelRunningFuture: true

使用

@Service
public class UserService {

    private final RestTemplate restTemplate;

    @CircuitBreaker(name = "userService", fallbackMethod = "fallback")
    @TimeLimiter(name = "userService")
    public CompletableFuture<User> getUser(Long id) {
        return CompletableFuture.supplyAsync(() ->
            restTemplate.getForObject(
                "http://user-service/api/users/" + id,
                User.class
            )
        );
    }

    // 降级方法
    public CompletableFuture<User> fallback(Long id, Throwable t) {
        return CompletableFuture.completedFuture(
            new User(0L, "降级用户", "default@example.com")
        );
    }
}

常见坑点

⚠️ 坑 1:Sentinel 规则不生效

// ❌ 规则配置类未加载
@Configuration
public class SentinelConfig {
    @PostConstruct
    public void initRules() {
        // 规则加载代码
    }
}

// ✅ 确保被扫描到
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo", "com.example.other"})

// ✅ 或手动触发加载
System.setProperty("csp.sentinel.log.dir", "/tmp/logs");

⚠️ 坑 2:限流误杀

# 调整滑动窗口大小
resilience4j:
  circuitbreaker:
    instances:
      backendA:
        slidingWindowSize: 20  # 增大窗口
        minimumNumberOfCalls: 10  # 增大最小调用数

⚠️ 坑 3:熔断后服务恢复慢

# 调整熔断恢复参数
resilience4j:
  circuitbreaker:
    instances:
      backendA:
        waitDurationInOpenState: 10s  # 缩短熔断时长
        permittedNumberOfCallsInHalfOpenState: 3  # 增加半开探测数

面试题

Q1:Sentinel 和 Hystrix 的区别?

参考答案

维度SentinelHystrix
隔离策略信号量隔离线程池隔离
熔断策略慢调用/异常比例异常数/超时
实时指标QPS/响应时间线程池指标
规则配置动态推送配置不可变
Dashboard功能完善功能有限
社区活跃活跃停止维护

Q2:什么是服务雪崩?如何避免?

参考答案

服务雪崩:服务调用链中,一个服务故障导致整个链路不可用

解决方案

方案说明
熔断器快速失败,阻止故障扩散
限流控制请求速率
降级返回兜底数据
隔离资源隔离(线程池/信号量)
超时控制设置合理超时时间
// 超时配置
feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 10000

Q3:限流算法原理?

参考答案

计数器:固定时间窗口内计数,超过阈值拒绝

  • 简单,但可能有临界问题

滑动窗口:将窗口划分为更小的桶,更精确

  • Redis ZSet 实现滑动窗口计数

漏桶:以固定速率消费请求

# 漏桶算法伪代码
bucket = []
rate = 10  # 每秒处理10个

while True:
    if len(bucket) < capacity:
        bucket.append(request)
    process_bucket()  # 按固定速率处理

令牌桶:以固定速率生成令牌,拿令牌处理

# 令牌桶算法伪代码
tokens = capacity
rate = 10  # 每秒生成10个令牌

while True:
    if tokens > 0:
        token = get_token()
        tokens -= 1
        process(token)
    else:
        wait()