1.引入依赖
<!--sentinel启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.添加yml配置,为微服务设置sentinel控制台地址
添加sentinel后,需要暴露/actuator/sentinel端点,而Springboot默认是没有暴露该端点的,所以需要设置,测试http://localhost:8800//actuator/sentinel
server:
port: 8861
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858
1.流控规则
流控规则(flow control):其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。 ==== FlowRule RT(响应时间) 1/0.2s = 5
同一个资源可以创建多条限流规则,FlowSlot会对资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果。
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS模式(1)或并发线程模式(0) | QPS模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝WarmUp均速+排队等待) 不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
参考文档:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
2.限流阈值类型
QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
QPS
进入簇点链路选择具体的访问的API,然后点击流控按钮。
并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争取(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的overhead比较大,特别是对低延时的调用有较大的影响。Sentinel并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
3.sentinel控制台QPS流控规则
1》添加QPS流控规则
2》代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/add")
public String add(){
log.info("下单成功!");
return "Hello World !";
}
@RequestMapping("/flow")
public String flow(){
return "正常访问!!!";
}
}
3》测试
4.sentinel控制台自定义QPS流控规则
1》代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/flow")
@SentinelResource(value = "flow", blockHandler = "flowBlockHandler")
public String flow(){
return "正常访问!!!";
}
public String flowBlockHandler(BlockException e){
return "自定义流控!!";
}
}
5.sentinel并发线程数流控规则
1》代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
public String flowBlockHandler(BlockException e){
return "自定义流控!!";
}
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread", blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "正常访问!!!";
}
}
3》测试
6.sentinel中的BlockException统一异常处理
BlockException异常统一处理
Springwebmvc接口资源限流入口在HandlerInterceptor的实现类AbstractSentinelInterceptor的preHandle方法中,对异常的处理是BlockExceptionHandler的实现类
sentinel 1.7.1 引入了sentinel-spring-webmvc-adapter.jar
1》自定义BlockExceptionHandler的实现类统一处理BlockException
@Component
@Slf4j
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("BlockExceptionHandler BlockException =======" + 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,"授权规则不通过");
}
//返回json
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(),r);
}
}
2》Result类
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result() {
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static Result error(Integer code, String msg){
return new Result(code, msg);
}
}
3》controller类
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/flow")
public String flow(){
return "正常访问!!!";
}
}
7.流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方; 一个方法可能会调用其它方法,形成一个调用链路的层次关系。
1》直接
资源调用达到设置的阈值后直接被流控抛出异常
2》关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db这两个资源分别代表数据库读写,我们可以给read_db设置限流规则来达到写优先的目的:设置strategy为RuleConstant.STRATEGY_RELATE同时设置refResource为write_db。这样当写操作过于频繁时,读数据的请求会被限流。
(1) 添加规则
(2) controller代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/add")
public String add(){
log.info("下单成功!");
return "生成订单";
}
@RequestMapping("/get")
public String get(){
log.info("查询订单");
return "查询订单";
}
}
(3) 此处需要借助JMeter工具,参考:JMeter下载安装
通过JMeter工具发送/order/add请求, 此时通过浏览器发送/order/get请求就提示限流了。
JMeter工具发送/order/add请求:
3》链路
根据调用链路入口限流。
NodeSelectorSlot中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一颗调用树。这颗树的根节点是一个名字为machine-root的虚拟节点,调用链的入口都是这个虚节点的子节点。
一颗典型的调用树如下图所示:
上图中来自入口/order/test1 和 /order/test2的请求都调用了资源getUser,Sentinel允许只根据某个入口的统计信息对资源限流。
(1) 添加规则
测试会发现链路规则不生效
注意:高版本此功能直接使用不生效,如何解决?
从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即可关闭收敛。
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858
web-context-unify: false # 默认将调用链路收敛
测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHanler处理BlockException
(2) Controller代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
IOrderService orderService;
/**
* 关联流控
*/
@RequestMapping("/test1")
public String test1(){
return orderService.getUser();
}
/**
* 关联流控
* @return
*/
@RequestMapping("/test2")
public String test2(){
return orderService.getUser();
}
}
(3) service类
public interface IOrderService {
public String getUser();
}
(4) serviceImpl类
@Service
public class OrderServiceImpl implements IOrderService{
@Override
@SentinelResource(value = "getUser", blockHandler = "blockHandlerGetUser")
public String getUser() {
return "查询用户";
}
public String blockHandlerGetUser(BlockException e){
return "流控用户";
}
}
快速失败
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流控方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm Up(激增流量)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子:codeFactor默认是3,即请求QPS从threshold / 3 开始,经预热时长逐渐升至设定的QPS阈值。
通常冷启动的过程系统允许通过的QPS曲线如下图所示:
测试:
匀速排队
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
1.JMeter模拟脉冲流量
添加规则:
2.排队等待使用
添加规则:
JMeter如上设置,执行结果:
1.熔断降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级与隔离
保护自身的手段
1》并发控制
2》基于慢调用比例熔断
3》基于异常比例熔断
通常在consumer端组合配置
触发熔断后的处理逻辑示例
1》提供fallback实现(服务降级)
2》返回错误result
3》读缓存(DB访问降级)
熔断降级规则说明
熔断降级规则(DegradeRule)包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0引入) | 5 |
statIntervalMs | 统计时长(单位为ms),如60*1000代表分钟级(1.8.0引入) | 1000ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0引入) |
2.熔断策略
1》慢调用比例
慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
2》添加降级规则
3》JMeter添加请求
4》controller代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/flowThread")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("正常访问!");
return "正常访问!!!";
}
}
5》测试
3.异常比例
异常比例(ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALT-OPEN状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0,1.0],代表0% - 100%。
1》添加降级规则
2》JMeter添加请求
3》controller代码
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/err")
public String err(){
int a = 1/0;
return "error";
}
}
4》测试
4.异常数
异常数(ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断,经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException)不生效。
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/err")
public String err(){
int a = 1/0;
return "error";
}
}
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--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>
</dependencies>
2》application.yml
server:
port: 8041
# 应用名称(nacos会将该名称当做服务名称)
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
username: nacos
password: nacos
namespace: public
feign:
sentinel:
# openfeign整合sentinel
enabled: true
3》openfeign接口
/**
* 添加feign接口和方法
* name 指定调用rest接口所对应的服务名
* path 指定调用rest接口所在的StockController指定的@RequestMapping
*/
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
/**
* 声明需要调用的rest接口对应的方法
* @return
*/
@RequestMapping("/reduct2")
String reduct2();
}
4》openfeign的fallback实现类
@Component
public class StockFeignServiceFallback implements StockFeignService{
@Override
public String reduct2() {
return "降级了~~~~~";
}
}
5》OrderController 类
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
StockFeignService stockFeignService;
@RequestMapping("/add")
public String add(){
log.info("下单成功!");
String result = stockFeignService.reduct2();
return "Hello Feign !" + result;
}
}
6》stock-service 服务中的StockController类
@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {
@Value("${server.port}")
public String port;
@RequestMapping("/reduct2")
public String reduct2(){
int a = 1/0;
log.info("扣减库存!");
return "扣减库存" + port;
}
}
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制,比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
1》热点规则需要使用@SentinelResource(“resourceName”)注解,否则不生效
2》参数必须是7种基本数据类型才会生效
配置热点参数规则
注意:资源名必须是@SentinelResource(value=“资源名”)中配置的资源名,热点规则依赖于注解
controller代码:
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
/**
* 热点规则,必须使用@SentinelResource
* @param id
* @return
*/
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById", blockHandler = "hotBlockHandler")
public String getById(@PathVariable("id") Integer id){
log.info("正常访问");
System.out.println("正常访问");
return "正常访问";
}
public String hotBlockHandler(@PathVariable("id") Integer id, BlockException e){
return "热点异常处理";
}
}
Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
1》Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超时设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的maxQps * minRt估算得出。设定参考值一般是 CPU cores * 2.5。
https://www.cnblogs.com/gentlemanhai/p/8484839.html
2》CPU usage(1.5.0+ 版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
3》平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。
4》并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
5》入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。
1.Sentinel持久化模式
Sentinel规则的推送有下面三种模式:
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | API将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource) | 简单,无任何依赖 | 不保证一致;规则保存在内存中,重启即消失。严重不建议用于生成环境 |
Pull模式 | 扩展写数据源(WritableDataSource),客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是RDBMS、文件等 | 简单,无任何依赖;规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。 |
Push模式 | 扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用Nacos、Zookeeper等配置中心,这种方式有更好的实时性和一致性保证。生成环境下一段采用push模式的数据源。 | 规则持久化;一致性;快速 | 引入第三方依赖 |
1.原始模式
如果不做任何修改,Dashboard的推送规则方式是通过API将规则推送至客户端并直接更新到内存中;
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。
2.拉模式
pull模式的数据源(如本地文件、RDBMS等)一般是可写入的。使用时需要在客户端注册数据源:将对应的数据源注册至对应的RuleManager,将写数据源注册至transport的WritableDataSourceRegistry中。
3.推模式
生产环境下一般更常用的是push模式的数据源。对于push模式的数据源,如远程配置中心(Zookeeper,Nacos,Apollo等等),推送的操作不应由Sentinel客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是配置中心控制台/Sentinel控制台 --> 配置中心 --> Sentinel数据源 --> Sentinel,而不是经Sentinel数据源推送至配置中心。这样的流程就非常清晰了。
1》基于Nacos配置中心控制台实现推送
官方demo:sentinel-demo-nacos-datasource
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
application.yml配置
server:
port: 8861
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858
web-context-unify: false # 默认将调用链路收敛
datasource:
flow-rule: # 可以自定义
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
dataId: order-sentinel-flow-rule
rule-type: flow
controller代码:
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@RequestMapping("/flow")
public String flow(){
return "正常访问!!!";
}
}