SpringCloud Sentinel 服务保护完整教程

目录

1. Sentinel 简介

1.1 什么是Sentinel

1.2 Sentinel的核心功能

流量控制

熔断降级

系统负载保护

1.3 Sentinel与其他组件对比

2. Sentinel 核心概念

2.1 资源(Resource)

资源的定义方式

2.2 规则(Rule)

规则的类型

2.3 插槽链(SlotChain)

核心Slot

3. 环境搭建与配置

3.1 项目依赖配置

Maven依赖

Gradle依赖

3.2 Sentinel Dashboard安装

下载方式

启动参数配置

3.3 应用配置

application.yml配置

4. 流量控制(Flow Control)

4.1 流控规则基础

流控规则的组成

4.2 流控模式详解

直接模式

关联模式

链路模式

4.3 流控效果详解

快速失败(默认)

Warm Up(预热/冷启动)

排队等待

4.4 代码示例

完整的流控示例

使用配置文件进行流控规则配置

5. 熔断降级(Circuit Breaking)

5.1 熔断降级原理

熔断状态机

5.2 熔断策略详解

慢调用比例

异常比例

异常数

5.3 降级处理机制

blockHandler与fallback的区别

降级处理最佳实践

5.4 实战案例

微服务熔断降级实战

6. 系统保护(System Protection)

6.1 系统保护规则

系统保护规则类型

6.2 系统指标监控

自定义系统指标收集器

6.3 自适应限流

系统保护规则配置

系统保护实战示例

系统指标监控端点

7. 热点参数限流(Hot Parameter Flow Control)

7.1 热点参数概念

热点参数的应用场景

7.2 参数限流规则

基本参数限流

参数限流规则配置

7.3 实际应用场景

场景一:商品秒杀限流

场景二:用户行为限流

高级热点参数限流配置

8. 授权规则(Authorization)

8.1 黑白名单机制

授权规则的组成

8.2 授权规则配置

自定义请求来源解析器

授权规则配置类

8.3 自定义授权数据源

基于Token的授权

动态授权规则管理

实际应用示例

9. 集群流控(Cluster Flow Control)

9.1 集群流控原理

集群流控架构

9.2 集群配置

Token Server配置

Token Client配置

9.3 分布式限流实现

嵌入模式集群流控

集群流控实战示例

集群流控监控

10. 规则持久化

10.1 持久化方案

持久化方案对比

10.2 Nacos集成

Nacos数据源配置

application.yml配置

规则管理服务

规则管理控制器

10.3 Apollo集成

Apollo数据源配置

Apollo配置

Apollo配置管理

规则同步工具

11. 监控与可视化

11.1 Sentinel Dashboard使用

Dashboard基本功能

Dashboard高级配置

11.2 监控指标详解

自定义指标监控

实时监控接口

11.3 日志配置

Sentinel日志配置

自定义日志处理

日志分析工具

12. 实际项目应用

12.1 微服务架构中的应用

电商系统架构示例

网关层统一限流

12.2 最佳实践

1. 分层防护策略


1. Sentinel 简介

1.1 什么是Sentinel

Sentinel是阿里巴巴开源的一款面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制熔断降级系统负载保护等多个维度来帮助用户保护服务的稳定性。

Sentinel具有以下特征:

丰富的应用场景:Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景
完备的实时监控:Sentinel提供了实时的监控功能
广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块
完善的SPI扩展点:Sentinel提供简单易用、完善的SPI扩展接口

1.2 Sentinel的核心功能

Sentinel的核心功能可以分为以下几个方面:

流量控制

流量控制在网络传输中是一个重要的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常重要的意义。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。

熔断降级

当调用链路中某个资源出现不稳定,例如,表现为timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

系统负载保护

Sentinel对系统的维度提供保护。当系统负载较高的时候,如果仍持续让请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其它的机器上去。

1.3 Sentinel与其他组件对比

特性 Sentinel Hystrix resilience4j
隔离策略 信号量隔离 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间或失败比率 基于失败比率 基于失败比率、响应时间
实时指标实现 滑动窗口 滑动窗口 Ring Bit Buffer
规则配置 支持多种数据源 支持多种数据源 限制
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持慢启动、匀速排队模式 不支持 简单的Rate Limiter模式
系统负载保护 支持 不支持 不支持
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善 不提供控制台,可对接其它监控系统

2. Sentinel 核心概念

2.1 资源(Resource)

资源是Sentinel的关键概念。它可以是Java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

资源的定义方式

方式一:通过注解定义资源

@SentinelResource(value = "hello", blockHandler = "handleException")
public String hello() {
    return "Hello Sentinel";
}

public String handleException(BlockException ex) {
    return "系统繁忙,请稍后再试";
}

