SA实战 ·《SpringCloud Alibaba实战》第10章-服务容错:项目整合Sentinel实现限流与容错

简介: SA实战 ·《SpringCloud Alibaba实战》第10章-服务容错:项目整合Sentinel实现限流与容错


一不小心《SpringCloud Alibaba实战》专栏都更新到第10章了,再不上车就跟不上了,小伙伴们快跟上啊!

注意:本项目完整源码加入 冰河技术 知识星球即可获取,文末有优惠券。


在《SpringCloud Alibaba实战》专栏前面的文章中,我们实现了用户微服务、商品微服务和订单微服务之间的远程调用,并且实现了服务调用的负载均衡。同时,我们详细介绍了服务雪崩和服务容错的一些方案。


文章总览

图片.png



章节概述

今天,我们就使用Sentinel实现接口的限流,并使用Feign整合Sentinel实现服务容错的功能,让小伙伴们体验下微服务使用了服务容错功能的效果。因为我们整个专栏的内容仅仅围绕着SpringCloud Alibaba技术栈展开,所以,这里我们使用的服务容错组件是阿里开源的Sentinel。

当然,能够实现服务容错功能的组件不仅仅有Sentinel,比如:Hystrix和Resilience4J也能够实现服务容错的目的,关于Hystrix和Resilience4J不是本专栏的重点,冰河就不再赘述了,小伙伴们可以自行了解。


关于Sentinel

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel的特征

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring  Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入  Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel的主要特性

image.gif

图片.png

Sentinel的开源生态

图片.pngimage.gif


Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器

注意:上述内容来自Sentinel官方文档,链接地址为:

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D


项目整合Sentinel

在微服务项目中整合Sentinel是非常简单的,只需要在项目的pom.xml文件中引入

Sentinel的依赖即可,不过在使用Sentinel时,需要安装Sentinel的控制台。


安装Sentinel控制台

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。

(1)到链接

https://github.com/alibaba/Sentinel/releases 

下载Sentinel控制台,如下所示,我这里下载的Sentinel控制台是1.8.4版本。

图片.png


(2)Sentinel控制台下载完成后,在本地启动Sentinel控制台,如下所示。

java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar

小伙伴们如果想在CentOS服务器上以后台进程方式启动Sentinel控制台,可以使用如下命令

nohup java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar >> /dev/null &

启动后在浏览器中输入 http://localhost:8888 访问Sentinel控制台,如下所示。

image.gif图片.png


输入默认的用户名sentinel和密码sentinel,登录Sentinel控制台,如下所示。

image.gif图片.png


至此,Sentinel控制台下载并启动成功。

项目集成Sentinel

(1)在订单微服务的shop-order的pom.xml文件中添加Sentinel的相关依赖,如下所示。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

(2)在订单微服务的shop-order的application.yml文中加入Sentinel相关的配置,如下所示。

spring:
  cloud:
    sentinel:
      transport:
        port: 9999   #指定和Sentinel控制台交互的端口,任意指定一个未使用的端口即可
        dashboard: 127.0.0.1:8888  #Sentinel控制台服务地址

(3)为了让大家直观的感受到Sentinel的功能,这里我们先在订单微服务的io.binghe.shop.order.controller.OrderController类中新增一个测试接口,如下所示。

@GetMapping(value = "/test_sentinel")
public String testSentinel(){
    log.info("测试Sentinel");
    return "sentinel";
}

(4)启动订单微服务,在浏览器中输入http://localhost:8080/order/test_sentinel访问在订单微服务中新增的接口,如下所示。

图片.png

image.gif图片.png


(5)刷新Sentinel页面,会发现已经显示了订单微服务的菜单,如下所示。

图片.png


注意:直接启动订单微服务和Sentinel,会发现Sentinel中没有订单微服务的数据,因为Sentinel是懒加载机制,所以需要访问一下接口,再去访问Sentinel 就有数据了。

至此,订单微服务成功集成了Sentinel。


集成Sentinel限流功能

这里,我们使用Sentinel为http://localhost:8080/order/test_sentinel接口限流,步骤如下所示。

(1)在Sentinel控制台找到server-order下的簇点链路菜单,如下所示。

图片.png


(2)在簇点链路列表中找到/test_sentinel,在右侧的操作中选择流控,如下所示。

image.gif图片.png

点击流控按钮会显示 新增流控规则 的弹出框,如下所示。

图片.png


这里,我们在单机阈值后直接填写1,如下所示。

图片.png


配置好之后点击新增按钮。上述配置表示http://localhost:8080/order/test_sentinel接口的QPS为1,每秒访问1次。如果每秒访问的次数超过1次,则会被Sentinel限流。

(3)在浏览器上不断刷新http://localhost:8080/order/test_sentinel接口,当每秒中访问的次数超过1次时,会被Sentinel限流,如下所示。

图片.pngimage.gif


对提交订单的接口限流

在提交订单的接口 http://localhost:8080/order/submit_order上实现限流,步骤如下。

(1)首先访问下提交订单的接口 http://localhost:8080/order/submit_order,使得Sentinel中能够捕获到提交订单的接口,并点击操作中的流控按钮,如下所示。

