SpringCloud源码探析(九)- Sentinel概念及使用

1.概述

在微服务的依赖调用中,若被调用方出现故障,出于自我保护的目的,调用方会主动停止调用,并根据业务需要进行对应处理,这种方式叫做熔断,是微服务的一种保护方式。为了保证服务的高可用性,springcloud中有专门的流量管控组件,负责熔断、限流和降级。springcloud中较为知名的熔断器有Hystrix和Sentinel,本文将分析Sentinel的优势及其使用。

2.Sentinel使用

在微服务调用中,可能会因为调用链中的一个服务发生故障,导致整个链路都无法访问的情况,这种现象叫雪崩。解决雪崩的常用方法有:

  • 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等等;
  • 舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离;
  • 熔断降级:由断路器统计业务执行的异常比例,如果超过阈值则会熔断该业务,拦截访问该业务的一起请求;
  • 流量控制:限制业务访问的QPS,避免因流量的突增而故障。

2.1 Hystrix和Sentinel对比

对比项 Sentinel Hystrix
隔离策略 信号量隔离 线程池/信号量隔离
熔断降级策略 基于慢调用比例或异常比例 基于失败比例
实时指标实现 滑动窗口 滑动窗口(基于RxJAVA)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
基于注解的支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持
流量整形 支持慢启动,匀速排队模式 不支持
系统自适应保护 支持 不支持
控制台 开箱即用、可配置规则、查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Cloud Netflix

Sentinel相对于Hystrix有更完善的限流机制,能够实现动态限流。由于Netflix已经宣布对Hystrix停止更新,意味着Hystrix不会再有新的功能迭代,而Sentinel是由阿里巴巴开源的一款微服务流量控制组件,具有丰富的应用场景(承接了阿里巴巴近10年的双十一大促流量的核心场景)、完备的实时监控、广泛的开源生态、完善的SPI扩展点。

2.2 Sentinel使用

2.2.1 Sentinel安装及使用

本文主要演示单机版本下sentinel的安装及使用,主要步骤如下:
(1)下载安装包:从官网下载sentinel-dashboard安装包;
(2)启动sentinel:通过java -jar命令启动sentinel,可以通过在启动命令中增加参数来调整,比如 -Dserver.port=8888,因为sentinel是基于springboot开发的JAVA项目;默认情况下,sentinel-dashboard以8080端口启动;
(3)验证是否启动成功:通过访问http://127.0.0.1:8080,若出现以下页面,则启动成功。
SpringCloud源码探析(九)- Sentinel概念及使用_第1张图片
注意事项:1.6版本以上才有这个登陆界面,默认用户名和密码均为sentinel,如果要修改用户名和密码,可以通过修改启动时配置:

-Dsentinel.dashboard.auth.username = sentinel://指定登录名称为sentinel;
-Dsentinel.dashboard.auth.password=123456://指定控制台的登录密码为:123456,默认值为sentinel
-Dserver.servlet.session.timeout = 7200://指定Spring Boot 服务端 session的过期时间,如7200表示7200秒,30m表示30分钟,默认为30分钟

2.2.2 SpringBoot整合Sentinel

1.引入pom文件

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

2.添加配置文件
指定sentinel的安装地址和IP,

spring.cloud.sentinel.transport.dashboard=http://localhost:8080

3.添加restful API

    @GetMapping("/test")
    public ResultBean test() {
        return ResultBean.success("请求sentinel成功");
    }

启动服务,请求接口后刷新Sentinel客户端,会出现如下界面:
SpringCloud源码探析(九)- Sentinel概念及使用_第2张图片

2.2.3 Sentinel流控模式

Sentinel的流控模式,主要有以下三种:

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认模式;这个比较容易理解,服务A触发阈值,就对服务A进行限流;
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流;例如有服务A和服务B两个资源,服务A触发阈值,却对服务B进行资源限流;
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流;例如有服务A、B、C,服务A、B均要访问服务C,只统计从服务A到C的请求,超过阈值,对A限流。

