文章目录
1. 服务雪崩
因服务提供者的不可用导致服务调用者不可用,并将不可用逐渐放大,就叫服务雪崩效应。
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。 所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应产生了。
1.1. 解决方案
提高服务的稳定性、恢复性(Reliability&Resilience)
1.1.1. 常见的容错机制
-
超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。 -
服务限流
限制某一段时间内只有指定数量的请求进入后台服务器,遇到流量高峰期或者流量突增时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮。 -
隔离
原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
3.1. 线程隔离
为每一个服务接口单独开辟一个线程池,保持与其他服务接口线程的隔离,提高该服务接口的独立性和高可用。3.2. 信号隔离
每次调用线程,当前请求通过计数信号量进行限制,当信号大于了最大请求数时,进行限制。 -
服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。
现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一 定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间
后(例如10秒) ,断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用:如果调用依然不成功,断路器继续回到打开状态,过段时
间再进入半开状态尝试——通过“跳闸”,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复"。
所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。 -
服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的allback (回退)回调,返回一个缺省值。例如: (备用接口/缓存/mock数据)。这样做,虽然服务水平
下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
2. Sentinel初体验
Sentinel是阿里巴巴开源的面向分布式服务架构的高可用防护组件。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
点击查看官方文档
2.1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sentinel核心库 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<!-- 如果要使用@SentinelResource -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
2.2. 添加启动类
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* @Author Xxx
* @Date 2021/11/24 18:49
* @Version 1.0
*/
@SpringBootApplication
public class SentinelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelDemoApplication.class, args);
}
// 注解支持的配置Bean
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}
}
2.3. 添加pojo及controller
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
/**
* @Author Xxx
* @Date 2021/11/25 14:45
* @Version 1.0
*/
@Data
@ToString
@AllArgsConstructor
public class User {
private String username;
}
/**
* @Author Xxx
* @Date 2021/11/24 18:26
* @Version 1.0
*/
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
}
2.4. 通过代码实现限流
controller中添加
// 进行sentinel流控
@RequestMapping("/hello")
public String hello(){
Entry entry = null;
try {
// sentinel根据资源进行限制
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("==={}===", str);
return str;
} catch (BlockException e) {
// 资源访问阻止,被限流或者降级
// 进行相应的处理操作
log.info("block!");
return "被流控了!";
} catch (Exception ex){
// 若需要配置限流规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
}finally {
if(entry != null){
entry.exit();
}
}
return null;
}
/**
* 定义规则
*/
@PostConstruct
private static void initFlowRules(){
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 设置受保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则类型 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
// set limit QPS 1 to 20
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
2.5. 通过注解实现限流
controller中添加
/**
* 定义规则
*/
@PostConstruct
private static void initFlowRules(){
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 通过@SentinelResource来定义资源并配置降级和流控的处理方法
FlowRule rule2 = new FlowRule();
// 设置受保护的资源
rule2.setResource(USER_RESOURCE_NAME);
// 设置流控规则类型 QPS
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
// set limit QPS 1 to 20
rule2.setCount(1);
rules.add(rule2);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
/**
* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
* 1. 添加依赖<artifactId>sentinel-annotation-aspectj</artifactId>
* 2. 配置Bean — SentinelResourceAspect
* value = 定义资源
* blockHandler = 流控降级的处理方法(默认该方法必须声明在同一个类中)
* clockHandlerClass = 通过这个属性来指定blockHandler的类,但是方法必须static修饰
* fallback = 当接口出现异常,就可以交给fallback指定的方法进行处理
* exceptionsToIgnore = 哪些异常不处理
* 如果blockHandler和fallback都指定了,异常和限流同时触发时blockHandler优先级更高
* @param id
* @return
*/
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME, exceptionsToIgnore = {
ArithmeticException.class}, fallback = "fallbackForGetUser",blockHandler = "blockHandlerForGetUser")
public User getUser(String id){
// 可以放开测试fallback
// int a = 1 / 0;
return new User("zhangsan");
}
public User fallbackForGetUser(String id, Throwable ex){
ex.printStackTrace();
return new User("异常");
}
/**
* 1. 方法必须被public修饰
* 2. 返回值必须和原方法一致,包含原方法的参数
* 3. 可以在参数最后添加BlockException类,可以区分是什么规则的处理方法
* @param id
* @param ex
* @return
*/
public User blockHandlerForGetUser(String id, BlockException ex){
ex.printStackTrace();
return new User("流控!");
}
2.6. 实现熔断降级
controller中添加
@PostConstruct
public void initDegradeRule(){
// 降级规则
List<DegradeRule> degradeRules = new ArrayList<>();
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource(DEGRADE_RESOURCE_NAME);
// 设置熔断规则:异常数
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 触发熔断异常数
degradeRule.setCount(2);
// 触发熔断的最小请求数
degradeRule.setMinRequestAmount(2);
// 统计时常(毫秒)
degradeRule.setStatIntervalMs(60 * 1000);
// setStatIntervalMs = 60 * 1000 , setMinRequestAmount = 2 , setCount = 2 则表示1分钟内执行了2次 出现了两次异常 就会熔断
// 熔断持续时常(秒)
// 一旦触发熔断,再次请求对应接口会调用降级方法(blockHandler)
// setTimeWindow时间过去后会转为半开状态,如果第一次请求异常则继续熔断,不会根据设置的条件判定
degradeRule.setTimeWindow(10);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
}
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME, entryType = EntryType.IN, blockHandler = "blockHandlerForFb")
public User degrade(String id) throws InterruptedException {
// 异常数\比例
throw new RuntimeException("异常");
/* 慢调用比例
TimeUnit.SECONDS.sleep(1);
return new User("正常");
*/
}
public User blockHandlerForFb(String id, BlockException ex) {
ex.printStackTrace();
return new User("降级!");
}
3. 控制台部署
jar包下载地址:https://github.com/alibaba/Sentinel/releases
版本关系见
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
启动控制台命令
java -jar sentinel-dashboard-1.8.1.jar
用户可以通过如下参数进行配置:
-Dsentinel.dashboard.auth.username=sentinel用于指定控制台的登录用户名为sentinel;
-Dsentinel.dashboard.auth.password=123456用于指定控制台的登录密码为123456;如果省略这两个参数,默认用户和密码均为sentinel;
-Dserver.servlet.session.timeout=7200用于指定Spring Boot服务端session的过期时间,如7200表示7200秒;60m 表示60分钟,默认为30分钟;
-Dserver.port=8858 用于指定sentinel控制台端口
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=xushu Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.1.jar
为了方便快捷启动可以在桌面创建bat文件
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=xushu -Dsentinel.dashboard.auth.password=123456 -jar D:\server\sentinel-dashboard-1.8.1.jar
pause
访问http://127.0.0.1:8080/#/login,默认用户密码:sentinel/sentinel
3.1. 客户端整合
3.1.1. 添加依赖
<!-- 整合Sentinel控制台 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.1</version>
</dependency>
启动时加入JVM参数-Dcsp.sentinel.dashboard.server=consoleIp:port指定控制台地址和端口。
若启动多个应用,则需要通过-Dcsp.sentinel.api.port=xxxx指定客户端监控API的端口(默认是8719)。
从1.6.3版本开始,控制台支持网关流控规则管理。您需要在接入端添加-Dcsp.sentinel.app.type=1启动参数以将您的服务标记为API Gateway,在接入控制台时您的服务会自动注册为网关类型,然后您即可在控制台配置网关规则和API分组。
除了修改JVM参数,也可以通过配置文件取得同样的效果。更详细的信息可以参考启动配置项。
4. Spring Cloud Alibaba整合Sentinel
4.1. 添加依赖
<!-- sentinel启动器,spring-cloud-starter-alibaba-nacos-discovery已经管理好版本 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
4.2. 添加配置文件
application.yml
spring:
application:
name: 服务名称
cloud:
sentinel:
transport:
dashboard: 控制台IP:端口
4.3. 流控规则
-
流量控制(flow control),其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。==== FlowRule RT(响应时间) 1/0.2s =5
最普适的场景:- Provider(服务提供)端控制脉冲流量
- 针对不同调用来源进行流控
- Web接口流控
如何配置规则:
- 梳理核心接口
- 通过事前压测评估核心接口容量,配置QPS阈值
4.4. QPS流控
当QPS超过阈值,会采取措施进行流控,策略包含:直接拒绝、Warm Up、匀速排队。
- 快速失败:为默认的流控方式:直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT),拒绝后抛出FlowException。适用于系统处理能力已知(系统处理能力的水位已知)
- Warm Up(激增流量),即预热/冷启动方式。通过冷启动,让通过的流量缓慢增加,逐渐增加到阈值上线(给系统一个预热时间),避免冷启动系统被压垮。
冷加载因子:codeFactor默认是3,即请求QPS从threshold/3开始,经预热市场逐渐升至设定的阈值。 - 匀速排队(脉冲流量):让请求匀速通过,对应的是令牌桶算法(注意:匀速排队模式暂时不支持 QPS > 1000 的场景。)
流控效果为快速失败时的脉冲流量(类型=QPS、阈值=5),可以看到部分成功部分失败
流控效果为匀速排队时的脉冲流量(类型=QPS、阈值=5、超时时间5000毫秒),可以看到所有的请求都成功了
4.4.1. 新建接口
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/flow")
@SentinelResource(value = "flow", blockHandler = "flowBlockHandler")
public String flow(){
return "正常访问";
}
public String flowBlockHandler(BlockException e){
e.printStackTrace();
return "流控";
}
}
4.4.2. 添加流控规则
新增后可在左侧流控规则中查看
4.5. 线程数流控
并发数控制用于保护业务线程池不被慢调用耗尽,可以采用线程池隔离的隔离方案(不同业务使用不同的线程池)。这种方法的缺点和代价是:线程数目太多,线程上下文切换的overhead比较大。
4.5.1. 新建接口
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread", blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
return "正常访问";
}
public String flowBlockHandler(BlockException e){
e.printStackTrace();
return "流控";
}
}
4.5.2. 添加流控规则
4.5.3. 测试注意项
通过两个浏览器来访问http://127.0.0.1:8060/order/flowThread
4.6. BlockException异常统一处理
springwebmvc接口资源限流入口再HandlerInterceptor的实现类AbstractSentinelInterceptor的preHandle方法中,对异常的处理是BlockExceptionHandle的实现类
代码
/**
* @Author Xxx
* @Date 2021/11/26 18:12
* @Version 1.0
*/
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result error(Integer code, String msg){
return new Result(code, msg);
}
}
/**
* @Author Xxx
* @Date 2021/11/26 18:07
* @Version 1.0
*/
@Component
@Slf4j
public class MyBlockExceptionHandler implements BlockExceptionHandler {
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// getRule() 资源 规则的详细信息
log.info("MyBlockExceptionHandler => {}",e.getRule());
Result r = null;
if(e instanceof FlowException){
r = Result.error(100, "接口被限流了");
}else if(e instanceof DegradeException){
r = Result.error(101, "服务被降级了");
}else if(e instanceof ParamFlowException){
r = Result.error(102, "热点参数限流了");
}else if(e instanceof SystemBlockException){
r = Result.error(103, "触发系统保护规则了");
}else if(e instanceof AuthorityException){
r = Result.error(104, "授权规则不通过");
}
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), r);
}
}
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/add")
public String add(){
log.debug("下单成功!");
return "下单成功!" ;
}
}
添加流控规则后测试
使用了@SentinelResource注解的资源BlockExceptionHandler将无法拦截
4.7. 流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。
4.7.1. 直接
资源调用达到设置的阈值后直接被流控抛出异常
4.7.2. 关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db和write_db 这两个资源分别代表数据库读写,我们可以给read_db设置限流规则来达到写优先的目的:设置strategy为RuleConstant.STRATEGY_ RELATE同时设置refResource为write_db。 这样当写库操作过于频繁时,读数据的请求会被限流。
添加代码
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/add")
public String add(){
log.debug("下单成功!");
return "下单成功!" ;
}
@RequestMapping("/get")
public String get(){
log.debug("查询订单!");
return "查询订单!" ;
}
}
4.7.3. 链路
根据调用链路入口限流。
NodeSelectorSlot中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。 这棵树的根节点是一个名字为machine-root的虚拟节点,调用链的入口都是这个虚节点的子节点。
添加代码
public interface IOrderService {
String getUser();
}
/**
* @Author Xxx
* @Date 2021/11/26 21:13
* @Version 1.0
*/
@Service
public class OrderServiceImpl implements IOrderService {
@SentinelResource(value = "getUser")
public String getUser() {
return "查询用户!";
}
}
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
private IOrderService orderService;
@RequestMapping("/test1")
public String test1(){
return orderService.getUser();
}
@RequestMapping("/test2")
public String test2(){
return orderService.getUser();
}
}
测试会发现链路规则不生效
注意,高版本此功能直接使用不生效,如何解决?
从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0版本开始(对应SCA的2.1.1.RELEASE), 官方在CommonFilter引了WEB_CONTEXT_UNIFY参数,用于控制是否收敛context.将其配置为false 即可根据不同的URL进行链路限流。
SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
目前官方还未发布SCA 2.1.2.RELEASE,所以我们只能使用2.1.1.RELEASE, 需要写代码的形式实
现
spring:
cloud:
sentinel:
web-context-unify: false #默认为true 将调用链路收敛
测试,仅在此场景全局异常处理器拦截不到BlockException, 对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHandler处理BlockException
总结:为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。 流控链路模式的问题等待官方后续修复,或者使用AHAS。
4.8. 熔断策略
4.8.1. 慢调用比例
慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT (即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
// 模拟慢调用接口
@RequestMapping("/flowThread")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "正常访问";
}
4.8.2. 异常比例
异常比例(ERROR RTIO):当单位统计时长(statIntervalMs) 内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0 - 1.0],代表0% - 100%。
// 模拟异常接口
@RequestMapping("/err")
public String err() {
int a = 1 / 0;
return "err";
}
4.8.3. 异常数
异常数(ERROR COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException) 不生效。
// 模拟异常接口
@RequestMapping("/err")
public String err() {
int a = 1 / 0;
return "err";
}
4.9. 热点参数流控
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
4.9.1. 配置一个热点流控接口
/**
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
// 测试热点参数流控接口,这种流控只能使用@SentinelResource来实现
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById", blockHandler = "HotBlockHandle")
public String getById(@PathVariable("id") int id){
return "正常访问";
}
public String HotBlockHandle(@PathVariable("id") int id, BlockException ex){
return "热点参数流控!";
}
}
4.10. 系统保护规则
Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的maxQps * minRt 估算得出。设定参考值一般是CPU cores * 2.5。
https://www.cnblogs.com/gentlemanhai/p/8484839.html - CPU usage (1.5.0+ 版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。
5. Openfeign 整合 Sentinel
5.1. 第一步
添加依赖
<!--openfeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
5.2. 第二步
application.yml添加配置项
spring:
cloud:
# 参考 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc#%E5%85%B3%E4%BA%8E-nacos-discovery-starter-%E6%9B%B4%E5%A4%9A%E7%9A%84%E9%85%8D%E7%BD%AE%E9%A1%B9%E4%BF%A1%E6%81%AF
nacos:
discovery:
# Nacos Server 启动监听的ip地址和端口
server-addr: 127.0.0.1:8848
# 注册的服务名
service: order-openfeign-sentinel
# 命名空间ID,Nacos通过不同的命名空间来区分不同的环境,进行数据隔离,服务消费时只能消费到对应命名空间下的服务。
namespace: public
# nacos服务的账号
username: nacos
# nacos服务的密码
password: nacos
feign:
sentinel:
# feign整合sentinel 默认值为false
enabled: true
5.3. 第三步
添加代码
/**
* spring启动类
* @Author Xxx
* @Date 2021/11/18 17:17
* @Version 1.0
*/
@SpringBootApplication
//@EnableDiscoveryClient
@EnableFeignClients
public class OrderOpenfeignSentinelApplication {
public static void main(String[] args) {
SpringApplication.run( OrderOpenfeignSentinelApplication.class, args );
}
}
// OpenFeign接口 fallback用于熔断
// name = 服务名称 / path = Controller类ReqeustMapper
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
// 声明需要调用的rest接口对应的方法
@RequestMapping("/reduct2")
public String reduct2();
}
/**
* 熔断处理类,fallback指定的类必须实现@FeignClient标记的接口
* @Author Xxx
* @Date 2021/11/29 15:49
* @Version 1.0
*/
@Component
public class StockFeignServiceFallback implements StockFeignService {
public String reduct2() {
return "降级了!";
}
}
/**
* 接口
* @Author Xxx
* @Date 2021/11/18 17:10
* @Version 1.0
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
// 第三步、注入feign接口,并调用方法
@Resource
private StockFeignService stockFeignService;
@RequestMapping("/add")
public String add(){
log.debug("下单成功!");
String result = stockFeignService.reduct2();
return "hello feign!" + result ;
}
}
stock-service服务controller代码
/**
* @Author Xxx
* @Date 2021/11/18 17:14
* @Version 1.0
*/
@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {
@Value("${server.port}")
private String port;
@RequestMapping("/reduct2")
// 用于测试Openfeign整合Sentinel
public String reduct2(){
int a = 1 / 0;
return "扣减库存成功!端口:" + port;
}
}
如此一来就能对远程服务调用的异常来进行降级,可以尝试修改feign.sentinel.enabled=false/true来测试
6. 规则持久化
6.1. 第一步
添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--sentinel规则持久化依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
6.2. 第二步
添加application.yml
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858
web-context-unify: true #默认为true 将调用链路收敛
# 持久化规则配置 参考https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
datasource:
flow-rule: #可以自定义
nacos:
server-addr: 127.0.0.1:8848
# nacos服务的账号
username: nacos
# nacos服务的密码
password: nacos
data-id: order-sentinel-flow-rule
rule-type: flow
# 参考 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc#%E5%85%B3%E4%BA%8E-nacos-discovery-starter-%E6%9B%B4%E5%A4%9A%E7%9A%84%E9%85%8D%E7%BD%AE%E9%A1%B9%E4%BF%A1%E6%81%AF
nacos:
discovery:
# Nacos Server 启动监听的ip地址和端口
server-addr: 127.0.0.1:8848
# nacos服务的账号
username: nacos
# nacos服务的密码
password: nacos
7. 代码下载
https://download.csdn.net/download/qq_42017523/52033780