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》:

相关推荐
fanly113 天前
Surging AI Agent 完整产品介绍
微服务·microservice
吃饱了得干活5 天前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
蝎子莱莱爱打怪9 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking10 天前
Java微服务练习方式
java·后端·微服务
米丘13 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质16 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧一居士16 天前
Feign的GET请求如何传递对象参数?
java·spring cloud
我登哥MVP16 天前
SpringCloud Alibaba 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
慧一居士16 天前
SpringCloud 微服务Feigin 用的完整调用端和被调用的示例
java·spring cloud
霸道流氓气质16 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化