image.gif

图片.png

这里的注意点还是:直接启动订单微服务和Sentinel,会发现Sentinel中没有订单微服务的数据,因为Sentinel是懒加载机制,所以需要访问一下接口,再去访问Sentinel 就有数据了。

(2)在新增流控规则显示框中的QPS单机阈值设置为1,点击新增按钮,如下所示。

image.gif图片.png


(3)在浏览器中不断刷新 http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1 使得每秒访问的频率超过1次,会被Sentinel限流,如下所示。

图片.pngimage.gif


至此,项目中集成了Sentinel并使用Sentinel实现了接口的限流。


Feign整合Sentinel实现容错

我们之前在项目中集成了Sentinel,并使用Sentinel实现了限流,如果订单微服务的下游服务,比如用户微服务和商品微服务出现故障,无法访问时,那订单微服务该如何实现服

务容错呢?使用Sentinel就可以轻松实现。


添加依赖并开启支持

(1)在订单微服务的shop-order的pom.xml文件中添加Sentinel的相关依赖,如下所示。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

注意:这一步是为了整个案例的完整性加上的,如果小伙伴们按照文章实现了项目整合Sentinel,并在订单微服务的shop-order的pom.xml文件中添加了上述配置,则可忽略此步骤。

(2)在订单微服务的application.yml文件中添加如下配置开启Feign对Sentinel的支持。

feign:
  sentinel:
    enabled: true

为远程调用实现容错

(1)需要在订单微服务shop-order中,为远程调用接口实现容错方法。这里,先为用户微服务实现容错。在订单微服务中新建io.binghe.shop.order.Feign.fallback 包,并在 io.binghe.shop.order.Feign.fallback包下创建UserServiceFallBack类实现UserService接口,用于调用用户微服务的容错类,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 用户服务容错类
 */
@Component
public class UserServiceFallBack implements UserService {
    @Override
    public User getUser(Long uid) {
        User user = new User();
        user.setId(-1L);
        return user;
    }
}

注意:容错类需要实现一个被容错的接口,并实现这个接口的方法。

接下来,在订单微服务的io.binghe.shop.order.Feign.UserService接口上的@FeignClient注解上指定容错类,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用用户微服务的接口
 */
@FeignClient(value = "server-user", fallback = UserServiceFallBack.class)
public interface UserService {
    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);
}

(2)在订单微服务中的 io.binghe.shop.order.Feign.fallback包下创建ProductServiceFallBack类实现ProductService接口,用于调用商品微服务的容错类,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 商品微服务的容错类
 */
@Component
public class ProductServiceFallBack implements ProductService {
    @Override
    public Product getProduct(Long pid) {
        Product product = new Product();
        product.setId(-1L);
        return product;
    }
    @Override
    public Result<Integer> updateCount(Long pid, Integer count) {
        Result<Integer> result = new Result<>();
        result.setCode(1001);
        result.setCodeMsg("触发了容错逻辑");
        return result;
    }
}

接下来,在订单微服务的io.binghe.shop.order.fegin.ProductService接口的@FeignClient注解上指定容错类,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用商品微服务的接口
 */
@FeignClient(value = "server-product", fallback = ProductServiceFallBack.class)
public interface ProductService {
    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);
    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}

(3)修改订单微服务的业务实现类中提交订单的业务方法,这里修改的方法位于io.binghe.shop.order.service.impl.OrderServiceV6Impl类中,同时需要将类上的@Service注解中指定bean的名称为orderServiceV6。

@Slf4j
@Service("orderServiceV6")
public class OrderServiceV6Impl implements OrderService {
    //省略所有代码
}

在提交订单的业务方法中,修改前的代码片段如下所示。

User user = userService.getUser(orderParams.getUserId());
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId());
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
//#####################省略N行代码##########################
Result<Integer> result = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}

修改后的代码片段如下所示。

