目录
7.1.4 同时配置fallback和blockHandler
1. Sentinel概述
1.1 Sentinel简介
As microservices become popular, the stability of service calls is becoming increasingly important. Sentinel takes "flow" as the breakthrough point, and works on multiple fields including flow control, circuit breaking and load protection to protect service reliability.
Sentinel分为两个部分:
- 核心库:不依赖任何框架/库,能够运行于所有Java运行时的环境,同时对Dubbo/Springcloud等框架也有较好的支持。
- Dashboard:基于Springboot开发,能直接运行而不需要额外的Tomcat等应用容器。
2.2 Sentinel安装配置
1) 下载Sentinel的sentinel-dashboard-1.8.1.jar。
2) 启动Sentinel(java -jar sentinel-dashboard-1.8.1.jar)。
3) 访问Sentinel(localhost:8080,登录账户密码均为 sentinel)。
1.3 案例工程初始化
以下案例代码的Github地址。
相关modules是:cloudalibaba-sentinel-service8401。
1) 添加Sentinel依赖。
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2) 配置Sentinel。
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
3) 添加接口:
@GetMapping("/testA")
public String testA() {
return "----testA";
}
@GetMapping("/testB")
public String testB() {
return "----testB";
}
4) 启动cloudalibaba-sentinel-service8401之后Sentinel控制台还是空空如也,因为Sentinel采用了懒加载,只要执行一次接口访问即可。
- localhost:8401/testA
- localhost:8401/testB
5) 回到Sentinel控制台,能看到正在被监控的service。
2. Sentinel流控规则
2.1 相关术语介绍
1) 资源名:唯一名称,默认请求路径。
2) 针对来源:Sentinel可以针对调用者进行限流、填写服务名,默认不区分来源。
3) 阈值类型/单机阈值:
- QPS(每秒请求数量):当调用该api的QPS达到阈值时进行限流。
- 线程数:当调用该api的线程数达到阈值时进行限流。
4) 流控模式:
- 直接:api达到限流条件时直接限流。
- 关联:当关联的资源达到阈值时就限流自己。
- 链路:只记录指定连路上的流量(指定资源从入口资源进来的流量,如果达到阈值就进行限流)。
5) 流控效果。
- 快速失败:直接失败、返回异常。
- Warm up(预热):根据ColdFactor(冷加载因子,默认是3),阈值除以coldFactor,经过预热时长后才会达到阈值并限流。
2.2 流控模式
1) 直接。
- 对接口(/testA)新增流控规则。
- 快速多次访问接口。
注:cloudalibaba-sentinel-service8401服务重启后,新增的监控规则会消失。
2) 关联。
- 接口A(/testA)关联接口B(/testB),当接口B达到阈值就会限流接口A。
- 用postman开启20个线程每隔0.3s请求/testB,这会导致接口A被限流。
3) 链路。
2.3 流控效果
1) 直接。直接失败,抛出异常(Blocked by Sentinel(flowing limit))。
2) 预热。
Warm Up(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。
主要应用场景:系统某一个瞬间流量上来了,很有可能拖垮服务,预热方式就是为了保护服务,慢慢地把流量放进来,将当前阈值增长到设置的阈值。
3) 排队等待。匀速排队,严格控制请求通过的间隔时间。
匀速排队(
RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo。
3. 降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
3.1 慢调用比例
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(
statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
1) 添加降级规则。
2) 如果1s之内请求数大于5且有20%的请求都超过200ms没有反应,那么接下来的1s之内的请求会自动熔断。经过1s后,如果接下来的一个请求响应时间小于200ms就可以结束熔断,否则继续熔断。
3.2 异常比例
异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。
1) 添加降级规则。
2) 如果1s内请求数大于5且异常的比例大于20%,那么接下来1内的请求会自动熔断。经过1s后,如果接下来的一个请求成功就可以结束熔断,否则继续熔断。
3.3 异常数
异常数 (
ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
1) 添加降级规则(异常数是按照分钟统计的)。
2) 如果1分钟内请求数大于5且异常数达到5个,那么接下来1内的请求会自动熔断。经过1s后,如果接下来的一个请求成功就可以结束熔断,否则继续熔断。
4. 热点Key限流
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
1) 添加接口:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
@RequestParam(value = "p2",required = false)String p2) {
return "----testHotKey";
}
public String deal_testHotKey(String p1, String p2, BlockException exception) {
return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
}
2) 添加限流规则。
3) 传递的第一个参数p1,当QPS超过1秒1次点击后马上被限流。
- localhost:8401/testHotKey?p1=1
- localhost:8401/testHotKey?p1=1&p2
4) 特例情况(当p1=5的时候,QPS可以达到200)。
5. 系统规则
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(
EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
1) Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。
2) CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
3) 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
4) 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
5) 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
6. 自定义限流处理逻辑@SentinelResource
1) 创建CustomerBlockHandler类用于自定义限流处理逻辑。
public class CustomerBlockHandler {
public static String handlerException(BlockException exception) {
return "444,按照客户自定义的Glogal 全局异常处理 ---- 1" + exception.getClass().getCanonicalName();
}
public static String handlerException2(BlockException exception) {
return "444,按照客户自定义的Glogal 全局异常处理 ---- 2" + exception.getClass().getCanonicalName();
}
}
2) 添加接口(指定了handlerException2作为限流时的处理方法):
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public String customerBlockHandler() {
return "200,按照客户自定义限流测试 serial003";
}
3) Sentinel控制台配置。
4) QPS是1,所以当我们1s内快速访问时会触发降级。
7. 服务熔断功能
以下案例代码的Github地址。
三个modules分别是:cloudalibaba-consumer-nacos-order84, cloudalibaba-provider-payment9003, cloudalibaba-provider-payment9004。
7.1 Ribbon系列
7.1.1 无fallback和blockHandler
1) cloudalibaba-provider-payment9003添加依赖。
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2) cloudalibaba-provider-payment9003配置application.yml。
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
3) cloudalibaba-provider-payment9003添加接口:
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, String> map = new HashMap<>();
static {
map.put(1L, "1L, 1111");
map.put(1L, "2L, 2222");
map.put(1L, "3L, 3333");
}
@GetMapping(value = "/paymentSQL/{id}")
public String paymentSQL(@PathVariable("id") Long id) {
String payment = map.get(id);
return "from mysql,serverPort: " + serverPort + " , " + payment;
}
4) 创建相同配置的cloudalibaba-provider-payment9004(注意application.yml的端口也改为9004)。
5) cloudalibaba-consumer-nacos-order84添加依赖(和cloudalibaba-provider-payment9004的一样)。
6) cloudalibaba-consumer-nacos-order84配置application.yml。
7) cloudalibaba-consumer-nacos-order84添加接口:
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")
public String fallback(@PathVariable Long id) {
String result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, String.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgument ,非法参数异常...");
} else if (result.contains("null")) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
8) 当前的服务。
9) 访问接口(localhost:84/consumer/fallback/3)。
7.1.2 只有fallback
1) 修改cloudalibaba-consumer-nacos-order84的接口:
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handlerFallback")
public String fallback(@PathVariable Long id) {
String result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, String.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgument ,非法参数异常...");
} else if (result.contains("null")) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
public String handlerFallback(@PathVariable Long id, Throwable e) {
return "异常handlerFallback,exception内容: " + e.getMessage() + ", id: " + id;
}
2) 访问接口(localhost:84/consumer/fallback/3)。
7.1.3 只有blockHandler
1) 修改cloudalibaba-consumer-nacos-order84的接口:
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback", blockHandler = "blockHandler")
public String fallback(@PathVariable Long id) {
String result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, String.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgument ,非法参数异常...");
} else if (result.contains("null")) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
public String blockHandler(@PathVariable Long id, BlockException e) {
return "444,blockHandler-sentinel 限流,BlockException: " + e.getMessage() + ", id: " + id;
}
2) 配置一个QPS。
3) 访问接口(localhost:84/consumer/fallback/3)。
7.1.4 同时配置fallback和blockHandler
1) 修改cloudalibaba-consumer-nacos-order84的接口:
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handlerFallback"
, blockHandler = "blockHandler")
public String fallback(@PathVariable Long id) {
String result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, String.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgument ,非法参数异常...");
} else if (result.contains("null")) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
2) 配置一个QPS(重启服务后流控规则会被清空)。
3) 访问接口(localhost:84/consumer/fallback/3)。
7.1.5 结论
- fallback管运行异常。
- blockHandler管配置违规。
7.2 Feign系列
1) cloudalibaba-consumer-nacos-order84增加Feign依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2) 添加FeignCLient。
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackServiceImpl.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public String paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackServiceImpl implements PaymentService {
@Override
public String paymentSQL(Long id) {
return "444,服务降级返回,---PaymentFallbackService ErrorSerial, id: " + id;
}
}
3) 添加接口:
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public String paymentSQL(@PathVariable("id") Long id) {
return paymentService.paymentSQL(id);
}
4) 访问接口(localhost:84/consumer/paymentSQL/2)。
5) 停掉cloudalibaba-provider-payment9003,cloudalibaba-provider-payment9004后再访问,出发服务降级。
7.3 熔断框架比较
8. Sentinel规则持久化
众所周知,一旦我们重启应用,sentinel的流控规则将消失,所以生产环境需要将配置规则进行持久化。
简单来说,将限流配置规则持久化进Nacos保存,只要刷新8401某个接口,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
1) 添加持久化依赖。
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2) 修改Sentinel配置。
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentin dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true #激活Sentinel 对Feign的支持
3) Nacos添加业务规则配置(对应接口/rateLimit/customerBlockHandler)。
[
{
"resource": "/rateLimit/customerBlockHandler",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- resource:资源名称。
- limitApp:来源应用。
- grade:阈值类型(0表示线程数、1表示QPS)。
- count:单机阈值。
- strategy:流经模式(0表示直接、1表示关联、2表示链路)。
- controlBehavior:流控效果(0表示快速失败、1表示warmup、2表示排队等待)。
- clusterMode:是否集群模式。
4) 查看Sentinel控制台(localhost:8080/#/dashboard/flow/cloudalibaba-sentinel-service)。
5) 快速访问接口(localhost:8401/rateLimit/customerBlockHandler)。
注:重新启动8401再看sentinel流控规则还是会懒加载,也就是需要访问一次接口才会出现在控制台。