目录
4、@SetinelReource指定ParamFlowException 异常降级方法
学习中文官网:Home · alibaba/Sentinel Wiki · GitHub
一、总体规则
五大规则
- 流量规则--流量控制(flow control)
- 降级规则--熔断降级
- 热点规则--热点参数(Paramflow)
- 系统规则--了解(见官网)
- 授权规则--黑白名单控制规则
- 资源名: 唯一名称,默认请求路径
- 针对来源: Sentinel可以战队调用者进行限流,填写服务名。默认为default(不区分来源)
- 阈值类型/单击阈值
QPS(每秒中的请求数量): 当调用该api的QPS达到阈值的时候,进行限流
线程数: 当调用该api的线程数达到阈值的时候,进行限流
- 是否集群: 不需要集群
- 流控模式:
直连: api达到限流条件时,直接限流
关联: 当关联的资源达到阈值的时候,就限流自己
链路: 指记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈 值,如果达到阈值,就进行限流)【api级别的针对来源】
- 流控效果:
快速失败: 直接失败,抛异常 + Warm Up: 根据codeFactor(冷加载因子,默认为3)的值,从阈值/codeFactor,经过预热时长,才打到设置的QPS的阈值。
匀速排队: 匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
本章案例都是 基于第二十一章 中案例代码。
二、流量规则
原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
1、QPS
原理:每秒中的请求次数超过设定的阈值的时候,进行限流。
例如 设置保护资源为 订单服务中的 /order/buy/{id},流控规则中阈值为2。如果没有配置规则,这1s内来多少个请求就要处理多少个请求。如果配置规则,例如阈值为1s内可以访问2个请求,若超出2个的第三个及以后的请求都会被拒接。
- 新建规则
- 选择阈值QPS、流控模式直接、流控效果快速失败
- 进行测试
2、线程数
原理: 当调用该api的线程数达到设定的阈值,进行限流
类似于在银行办业务,同一个窗口同一时间只有一个客户在办理,其他客户必须等该客户办理完之后才可以办理
- 设置订单服务的 OrderController 中 buy 方法
在 buy 方法中设置延迟时间超过1s,模拟银行工作人员为顾客办理业务时间。
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
//每个线程进入后都要等待0.8s,例如银行业务人员给顾客办理业务时间
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setId(id);
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置线程数请求阈值
- 请求测试
点击不断刷新
3、关联
原理:当关联的资源达到阈值的时候,就限流自己
例如订单的支付接口繁忙时候,则对下订单接口进行限流。本案例中订单服务中有两个资源 /order/buy/{id} 与 /order/select/{id},因为在同一个controller中,因此是关联的。我们在 /order/buy/{id} 繁忙的时候,对 /order/select/{id} 限流,所以保护的资源是 /order/select/{id},关联的资源是 /order/buy/{id},流控规则中阈值为1。当关联的资源 /order/buy/{id}的请求超出1后,资源 /order/select/{id} 会被限流。
- 代码
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
Product product = orderFeignService.findOrderById(id);
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setId(id);
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置规则
- 进行测试
jmeter下载地址:压力测试工具----JMeter_爱吃面的猫的博客-CSDN博客
压力测试访问关联资源 http://localhost:9000/order/buy/1
当关联资源http://localhost:9000/order/buy/1 请求超出阈值后,则http://localhost:9000/order/select/1 被限流。
4、预热
定义:Warm Up ( RuleConstant.CONTROL_BEHAVIOR_MARA_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统装压垮。
应用场景:如:秒杀系统开启的瞬间,会有很多流量上来,很有可能会把系统打死,预热方式就是为了把系统保护起来, 可慢慢的把流量放进来,慢慢的把阈值增加到设定的值。
原理:根据codeFactor(冷加载因子,默认的codeFactor为3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS的阈值。
案例:阈值10+,预热时间设置5秒,系统初始化的阈值为10/3约等于3,即阈值刚开始为3,经过了5秒后阈值才慢慢升高到10。
- 源码:
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
Product product = orderFeignService.findOrderById(id);
System.out.println("/buy/{id} 执行");
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setId(id);
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置规则
- 进行测试
开始不行,后来慢慢就可以了,刚开始狂点 http://localhost:9000/order/buy/1,会发现报错Blocked by Sentinel (flow limiting),然后5秒后,阈值从3->10后,会发现狂点刷新浏览器不会出现限流。
5、排队等待
让请求以均匀的请求速度通过,阈值类型必须为QPS,否则无效
设定 /order/buy/{id} 每秒1次请求,请超时的话需要排队等待,等待的超时时间为2000毫秒(即2秒)
代码
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setId(id);
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置规则
- 进行测试
执行半秒后关闭。会看到控制台上依然在打印消息。
三、熔断降级
服务熔断作用:就是防止服务雪崩现象出现。
服务熔断:正常情况出现超时、异常、宕机等情况就是服务降级,只有当默认10秒内超过20个请求次数 或者 默认10内超过50%的请求失数 的情况,才是熔断,此时才开启熔断器。
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
Sentinel熔断降级是没有半开状态的。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
1、慢调用比例
- 1.RT:响应时间,指系统对请求作出响应的时间。
- 2.统计时长:时间条件,在该时间段内发生的请求,单位毫秒。
- 3.最小请求数:在统计时长内发出的请求次数。例如在统计时长1s内发出的QPS阈值是5个。
- 4.慢调用次数:当调用的时间(响应的实际时间)>设置的RT的时间的请求次数。
- 5.慢调用比例:在所以调用中,慢调用占有实际的比例,= 慢调用次数 / 调用次数
- 6.比例阈值:自己设定的 , 慢调用次数 / 调用次数=比例阈值
当平均响应时间 超出阈值 且 在时间窗口通过的请求>=默认5个,两个条件同时满足后,触发降级。窗口期过后关闭断路器。
- 设置规则
这里表示在在1000毫秒,也就是1秒内,发送请求 /order/buy/{id} 数超过5时,并且在这些请求中,平均响应时间RT超过200毫秒的有30%,则对请求进行熔断,熔断时长为20秒钟,10秒以后恢复正常。即触发条件:
- 1s内最小请求数必须大于5
- 慢请求 ÷ 总请求 >= 0.3
- 代码
前面设置RT的最长响应时间是0.2s,此处代码中设置最长响应时间为1s。
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setId(id);
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 进行测试
通过JMeter测试,1秒钟发起10个线程请求 /order/buy/1 ,此时请求数>5,且每个请求的响应时间>RT的0.2s,因此触发熔断效果,停止测试以后,20秒钟以后恢复正常。
浏览器刷新请求 http://localhost:9000/order/buy/1
2、异常比例
异常比例(DEGRADE_GRADE_EXCEPTION_RATIo):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0]。
- 代码
设置异常,如果id<=0则抛出异常。
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
if (id<=0){
throw new RuntimeException("selectById 执行异常");
}
Product product = new Product();
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置规则
设置1000毫秒即1s内最小请求是5个,如果错误请求/总的请求数超过>=阈值0.3 则触发熔断。
触发条件:
1s内最小请求数大于5
异常数/总请求数 > 0.3
- 进行测试
访问 http://localhost:9000/order/select/-1
访问 http://localhost:9000/order/select/1
通过JMeter测试,1秒钟发起10个线程请求 /order/select/-1 ,此时请求数>5,且错误请求/总的请求数>0.3,因此触发熔断效果,停止测试以后,10秒钟以后恢复正常。
浏览器同时访问 http://localhost:9000/order/select/1
3、异常数
异常数( DEGRADE_GRADE_EXCEPTION_COUNT ):单位时间内异常数目超过阈值之后会进行熔断。
- 代码:
package com.hwadee.springcloud.controller;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
public Product selectById(@PathVariable Long id) {
if (id<=0){
throw new RuntimeException("selectById 执行异常");
}
Product product = new Product();
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
- 设置规则
触发条件:
每秒请求数必须大于5
每秒的异常数必须大于3【我们设置的阈值】
- 进行测试
不断刷新 http://localhost:9000/order/select/-1
四、热点key限流
热点:即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。
比如:商品秒杀,在某一时间内对此秒杀的商品进行限流,如商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。又如:用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
1、@SetinelReource指定别名
热点参数限流时候,不能使用资源路径,只能使用资源路径的别名。资源路径别名的指定是在注解@SetinelReource中使用value属性指定。 例如 对 selectById 方法 使用 @SentinelResource(value = "selectById") 指定别名为 selectById。
package com.hwadee.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
@SentinelResource(value = "selectById")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setName("selectById 执行");
System.out.println(product);
return product;
}
}
2、设置规则
3、进行测试
4、@SetinelReource指定ParamFlowException 异常降级方法
使用 @SetinelReource中 blockHandler 属性指定降级方法,如果不指定,blockHandler会抛出异常,即BlockException异常的子异常 ParamFlowException。
- 代码
package com.hwadee.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
@SentinelResource(value = "selectById",blockHandler = "handle_selectById")
public Product selectById(@PathVariable Long id) {
Product product = new Product();
product.setName("selectById 执行");
System.out.println(product);
return product;
}
public Product handle_selectById(Long id, BlockException exception) {
Product product = new Product();
product.setName("哎吆,服务走神了,请稍后重试");
System.out.println(product);
return product;
}
}
- 指定规则
- 进行测试
5、@SetinelReource指定 业务异常 降级方法
使用 @SetinelReource中 fallback 或则 defaultFallback 指定业务中的异常降级,其中 fallback 用于指定业务报错时候的自定义方案,defaultFallback 用于指定业务报错时候的默认方案。如果二者同时有,则选择自定义,如果没有自定义,则选择默认。
- 代码
package com.hwadee.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Autowired
IOrderFeignService orderFeignService;
@RequestMapping("/buy/{id}")
public Product buy(@PathVariable Long id) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = orderFeignService.findOrderById(id);
System.out.println(Thread.currentThread().getName()+"\t"+"/buy/"+id+" 执行");
return product;
}
@RequestMapping("/select/{id}")
@SentinelResource(value = "selectById",blockHandler = "handle_selectById",
fallback ="fallback__selectById",
defaultFallback = "defaultFallback_selectById")
public Product selectById(@PathVariable Long id) {
if (id<=0){
throw new RuntimeException("selectById 执行异常");
}
Product product = new Product();
product.setName("selectById 执行");
System.out.println(product);
return product;
}
public Product handle_selectById(Long id, BlockException exception) {
Product product = new Product();
product.setName("哎吆,服务走神了,请稍后重试");
System.out.println(product);
return product;
}
public Product fallback__selectById(Long id) {
Product product = new Product();
product.setName("哎吆,服务走神了,请稍后重试fallback__selectById");
System.out.println(product);
return product;
}
public Product defaultFallback_selectById() {
Product product = new Product();
product.setName("哎吆,服务走神了,请稍后重试defaultFallback_selectById");
System.out.println(product);
return product;
}
}
- 进行测试
先进行单次浏览,发现会发生异常,但会调用降级方法
1s内多次刷新,即使报错,达到热点参数的阈值也会触发热点参数的条件。
6、参数列外项
- 设置规则
当我们对华为手机秒杀进行限流的时候,希望对华为手机的id=5的品牌进行不限流,id=5即使例外项。
参数下标为第0个的参数(后台接收的时候知道是Long,需要选择对应的数据类型),正常情况下QPS阈值为3,超过马上被限流,但是如果id的参数值是5(id=5)的时候,他的QPS阈值为200。
- 进行测试