• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
打工人丶
博客园    首页    新随笔    联系   管理    订阅  订阅

SpringCloud Alibaba-6-服务容错

1. 微服务架构中高并发带来的问题

在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,

但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用。

如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。




1.1 熔断与降级的区别

熔断:表示断开的意思,当系统出现异常或超过设定的阈值时,熔断器会中断对该服务的调用,避免继续请求对服务造成更大的影响。熔断器会在一段时间后尝试恢复服务,以检测服务是否已经恢复正常。


降级:表示程序再出现问题时,仍然能保证有限功能仍能使用。降级通常是在服务无法正常提供时,提供一个备用的、简化的服务,以保证系统的可用性和稳定性。






2. Sentinel介绍

官网,介绍的很详细:https://github.com/alibaba/Sentinel/wiki/如何使用
Sentinel:是阿里开源的一套用于服务容错的综合性解决组件。
     主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。


2.1 sentinel基本概念

资源:资源是 Sentinel 的关键概念(就是sentinel要保护的东西就是资源)。它可以是 Java 应用程序中的任何内容甚至可以是一段代码。。

规则:规则是围绕资源的实时状态设定的规则(就是以什么规则保护资源),可以包括流量控制规则、熔断降级规则以及系统负载保护规则。所有规则都可以动态实时调整。


2.2 sentinel的功能

  1. 流量控制:请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行限流。

  2. 熔断降级:当调用链路中某个资源出现不稳定,例如,表现为请求超时,请求异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源。

  3. 系统负载保护:当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。






3. Sentinel快速开始

Sentinel 分为两个部分:

  • 核心库(Java 客户端):不依赖控制台,即:可以通过代码调用API的方式控制sentinel。

  • 控制台(Dashboard):是一个图形化的控制Sentinel的控制台(可以完全不依赖代码),直接在上面进行操作,控制sentinel。


3.1 启动Sentinel控制台

控制台下载网址:https://github.com/alibaba/Sentinel/releases 注意,根据你的alibba版本,不同的版本需要下载不同版本的sentinel。

启动控制台命令

    java -Dserver.port=9000 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar  // -jar sentinel-dashboard-1.7.jar  jar后面就是你的jar包名,

    // 其中 -Dserver.port=9000 用于指定 Sentinel 控制台端口为 9000   这个不做固定,你随意写

    // 从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
    // 如果你需要更改用户名和密码:https://sentinelguard.io/zh-cn/docs/dashboard.html#%E9%89%B4%E6%9D%83
        // 注:若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以通过 Spring 配置文件来指定配置,详情请参考 https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

3.2 添加依赖

sentinel是用来在消费方处理的,所以只需要在消费方添加依赖。

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

3.3 添加配置文件

spring:
  cloud:
    sentinel:
      transport:  # 这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。
        port: 8719 # 比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Server 接收,这个 Server 再将规则注册到 Sentinel 中。
        dashboard: localhost:9000 # 用于指定你设置的 Sentinel 控制台访问地址
      eager:
        true  # 开启对sentinel看板的饥饿式加载。sentinel默认是懒加载机制,只有访问过一次的资源才会被监控,通过关闭懒加载,在项目启动时就连接sentinel控制台。(未验证,网上说的)

3.3 查看效果

因为sentinel具有懒加载的特点,你需要先访问你的接口几次,控制台才会有显示。


如果你不可以指定资源名,默认资源名就是访问的路径名。你可以通过 @SentinelResource(value = "资源名") 来指定你的资源名

@SentinelResource 介绍:

    @SentinelResource 注解用于定义资源埋点,但不支持 private 方法。

    默认情况下,Sentinel 对控制资源的保护处理是直接抛出异常,这样对用户不友好,所以我们需要通过可选的异常处理 blockHandler 和 fallback 配置项处理一下异常信息。

    @SentinelResource 注解包含以下属性:
        value:资源名称,必需项

        entryType: entry 类型,可选项(默认为 EntryType.OUT)

        blockHandler/blockHandlerClass: blockHandler 指定函数负责处理 BlockException 异常,可选项。
            // blockHandler 函数默认需要和原方法在同一个类中。通过指定 blockHandlerClass 为对应类的 Class 对象,则可以指定其他类中的函数,但注意对应的函数必需为 static 函数,否则无法解析。
            // blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型必须和原方法一致并且最后加一个类型为 BlockException 的异常参数用于接收对应的异常。

        fallback /fallbackClass: fallback 指定的函数负责处理业务运行的异常,可选项。
            // fallback 函数默认需要和原方法在同一个类中。通过指定fallbackClass 为对应类的 Class 对象,则可以指定指定为其他类的函数,但注意对应的函数必需为 static 函数,否则无法解析。
            // fallback 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型必须和原方法一致并且最后加一个类型为 Throwable 的异常参数用于接收对应的异常。

        defaultFallback:默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑。
            // 默认需要和原方法在同一个类中,通过指定 fallbackClass 为对应类的 Class 对象,则可以指定指定为其他类的函数,但注意对应的函数必需为 static 函数,否则无法解析。
            // defaultFallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了fallback和 defaultFallback,则只有 fallback会生效。

        exceptionsToIgnore:用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。





