Spring Cloud Alibaba Sentinel之服务熔断篇

目录

一、简介

二、搭建服务提供者

三、搭建服务消费者

四、Sentinel服务熔断 - 没有配置fallback和blockHandler

五、Sentinel服务熔断 - 只配置fallback

六、Sentinel服务熔断 - 只配置blockHandler

七、Sentinel服务熔断 - 配置fallback和blockHandler

八、Sentinel服务熔断 -  配置exceptionsToIgnore

九、Sentinel服务熔断OpenFeign

十、总结


一、简介

前面一篇文章我们介绍了如何使用@SentinelResource定义一个资源并且如何配置降级规则等,我们都知道,微服务之间的调用要么采用ribbon借助RestTemplate进行调用,要么是通过OpenFeign声明式服务调用,涉及到跨服务调用,难免会出现网络故障,此时如果我们的接口报错,直接将报错信息展示到前端,客户看到很不友好。Sentinel提供了丰富的熔断之后的降级处理方法,本篇文章将Sentinel与Ribbon负载均衡、Sentinel与OpenFeign进行整合,分别介绍如何配置处理降级。

二、搭建服务提供者

新建一个子模块工程【springcloudalibaba-provider-payment9003】和【springcloudalibaba-provider-payment9004】作为我们的服务提供者,两个子模块代码除了端口不一致,其他一模一样,下面以【springcloudalibaba-provider-payment9003】为例说明

【a】pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud2020</artifactId>
        <groupId>com.wsh.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloudalibaba-provider-payment9003</artifactId>


    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <!--引入自己定义的api通用包 -->
            <groupId>com.wsh.springcloud</groupId>
            <artifactId>springcloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

【b】application.yml配置文件

server:
  port: 9003
spring:
  application:
    name: springcloudalibaba-provider-payment
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址
management:
  endpoints:
    web:
      exposure:
        include: '*'  #暴露端点配置

【c】主启动类

package com.wsh.springcloud.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class SpringCloudAlibabaPaymentServiceApplication9003 {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaPaymentServiceApplication9003.class, args);
    }
}

【d】controller业务层方法

package com.wsh.springcloud.alibaba.controller;

import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    /**
     * 模拟静态数据
     */
    private static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L, "001"));
        hashMap.put(2L, new Payment(2L, "002"));
        hashMap.put(3L, new Payment(3L, "003"));
    }

    @GetMapping(value = "/payment/{id}")
    public JsonResult<Payment> payment(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        return new JsonResult(200, "serial: " + payment.getSerial() + ",serverPort:  " + serverPort, payment);
    }

}

【e】测试

启动9003和9004服务提供者,浏览器访问:http://localhost:9003/payment/1

http://localhost:9004/payment/3 

可见,接口都成功返回数据,至此我们的服务提供方【9003和9004】也就搭建成功。

三、搭建服务消费者

新建子模块【springcloudalibaba-consumer-nacos-order84】,作为我们的服务消费者。

【a】pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud2020</artifactId>
        <groupId>com.wsh.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloudalibaba-consumer-nacos-order84</artifactId>

    <dependencies>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包 -->
        <dependency>
            <groupId>com.wsh.springcloud</groupId>
            <artifactId>springcloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

【b】application.yml配置文件

server:
  port: 84
spring:
  application:
    name: springcloudalibaba-consumer-nacos-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #配置nacos服务器地址
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

【c】主启动类

package com.wsh.springcloud.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@SpringBootApplication
public class SpringCloudAlibabaConsumerOrderServiceApplicaiton84 {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaConsumerOrderServiceApplicaiton84.class, args);
    }
}

【d】业务controller方法

package com.wsh.springcloud.alibaba.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class CircleBreakerController {
    /**
     * 指定服务提供者的微服务名称
     */
    private static final String SERVICE_URL = "http://springcloudalibaba-provider-payment";

    /**
     * 负载均衡调用类
     */
    @Resource
    private RestTemplate restTemplate;

    /**
     * 没有配置fallback和blockHandler
     */
    @RequestMapping("/consumer/noFallback/{id}")
    @SentinelResource(value = "noFallback") //没有配置
    public JsonResult<Payment> noFallback(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,暂未找到对应记录......");
        }
        return result;
    }

}

