使用前请确保自己的SpringCloud、Boot、Alibaba的版本要一致哦,不然会报错https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

官网:https://github.com/alibaba/Sentinelhttps://sentinelguard.io/zh-cn/

流量控制

隔离、降级

授权规则

服务雪崩

服务A依赖于服务B、服务B依赖于服务C,如果服务B执行比较缓慢、阻塞,Tomcat资源就被占用了,导致所有依赖于服务B的服务都被牵制!

解决方案:

超时处理:超时就返回错误信息,不会无休止等待

舱壁模式:限定每个业务使用的线程数,避免tomcat资源被消耗,实现线程隔离

熔断降级:由断路器统计业务执行异常比例,如果超过限定值,就会拦截,不允许访问

流量控制:限制业务访问QPS,避免业务因流量突增而故障

上面都是服务保护的解决方案,

我们主要是使用Sentinel,Hystrix

去官网下载Sentinel- dashboard.jar 使用java -jar 即可,默认端口是8080 ,默认账号、密码都是sentinel

# 自行修改即可
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar XXXX.jar

访问 127.0.0.1:8080 输入账号密码即可

微服务整合Sentinel

参考URL:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

项目引入Sentinel依赖(如果你准备网关接入,请引用其他网关-Sentinel的依赖)

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

项目配置文件添加配置信息

# 配置控制台地址,必须访问过我们当前服务才能触发sentinel的监控
spring.cloud.sentinel.transport.dashboard=localhost:9090

必须要去访问一下我们的任意一个接口才能触发Sentinel监控

以后我们就可以通过Sentinel实现服务的熔断了。

流控模式

直接:对当前资源进行限流

关联:高级优先级资源出发阈值,对低优先级资源限流

链路:阈值统计时,只统计A资源进入当前资源的请求,对来源A资源进行限流,其他资源访问当前资源就不受到限制

流控效果

快速失败:达到流控模式的规则,直接失败,抛出异常,拒绝请求。

Warm Up:冷启动(设置warm up时要求设置个预热时长),在项目启动的预热时长内,最大qps逐步增加。项目初始时最高qps=流控模式设定最大的qps/3,然后在预热时间内,逐步提高最大qps,直到最大设定的qps。避免了项目刚启动来了大量并发导致服务宕机。如果达到当时的最大qps也是抛出异常,拒绝请求。

排队等待:超过qps,快速失败与warm ip会抛出异常,但排队等待会将请求放入一个队列中,针对阈值允许的时间间隔一次执行。后来的请求必须等前面完成,如果请求预期的时间超过最大时长(设置排队等待时,要求输入一个超时时间),则会被拒绝

模拟限流

Sentinel应用实际开发解决方案

登陆服务需要查询账号是否存在,注册服务也要查询账号是否存在,那么查询账号是否存在的方法我们就无法进行流量控制,因为请求入口都是从Controller进入的。但我们要限流的是方法,我们就需要修改一些配置以实现对方法对控制

Sentinel默认会标记Controller中的方法作context上下文整合,就会导致链路模式失效,我们可以在配置文件关闭配置即可,同时针对我们的Service的方法加入注解

# 关闭context整合
spring.cloud.sentinel.web-context-unify=false

方法加入注解

    @SentinelResource("checkAccountExist")
    public String checkAccountExist() {
        return "Server执行中...";
    }

我们自己触发一下相关Login、Register接口,我们就可以在Sentinel看到此配置了

这样就可以实现对Login、Register调用Checking方法进行限流!

流控规则-热点规则

场景:我们有一个高并发的秒杀功能,我们对不同额度优惠券的领取有不同的限制,比如3元优惠券,我们可以放行到每秒10个。优惠券10元的,每秒1个。我们优惠券种类通过id来区分。我们可以针对领取优惠券接口进行限流。

注意热点规则对默认SpringMVC资源无效,我们通过加入注解实现@SentinelResource

    @SentinelResource("hot")
    @RequestMapping("/register")
    public String hello(long id) { 
        System.out.println("Server执行中...");
        return "Server执行中...";
    }

添加热点规则

从找到DashBoard,热点规则,点击 新增热点规则。(如果你从簇点链路- 热点创建的,创建完成后,点击热点规则才能设定参数例外项)

隔离与降级

FeignClient整合Sentinel

配置文件开启

feign.sentinel.enabled=true

编写Feign因调用失败的降级逻辑

  • FallbackClass 无法对远程调用的异常作处理
  • FallbackFactory 可以对远程调用的异常作处理,我们使用这个
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
// 实现FallbackFactory接口,指定Feign的
public class DIYFallbackFactory implements FallbackFactory<TestFeignService> {
    @Override
    public TestFeignService create(Throwable throwable) {
        // 重写新的失败回调方法
        return new TestFeignService() {
            @Override
            public String yes(String id) {
                return "出错啦,你看到我,就不是默认的Sentinel出现异常的返回了";
            }
        };
    }
}

使用需要注入Bean,不然不会生效

@Bean
public DIYFallbackFactory dIYFallbackFactory(){
    return new DIYFallbackFactory();
}

然后在@FeignClient注解内部指定fallbackFactory

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