4. Sentinel控制台功能简介

4.1 实时监控

用来显示同一个服务下的所有机器的簇点信息,并且是以秒级展示的。
实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。
注意:请确保 Sentinel 控制台所在的机器时间与自己应用的机器时间保持一致,否则会导致拉不到实时的监控数据

4.2 簇点链路

实时的去拉取指定客户端资源的运行情况。它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种以列表展示资源的运行情况。
簇点监控是内存态的信息,它仅展示启动后调用过的资源。




4.3 规则管理

1. 流控规则:监控流量的QPS(每秒访问频率)或并发线程数。当达到指定的阈值时,就对流量进行控制,避免被瞬时的流量高峰击垮。

  1. 点击簇点链路,可以看到访问过的接口地址。
  2. 点击对应的流控按钮,就进入流控规则配置页面。

   // 资源名:资源唯一名称,默认就是请求路径。

   // 针对来源:对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制

   // 阈值类型/单机阈值;
        // QPS: 当调用该接口的QPS达到阈值的时候,进行限流
        // 线程数:当调用该接口的线程数达到阈值的时候,进行限流

   // 流控模式:
        // 直接:接口达到限流条件时,该接口开启限流
        // 关联:当关联的资源接口达到限流条件(此时你页面填的什么QPS,这时是用于设置关联接口的,那个接口满足这个条件时)时,开启对指定接口的限流               关联:选择关联的话,会多出一行,让你填 关联 资源(写上你的访问路径)
        // 链路:当从某个接口过来的资源达到限流条件时,该接口开启限流         链路:选择关联的话,会多出一行,让你填 入口 资源(写上你的访问路径)
                // 解释一下,比如控制器有2个方法 A方法与B方法,需要调用某个资源(C方法),对该资源进行链路流控,设置入口资源为A方法,阈值类型选QPS,单机阈值填3,则当通过A方法访问C方法时,每秒访问量大于3的时候就进行流控,而通过B方法调用C方法不会出现流控。

   // 流控效果:
        // 快速失败: 直接失败,抛出异常(FlowException),不做任何额外的处理,是最简单的效果。
        // Warm Up:它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。     点该选项,会多出一行,让你填 预热时长 单位S
        // 排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求时间超过超时间时间还未处理,则会被丢弃。               点该选项,会多出一行,让你填 超时时间 单位ms


关于Warm Up

    关于Warm Up:该方式只针对 QPS 流控,对并发线程数流控不支持
        该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。      
        
        比如秒杀系统在开启瞬间,会有很多流量上来,很可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。        

        预热底层是根据令牌桶算法实现的,源码对应得类在 com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController 中,算法中有一个冷却因子coldFactor,默认值是3,即请求 QPS 从 threshold(阈值) / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

        比如通过 sentinel-dashboard 设定 testWarmUP 资源的 QPS 阈值为20,流控效果为 warm up,预热时长为10秒,如下图所示,testWarmUP 资源刚开始限流的阈值为 20/3=7,但经过10秒的预热后,慢慢将阈值升至20。



关于排队等待

    关于排队等待:该方式只针对QPS流控,并发线程数流控不支持。
        该方式主要用于处理间隔性突发的流量。假设某时刻来了大流量的请求,如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。
        但其实接下来几秒可能系统处于空闲状态,若直接把多余的请求丢弃则没有充分利用系统的处理能力,所以我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

        排队等待的方式会以匀速排队方式严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,其余的排队等待,它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。

        Sentinel会以固定的间隔时间让请求通过, 访问资源,当请求到来时,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;
        否则,计算当前请求的预期通过时间,如果该请求的预期通过时间大于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;反之,则马上抛出阻塞异常。

         sentinel-dashboard 对 service 资源设置限流阈值为10,流控效果为排队等候,每秒10次请求时,再有请求就排队等候,等待超时时间为 10000ms,超时过后,请求将被踢出排队队列,返回限流异常。



2. 降级规则:设置当满足什么条件的时候,对服务进行降级。
降级:当调用链路中某个资源出现不稳定,例如,表现为请求超时,请求异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源。

RT:平均响应时间,当资源的平均响应时间超过阈值(单位ms)后,资源进入准降级状态。
异常比例数:当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态。
异常数:当资源在 1 分钟的异常数目超过阈值之后会进行服务降级。