【e】测试

启动服务提供者9003和9004项目以及服务消费者84服务,当nacos服务注册列表看到三个服务都成功注册之后,

浏览器访问:http://localhost:84/consumer/noFallback/1

根据运行结果,我们发现,服务消费者84已经成功实现了对服务提供者8003和8004的负载均衡调用,默认采用轮训的方式进行调用,至此我们的服务消费方也算搭建成功。

下面我们详细介绍五种不同配置下的服务熔断处理方法。 

四、Sentinel服务熔断 - 没有配置fallback和blockHandler

【a】控制层增加如下代码

  /**
     * 没有配置fallback和blockHandler
     */
    @RequestMapping("/consumer/noFallback/{id}")
    @SentinelResource(value = "noFallback") //没有配置
    public JsonResult<Payment> noFallback(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,暂未找到对应记录......");
        }
        return result;
    }

【b】测试

浏览器访问:http://localhost:84/consumer/noFallback/2

当我们id = 4时,根据上面的程序判断将会抛出非法参数异常,浏览器访问:http://localhost:84/consumer/noFallback/4 当我们id = 5时,根据上面的程序判断将会抛出空指针异常,浏览器访问:http://localhost:84/consumer/noFallback/5

 

【c】小总结

由上面的测试结果可以看到,当我们什么都没有配置的时候,如果发生异常,那么异常信息将直接展示到前台给客户,很不友好。

五、Sentinel服务熔断 - 只配置fallback

【a】业务层增加如下方法

/**
     * fallback只负责业务异常
     */
    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback")
    public JsonResult<Payment> fallback(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录......");
        }
        return result;
    }

 /**
     * 业务异常兜底降级方法
     */
    public JsonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new JsonResult<>(444, "[业务异常兜底降级方法],exception内容:  " + e.getMessage(), payment);
    }

【b】测试

浏览器访问:http://localhost:84/consumer/fallback/1

当我们id = 4时,根据上面的程序判断将会抛出非法参数异常,浏览器访问:http://localhost:84/consumer/fallback/4

 

 当我们id = 5时,根据上面的程序判断将会抛出空指针异常,浏览器访问:http://localhost:84/consumer/fallback/5

【c】小总结

当我们配置了fallback属性后,如果程序发生Java异常,那么错误信息将不会展示到前台展示,它将执行我们指定的业务异常兜底降级方法,错误信息可以自定义,比如提示 "服务器异常,请稍后重试" 等等,比起前面一种方式显然友好很多。

六、Sentinel服务熔断 - 只配置blockHandler

【a】业务层增加如下方法

   /**
     * blockHandler只负责sentinel控制台配置的违规情况,业务运行时异常sentinel不管,该抛出还是抛出
     */
    @RequestMapping("/consumer/withBlockHandler/{id}")
    @SentinelResource(value = "withBlockHandler", blockHandler = "blockHandler")
    public JsonResult<Payment> withBlockHandler(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录......");
        }
        return result;
    }

/**
     * sentinel控制台配置违反兜底降级方法
     */
    public JsonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        return new JsonResult<>(445, "[sentinel控制台配置违反兜底降级方法],无此流水: blockException  " + blockException.getMessage(), new Payment(id, "null"));
    }

【b】Sentinel配置

【c】测试

浏览器访问:http://localhost:84/consumer/withBlockHandler/1

 

可以看到,如果一秒钟访问一次,接口是没有问题的。但是如果我们1秒内访问多次该接口:http://localhost:84/consumer/withBlockHandler/1

可以看到,当违反了sentinel控制台配置的降级规则时,将会触发blockHandler方法指定的降级处理逻辑。

下面我们看看blockHandler是否会管业务运行时异常?

当我们id = 4时,根据上面的程序判断将会抛出非法参数异常,浏览器访问:http://localhost:84/consumer/withBlockHandler/4

当我们id = 5时,根据上面的程序判断将会抛出空指针异常,浏览器访问:http://localhost:84/consumer/withBlockHandler/5