User user = userService.getUser(orderParams.getUserId());
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
if (user.getId() == -1){
    throw new RuntimeException("触发了用户微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId());
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
if (product.getId() == -1){
    throw new RuntimeException("触发了商品微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}
//#####################省略N行代码##########################
Result<Integer> result = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() == 1001){
    throw new RuntimeException("触发了商品微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}

可以看到,修改后的提交订单的业务方法主要增加了服务容错的判断逻辑。

(4)在io.binghe.shop.order.controller.OrderController中注入bean名称为orderServiceV6的OrderService对象,如下所示。

@Autowired
@Qualifier(value = "orderServiceV6")
private OrderService orderService;

至此,我们在项目中使用Sentinel实现了服务容错的功能。

测试服务容错

(1)停掉所有的商品微服务(也就是只启动用户微服务和订单微服务),在浏览器中访问http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,结果如下所示。

图片.pngimage.gif


返回的原始数据如下所示。

{"code":500,"codeMsg":"执行失败","data":"触发了商品微服务的容错逻辑: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}

说明停掉所有的商品微服务后,触发了商品微服务的容错逻辑。

(2)停掉所有的用户微服务(也就是只启动商品微服务和订单微服务)在浏览器中访问http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,结果如下所示。

图片.pngimage.gif


返回的原始数据如下所示。

{"code":500,"codeMsg":"执行失败","data":"触发了用户微服务的容错逻辑: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}

(3)停掉所有的用户微服务和商品微服务(也就是只启动订单微服务),在浏览器中访问http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,结果如下所示。

图片.png


返回的原始数据如下所示。

{"code":500,"codeMsg":"执行失败","data":"触发了用户微服务的容错逻辑: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}

说明项目集成Sentinel成功实现了服务的容错功能。


容错扩展

如果想要在订单微服务中获取到容错时的具体信息时,可以按照如下方式实现容错方案。

实现容错时获取异常

(1)在订单微服务shop-order中新建io.binghe.shop.order.fegin.fallback.factory包,在io.binghe.shop.order.fegin.fallback.factory包中新建UserServiceFallBackFactory类,并实现FallbackFactory接口,FallbackFactory接口的泛型指定为UserService,源码如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 用户微服务容错Factory
 */
@Component
public class UserServiceFallBackFactory implements FallbackFactory<UserService> {
    @Override
    public UserService create(Throwable cause) {
        return new UserService() {
            @Override
            public User getUser(Long uid) {
                User user = new User();
                user.setId(-1L);
                return user;
            }
        };
    }
}

(2)在订单微服务的 io.binghe.shop.order.fegin.UserService 接口上的@FeignClient注解上指定fallbackFactory属性,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用用户微服务的接口
 */
//@FeignClient(value = "server-user", fallback = UserServiceFallBack.class)
@FeignClient(value = "server-user", fallbackFactory = UserServiceFallBackFactory.class)
public interface UserService {
    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);
}

(3)在io.binghe.shop.order.fegin.fallback.factory包中新建ProductServiceFallBackFactory类,并实现FallbackFactory接口,FallbackFactory接口的泛型指定为ProductService,源码如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 商品微服务容错Factory
 */
@Component
public class ProductServiceFallBackFactory implements FallbackFactory<ProductService> {
    @Override
    public ProductService create(Throwable cause) {
        return new ProductService() {
            @Override
            public Product getProduct(Long pid) {
                Product product = new Product();
                product.setId(-1L);
                return product;
            }
            @Override
            public Result<Integer> updateCount(Long pid, Integer count) {
                Result<Integer> result = new Result<>();
                result.setCode(1001);
                result.setCodeMsg("触发了容错逻辑");
                return result;
            }
        };
    }
}

(4)在订单微服务的 io.binghe.shop.order.fegin.ProductService 接口上的@FeignClient注解上指定fallbackFactory属性,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 调用商品微服务的接口
 */
//@FeignClient(value = "server-product", fallback = ProductServiceFallBack.class)
@FeignClient(value = "server-product", fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);
    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}


测试服务容错

与“Feign整合Sentinel实现容错-测试服务容错”中的测试方法相同,这里不再赘述。

至此,使用Sentinel实现限流和容错的功能就完成了。

最后,需要注意的是:使用Sentinel实现服务容错时,fallback和fallbackFactory只能使用其中一种方式实现服务容错,二者不能同时使用。

好了,至此,我们使用Sentinel实现了服务的限流与容错功能,限于篇幅,文中并未给出完整的源代码,想要完整源代码的小伙伴可加入【冰河技术】知识星球获取源码。也可以加我微信:hacker_binghe,一起交流技术。

另外,一不小心就写了10章了,小伙伴们你们再不上车就跟不上了!!!

相关文章
|
2月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
3月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
129 1
|
15天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
49 3
|
1月前
|
JSON SpringCloudAlibaba Java
Springcloud Alibaba + jdk17+nacos 项目实践
本文基于 `Springcloud Alibaba + JDK17 + Nacos2.x` 介绍了一个微服务项目的搭建过程,包括项目依赖、配置文件、开发实践中的新特性(如文本块、NPE增强、模式匹配)以及常见的问题和解决方案。通过本文,读者可以了解如何高效地搭建和开发微服务项目,并解决一些常见的开发难题。项目代码已上传至 Gitee,欢迎交流学习。
145 1
Springcloud Alibaba + jdk17+nacos 项目实践
|
1月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
人工智能 前端开发 Java
Spring Cloud Alibaba AI,阿里AI这不得玩一下
🏀闪亮主角: 大家好,我是JavaDog程序狗。今天分享Spring Cloud Alibaba AI,基于Spring AI并提供阿里云通义大模型的Java AI应用。本狗用SpringBoot+uniapp+uview2对接Spring Cloud Alibaba AI,带你打造聊天小AI。 📘故事背景: 🎁获取源码: 关注公众号“JavaDog程序狗”,发送“alibaba-ai”即可获取源码。 🎯主要目标:
98 0
|
3月前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
1400 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14980 29
|
3月前
|
Java 微服务 Spring
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
文章介绍了如何利用Spring Cloud Alibaba快速构建大型电商系统的分布式微服务,包括服务限流降级等主要功能的实现,并通过注解和配置简化了Spring Cloud应用的接入和搭建过程。
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
|
3月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
70 0
下一篇
无影云桌面