3. 热点规则:一种更细粒度的流控规则, 它允许将规则具体到参数级别上。
热点就是经常访问的数据,很多时候我们肯定希望统计某个访问频次数据并对其进行限流,比如:
  商品 ID 为参数,针对一段时间内最常购买的商品 ID 并进行限制。
  用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

如果你想对参数的值设置例外项,比如值为多少,就不对其限流



4. 授权规则:根据调用来源来判断该次请求是否允许放行。



资源名:指被访问的资源,只有符合要求的请求来源才能访问该资源。

流控应用:指请求来源标识。多个用 “,” 分隔

流控应用怎么声明?:Sentinel提供了 RequestOriginParser 接口, 我们只需要实现该接口即可。
实现的本质:利用request判断请求来源。

    // 举例:若来源请求参数上有 serviceName 这个参数,且参数值是pc时,则不能进行访问。
    @Component
    public class RequestOriginParserDefinition implements RequestOriginParser{
      @Override
      public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName");
        return serviceName;
     }
    }

访问 http://localhost:8091/order/message1?serviceName=pc观察结果。



5. 系统规则:是运维级别的人员去处理的,不介绍。






5. Sentinel自定义异常返回

当出现异常时,sentinel都会返回这个页面,让我们不知道,它是犯了什么规则出现的异常。所以我们需要自定义异常返回。


//sentinel提供了UrlBlockHandler接口,用于我们自定义Sentinel异常。
@Component
public class ExceptionHandlerPage implements UrlBlockHandler {
    // BlockException 异常接口,包含Sentinel的五个异常
          // FlowException 限流异常
          // DegradeException 降级异常
          // ParamFlowException 参数限流异常
          // AuthorityException 授权异常
          // SystemBlockException 系统负载异常
    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) {
      if (e instanceof FlowException) {
        // 限流异常
     } else if (e instanceof DegradeException) {
        // 降级异常
     } else {
       // 自定义处理
     }
   }
}





6. @SentinelResource注解

@SentinelResource 用于定义资源埋点,设置资源名称。且还能提供异常处理配置。

// 需求:要对 message 这个资源做处理。
@Service
@Slf4j
public class OrderServiceImpl3 {

      int i = 0;

      @SentinelResource(
          value = "message", // 0. 定义资源名为message
          blockHandlerClass = OrderServiceImpl3BlockHandlerClass.class, // 1. 用于指定异常处理所在的类。
          blockHandler = "blockHandler",  // 2. 用于指定是异常处理类中的哪个方法。
          fallbackClass = OrderServiceImpl3FallbackClass.class, 
          fallback = "fallback"
     )
      public String message() {
          i++;
          if (i % 3 == 0) {
            throw new RuntimeException();
         }
          return "message4";
     }
}


@Slf4j
public class OrderServiceImpl3BlockHandlerClass {

      //注意这里必须使用static修饰方法
      public static String blockHandler(BlockException ex) {
        log.error("{}", ex);
        return "接口被限流或者降级了...";
     }

}


@Slf4j
public class OrderServiceImpl3FallbackClass {

      //注意这里必须使用static修饰方法
        public static String fallback(Throwable throwable) {
          log.error("{}", throwable);
          return "接口发生异常了...";
       }
}





7. Feign整合Sentinel

以Springcloud Alibaba-5-并发访问为例

7.1 添加依赖

<!--sentinel客户端-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

7.2 配置文件开启Feign对sentinel的支持

server:
  port: 8091
  tomcat:
    threads:
      max: 10 #tomcat的最大并发值修改为10

spring:
  application:
    name: service-order

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:9000

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    
feign: # 开启Feign对sentinel的支持
  sentinel:
    enabled: true

mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

7.3 为订单服务增加降级效果

需求:为订单服务增加降级效果

@FeignClient(name = "service-product", fallback = ProductServiceApiFallBack.class)
public interface ProductServiceApi {
	
	// 根据商品名称获取商品信息
	@GetMapping("/product/getProductInfo/{pname}")
	public Product getProductInfo(@PathVariable String pname);
	
	// 保存或更新商品信息
	@PutMapping("/product/saveOrUpdate}")
	public void saveOrUpdate(@RequestBody Product product);
}





//容错类要求必须实现被容错的接口,并为每个方法实现容错方案
@Component
public class ProductServiceApiFallBack implements ProductServiceApi {
	
	// 根据商品名称获取商品信息
	@Override
	public Product getProductInfo(String pname) {
		Product product = new Product();
		product.setPname("兜底商品数据");
		return product;
	}
	
	// 保存或更新商品信息
	@Override
	public void saveOrUpdate(Product product) {
		System.out.println("兜底数据");
	}
}



现象:把产品服务关了,订单服务在调用远程服务不成功时,就会返回一个 商品名为 兜底商品数据的 商品给订单服务
posted @ 2021-10-11 17:40  &emsp;不将就鸭  阅读(144)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3