我们发现,当配置了blockHandler的时候,它只会针对违反sentinel控制台的降级规则生效,对于业务运行时抛出的异常,blockHandler是不会进行降级处理的。

【d】小总结

当配置了blockHandler的时候,它只会针对违反sentinel控制台降级规则的情况下生效,对于业务运行时抛出的异常,blockHandler是不会进行降级处理的,依旧会把运行时异常直接展示给客户页面,不太友好。

七、Sentinel服务熔断 - 配置fallback和blockHandler

【a】业务层增加如下方法

/**
     * blockHandler只负责sentinel控制台配置的违规情况,业务运行时异常sentinel不管,该抛出还是抛出
     * fallback只负责业务异常
     */
    @RequestMapping("/consumer/withBlockHandlerAndFallback/{id}")
    @SentinelResource(value = "withBlockHandlerAndFallback", fallback = "handlerFallback", blockHandler = "blockHandler",)
    public JsonResult<Payment> withBlockHandlerAndFallback(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录......");
        }
        return result;
    }

    /**
     * 业务异常兜底降级方法
     */
    public JsonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new JsonResult<>(444, "[业务异常兜底降级方法],exception内容:  " + e.getMessage(), payment);
    }

    /**
     * sentinel控制台配置违反兜底降级方法
     */
    public JsonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        return new JsonResult<>(445, "[sentinel控制台配置违反兜底降级方法],无此流水: blockException  " + blockException.getMessage(), new Payment(id, "null"));
    }

【b】sentinel配置

【c】测试

浏览器访问:http://localhost:84/consumer/withBlockHandlerAndFallback/2

可以看到,如果一秒钟访问一次,接口是没有问题的。但是如果我们1秒内访问多次该接口:

可以看到,当违反了sentinel控制台配置的降级规则时,将会触发blockHandler方法指定的降级处理逻辑。 下面我们来看看业务异常怎么处理?同上,

当我们id = 4时,根据上面的程序判断将会抛出非法参数异常,浏览器访问:http://localhost:84/consumer/withBlockHandlerAndFallback/4

当我们id = 5时,根据上面的程序判断将会抛出空指针异常,浏览器访问:http://localhost:84/consumer/withBlockHandlerAndFallback/5

【c】小总结

由前面的测试结果可以看到,当同时配置了fallback和blockHandler的时候,两者并不冲突,各处理各的。blockHandler处理sentinel控制台违反规则的降级处理;fallback处理业务运行时异常的降级处理。

注意:如果是疯狂访问:http://localhost:84/consumer/withBlockHandlerAndFallback/5,很明显,程序会抛出异常,并且也违反了sentinel控制台降级规则,小伙伴们注意,这时候,是以blockHandler降级处理为准的。

八、Sentinel服务熔断 -  配置exceptionsToIgnore

【a】业务层方法增加如下配置:使用exceptionsToIgnore属性来排除某些异常,意思就是假如程序抛出该异常时,不再进入业务异常降级处理方法,没有降级效果。

  /**
     * blockHandler只负责sentinel控制台配置的违规情况,业务运行时异常sentinel不管,该抛出还是抛出
     * fallback只负责业务异常
     */
    @RequestMapping("/consumer/withBlockHandlerAndFallback/{id}")
    @SentinelResource(value = "withBlockHandlerAndFallback", fallback = "handlerFallback", blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    public JsonResult<Payment> withBlockHandlerAndFallback(@PathVariable Long id) {
        JsonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, JsonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录......");
        }
        return result;
    }

 【b】测试

当我们id = 4时,根据上面的程序判断将会抛出非法参数异常,也即是我们上面忽略的这个异常,浏览器访问:http://localhost:84/consumer/withBlockHandlerAndFallback/4

可以看到,当程序抛出IllegalArgumentException异常时,将不再走fallback降级处理方法。

浏览器访问:http://localhost:84/consumer/withBlockHandlerAndFallback/5

可以看到,处理上面排除的异常,其他异常还是继续会走降级处理。 

【c】小总结

exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。使用exceptionsToIgnore属性来排除某些异常,意思就是假如程序抛出该异常时,不再进入业务异常降级处理方法,没有降级效果。