方式二:通过API定义资源

try (Entry entry = SphU.entry("hello")) {
    // 被保护的逻辑
    return "Hello Sentinel";
} catch (BlockException ex) {
    // 处理被流控的逻辑
    return "系统繁忙,请稍后再试";
}

2.2 规则(Rule)

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

规则的类型

流量控制规则(FlowRule):用于控制流量的规则
熔断降级规则(DegradeRule):用于熔断降级的规则
系统保护规则(SystemRule):用于系统保护的规则
来源访问控制规则(AuthorityRule):用于访问控制的规则
热点参数规则(ParamFlowRule):用于热点参数限流的规则

2.3 插槽链(SlotChain)

Sentinel将不同的Slot按照顺序串在一起(责任链模式),从而将不同的功能组合在一起。SlotChain其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)。

核心Slot

NodeSelectorSlot:负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级
ClusterBuilderSlot:用于存储资源的统计信息以及调用者信息,例如该资源的RT、QPS、thread count等等
StatisticSlot:用于记录、统计不同维度的runtime指标监控信息
FlowSlot:用于根据预设的限流规则以及前面slot统计的状态,来进行流量控制
AuthoritySlot:根据配置的黑白名单和调用来源信息,来做黑白名单控制
DegradeSlot:通过统计信息以及预设的规则,来做熔断降级
SystemSlot:通过系统的状态,例如load1等,来控制总的入口流量


3. 环境搭建与配置

3.1 项目依赖配置

Maven依赖
<dependencies>
    <!-- Spring Cloud Alibaba Sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.7.RELEASE</version>
    </dependency>
    
    <!-- Sentinel Dashboard 客户端 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-transport-simple-http</artifactId>
        <version>1.8.4</version>
    </dependency>
    
    <!-- Sentinel 数据源扩展 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
        <version>1.8.4</version>
    </dependency>
</dependencies>
Gradle依赖
dependencies {
    implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel:2.2.7.RELEASE'
    implementation 'com.alibaba.csp:sentinel-transport-simple-http:1.8.4'
    implementation 'com.alibaba.csp:sentinel-datasource-nacos:1.8.4'
}

3.2 Sentinel Dashboard安装

下载方式

方式一:直接下载jar包

# 下载最新版本的Sentinel Dashboard
wget https://github.com/alibaba/Sentinel/releases/download/v1.8.4/sentinel-dashboard-1.8.4.jar

# 启动Dashboard
java -jar sentinel-dashboard-1.8.4.jar

方式二:Docker方式

# 拉取Docker镜像
docker pull bladex/sentinel-dashboard:1.8.4

# 运行容器
docker run --name sentinel-dashboard -d -p 8080:8080 bladex/sentinel-dashboard:1.8.4
启动参数配置
java -Dserver.port=8080 
     -Dcsp.sentinel.dashboard.server=localhost:8080 
     -Dproject.name=sentinel-dashboard 
     -jar sentinel-dashboard-1.8.4.jar

主要启动参数说明:

-Dserver.port:指定Sentinel Dashboard的端口,默认为8080
-Dcsp.sentinel.dashboard.server:指定控制台地址和端口
-Dproject.name:指定程序的名称

3.3 应用配置

application.yml配置
server:
  port: 8081

spring:
  application:
    name: sentinel-demo
  cloud:
    sentinel:
      # 配置Sentinel Dashboard地址
      transport:
        dashboard: localhost:8080
        port: 8719
      # 配置数据源
      datasource:
        # 流控规则
        flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow
        # 降级规则
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade
      # 开启饥饿加载
      eager: true
      # 配置日志
      log:
        dir: ./logs/sentinel
        switch-pid: true

# 开启Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

# 开启Actuator健康检查
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

4. 流量控制(Flow Control)

4.1 流控规则基础

流量控制(flow control),其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

流控规则的组成

一条流控规则主要由下面几个因素组成:

资源名:规则的作用对象
针对来源:针对特定的调用者进行限流,若为default则不区分调用者
阈值类型/单机阈值

QPS模式:当QPS超过某个阈值的时候,则采取措施进行流量控制
线程数模式:当线程数超过某个阈值的时候,则采取措施进行流量控制

流控模式

直接:当前资源达到限流条件时,直接限流
关联:当关联资源达到限流条件时,限流自己
链路:当从某个入口的资源达到限流条件时,开启限流

流控效果

快速失败:直接失败,抛异常
Warm Up:预热模式,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限
排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

4.2 流控模式详解

直接模式

直接模式是最简单的模式,当当前资源达到限流条件时,直接限流。

代码示例:

@RestController
public class FlowController {
    
    @GetMapping("/testA")
    @SentinelResource(value = "testA", blockHandler = "handleTestA")
    public String testA() {
        return "------testA";
    }
    
