springcloud篇5-微服务保护(Sentinel)

一、Sentinel

1.1 雪崩问题

当一个服务依赖多个其他服务时(如上图),当服务D出故障时,无法响应服务A的请求,服务阻塞,不会释放tomcat连接。

当服务A中有多个依赖于服务D的请求传来时,会占用多个tomcat连接,最终导致tomcat资源耗尽:

当在一个微服务链路中,一个服务出故障可能导致整个链路的所有服务都不可用(多米诺骨牌),这就是雪崩。

1.1.1 解决雪崩问题的常见方式

(1)超时处理

设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待(不是根本解决办法,当超时时间内传来大量请求时还是会导致雪崩)。

(2)舱壁模式

给每个业务设置线程池,限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

例如,上图中服务A的业务1和2都使用线程池,设置线程数量为10,当服务C出故障时,服务A最多向服务C发送10个请求,不会导致tomcat资源耗尽。

注意:该方法解决了雪崩问题,但是降低了资源利用率。

(3)熔断降级

由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

(4)流量控制(sentinel)

限制业务访问的QPS,避免服务因流量的突增而故障。该方法可以按照服务所能承受的频率释放请求。

1.2 服务保护技术对比

隔离策略:线程池隔离类似于舱壁模式。信号量隔离是不会为业务创建线程池,而是统计当前业务使用的线程数,限制数量,当达到阈值后再阻止。

1.3 Sentinel

官网地址:https://sentinelguard.io/zh-cn/index.html

1.3.1 安装sentinel控制台


powershell 复制代码
java -jar sentinel-dashboard-1.8.1.jar


sentinel内部提供了很多配置项,可以在运行jar修改参数信息:

1.3.2 导入项目cloud-demo

(springcloud篇1、篇2的项目)

1.3.3 微服务整合sentinel

(一)order-service服务中引入sentinel依赖

xml 复制代码
<!-- 引入sentinel依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

(二)order-service配置文件中配置sentinel控制台地址

yaml 复制代码
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel控制台地址

(三)访问微服务的任意端点,触发sentinel监控

确保user-service、order-service、sentinel与nacos服务启动成功。

二、sentinel限流规则

之前提到的雪崩问题提出了四种解决方案,sentinel 可以实现其中的三种(流量控制、舱壁模式和熔断降级)

2.1 簇点链路

簇点链路:就是项目中的调用链路(如从controller到service到mapper),链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控SpringMVC的每一个端点,因此SpringMVC的每一个端点就是调用链路中的一个资源。

2.1.1 sentinel限流规则设置

对于1.3.3中微服务整合sentinel的测试中,一次http://localhost:8083/order/103的请求,sentinel的簇点链路监控如下:

流控、熔断等都是针对簇点链路中的资源来设置的,从上图中可以看到,我们可以通过点击对应资源后面的按钮来设置规则。

比如,点击"流控"按钮:


资源名 默认。
针对来源 :设置从哪里发来的请求需要被限制,default表示一切进来的请求都要被限制。
阈值类型 :一般选QPS(并发量,即每秒钟请求的数量)。
单机阈值:即QPS的上限(为1即表示每秒钟最多处理一个请求,超出的请求会被拦截并报错)。

2.1.2 案例


(一)sentinel设置流控规则


(二)使用jmeter进行测试

打开学习资料中已经写好的测试计划:


上图中的第一个测试计划为:2秒钟发送20次请求(QPS为10)。

右键启动:



补充1:sentinel限流规则高级模式


1.流控模式
(1)直接(默认)

统计当前资源的请求,触发阈值时对当前资源直接限流。

(2)关联

统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流。

关联模式使用场景:

配置方法:


java 复制代码
    @GetMapping("/query")
    public String queryOrder() {

        return "查询订单成功";
    }
    @GetMapping("/update")
    public String updateOrder() {

        return "更新订单成功";
    }

重启服务,在浏览器中分别发送请求:






在上述请求运行时去访问query:

可以看到,query请求被sentinel限制了。

(3)链路

统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。

案例如下:

java 复制代码
    public void queryGoods(){

        System.err.println("查询商品");
    }
java 复制代码
    @GetMapping("/query")
    public String queryOrder() {
        // 查询商品
        orderService.queryGoods();
        // 查询订单
        System.err.println("查询订单");
        return "查询订单成功";
    }

    @GetMapping("/save")
    public String saveOrder() {
        // 查询商品
        orderService.queryGoods();
        // 查询订单
        System.err.println("新增订单");
        return "新增订单成功";
    }

注意:

(1)sentinel只默认标记controller中的方法为资源,如果要标记其他方法(比如service中的方法),需要使用@SentinelResource注解。

(2)sentinel默认会将controller方法做context整合,导致链路模式的流控失效,需需要修改sentinel配置:

yaml 复制代码
web-context-unify: false # 关闭context整合




注意:上图中的goods时同一个资源,任意选择一个进行配置即可。


启动上面的测试任务:


2.流控效果

流控效果是指请求达到流控阈值时应该采取的措施,包括3种。