九、Sentinel服务熔断OpenFeign

前面我们介绍了Sentinel和Ribbon负载均衡结合起来,并通过示例详细介绍了五种服务降级的配置以及各自产生的效果。接下来我们介绍一些Sentinel整合OpenFeign远程服务调用实现服熔断降级。所以需要对【springcloudalibaba-consumer-nacos-order84】进行改造:

【a】pom.xml依赖:加入open-feign依赖

<!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

【b】application.yml配置文件:加入feign对sentinel的支持

feign:
  sentinel:
    enabled: true  # 激活Sentinel对Feign的支持

【c】主启动类加上注解

@EnableDiscoveryClient
@SpringBootApplication
//开启远程调用
@EnableFeignClients
public class SpringCloudAlibabaConsumerOrderServiceApplicaiton84 {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaConsumerOrderServiceApplicaiton84.class, args);
    }
}

【d】编写FeignClient接口

package com.wsh.springcloud.alibaba.feign;

import com.wsh.springcloud.alibaba.feign.fallback.PaymentFeignClientFallback;
import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Description Feign远程调用类
 * @Date 2020/9/6 16:07
 * @Author weishihuai
 * 说明:
 */
@FeignClient(value = "springcloudalibaba-provider-payment", fallback = PaymentFeignClientFallback.class)
public interface PaymentFeignClient {

    @GetMapping(value = "/payment/{id}")
    Object payment(@PathVariable("id") Long id);

}

【e】编写FeignClient接口失败回调类

package com.wsh.springcloud.alibaba.feign.fallback;

import com.wsh.springcloud.alibaba.feign.PaymentFeignClient;
import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import org.springframework.stereotype.Component;

/**
 * @Description 远程调用失败回调类
 * @Date 2020/9/6 16:08
 * @Author weishihuai
 * 说明: 需实现FeignClient中定义的所有方法
 */
@Component
public class PaymentFeignClientFallback implements PaymentFeignClient {
    @Override
    public Object payment(Long id) {
        return new JsonResult<>(500, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial"));
    }
}

【f】业务controller新增如下方法

    @Resource
    private PaymentFeignClient paymentFeignClient;

    @GetMapping(value = "/consumer/openfeign/payment/{id}")
    public Object payment(@PathVariable("id") Long id) {
        Object payment = paymentFeignClient.payment(id);
        return payment;
    }

【g】测试

重启服务消费者84服务,浏览器访问:http://localhost:84/consumer/feignFallback/1

可见,接口成功返回数据,证明我们远程调用成功。

下面我们关闭两个服务提供者9003和9004,再次访问:http://localhost:84/consumer/feignFallback/1

可以看到,此时调用了服务降级,说明OpenFeign的fallback处理逻辑生效了,这就实现了sentinel与openfeign的服务降级处理。

十、总结

本篇文章通过将Sentinel与ribbon和openfeign进行整合,实现了跨服务调用之间的接口的熔断降级处理,并分析了几种不同的配置情况下,Sentinel是如何处理降级的,在实际项目中,推荐fallback和blockHandler都进行配置,这样就能兼容业务异常和sentinel流控规则违反情况的降级处理。

以上相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。

下面是笔者总结的关于Spring Cloud Alibaba教程系列文章目录,有需要的小伙伴可以前往学习:

1. Spring Cloud Alibaba入门简介

2. Spring Cloud Alibaba Nacos之服务注册中心

3. Spring Cloud Alibaba Nacos之服务配置中心

4. Spring Cloud Alibaba Nacos集群和持久化配置

5. Spring Cloud Alibaba Sentinel之入门篇

6. Spring Cloud Alibaba Sentinel之流控规则篇

7. Spring Cloud Alibaba Sentinel之服务降级篇

8. Spring Cloud Alibaba Sentinel之热点参数限流篇

9. Spring Cloud Alibaba @SentinelResource配置详解

10. Spring Cloud Alibaba Sentinel之服务熔断篇

11. Spring Cloud Alibaba Sentinel之持久化篇

12. Spring Cloud Alibaba Seata处理分布式事务及案例实战

13. Spring Cloud Alibaba Seata工作原理

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值