Bootstrap

SpringCloudAlibaba-Sentinel-熔断与限流

版本说明

<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.1.2</spring.cloud.alibaba.version>

是什么

能干嘛

面试题

服务雪崩

安装使用

后台 8719,前端 8080,用户名密码 sentinel

本次版本 1.8.6

服务加入监控

启动 nacos, sentinel

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
  port: 8401

spring:
  application:
    name: cloud-alibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719 # 默认端口
@Slf4j
@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
        return "into ... A ...";
    }

    @GetMapping("/testB")
    public String testB() {
        return "into ... B ...";
    }

}

GET http://localhost:8401/testA

流控规则

流控模式

直接

每秒超过2qps

关联

压测 testB,访问 testA

链路

厚此薄彼

添加一个service 资源,cd来调用同一个 service

@Slf4j
@Service
public class FlowLimitService {

    @SentinelResource("common")
    public void common() {
        log.info("into ... common ...");
    }
}

// controller
@GetMapping("/testC")
public String testC() {
    flowLimitService.common();
    return "into ... C ...";
}

@GetMapping("/testD")
public String testD() {
    flowLimitService.common();
    return "into ... D ...";
}

新增配置

spring.cloud.sentinel:
  web-context-unify: false # controller 对 service 调用默认不是一个根链路

common上添加链路流控

c 超过qps会异常,d正常

流控效果

快速失败

warm up

排队等待

流控效果v2-并发线程数(对比 qps)

流控效果默认直快速失败,可能存在不准确?

熔断规则

慢调用比例

异常比例

异常数

@SentinelResource

rest 地址限流

@GetMapping("/")
public String rateLimitByUrl() {
    return "限流测试未使用注解";
}

返回默认信息

按SentinelResource+自定义限流返回

@GetMapping("/rateLimitByResource")
@SentinelResource(value = "ByResource", blockHandler = "byResourceHandler")
public String rateLimitByResource() {
    return "限流测试使用注解, 返回指定的字符串";
}

public String byResourceHandler(BlockException blockException) {
    return "服务不可用, 这是自定义返回的字符串";
}

额外添加服务降级

@GetMapping("/rateLimitByFallback/{i}")
    @SentinelResource(value = "rateLimitByFallback", blockHandler = "byBlockHandler", fallback = "byFallback")
    public String rateLimitByFallback(@PathVariable("i") Integer i) {
        if (i == 0) {
            throw new RuntimeException("i == 0 异常");
        }
        return "使用注解并使用, Fallback";
    }

    public String byBlockHandler(@PathVariable("i") Integer i, BlockException blockException) {
        log.error("配置了自定义限流, {}", blockException.getMessage());
        return "服务不可用, 这是自定义返回的字符串";
    }

    public String byFallback(@PathVariable("i") Integer i, Throwable throwable) {
        log.error("程序逻辑异常, {}", throwable.getMessage());
        return "逻辑异常, 这是自定义返回的字符串";
    }

热点规则

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "testHotKeyBlockHandler")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
    return "testHotKey ...";
}

public String testHotKeyBlockHandler(@RequestParam(value = "p1", required = false) String p1,
                                     @RequestParam(value = "p2", required = false) String p2, BlockException blockException) {
    return "testHotKey blockException ...";
}

对 p1 进行限流

参数例外项

授权规则

@Slf4j
@RestController
public class EmpowerController {
    @GetMapping("empower")
    public String requestSentinel() {
        log.info("授权规则");
        return "授权规则";
    }
}

public class CustomRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter("serverName");
    }
}

持久化规则

持久化到 nacos

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            data-id: ${spring.application.name}
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType (flow代表流控)

rule-type?

针对每一个rule-type单独配置 数据源

配置

nacos中添加 json 格式配置

测试重启,规则仍存在

整合OpenFeign实现 fallback 统一服务降级

blockHandler 处理 sentinel 流控问题,fallback 处理方法内抛出的异常

本例中,把 fallback 交给feign处理,再公共api-common中配置

服务提供者

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
  port: 9001
spring:
  application:
    name: nacos-pay-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos地址
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719 # 默认端口8719
// openfeign和sentinel 进行流量降级和流量监控的case
@GetMapping("/pay/nacos/get/{orderNo}")
@SentinelResource(value = "getPayByOrderNo", blockHandler = "handlerBlockHandler")
public Result<PayDTO> getPayByOrderNo(@PathVariable("orderNo") String orderNo) {
    // 模拟查询
    PayDTO payDTO = new PayDTO(1024, orderNo, "pay" + IdUtil.simpleUUID(), 1, BigDecimal.valueOf(9.9));
    return Result.success(payDTO);
}

public Result<PayDTO> handlerBlockHandler(String orderNo, BlockException e) {
    return Result.fail("服务提供者" + e.getMessage());
}

common

@FeignClient(value = "nacos-pay-provider", fallback = PayFeignSentinelFallback.class)
public interface PayFeignSentinelApi {

    @GetMapping("/pay/nacos/get/{orderNo}")
    Result<PayDTO> getPayByOrderNo(@PathVariable("orderNo") String orderNo);
}

@Component
public class PayFeignSentinelFallback implements PayFeignSentinelApi {
    @Override
    public Result<PayDTO> getPayByOrderNo(String orderNo) {
        return Result.fail("服务不可达");
    }
}

消费者

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
feign:
  sentinel:
    enabled: true
@GetMapping("/consume/pay/nacos/get/{orderNo}")
Result<PayDTO> getPayByOrderNo(@PathVariable("orderNo") String orderNo){
    return payFeignSentinelApi.getPayByOrderNo(orderNo);
}

sentinel配置&测试流控

单独测试服务端下流流控

localhost:9002/pay/nacos/get/1

测试 feign 异常

GET http://localhost:9000/consume/pay/nacos/get/1

让服务端宕机

整合 Gateway 实现服务限流

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-transport-simple-http</artifactId>
  <version>1.8.6</version>
</dependency>
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
  <version>1.8.6</version>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- loadbalancer -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.2</version>
  <scope>compile</scope>
</dependency>
server:
  port: 9528

spring:
  application:
    name: cloud-alibaba-sentinel-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos地址
    gateway:
      routes:
        - id: pay_routh1
          uri: lb://nacos-pay-provider
          #          uri: http://localhost:9001
          predicates:
            - Path=/pay/**
@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers;
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        initBlockHandler();
    }

    private void initBlockHandler() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(
            new GatewayFlowRule("pay_routh1")
            .setCount(2)
            .setIntervalSec(1)
        );
        GatewayRuleManager.loadRules(rules);

        BlockRequestHandler handler = (serverWebExchange, throwable) -> {
            HashMap<String, String> map = new HashMap<>();
            map.put("ErrorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
            map.put("ErrorMessage", "请求过于频繁, 触发了sentinel限流 ... ");
            return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(map));
        };

        GatewayCallbackManager.setBlockHandler(handler);
    }
}

http://localhost:9528/pay/nacos/get/1

;