(1)快速失败(默认)

指达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。

(2)warm up

预热模式,对超出阈值的请求同样是拒绝并抛出异常,但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。

是应对服务冷启动(服务刚启动就达到阈值可能会使服务响应不了)的一种方案。请求阈值初始值是threshold/coldFactor(coldFactor默认为3),持续指定时长后,逐渐提高到threshold值。

一个案例:






(3)排队等待

让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长。设置一个超时时间,若是在队列中等待的时间超时,则会抛出异常。

一个案例:



可以看到接收的QPS是稳定的。

补充2:热点参数限流

之前的限流都是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

一个例子:

注意:热点参数限流对springmvc默认资源无效,需要加@SentinelResource注解。




结果如下:

注意:单机阈值为2。

三、 隔离和降级

3.1 feign与sentinel整合

隔离和降级即舱壁模式(线程隔离)。

从前面可知,不论是线程隔离还是熔断降级,都是对客户端(调用方)的保护。springcloud中远程调用使用feign,所以线程隔离和熔断降级使用feign整合sentinel 的模式。

步骤:

(一)在调用者(order-service,被调用者是user-service)中开启feign对sentinel的支持

yaml 复制代码
  sentinel:
    enabled: true # 开启feign对sentinel的支持

(二)给FeignClient(feign-api服务)编写失败后的降级逻辑FallBackFactory

java 复制代码
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                log.error("查询用户异常", throwable);
                return new User();
            }
        };
    }
}

回忆一下之前写的UserClient:

(三)注册写好的FallbackFactory

java 复制代码
    @Bean
    public UserClientFallbackFactory userClientFallbackFactory(){

        return new UserClientFallbackFactory();
    }

(四)在Userlient注解上声明FallbackFactory



3.2 线程隔离

线程隔离有两种实现方式:

(1)线程池隔离

(2)信号量隔离(sentinel默认采用)

不会创建新的线程,使用原来处理请求的线程。一个请求信号量的计数器减1,业务处理完信号量加1,计数器为0时再有请求会被拒绝。

一个案例:







3.3 熔断降级

由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

熔断器熔断(closed状态到Open状态即2)策略有三种:慢调用、异常比例、异常数

3.3.1慢调用

一个例子:

java 复制代码
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          @RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
       if(id==1){
           //休眠60毫秒,触发熔断
           Thread.sleep(60);
       }
        return userService.queryById(id);
    }




执行完jmeter测试任务后,访问102查不到用户,即触发了熔断:

3.3.2异常比例和异常数


java 复制代码
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          @RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
       if(id==1){
           //休眠60毫秒,触发熔断
           Thread.sleep(60);
       }else if(id==2){
           throw new RuntimeException("故意出错,触发熔断");
       }
        return userService.queryById(id);
    }

降级策略按例子配置,这里不在举例了。

四、授权规则

4.1 sentinel授权规则配置

针对绕过网关直接访问微服务的情况(当然这是异常情况,只是多加一层保护)。


(一)新增继承RequestOriginParser的类

java 复制代码
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request){
        // 1.获取请求头
        String origin = request.getHeader("Origin");
        //2.非空判断
        if(StringUtils.isEmpty(origin)){
            origin = "blank";
        }
            return origin;
    }
}

(二)修改gateway服务,给所有请求增加请求头origin

yaml 复制代码
      default-filters:
        - AddRequestHeader=origin,gateway

重启order-service服务和gateway服务。
(三)在sentinel控制台添加授权规则




4.2 自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口。

可以通过判断BlockException的异常类型从而进行不同的处理,异常类型包括:

(一)添加继承BlockExceptionHandler的类

java 复制代码
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

重启服务进行测试:

4.3 规则持久化

服务重启时,配置与该服务相关的sentinel规则就会消失,这是因为sentine规则l默认保存在内存,生产环境需持久化。

4.3.1 pull模式

4.3.2 Push模式

push模式实现较为复杂,依赖于nacos,并且需要修改sentinel控制台源码。

具体步骤见资料《sentinel规则持久化.md》:

相关推荐
天上掉下来个程小白4 小时前
微服务-25.网关登录校验-网关传递用户到微服务
java·数据库·微服务
java亮小白19979 小时前
Spring Cloud 快速通关之Sentinel
java·spring cloud·sentinel
叫我阿柒啊11 小时前
Java全栈开发面试实战:从基础到微服务的深度解析
java·jvm·微服务·vue3·springboot·全栈开发·restfulapi
guojl12 小时前
Gateway源码分析
后端·微服务
007php0071 天前
Jenkins+docker 微服务实现自动化部署安装和部署过程
运维·数据库·git·docker·微服务·自动化·jenkins
叫我阿柒啊1 天前
从Java全栈到前端框架的深度探索
java·微服务·typescript·vue3·springboot·前端开发·全栈开发
mask哥1 天前
详解flink SQL基础(四)
java·大数据·数据库·sql·微服务·flink
阿登林1 天前
C#微服务架构:实现指南与问题解决方案
微服务·架构·c#