SpringCloud源码探析(九)- Sentinel概念及使用_第3张图片
流控效果是指请求达到流控阈值时应采取的措施,包括三种:

  • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException一次,是默认的处理方式;
  • warm up(预热模式):对超出阈值的请求同样是拒绝并抛出异常,但这种模式阈值会动态变化;预热模式是应对冷启动的一种方案,请求阈值初始值是threshold/coldFactor,持续指定时长后,逐渐提高到threshold,而coldFactor的默认值是3;例如:设置QPS的threshold为10,预热时间为10s,那么初始阈值就是10/3,也就是3,然后在5s后逐渐增长到10;
  • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长。排队等待让所有请求进入到一个队列中,然后按照阈值允许的时间间隔依次执行,后来的请求必须等前面的执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。例如:QPS=5,意味着每200ms处理一个队列中的请求;timeout=2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常。

2.2.3 OpenFeign整合Sentinel

这里演示订单服务order-service调用用户服务user-service查询用户信息,在order-service服务中添加Feign接口,并添加失败调用逻辑类FallbackFactory,里面是调用order-service接口异常所返回的逻辑。
OpenFeign整合Sentinel的核心步骤如下:
1.在配置文件中添加配置

 #配置sentinel注册地址
 spring.cloud.sentinel.transport.dashboard=http://127.0.0.1:8080
 #开启Feign的sentinel功能
 feign.sentinel.enabled=true

2.实现FallbackFactory

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserFeign> {
    public UserFeign create(Throwable throwable) {
        return new UserFeign() {
            public String getUserById() {
                return "查询失败,用户不存在";
            }
        };
    }
}

3.将UserClientFallbackFactory注册为Bean

@Configuration
public class DefaultFeignConfiguration {

    @Bean
    public UserClientFallbackFactory userClientFallbackFactory() {
        return new UserClientFallbackFactory();
    }
}

4.在feign接口中配置UserClientFallbackFactory

@Component
@FeignClient(name = "user-service",fallbackFactory = UserClientFallbackFactory.class)
public interface UserFeign {

    @GetMapping("/user/findOrderByUserId")
    String getUserById();

}

通过请求订单服务中的/order/feign接口,内部会调用user-service中/user/findOrderByUserId接口,完整链路如下图所示,此时就可以对user-service中/user/findOrderByUserId接口进行限流规则配置。
SpringCloud源码探析(九)- Sentinel概念及使用_第4张图片

2.2.4 利用Sentinel实现线程隔离与降级熔断

1.线程隔离
限流可以尽量避免因高并发而引起的服务故障,但故障还是会因为其它原因而故障。需要将这些故障控制在一定范围,避免雪崩,就要靠现场隔离(舱壁模式)和熔断降级手段了。不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护。线程池隔离主要有两种方式:

1.线程池隔离:服务A请求服务B,在服务A开辟一个线程池(含指定数量线程),这个线程池专门负责请求服务B,该线程池隔离了服务A与其它服务的交互(只针对B服务),即使服务B出现故障,也能保证服务A不被影响,保证了服务A的独立性和高可用性;
2.信号量隔离:信号量的资源隔离,仅限制对某个资源调用的并发数,而不是显示地去创建线程池,效果更好。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。

线程池隔离与信号量隔离对比:

对比项 线程池隔离 信号量隔离
优点 支持主动超时,支持异步调用 轻量级,无额外开销
缺点 增加线程额外开销 不支持主动超时,不支持异步调用
适用场景 适用于高频调用场景、高扇出(横向调用服务多) 低扇出(横向调用服务少)

2.熔断降级
熔断降级是解决雪崩问题的重要手段,其核心思想是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务(拦截一切进入该服务的请求);当服务恢复时,断路器会放行访问该服务的请求。

断路器熔断策略主要有三种:慢调用、异常比例、异常数。
慢调用:业务的响应时长(RT)大于指定时长的请求就认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
SpringCloud源码探析(九)- Sentinel概念及使用_第5张图片

上述配置解读:RT超过500ms的调用就是慢调用,统计最近10s内的请求,如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5s,然后进入half-open状态,放行一次请求做测试,通过才继续放行请求进入服务。
SpringCloud源码探析(九)- Sentinel概念及使用_第6张图片

异常比例:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。上述配置在1s内最小请求数为5,若异常比例超过0.2,则需要熔断,熔断时长为5s。
SpringCloud源码探析(九)- Sentinel概念及使用_第7张图片
异常数量:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常超过指定异常数,则触发熔断。
上述配置解读:统计最近1s内的请求,如果请求量超过10次,并且异常数超过2次,则触发熔断,熔断时长为5s,然后进入half-open状态,放行一次请求做测试,通过才继续放行请求进入服务。

