文章知识来源主要来源于:赵俊夫先生的博客 以下为原文链接
https://blog.csdn.net/u011177064/category_9572944.html
1:微服务限流?
在之前一节 已经讲过了Sentinel在GateWay进行限流了,
下面所讲的是在微服务进行限流。
主要应用于在给该服务的一些核心接口进行限流,防止被频繁调用。
比如说 现金提现 转账之类的。
2:什么是熔断降级?
很多时候,在网上都会把熔断和降级区分成狭义上两个概念。
比如说:单个服务的熔断 整体服务的降级
但我认为实际上是一个东西 也就是因为熔断才导致降级的 所有没有必要区分清楚
首先我们需要认识到微服务架构的一大特点就是服务被拆分地非常细:
所以一个可供前端使用的、完整的接口,调用经常会需要涉及到多个微服务,
比如说一个获取订单详情的接口,开放给前端的就是单单一个接口,
但是这个接口里面可能调用了订单数据、然后同时还调用了商品的接口、用户的接口,最后把数据聚合后返回给前端。
假设其中有某个接口突然失效了
比如说因为各种原因(比如缓存失效,数据库崩溃等)导致响应时间过长,那么所有需要调用到此接口的其他服务将全部“卡死”在这儿了,
随之而来的就是整个系统逐渐把所有网络连接挂起,服务器连接数暴增,最后系统崩溃了,通常这种情况叫做雪崩。
熔断降级就是解决此类问题:
它可以以响应时间、异常比例或次数等多个纬度来定制熔断规则,即达到设定的条件后,马上把该服务熔断,
让其他调用者马上知道这个服务暂时不可用,并执行自己的应对措施,从而保证系统的整体稳定可用。
因为这样子的整体稳定是一种“降级”的稳定,所以我们一般把它称为熔断降级。
3:需要进行熔断的服务的Pom文件(consumer 或者 provider)
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
4:编写 微服务限流 的接口
使用 @SentinelResource 注解,代替 Sentinel 编程式风格,更为简洁。
@RestController
@RequestMapping(value = "/sentinelFlow")
public class SentinelFlow {
/**
* 在构造方法里初始化了 限流 规则。
* 写在这里只是为了方便测试,实际开发中,规则的初始化可以放在统一的地方,而不是写在每个控制器里
*/
public SentinelFlow() {
initFlowRules();
}
/**
* 熔断测试接口
*
* @return
* @SentinelResource 中的 value 即为熔断规则中设置的 Resource 名
* blockHandler与blockHandlerClass 结合起来是定义
* 熔断后处理逻辑由ExceptionUtil.handleException(前面可加自己的参数,BlockException e)控制
*/
@GetMapping(value = "/test")
@SentinelResource(value = "sentinelFlowTest", blockHandler = "handleException", blockHandlerClass = {SentinelException.class})
public String test() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sentinel pass";
}
/**
* 初始化限流规则
*/
private static void initFlowRules() {
/*
* resource:资源名,即限流规则的作用对象
count: 限流阈值
grade: 限流阈值类型(QPS 或并发线程数)
limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
strategy: 调用关系限流策略
controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
*/
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("sentinelFlowTest");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(10);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
5:编写 熔断降级 的接口
@RestController
@RequestMapping(value = "/sentinelDegrade")
public class SentinelDegrade {
/**
* 在构造方法里初始化了熔断 规则。
* 写在这里只是为了方便测试,实际开发中,规则的初始化可以放在统一的地方,而不是写在每个控制器里
*/
public SentinelDegrade() {
initDegradeRules();
}
/**
* 熔断测试接口
*
* @return
* @SentinelResource 中的 value 即为熔断规则中设置的 Resource 名
* blockHandler与blockHandlerClass 结合起来是定义
* 熔断后处理逻辑由ExceptionUtil.handleException(前面可加自己的参数,BlockException e)控制
*/
@GetMapping(value = "/test")
@SentinelResource(value = "sentinelDegrade", blockHandler = "handleException", blockHandlerClass = {SentinelException.class})
public String test() {
try {
//睡眠500ms
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sentinel pass";
}
/**
* 初始化熔断规则
*/
private static void initDegradeRules() {
// 【策略1】
// 平均响应时间 (DEGRADE_GRADE_RT) 触发: 1s 内持续进入超过 5 个请求
// 平均响应时间超过 count (10ms) 则熔断 timewindow(10s)
// rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// rule.setCount(10);
// rule.setTimeWindow(10);
List<DegradeRule> rules = new ArrayList<DegradeRule>();
DegradeRule rule = new DegradeRule();
rule.setResource("sentinelDegrade");
//降级策略
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
//平均响应时间 ms
rule.setCount(10);
//降级时间 s
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
// 【策略2】
// 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO) 触发:每秒请求量 >= 5
// 每秒异常总数占通过量的比值超过阈值count([0.0, 1.0],代表 0% - 100%) 之后 则熔断 timewindow(10s)
// rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
// rule.setCount(0.1);
// rule.setTimeWindow(10);
//
// 【策略3】
// 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT)
// 当资源近 1 分钟的异常数超过阈值 (count) 之后会进行熔断
// 注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
// rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// rule.setCount(5);
// rule.setTimeWindow(90);
}
}
6:编写 限流功能 和 熔断降级的 单元测试
由于只使用Postman的并发测试,很难模拟 每隔多少秒进行请求的场景
因此编写单元测试 进行并发的测试
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class SentinelJunit {
/**
* 限流
* @throws InterruptedException
*/
@Test
public void sentinelFlowJunit() throws InterruptedException {
while(true) {
//每秒发20次请求
for (int i = 0; i < 20; i++) {
final int i1=i;
new Thread(() ->{
System.out.println((new Date()+"---"+i1+"---"+
sendGetRequest("http://localhost:9991/sentinelFlow/test")));
}).start();
}
Thread.sleep(1000);
}
}
/**
* 熔断降级
* @throws InterruptedException
*/
@Test
public void sentinelDegradeJunit() throws InterruptedException {
while(true) {
//每秒发20次请求
for (int i = 0; i < 20; i++) {
final int i1=i;
new Thread(() ->{
System.out.println((new Date()+"---"+i1+"---"+
sendGetRequest("http://localhost:9991/sentinelDegrade/test")));
}).start();
}
Thread.sleep(1000);
}
}
public String sendGetRequest(String getUrl) {
StringBuffer sb = new StringBuffer();
InputStreamReader isr = null;
BufferedReader br = null;
try {
URL url = new URL(getUrl);
URLConnection urlConnection = url.openConnection();
urlConnection.setAllowUserInteraction(false);
isr = new InputStreamReader(url.openStream());
br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
7:限流与熔断对比
注解说明
使用 @SentinelResource 注解,代替 Sentinel 编程式风格,更为简洁。
@GetMapping(value = "/test")
@SentinelResource(value = "sentinelTest", blockHandler = "handleException", blockHandlerClass = {SentinelException.class})
该注解会把test的这个接口路径 注册成为自定义名称的sentinelTest
然后该自定义名称 sentinelTest 提供给到 限流或者熔断功能去监听
当限流以及熔断功能同时监听了该 名称
限流功能先进行限流 然后再进行熔断的判断
最后:
限流 一个接口限流后 实时不可用 (可以扩展成 多个接口 -> 服务)
熔断 一个接口熔断后 一段时间不可用 (可以扩展成 多个接口 -> 服务)
限流测试结果
1秒钟里面的20个请求必定有10个被拦截
熔断测试结果
当该接口进行熔断的时候 10秒内不可用
项目连接
请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/spring-cloud-alibaba-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/spring-cloud-alibaba-project.git
在service-consumer-demo 模块下
项目代码 sentinel包
单元测试 SentinelJunit
网友评论