写在前面
在上一篇文章《微服务架构入门之流量控制与熔断降级解决方案----Sentinel初体验》 当中,我简单介绍了Sentinel的使用。但是,遗留下来了一个问题,那就是Sentinel的配置信息还没有做到持久化,服务重启后,配置信息将丢失。在这篇文章当中,我们主要来解决这个问题。
使用Nacos作为Sentinel配置信息数据源
在默认的sentinel当中,配置信息直接存放在本地内存当中,当服务器重启后,配置信息将丢失。并且,由于是细粒度的配置,需要针对每一个资源进行单独的配置操作,也不利于配置信息的重用。
Sentinel动态规则配置
在sentinel的数据源扩展当中,提供了如下两种模式:
- 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
- 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
我们将使用上篇文章学到的Nacos作为Sentinel的配置信息的数据源(推模式),从而实现Sentinel配置信息的持久化和动态配置。
在这之前,如果你还没有学习过Nacos,那你需要先学习nacos的内容,可以阅读我的上一篇文章《微服务架构入门之服务注册发现与配置中心|Spring Cloud Alibaba Nacos》 。
引入依赖
首先,我们需要在原有的依赖当中,加入sentinel的nacos数据源依赖。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.5.2</version>
</dependency>
添加配置
server:
port: 9004
tomcat:
uri-encoding: UTF-8
spring:
application:
name: sentinel-demo
cloud:
nacos:
server-addr: 172.17.172.49:8848
sentinel:
transport:
dashboard: 127.0.0.1:8080
datasource:
# 名字随便起
ds1:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}-sentinel
groupId: DEFAULT_GROUP
# 规则类型,这里是限流规则
#取值见: org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
ds2:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}-sentinel-degrade
groupId: DEFAULT_GROUP
# 降级规则
rule-type: degrade
ds3:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}-sentinel-param-flow
groupId: DEFAULT_GROUP
# 热点参数限流规则
rule-type: param-flow
ds4:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}-sentinel-system
groupId: DEFAULT_GROUP
# 系统自适应限流
rule-type: system
ds5:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}-sentinel-authority
groupId: DEFAULT_GROUP
# 黑白名单配置
rule-type: authority
在nacos中添加对应的sentinel规则配置
限流规则
[
{
"resource": "/test/info",
"limitApp": "default",
"grade": 1,
"count": 3,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
关于配置信息的说明:
resource:资源名,即限流规则的作用对象limitApp:流控针对的调用来源,若为 default 则不区分调用来源grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制count:限流阈值strategy:调用关系限流策略,0 表示直接,1 表示关联,2 表示链路;controlBehavior:流量控制效果(0 直接拒绝、1 Warm Up、2 匀速排队)clusterMode:是否为集群模式
熔断降级规则
[
{
"resource": "/test/info",
"grade": 2,
"count": 3,
"timeWindow": 10,
"minRequestAmount": 1,
"statIntervalMs": 1000
}
]
关于配置信息的说明:
resource: 资源名grade: 熔断策略,支持慢调用比例(0)/异常比例(1)/异常数策略(2)count: 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值timeWindow: 熔断时长,单位为 sminRequestAmount: 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)statIntervalMs: 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)slowRatioThreshold: 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
热点参数限流配置:
[
{
"resource": "/test/getInfoById",
"count": 3,
"durationInSec": 1,
"controlBehavior": 0,
"maxQueueingTimeMs": 3000,
"paramIdx": 0
}
]
关于配置信息的说明:
resource: 资源名count: 限流阈值durationInSec: 统计窗口时间长度(单位为秒),1.6.0 版本开始支持controlBehavior: 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持maxQueueingTimeMs: 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持paramIdx: 参数索引值,从0开始
系统自适应限流配置
[
{
"highestSystemLoad": 3
},
{
"highestCpuUsage": 0.8
},
{
"avgRt": 10
},
{
"qps": 20
},
{
"maxThread": 20
}
]
关于配置信息的说明:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt估算得出。设定参考值一般是CPU cores * 2.5。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
黑白名单配置:
[
{
"resource": "/test/info",
"limitApp": "sentinel-demo-client",
"strategy": 0
}
关于配置信息的说明:
resource: 资源名limitApp: 对应的黑名单/白名单,不同 origin 用,分隔,如appA,appB。strategy: 名单类型,0表示白名单,1表示黑名单。默认为白名单模式。
启动并测试
-
启动nacos、sentinel和本模块
-
访问接口
-
检查Sentinel控制台,看是否收到nacos的推送信息
流控规则
熔断降级规则
热点参数限流规则
系统自适应限流规则
权限规则
可以看到sentinel已经收到了nacos的推送。
关于流量控制规则修改的说明
-
Sentinel控制台中修改规则:仅存在于服务的内存中,不会修改Nacos中的配置值,重启后恢复原来的值。
-
Nacos控制台中修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。
自定义降级消息提示
Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等
属性说明:
-
value:资源名称,必需项(不能为空) -
entryType:entry 类型,可选项(默认为EntryType.OUT) -
blockHandler/blockHandlerClass:blockHandler对应处理BlockException的函数名称,可选项。blockHandler 函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass为对应的类的Class对象,注意对应的函数必需为 static 函数,否则无法解析。 -
fallback/fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass为对应的类的Class对象,注意对应的函数必需为 static 函数,否则无法解析。
-
defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass为对应的类的Class对象,注意对应的函数必需为 static 函数,否则无法解析。
-
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注意:
- @SentinelResource 不支持
private方法 1.6.0之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。- 如果 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出
BlockException时只会进入blockHandler处理逻辑。 - 若未配置
blockHandler、fallback和defaultFallback,则被限流降级时会将BlockException直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层UndeclaredThrowableException)。
使用步骤
引入依赖
如果使用sentinel的aop处理,需要引入以下依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>
使用
/**测试接口
* @author 赖柄沣 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/26 10:37
*/
@RestController
@RequestMapping("/test")
public class DemoController {
/**
* 原函数
* @return map
*/
@SentinelResource(value = "/test/info",
blockHandler = "blockExceptionHandler",
fallback = "infoFallback",
exceptionsToIgnore = RuntimeException.class
)
@GetMapping("/info")
public Map info() {
HashMap<String, Object> map = new HashMap<>(3);
map.put("code",0);
map.put("msg","Wxb: Java开发实践");
return map;
}
/**处理降级异常
* fallback函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
* @return
*/
public Map infoFallback() {
HashMap<String, Object> map = new HashMap<>(3);
map.put("code",501);
map.put("msg","正忙,请稍后重试");
return map;
}
/**处理限流异常
* Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
* @return
*/
public Map backExceptionhandler(BlockException e){
HashMap<String, Object> map = new HashMap<>(3);
map.put("code",500);
map.put("msg","系统正忙,请稍后重试");
return map;
}
}
启动并测试
在浏览器中快速访问接口
注意:1. 如果异常被@Exceptionhandler拦截处理了,这将使得sentinel配置的降级策略发生错误。被@Exceptionhandler处理的异常将不被统计到统计信息中
##Sentinel对RestTemplate的支持·@SentinelRestTemplate
在上面的例子当中,我们通过配置限流、降级的处理函数,从而使得发生限流、降级时能得到相应的处理。但是,可以看到,限流、降级两个方法的格式有强制的要求,因此,这两个方法很难做到复用。如果想要复用就得统一参数、统一返回结果。或者真对每个sentinel资源都编写对应的限流和降级处理函数,这显然不现实。
sentinel支持在服务上游(服务调用方)定义降级策略,并可以编写统一的降级处理方法。
实现步骤
我们新建一个模块sentinel-demo-client,演示服务调用。
1. 添加依赖
<dependencies>
<!-- nacos服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel服务流控-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 编写启动类
/**
* @author 赖柄沣 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/30 20:22
*/
@SpringBootApplication(scanBasePackages = "pers.lbf.spring.cloud.alibaba.demo.sentinel.demo.client")
@EnableDiscoveryClient
public class ClientApp {
public static void main(String[] args) {
SpringApplication.run(ClientApp.class,args);
}
}
3. 编写配置
server:
port: 8010
spring:
application:
name: sentinel-demo-client
cloud:
nacos:
server-addr: 172.17.172.49:8848
sentinel:
transport:
dashboard: 127.0.0.1:8080
4. 编写限流、降级处理逻辑
/**sentinel限流、降级处理类
* @author 赖柄沣 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/30 20:25
*/
public class SentinelUtils {
/**
* 限流处理函数
* @param request
* @param body
* @param execution
* @param ex
* @return
*/
public static SentinelClientHttpResponse handleException(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
System.err.println("fallback: 服务被限流" );
return new SentinelClientHttpResponse("系统正忙,请稍后重试");
}
/**
* 服务降级处理函数
* @param request
* @param body
* @param execution
* @param ex
* @return
*/
public static SentinelClientHttpResponse defaultFallbackMethod(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
System.err.println("fallback: 服务被降级" );
return new SentinelClientHttpResponse("系统正忙,请稍后重试1");
}
}
5 配置RestTemplate
我们需要配置sentinel支持的RestTemplate,从而使得服务提供方发生降级、限流时能得到处理。配置的方式很简单,只需要在注入RestTemplate时标记@SentinelRestTemlpate注解即可。
/**
* @author 赖柄沣 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/30 20:22
*/
@SpringBootApplication(scanBasePackages = "pers.lbf.spring.cloud.alibaba.demo.sentinel.demo.client")
@EnableDiscoveryClient
public class ClientApp {
public static void main(String[] args) {
SpringApplication.run(ClientApp.class,args);
}
@Bean
@SentinelRestTemplate(fallbackClass = SentinelUtils.class,
fallback = "defaultFallbackMethod",
blockHandlerClass = SentinelUtils.class,
blockHandler = "handleException")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
SentinelRestTemlpate参数说明:
fallbackClass: 降级处理函数所在类fallback: 对应的降级处理函数名blockHandlerClass: 限流处理函数所在类blockHandler: 限流处理函数
注意:fallback和blockhandler方法的方法签名和方法返回类型是固定写法,并且这两个方法必须是静态方法
6 编写测试接口,调用服务
/**测试接口,负责演示调用服务
* @author 赖柄沣 bingfengdev@aliyun.com
* @version 1.0
* @date 2020/10/30 20:52
*/
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
public ResultVO test() throws Exception{
ResultVO resultVO = restTemplate.getForObject("http://127.0.0.1:9004/test/info", ResultVO.class);
System.out.println(Objects.requireNonNull(resultVO).toString());
return resultVO;
}
}
7 启动nacos、sentinel和sentinel两个测试模块,添加服务降级配置
添加降级配置:
这里需要注意的是,我们在服务调用方添加对被调用服务的降级配置,这时,达到触发降级(熔断)条件时,将执行降级逻辑,不再进行远程服务调用。并且,熔断时长超过后,再次调用时,一但这次调用还是发生异常的话,将再次触发熔断机制。
测试:
可以看到第二批次访问时,由于第一个请求没有成功,所以再次发生熔断
写在最后
关于sentine入门我们就简单介绍到这里。
如果您觉得这篇文章能给您带来帮助,那么可以点赞鼓励一下。如有错误之处,还请不吝赐教。在此,谢过各位乡亲父老!
在下一篇Spring Cloud Alibaba系列的文章当中,我们将开始学习微服务之服务调用。
**本文代码链接:**github.com/code81192/a…