2.2.5 授权规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

白名单:来源(origin)在白名单内的调用者允许访问;
黑名单:来源(origin)在黑名单内的调用者不允许访问。

规则配置界面如下:
SpringCloud源码探析(九)- Sentinel概念及使用_第8张图片
这里以白名单功能为例演示(从网关gateway服务访问的请求允许放行,从浏览器或其它服务访问则被拒绝),具体操作如下:
1.新增授权规则配置
SpringCloud源码探析(九)- Sentinel概念及使用_第9张图片
资源名指的是所访问资源的请求路径,流控应用指请求头中所携带的约束字段,当从网关gateway服务请求到order-service中的/order/feign资源时,携带值为gateway的请求头,order-service获取访问资源的请求头,解析是否存在关键值gateway,存在则允许访问。
2.网关服务添加请求头

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - AddRequestHeader=origin,gateway
        

在配置文件中添加filers配置,添加关键字为origin,值为gateway的配置。

3.在order-service中解析请求头

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Slf4j
@Component
public class RequestParse implements RequestOriginParser {

    public String parseOrigin(HttpServletRequest httpServletRequest) {
        String origin = httpServletRequest.getHeader("origin");
        if (StringUtils.isEmpty(origin) || !origin.equals("gateway")) {
            origin = "blank";
        }
        log.info("origin:{}", origin);
        return origin;
    }
}

在order-service服务中定义类RequestParse实现sentinel中的RequestOriginParser接口(通过该接口获取请求来源),解析请求头中是否携带key为origin,值为gateway的字段,存在则返回,不存在则赋值为blank返回,并注册为Bean。

4.自定义异常返回类(可选)

在order-service中自定义异常类实现sentinel的BlockException,可返回自定义异常。BlockException包含很多个子类,分别对应不同的场景:

异常 说明
FlowException 限流异常
ParamFlowException 热点参数限流的异常
DegradeException 降级异常
AuthorityException 授权规则异常
SystemBlockException 系统规则异常

自定义异常类如下:

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: Marinc
 * @CreateTime: 2023-08-09  22:53
 * @Description: TODO
 * @Version: 1.0
 */
@Component
public class ExceptionHandler implements BlockExceptionHandler {

    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        String msg = "默认未知异常";
        Integer code = 1001;
        if (e instanceof FlowException) {
            msg = "限流异常";
            code = 1002;
        } else if (e instanceof ParamFlowException) {
            msg = "热点限流异常";
            code = 1003;
        } else if (e instanceof DegradeException) {
            msg = "降级异常";
            code = 1004;
        } else if (e instanceof AuthorityException) {
            msg = "授权异常";
            code = 1005;
        } else {
            msg = "系统异常";
            code = 1009;
        }
        httpServletResponse.setStatus(code);
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().print("{"+"message:" + msg + ",code:" + code+"}");
    }
}

验证结果,从经过网关gateway访问order-service服务的请求结果为:
SpringCloud源码探析(九)- Sentinel概念及使用_第10张图片
直接从访问order-service服务的请求结果为:
SpringCloud源码探析(九)- Sentinel概念及使用_第11张图片

2.2.5 Sentinel规则管理模式

Sentinel的控制台规则管理有三种模式:

1.原始模式:将规则保存在内存,重启服务会丢失;
2.pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存至本地文件或数据库中,以后会定时去本地文件或数据库中查询,更新本地规则;
3.push模式:控制台将配置规则推送到远程配置中心,例如nacos,sentinel客户端监听nacos,获取配置变更的推送消息,完成本地配置更新。

这三种管理模式可根据场景进行选择使用,pull模式和push模式在下文中进行演示。

3.小结

1.sentinel相对于Hystrix,支持更多流控规则的制定,适配更多外部框架,功能更强大;
2.sentinel可以更加灵活地配置熔断时长,拥有更多灵活的配置策略;
3.sentinel可以将配置规则保存至内存、文件和注册中心,可以根据场景进行选择,更加灵活。

4.参考文献

1.https://www.bilibili.com/video/BV1LQ4y127n4
2.https://juejin.cn/post/6983824085306310692
3.https://sentinelguard.io/zh-cn/

5.附录

1.https://gitee.com/Marinc/nacos.git

你可能感兴趣的:(spring,cloud,sentinel)