微服务-SpringCloud-05-项目整合Sentinel实现限流

第十章 服务容错:项目整合Sentinel实现限流与容错

章节概述

今天,我们就使用Sentinel实现接口的限流,并使用Feign整合Sentinel实现服务容错的功能。
当然,能够实现服务容错功能的组件不仅仅有Sentinel,比如:Hystrix和Resilience4J也能够实现服务容错的目的,关于Hystrix和Resilience4J不是本专栏的重点。

关于Sentinel

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

Sentinel的特征

  • 丰富的应用场景

承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

  • 完备的实时监控

提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

  • 广发的开源生态

: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。

  • 完善的SPI扩展机制

Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

安装Sentinel控制台

在微服务项目中整合Sentinel非常简单,只需要在项目的pom.xml文件引入Sentinel的依赖即可。
不过使用Sentinel时,需要安装Sentinel的控制台。

  1. 在本地启动Sentinel控制台。
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.3.jar

启动之后,在浏览器输入http://localhost:8888/访问Sentinel控制台。
输入默认的用户名sentinel和密码sentinel。登录Sentinel控制台。

项目集成Sentinel

  1. 订单微服务的shop-order的pom.xml文件中添加Sentinel的相关依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 在订单微服务的shop-order的application.properties文中加入Sentinel相关的配置
# Sentinel相关配置
# 指定和Sentinel控制台交互的端口,任意指定一个未使用的端口即可
spring.cloud.sentinel.transport.port=9999
# Sentinel控制台服务地址
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8888
  1. 为了直观的感受到Sentinel的功能,在订单微服务的io.binghe.shop.order.controller.OrderController类中新增一个测试接口,如下所示
@GetMapping(value = "/test_sentinel")
public String testSentinel(){
    log.info("测试Sentinel");
    return "sentinel";
}
  1. 启动订单微服务。http://localhost:8080/order/test_sentinel访问在订单微服务中新增的接口,如下所示。

Sentinel.jpg

  1. 刷新Sentinel控制台页面,会发现已经显示了订单微服务的菜单,如下所示。

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

集成Sentinel限流功能

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

  1. 在Sentinel控制台找到server-order下的簇点链路菜单。

Sentinel_03.jpg

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

Sentinel_04.jpg
点击流控会显示新增流控规则的弹出框。
Sentinel_05.jpg
这里,单机阈值填上1,点击新增。流控规则下会显示如下:
Sentinel_06.jpg
上述配置表示http://localhost:8080/order/test_sentinel接口的QPS为1,每秒访问1次。如果每秒访问的次数超过1次,则会被Sentinel限流。

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

Sentinel_07.jpg

对提交订单的接口限流

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

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

注意:会发现Sentinel中没有订单微服务的数据,因为Sentinel是懒加载机制,所以需要访问一下接口,再去访问Sentinel 就有数据了。
访问http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1接口。
Sentinel_08.jpg

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

Sentinel_09.jpg

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

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

Fegin整合Sentinel实现容错

如果订单微服务的下游服务,比如用户微服务和商品微服务出现故障,无法访问时,那订单微服务该如何实现服务容错呢?使用Sentinel就可以轻松实现。

添加依赖并开启支持
  1. 在订单微服务的shop-order的pom.xml文件中添加Sentinel的相关依赖
<!--  Sentinel   -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 在订单微服务的application.properties文件中添加如下配置开启Feign对Sentinel的支持。
为远程调用实现容错
  1. 在订单微服务shop-order中,为远程调用接口实现容错方法。这里在订单微服务下新建io.binghe.shop.order.fegin.fallback包,并在包下创建UserServiceFallBack类实现UserService接口,用于调用用户微服务的容错类。

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

@Component
public class UserServiceFallBack implements UserService {
    @Override
    public User getUserById(Long userId) {
        User user = new User();
        user.setId(-1L);
        return user;
    }
}
  1. 在订单微服务的io.binghe.shop.order.Feign.UserService接口上的@FeignClient注解上指定容错类,如下所示。
@FeignClient(value = "server-user", fallback = UserServiceFallBack.class)
public interface UserService {
    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);
}
  1. 订单微服务中的 io.binghe.shop.order.Feign.fallback包下创建ProductServiceFallBack类实现ProductService接口,用于调用商品微服务的容错类
@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
 * @IntelliJ IDEA
 * @Date 2023/4/6 14:23
 * @description 调用商品微服务的接口
 */
@FeignClient(value = "server-product",fallback = ProductServiceFallBack.class)
public interface ProductService {
    /**
     * 获取商品信息
     * @param pid
     * @return
     */
    @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);
}
  1. 这里修改的方法位于io.binghe.shop.order.service.impl.OrderServiceV4Impl类中,同时需要将类上的@Service注解中指定bean的名称为orderServiceV4。
@Service("orderServiceV4")
public class OrderServiceV4Impl 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());
logger.info("返回的结果为 {}", result);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
logger.info("库存扣减成功");

修改后的代码片段如下

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());
logger.info("返回的结果为 {}", result);
if (result.getCode() == 1001){
    throw new RuntimeException("触发了商品微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}

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

  1. 在io.binghe.shop.order.controller.OrderController中注入bean名称为orderServiceV4的OrderService对象。
@Autowired
@Qualifier(value = "orderServiceV4")
private OrderService orderService;

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

测试服务容错

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

触发了用户微服务的容错逻辑: {"count":1,"empty":false,"productId":1001,"userId":1001}

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

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

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

容错扩展

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

实现容错时获取异常

在订单微服务新建io.binghe.shop.order.fegin.fallback.factory包,并新建UserServiceFallBackFactory类,。
并实现FallbackFactory接口,FallbackFactory接口的泛型指定为被容错的接口UserService,

@Component
public class UserServiceFallBackFactory implements FallbackFactory<UserService> {
    @Override
    public UserService create(Throwable throwable) {
        return new UserService() {
            @Override
            public User getUser(Long uid) {
                User user = new User();
                user.setId(-1L);
                return user;
            }
        };
    }
}
  1. 在订单微服务的 io.binghe.shop.order.fegin.UserService 接口上的@FeignClient注解上指定fallbackFactory属性,如下所示。
/**
 * @author
 * @IntelliJ IDEA
 * @Date 2023/4/6 14:22
 * @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);
}

  1. FallbackFactory接口的泛型指定为ProductService。
/**
 * @author
 * @IntelliJ IDEA
 * @Date 2023/4/7 15:14
 */
@Component
public class ProductServiceFallBackFactory implements FallbackFactory<ProductService> {

    @Override
    public ProductService create(Throwable throwable) {
        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;
            }
        };
    }
}
  1. 在订单微服务的 io.binghe.shop.order.fegin.ProductService 接口上的@FeignClient注解上指定fallbackFactory属性。
/**
 * @author
 * @IntelliJ IDEA
 * @Date 2023/4/6 14:23
 * @description 调用商品微服务的接口
 */
//@FeignClient(value = "server-product",fallback = ProductServiceFallBack.class)
@FeignClient(value = "server-product",fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
    /**
     * 获取商品信息
     * @param pid
     * @return
     */
    @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);
}


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值