// @FeignClient("eurekaClient") // 声明目标服务注册到Eureka的名字
@FeignClient(value = "eurekaClient",fallbackFactory = DIYFallbackFactory.class) // 声明目标服务注册到Eureka的名字
public interface TestFeignService {

    // 重写映射关系,要与目标方法写成一样的映射地址
    // 将来通过Eureka拿到FeignClient拿到目标服务地址 拼接映射会爆404
    @GetMapping("/abc")
    @ResponseBody
    String yes(String id);
}

@SentinelResource的使用

# 使用前先关闭context整合,本文上面有说明为啥要关闭这个
spring.cloud.sentinel.web-context-unify=false

注意,如果限定的方法被限流,最好指定blockHandler与fallback

    @RequestMapping("/login")
    @SentinelResource(value = "/login",
            blockHandler = "diyBlock",// 指定流控的处理方法
            fallback = "diyFallBack") // 用于在抛出异常的时候提供 fallback 处理逻辑。
    public HashMap login() {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("username","张三");
        hashMap.put("age",10);
        return hashMap;
    }

    @RequestMapping("/loginWithERROR")
    @SentinelResource(value = "/loginWithERROR",
            blockHandler = "diyBlock",// 指定流控的处理方法
            fallback = "diyFallBack") // 用于在抛出异常的时候提供 fallback 处理逻辑。
    public HashMap loginWithERROR() {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("username","张三");
        hashMap.put("age",10);
        int i= 1/0;
        return hashMap;
    }

    // Block 管理限流,使用方法参数最后须多一个BlockException,其余与原函数一致.
    public static HashMap diyBlock(BlockException e) {
        System.out.println("限流");
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("info","当前被限流处理成功");
        return hashMap;
    }

    // Fallback 业务运行异常 使用方式:函数签名与原函数一致或加一个 Throwable 类型的参数.
    public static HashMap diyFallBack() {
        System.out.println("异常");
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("info","当前被异常Fallback处理成功");
        return hashMap;
    }

测试上面2个请求需要请求一次,然后再Sentinel添加限流后,再次请求就会触发相应的规则提示

流控规则-线程隔离

  1. 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
  2. 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

在Sentinel的Dashpoard 找一个接口进行限流,选择:

配置完成后,一旦达到限流,就会触发我们之前的失败降级逻辑

熔断降级

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

断路器控制熔断和放行是通过状态机来完成的。状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态
    • 请求失败:则切换到open状态

断路器熔断策略有三种:慢调用、异常比例、异常数

配置熔断规则

1、慢调用比例

在指定的统计时长内,如果请求数超过最小请求数,同时请求时间大于最大时间的RT且其比例超过比例阈值,就会触发熔断

2、异常比例 与 异常数 我们一起说明

统计指定时间内的调用,如果调用次数超过指定最小请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

全局熔断-限流 提示

无论是熔断、限流,一旦触发返回的内容都是Sentinel的提示,用起来很不舒服,我们需要配置自己的全局熔断-限流提示

主需要一个配置类,就可以实现全局降级的提示,避免Sentinel默认的提示

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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 全局降级处理,如果想要触发,可以尝试去Sentinel添加限流规则,然后在调用被限流接口即可
 */
@Slf4j
@Component
public class GlobalBlockHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        String returnInfo = "";
        if (e instanceof FlowException) {
            returnInfo = "统一处理方法:接口被限流了";
        } else if (e instanceof DegradeException) {
            returnInfo = "统一处理方法:服务降级了";
        } else if (e instanceof ParamFlowException) {
            returnInfo = "统一处理方法:热点参数限流了";
        } else if (e instanceof AuthorityException) {
            returnInfo = "统一处理方法:处理授权规则不通过";
        } else if (e instanceof SystemBlockException) {
            returnInfo = "统一处理方法:处理系统规则不通过";
        }
        httpServletResponse.setStatus(200);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = null;
        try {
            writer = httpServletResponse.getWriter();
            writer.write(JSON.toJSONString(returnInfo));
            writer.flush();
        } catch (IOException ioException) {
            log.error("异常:{}", ioException);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}

测试全局降级:请求任意一个接口,然后去Sentinel添加限流(QPS=1),然后再频繁请求此接口。看到我们配置类添加的限流提示即可!

com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

注意:这个狗问题,我当时测试的是限流一个Controller下的Mapping调用的Service,该Service配置了@SentinelResource。我们对该Service限流后,就会报这个异常。

产生原因:Service被限流QPS=1后,频繁请求就会爆错。请求慢一些,就没问题。说明是限流的问题。

经过测试:被限流的方法,是有指定的blockHandler方法处理过后就可以了。但是由于Service可能会抛出异常,还是要写一下fallback处理一下,确保100%没有问题。其他服务就可以限制了。

结论:我们必须保证被限流的接口不报错!也就是说@SentinelResource注解必须配置 fallback,blockHandler。

授权规则

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

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

目标服务开启Orign认证

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

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;
    }
}

测试:先对接口添加白名单规则,再去postman请求头添加origin=白名单的内容,如果你不添加白名单内容,就被限流,如果你开启本文全局限流配置后,就能看到具体的错误信息了 "统一处理方法:处理授权规则不通过"

测试即可

如果是Gateway接入,配置文件添加,在请求头标注origin即可

spring.cloud.gateway.default-filters[0]=AddRequestHeader=origin,gateway
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