    public String handleTestA(BlockException exception) {
        return "testA服务不可用";
    }
}

规则配置:

@PostConstruct
public void initFlowRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("testA");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(2);
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}
关联模式

当关联的资源达到阈值时,就限流自己。比如,当与A关联的资源B达到阈值后,就限流A自己。

应用场景: 当支付接口达到阈值时,就要限流下订单的接口,防止连坐效应带来的不稳定因素。

代码示例:

@RestController
public class FlowController {
    
    @GetMapping("/testA")
    @SentinelResource(value = "testA", blockHandler = "handleTestA")
    public String testA() {
        return "------testA";
    }
    
    @GetMapping("/testB")
    @SentinelResource(value = "testB")
    public String testB() {
        return "------testB";
    }
    
    public String handleTestA(BlockException exception) {
        return "testA关联资源testB限流";
    }
}

规则配置:

@PostConstruct
public void initFlowRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("testA");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(1);
    rule.setStrategy(RuleConstant.STRATEGY_RELATE);
    rule.setRefResource("testB"); // 关联资源
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}
链路模式

只记录指定链路上的流量(指定资源从入口资源进来的流量),如果达到阈值,就对指定链路限流。

代码示例:

@Service
public class FlowService {
    
    @SentinelResource("common")
    public String common() {
        return "common";
    }
}

@RestController
public class FlowController {
    
    @Autowired
    private FlowService flowService;
    
    @GetMapping("/testA")
    public String testA() {
        return flowService.common();
    }
    
    @GetMapping("/testB")
    public String testB() {
        return flowService.common();
    }
}

4.3 流控效果详解

快速失败(默认)

快速失败是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。

Warm Up(预热/冷启动)

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

代码示例:

@PostConstruct
public void initFlowRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("testA");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(10);
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
    rule.setWarmUpPeriodSec(5); // 预热时间5秒
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}
排队等待

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

代码示例:

@PostConstruct
public void initFlowRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("testA");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(1);
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
    rule.setMaxQueueingTimeMs(20000); // 最大排队时间20秒
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

4.4 代码示例

完整的流控示例
@RestController
@Slf4j
public class FlowControlController {
    
    @GetMapping("/flow/qps")
    @SentinelResource(
        value = "flowQps",
        blockHandler = "flowQpsBlockHandler"
    )
    public Result<String> flowQps() {
        log.info("QPS流控测试");
        return Result.success("QPS流控测试成功");
    }
    
    @GetMapping("/flow/thread")
    @SentinelResource(
        value = "flowThread",
        blockHandler = "flowThreadBlockHandler"
    )
    public Result<String> flowThread() throws InterruptedException {
        log.info("线程数流控测试");
        Thread.sleep(2000); // 模拟业务处理
        return Result.success("线程数流控测试成功");
    }
    
    public Result<String> flowQpsBlockHandler(BlockException exception) {
        log.warn("QPS流控触发:{}", exception.getMessage());
        return Result.fail("系统繁忙,请稍后再试");
    }
    
    public Result<String> flowThreadBlockHandler(BlockException exception) {
        log.warn("线程数流控触发:{}", exception.getMessage());
        return Result.fail("系统繁忙,请稍后再试");
    }
}
使用配置文件进行流控规则配置
@Component
public class FlowRuleConfig {
    
    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        
        // QPS流控规则
        FlowRule qpsRule = new FlowRule();
        qpsRule.setResource("flowQps");
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        qpsRule.setCount(2);
        qpsRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(qpsRule);
        
        // 线程数流控规则
        FlowRule threadRule = new FlowRule();
        threadRule.setResource("flowThread");
        threadRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        threadRule.setCount(2);
        threadRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(threadRule);
        
        FlowRuleManager.loadRules(rules);
        log.info("流控规则初始化完成,共{}条规则", rules.size());
    }
}

5. 熔断降级(Circuit Breaking)

5.1 熔断降级原理

熔断降级是为了保证在依赖的服务不可用或者服务响应过慢的时候,当失败的调用到一定阈值缺陷时,通过熔断依赖的服务调用,直接返回错误响应信息。这样就可以避免在调用链中,高延迟的服务影响到其他的服务,进而避免级联失败。

熔断状态机

Sentinel熔断器有三种状态:

CLOSED(关闭):熔断器关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制
OPEN(打开):熔断器打开状态,此时对下游的调用都内部直接返回错误,不走网络,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置为平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态
HALF_OPEN(半开):半熔断状态,允许定量的服务请求,如果调用都成功(或一定比例)则认为恢复了,关闭熔断器,否则认为还没好,又回到熔断器打开状态

5.2 熔断策略详解

慢调用比例

