前言
双11期间存在秒杀情景,访问量会大于并发数,所以为了保证在该情形下保证系统的稳定运行,需要采取一定的保护措施,常见的策略有服务降级、熔断和限流。
一、服务限流
1.1 、目的
限流的主要目的就是限制并发访问数或者限制在一定时间内通过限制请求的数来进行保护。
1.2 、 限流算法
1.2.1 、 计数器算法
在指定周期内累加访问次数,当访问次数到达一定的阈值后触发限流策略 ,当进入下一个周期则计数器清零。
比如:限制一分钟短信的发送次数。
1.2.2 、 滑动窗口算法(sentinel)
为了解决计数器算法出现的临界的问题,所以引入滑动窗口算法。在固定窗口中分割出多个时间小口,分别在每个窗口记录访问次数。
1.2.3 、 令牌桶限流法
对于每个请求都需要从令牌桶中获取令牌,如果没有获取到令牌,则需要触发限流。
假设QPS=10,即令牌的生成速度是每秒10个,那么就会出现三种情况:
1)如果客户端请求的速度大于令牌生成的速度,那么后边的所有请求就会被限流;
2)如果客户端请求速度大致等于令牌的生成速度,那么系统相对稳定;
3)如果客户端请求速度小于令牌的生成速度,那么可以应对较大的并发量;
1.2.3 、 漏桶限流算法
控制数据的注入网络的速度,平滑网络上的突发刘流量。
1)当请求速度大于水流的速度,相当于水桶中的水满了,则多出的请求则会触发限流;
2)当请求速度小于水流速度,则系统相对稳定。
二、服务熔断
服务熔断就是当其中一个系统不能使用,比如请求超时、服务异常,为了防止出现服务雪崩的效应,暂时将出现问题的接口隔离开来,断绝与其他服务的所有联系,触发熔断后,则后续的所有请求都会被拒绝。
三、服务降级
参考指标:
平均响应时间:对应单位时间的响应个数超过阈值
异常比例:某个方法被调用出现异常的比例超过阈值
异常数量:在指定的时间窗口出现异常的数量超过阈值
四、分布式限流框架—— Sentinel
4.1 sentinel的特点:
1.丰富的应用场景
2.实时监控
3.开源支持生态
4.2 sentinel的组成
1.java核心库:不依赖任何框架,可以在java环境进行运行,可以很好的与springboot、dubbo进行整合
2.Dashboard :基于Springboot进行开发,可直接部署;
4.3 sentinel的基本应用
1.定义资源
2.定义限流规则
3.校验规则是否生效
4.3.1 实现限流
依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.2</version>
</dependency>
1.定义一个普通的方法:
// 资源名称
private String resourceName="sayHello";
@GetMapping("/hello")
public String sayHello(@PathParam("id")String id){
Entry entry=null;
try {
// 在这个方法里实现限流
entry= SphU.entry(resourceName);
return "access";
} catch (BlockException e) {
e.printStackTrace();
return "block";
}finally {
if(entry!=null){
entry.exit();
}
}
}
2.设置限流的规则
@PostConstruct
public void init() {
List<FlowRule> paramFlowRule = new ArrayList<>();
FlowRule flowRule=new FlowRule();
// 资源名称与Spu中的保持一致
flowRule.setResource(KEY);
// qps值
flowRule.setCount(2);
// 限流阈值类型:qps
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
paramFlowRule.add(flowRule);
FlowRuleManager.loadRules(paramFlowRule);
}
请求超过阈值,可以查看日志:
1625365158000|2021-07-04 10:19:18|sayHello|1|0|1|0|19|0|0|0
1625365159000|2021-07-04 10:19:19|sayHello|2|3|2|0|0|0|0|0
1625365160000|2021-07-04 10:19:20|sayHello|2|3|2|0|1|0|0|0
1625365161000|2021-07-04 10:19:21|sayHello|1|2|1|0|0|0|0|0
还可以通过@SentinelResource来定义资源
private static final String KEY ="nacos-order";
/**
* 限流实现方式一: 抛出异常的方式定义资源
*
* @param orderId
* @return
*/
@RequestMapping("/getOrder1")
@ResponseBody
public String queryOrder2(@RequestParam("orderId") String orderId) {
Entry entry = null;
// 资源名
String resourceName = KEY;
try {
// entry可以理解成入口登记
entry = SphU.entry(resourceName);
// 被保护的逻辑, 这里为订单查询接口
return orderQueryService.queryOrderInfo(orderId);
} catch (BlockException blockException) {
// 接口被限流的时候, 会进入到这里
log.warn("---getOrder1接口被限流了---, exception: ", blockException);
return "接口限流, 返回空";
} finally {
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null) {
entry.exit();
}
}
}
@SentinelResource(value = "getOrder" ,blockHandler = "handleFlowQpsException",
fallback = "queryOrderInfo2Fallback")
public String queryOrderInfo1(String orderId) {
// 模拟接口运行时抛出代码异常
if ("000".equals(orderId)) {
throw new RuntimeException();
}
System.out.println("获取订单信息:" + orderId);
return "return OrderInfo :" + orderId;
}
/**
* 订单查询接口抛出限流或降级时的处理逻辑
*
* 注意: 方法参数、返回值要与原函数保持一致
* @return
*/
public String handleFlowQpsException(String orderId, BlockException e){
e.printStackTrace();
return "handleFlowQpsException for queryOrderInfo2: " + orderId;
}
/**
* 订单查询接口运行时抛出的异常提供fallback处理
*
* 注意: 方法参数、返回值要与原函数保持一致
* @return
*/
public String queryOrderInfo2Fallback(String orderId, Throwable e) {
return "fallback queryOrderInfo2: " + orderId;
}
基于并发数和QPS的流量控制
并发线程数:统计当前请求的上下文的线程数量,如果超过阈值则新的请求就会被拒绝
QPS:每秒的查询数,一台服务器每秒能响应的查询次数
QPS流量控制行为:
1)直接拒绝:请求超过流量限制,则直接进行抛出FlowException
2)冷启动:当流量突然增大时,防止大量的请求可能会把服务器压垮,我们希望请求处理的数量逐步递增,并不是将QPS拉到最大值,而是逐步增加阈值,直到最大值。
3)匀速排队:让请求匀速通过
4)冷启动+匀速排队
4.3.2 实现服务熔断
@PostConstruct
public void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// 80s内调用接口出现异常次数超过5的时候, 进行熔断
rule.setCount(5);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setTimeWindow(80);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}