Sentinel
Sentinel 是面向分布式服务架构的高可用防护组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。
GitHub:Sentinel
官网:Sentinel
Sentinel 功能和设计理念
-
流量控制
任意时间到来的请求往往是不可控的,而系统的处理能力是有限的。这就需要根据系统的能力对流量进行控制。
流量控制有一下几个角度:
- 资源的调用关系(资源的调用链路,资源与资源之间的关系等)
- 运行指标(QPS、线程池、系统负载等)
- 控制的效果(直接限流、冷启动、排队等)
Sentinel的设计可以自由选择控制的角度,并进行灵活的组合,从而达到理想的效果。
-
熔断降级
Sentinel和Hystrix的原则是一致的:当调用链路中某个资源不稳定,则对这个资源的调用进行限制,并让请求快速失败,避免影响其他的资源,导致请求发生堆积,产生雪崩效应。
在限制手段上,Hystrix通过线程池的方式,使资源和资源之间做到了彻底的隔离。缺点是增加了线程切换的成本,还需预先给各个资源做线程池大小分配。
Sentinel对这个问题采取两种手段:
-
通过并发数进行限制
当线程数在特定资源上堆积到一定数量,对该资源的新请求会被拒绝,堆积的线程完成任务后才开始继续接收请求。
-
通过响应时间对资源进行降级
当依赖的资源响应时间过长,所有对该资源的访问都被直接拒绝,直到过了指定的时间窗口之后才恢复。
-
-
系统负载保护
当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载会把本应这台机器承载的流量转发到其他机器上,如果此时其他机器也处在一个边缘状态,增加的流量就会导致这台机器也崩溃,最终导致整个集群不可用。针对这个情况,Sentinel提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围内处理最多的请求。
Sentinel 的主要工作机制如下:
- 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
- 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
- Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。
关于更加详细的介绍,这里就不在介绍了,直接去官网查看官方文档,以及GitHub上面给的例子即可。
demo:sentinel-demo
文档:Sentinel用户文档
上手使用
-
准备工作
上篇博客使用了Spring Cloud Alibaba Nacos作为服务注册中心,并在Idea中使用Alibaba Cloud Tookit插件创建项目。这里沿用昨天的book-service服务,再新建student-service服务,并使用OpenFeign调用book-service的服务。关于OpenFeign声明式服务调用在前面博客也讲解过,直接使用即可。引入OpenFeign依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
先在book-service中编写demo
在student-service中编写demo,并使用OpenFeign调用book-service中的服务。这里要在启动类添加
@EnableFeignClients
在Nacos中确认两个服务均已注册。
补充一下:这里为啥启动类中没有加
@EnableDiscoveryClient
?我们在查看生成的项目中,有一个nacosdiscovery包中有一个NacosDiscoveryConfiguration
,该类中添加了@EnableDiscoveryClient
注解运行student-service调用book-service服务
-
安装Sentinel Dashboard
下载地址:Sentinel Dashboard
下载完成以后,可以在本地运行。
java -jar sentinel-dashboard-1.8.0.jar
。启动以后发现Sentinel Dashboard的默认端口为8080,占用了别的端口,直接修改启动端口:java -jar -Dserver.port=8888 sentinel-dashboard-1.8.0.jar
直接访问ip+port 访问(用户名密码均为sentinel)
这里面暂时是没有服务列表的。需要在服务中进行配置登记。
-
服务调用方(student-service)添加Sentinel依赖。完整核心pom.xml
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.4.RELEASE</spring-boot.version> <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
在配置文件中添加配置:
spring: application: name: student-service cloud: nacos: discovery: username: nacos password: nacos server-addr: localhoost:8848 sentinel: transport: dashboard: localhost:8888
启动项目调用服务后,刷新Sentinel Dashboard页面。
我们可以在这里设置流控、降级。根据不同的情况设置。
设置流控
设置降级
这里简单验证:设置流控
频繁刷新以后
补充:以student-service为例,微服务如何与Sentinel Dashboard通信?
在微服务中引入Sentinel依赖以后,微服务中有一个sentinel-transport-simple-http客户端,该客户端默认在主机上开启一个新的端口(并不会使用微服务的端口),默认从8719开始,如果端口被占用,依次+1再次尝试。
sentinel-transport-simple-http客户端会获取微服务的状态等信息,进行健康检查(默认10秒向Sentinel Dashboard发送心跳包通知健康状态)。而如果我们在Sentinel Dashboard中设置了微服务的流控,降级,则Sentinel Dashboard会调用sentinel-transport-simple-http客户端中提供的API进行设置。
从Sentinel Dashboard中我们看到微服务student-service中的sentinel-transport-simple-http端口为8720(8719已经被占用了)。我们直接访问
ip
+port
+/api
-
上面的方法是在Sentinel Dashboard中设置流控,降级规则,我们也可以在代码中实现:
流量规则
Field 说明 默认值 resource 资源名,资源名是限流规则的作用对象 count 限流阈值 grade 限流阈值类型,QPS 或线程数模式 QPS 模式 limitApp 流控针对的调用来源 default
,代表不区分调用来源strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接) controlBehavior 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 直接拒绝 private void initFlowRule(){ List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //设置资源名称 rule.setResource("/student/info/{stuNo}/{id}"); //设置限流阈值 rule.setCount(100); //设置限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //设置策略 rule.setStrategy(RuleConstant.STRATEGY_DIRECT); //设置流控效果 设为预热 rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); //设置预热时间 rule.setWarmUpPeriodSec(10); //设置调用来源 rule.setLimitApp("default"); rules.add(rule); //定义流量控制规则 FlowRuleManager.loadRules(rules); }
熔断降级规则
Field 说明 默认值 resource 资源名,即规则的作用对象 grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例 count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 timeWindow 熔断时长,单位为 s minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5 statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) private static void initDegradeRule() { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule("/student/info/{stuNo}/{id}"); rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType()) .setCount(0.7) .setMinRequestAmount(100) .setStatIntervalMs(30000) .setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); }
系统保护规则
Field 说明 默认值 highestSystemLoad load1
触发值,用于触发自适应控制阶段-1 (不生效) avgRt 所有入口流量的平均响应时间 -1 (不生效) maxThread 入口流量的最大并发数 -1 (不生效) qps 所有入口资源的 QPS -1 (不生效) highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效) private void initSystemProtectionRule() { List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(1.0); rules.add(rule); SystemRuleManager.loadRules(rules); }
更多讲解,参见文档:基本使用 - 资源与规则
-
Sentinel异常处理
通过之前的测试,从页面返回的信息查看不够完整。
这里新建异常处理类,实现
BlockExceptionHandler
/** * 自定义Sentinel异常返回信息 */ @Component public class BlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { String message = null; if(e instanceof FlowException){ message = "请求已被限流!"; } if(e instanceof DegradeException){ message = "熔断!"; } if(e instanceof ParamFlowException){ message = "热点数据限流"; } if(e instanceof SystemBlockException){ message = "系统规则异常"; } if(e instanceof AuthorityException){ message = "授权规则异常"; } httpServletResponse.setStatus(500); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json"); //使用ObjectMapper将对象转换成字符串 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.writeValue(httpServletResponse.getWriter(),new Result(500,message)); } }
补充:统一返回结果类
public class Result<T> { private Integer code; private String message; private T data; public Result(){} public Result(T data){ this.code = 200; this.message = "success"; this.data = data; } public Result(Integer code, T data) { this.code = code; this.data = data; } public Result(Integer code, String message, T data) { this.code = code; this.message = message; this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
设置限流规则以后再次尝试刷新
-
Sentinel与OpenFeign整合
配置文件中新增配置
feign: sentinel: enabled: true
编写fallback回调方法
@FeignClient(name = "book-service",fallbackFactory = BookServiceFallback.class) public interface BookService { @GetMapping("/book/info/{id}") Result<Book> bookMessage(@PathVariable("id")String id); }
回调
@Component public class BookServiceFallback implements FallbackFactory<BookService> { @Override public BookService create(Throwable throwable) { return new BookService() { @Override public Result<Book> bookMessage(String id) { return new Result<>(500,throwable.getMessage(),null); } }; } }
划重点:
- 回调方法实现
FallbackFactory
接口并添加对应的泛型。 - 实现create方法,编写回调逻辑。
- 添加
@Component
注解
- 回调方法实现
-
Sentinel在Nacos配置中心配置持久化
在上述进行试验验证的过程中,会发现项目每次重启以后,设置的限流策略就失效了,需要重新设置。Nacos作为配置中心的讲解,后续在博客中更新。
访问Nacos地址,在配置管理类-》配置列表中新建配置
这里的值与规则列表中的值一致。
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置文件中新建配置
完整配置文件
server:
port: 8050
spring:
application:
name: student-service
cloud:
nacos:
discovery:
username: nacos
password: nacos
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8888
datasource:
flow-bs: #自定义名称
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
dataId: book-service-rule #data id
groupId: DEFAULT_GROUP #分组
ruleType: flow #flow代表流控规则,degrade代表熔断规则
feign:
sentinel:
enabled: true
同理配置熔断规则与限流规则一样操作即可(在Nacos中新建配置)。
在配置文件中新增配置:
验证:
重启项目,调用服务后,直接在Sentinel Dashboard流控规则中可看到。