慢调用比例模式下,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。

代码示例:

@RestController
@Slf4j
public class DegradeController {
    
    @GetMapping("/degrade/slow")
    @SentinelResource(
        value = "degradeSlow",
        blockHandler = "degradeSlowBlockHandler",
        fallback = "degradeSlowFallback"
    )
    public Result<String> degradeSlow() throws InterruptedException {
        // 模拟慢调用
        Thread.sleep(1000);
        log.info("慢调用测试");
        return Result.success("慢调用测试成功");
    }
    
    public Result<String> degradeSlowBlockHandler(BlockException exception) {
        log.warn("慢调用熔断触发:{}", exception.getMessage());
        return Result.fail("服务降级,慢调用比例过高");
    }
    
    public Result<String> degradeSlowFallback(Throwable throwable) {
        log.warn("慢调用异常降级:{}", throwable.getMessage());
        return Result.fail("服务异常,降级处理");
    }
}

规则配置:

@PostConstruct
public void initDegradeRules() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("degradeSlow");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    rule.setCount(500); // 响应时间阈值500ms
    rule.setTimeWindow(10); // 熔断时长10秒
    rule.setMinRequestAmount(5); // 最小请求数
    rule.setSlowRatioThreshold(0.5); // 慢调用比例阈值
    rule.setStatIntervalMs(30000); // 统计时长30秒
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}
异常比例

异常比例模式下,当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。

代码示例:

@GetMapping("/degrade/exception")
@SentinelResource(
    value = "degradeException",
    blockHandler = "degradeExceptionBlockHandler",
    fallback = "degradeExceptionFallback"
)
public Result<String> degradeException() {
    // 模拟异常
    if (System.currentTimeMillis() % 3 == 0) {
        throw new RuntimeException("模拟异常");
    }
    log.info("异常比例测试");
    return Result.success("异常比例测试成功");
}

public Result<String> degradeExceptionBlockHandler(BlockException exception) {
    log.warn("异常比例熔断触发:{}", exception.getMessage());
    return Result.fail("服务降级,异常比例过高");
}

public Result<String> degradeExceptionFallback(Throwable throwable) {
    log.warn("异常降级处理:{}", throwable.getMessage());
    return Result.fail("服务异常,降级处理");
}

规则配置:

@PostConstruct
public void initDegradeRules() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("degradeException");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    rule.setCount(0.5); // 异常比例阈值50%
    rule.setTimeWindow(10); // 熔断时长10秒
    rule.setMinRequestAmount(5); // 最小请求数
    rule.setStatIntervalMs(30000); // 统计时长30秒
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}
异常数

异常数模式下,当单位统计时长内的异常数目超过阈值之后会自动进行熔断。

规则配置:

@PostConstruct
public void initDegradeRules() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("degradeExceptionCount");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    rule.setCount(3); // 异常数阈值3个
    rule.setTimeWindow(10); // 熔断时长10秒
    rule.setMinRequestAmount(2); // 最小请求数
    rule.setStatIntervalMs(60000); // 统计时长60秒
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

5.3 降级处理机制

blockHandler与fallback的区别

blockHandler:处理BlockException异常,即Sentinel规则的违规(流控、熔断、系统保护等)
fallback:处理Java运行时异常,即业务代码抛出的异常

降级处理最佳实践
@Component
@Slf4j
public class OrderService {
    
    @SentinelResource(
        value = "getOrder",
        blockHandler = "getOrderBlockHandler",
        fallback = "getOrderFallback",
        exceptionsToIgnore = {IllegalArgumentException.class}
    )
    public Result<Order> getOrder(Long orderId) {
        if (orderId == null || orderId <= 0) {
            throw new IllegalArgumentException("订单ID不能为空或小于等于0");
        }
        
        // 模拟业务处理
        if (System.currentTimeMillis() % 5 == 0) {
            throw new RuntimeException("数据库连接异常");
        }
        
        Order order = new Order();
        order.setId(orderId);
        order.setOrderNo("ORDER" + System.currentTimeMillis());
        order.setStatus("PAID");
        
        return Result.success(order);
    }
    
    // 处理Sentinel规则违规
    public Result<Order> getOrderBlockHandler(Long orderId, BlockException exception) {
        log.warn("订单查询被限流或降级,orderId: {}, exception: {}", 
                orderId, exception.getClass().getSimpleName());
        return Result.fail("系统繁忙,请稍后重试");
    }
    
    // 处理业务异常
    public Result<Order> getOrderFallback(Long orderId, Throwable throwable) {
        log.error("订单查询异常,orderId: {}", orderId, throwable);
        return Result.fail("订单查询失败,请稍后重试");
    }
}

5.4 实战案例</

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
米饭团子funny的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容