使用前请确保自己的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/Sentinel、https://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添加限流后,再次请求就会触发相应的规则提示
流控规则-线程隔离
- 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
- 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。
在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
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