Springcloud基础知识(13)- Spring Cloud Alibaba Sentinel (一) | Sentinel 控制台、Sentinel 整合、定义资源
作者:互联网
Sentinel 是由阿里巴巴中间件团队开发的开源项目,是一种面向分布式微服务架构的轻量级高可用流量控制组件。
Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度帮助用户保护服务的稳定性。
Sentinel 具有以下优势:
(1) 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的“双十一”大促流量的核心场景,例如秒杀(将突发流量控制在系统可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用服务等。
(2) 完备的实时监控:Sentinel 提供了实时监控功能。用户可以在控制台中看到接入应用的单台机器的秒级数据,甚至是 500 台以下规模集群的汇总运行情况。
(3) 广泛的开源生态:Sentinel 提供了开箱即用的与其它开源框架或库(例如 Spring Cloud、Apache Dubbo、gRPC、Quarkus)的整合模块。我们只要在项目中引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。此外,Sentinel 还提供 Java、Go 以及 C++ 等多语言的原生实现。
(4) 完善的 SPI 扩展机制:SPI(Service Provider Interface),是一种服务发现机制。它可以在 ClassPath 路径下的 META-INF/services 文件夹查找文件,并自动加载文件中定义的类。Sentinel 提供简单易、完善的 SPI 扩展接口,我们可以通过实现这些扩展接口快速地定制逻辑,例如定制规则管理、适配动态数据源等。
从功能上来说,Sentinel 与 Spring Cloud Netfilx Hystrix 类似,但 Sentinel 要比 Hystrix 更加强大,例如 Sentinel 提供了流量控制功能、比 Hystrix 更加完善的实时监控功能等等。
Sentinel 主要由以下两个部分组成:
(1) Sentinel 核心库:Sentinel 的核心库不依赖任何框架或库,能够运行于 Java 8 及以上的版本的运行时环境中,同时对 Spring Cloud、Dubbo 等微服务框架提供了很好的支持。
(2) Sentinel 控制台(Dashboard):Sentinel 提供的一个轻量级的开源控制台,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。
Sentinel 核心库不依赖 Sentinel Dashboard,但两者结合使用可以有效的提高效率,让 Sentinel 发挥它最大的作用。
Sentinel 的基本概念有两个,它们分别是:资源和规则。
基本概念 | 描述 |
资源 |
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如由应用程序提供的服务或者是服务里的方法,甚至可以是一段代码。 我们可以通过 Sentinel 提供的 API 来定义一个资源,使其能够被 Sentinel 保护起来。 通常情况下,我们可以使用方法名、URL 甚至是服务名来作为资源名来描述某个资源。 |
规则 | 围绕资源而设定的规则。Sentinel 支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则,所有这些规则都可以动态实时调整。 |
@SentinelResource 注解是 Sentinel 提供的最重要的注解之一,它还包含了多个属性,如下表。
属性 | 描述 |
value | 必填项,用于指定资源的名称。 |
entryType | 可选项(默认为 EntryType.OUT),entry 类型。 |
blockHandler |
可选项,服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。 简单点说,该属性用于指定服务限流后的后续处理逻辑。 |
blockHandlerClass | 可选项,若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。不能单独使用,必须与 blockHandler 属性配合使用;该属性指定的类中的 blockHandler 函数必须为 static 函数,否则无法解析。 |
fallback | 可选项,用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
fallbackClass | 可选项,若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 不能单独使用,必须与 fallback 或 defaultFallback 属性配合使用;该属性指定的类中的 fallback 函数必须为 static 函数,否则无法解析。 |
defaultFallback | 可选项,默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
exceptionsToIgnore | 可选项,用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 |
注:在 Sentinel 1.6.0 之前,fallback 函数只针对降级异常(DegradeException)进行处理,不能处理业务异常。
Sentinel: https://sentinelguard.io/zh-cn/docs/introduction.html
Sentinel GitHub: https://github.com/alibaba/Sentinel/
1. Sentinel 控制台
Sentinel 提供了一个轻量级的开源控制台 Sentinel Dashboard,它提供了机器发现与健康情况管理、监控(单机和集群)、规则管理与推送等多种功能。
Sentinel 控制台提供的功能如下:
(1) 查看机器列表以及健康情况:Sentinel 控制台能够收集 Sentinel 客户端发送的心跳包,判断机器是否在线。
(2) 监控(单机和集群聚合):Sentinel 控制台通过 Sentinel 客户端暴露的监控 API,可以实现秒级的实时监控。
(3) 规则管理和推送:通过 Sentinel 控制台,我们还能够针对资源定义和推送规则。
(4) 鉴权:从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
Sentinel Dashboard 是配置和管理规则(例如流控规则、熔断降级规则等)的重要入口之一。通过它,不仅可以对规则进行配置和管理,还能实时查看规则的效果。
本文以 Sentinel Dashboard 1.8.2 为例,演示 Windows 下安装和运行 Sentinel Dashboard,步骤如下。
1) 下载
浏览器访问 Sentinel Dashboard 下载页面(https://github.com/alibaba/Sentinel/releases/tag/1.8.2),并在页面最下方点击链接 sentinel-dashboard-1.8.2.jar。
打开命令行窗口,进入 sentinel-dashboard-1.8.2.jar 所在的目录,执行以下命令,启动 Sentinel Dashboard。
> java -jar sentinel-dashboard-1.8.2.jar
2) 访问控制台页面
浏览器访问 “http://localhost:8080/”,跳转到登陆页面,输入登录名和密码(默认 sentinel/sentinel),点击提交按钮,跳转到 Sentinel Dashboard 控制台页面。
2. Sentinel 整合
为了减少开发的复杂程度,Sentinel 对大部分的主流框架都进行了适配,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux 和 Reactor 等。以 Spring Cloud 为例,我们只需要引入 spring-cloud-starter-alibaba-sentinel 的依赖,就可以方便地将 Sentinel 整合到项目中。
本文在 “ Springcloud基础知识(11)- Spring Cloud Alibaba Nacos (一) | Nacos 简介、服务注册中心 ” 里 SpringcloudDemo04 项目基础上 ,演示将 Sentinel 整合到 Spring Cloud 项目中。
1) 创建 ServiceSentinel 子模块
选择左上的项目列表中的 SpringcloudDemo04,点击鼠标右键,选择 New -> Module 进入 New Module 页面:
Maven -> Project SDK: 1.8 -> Check "Create from archtype" -> select "org.apache.maven.archtypes:maven-archtype-quickstart" -> Next
Name: ServiceSentinel
GroupId: com.example
ArtifactId: ServiceSentinel
-> Finish
2) 修改 pom.xml,内容如下。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 5 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6 <parent> 7 <artifactId>SpringcloudDemo04</artifactId> 8 <groupId>com.example</groupId> 9 <version>1.0-SNAPSHOT</version> 10 </parent> 11 <modelVersion>4.0.0</modelVersion> 12 <artifactId>ServiceSentinel</artifactId> 13 <name>ServiceSentinel</name> 14 15 <!-- FIXME change it to the project's website --> 16 <url>http://www.example.com</url> 17 18 <properties> 19 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 20 <maven.compiler.source>1.8</maven.compiler.source> 21 <maven.compiler.target>1.8</maven.compiler.target> 22 </properties> 23 24 <dependencies> 25 <dependency> 26 <groupId>junit</groupId> 27 <artifactId>junit</artifactId> 28 <scope>test</scope> 29 </dependency> 30 <dependency> 31 <groupId>org.springframework.boot</groupId> 32 <artifactId>spring-boot-starter-web</artifactId> 33 </dependency> 34 <dependency> 35 <groupId>org.springframework.boot</groupId> 36 <artifactId>spring-boot-starter-test</artifactId> 37 <scope>test</scope> 38 </dependency> 39 <!-- Nacos 依赖 --> 40 <dependency> 41 <groupId>com.alibaba.cloud</groupId> 42 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> 43 </dependency> 44 <!-- Sentinel 依赖 --> 45 <dependency> 46 <groupId>com.alibaba.cloud</groupId> 47 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> 48 </dependency> 49 <dependency> 50 <groupId>com.alibaba.csp</groupId> 51 <artifactId>sentinel-datasource-nacos</artifactId> 52 </dependency> 53 <dependency> 54 <groupId>org.springframework.boot</groupId> 55 <artifactId>spring-boot-starter-actuator</artifactId> 56 </dependency> 57 </dependencies> 58 59 <build> 60 <plugins> 61 <plugin> 62 <groupId>org.springframework.boot</groupId> 63 <artifactId>spring-boot-maven-plugin</artifactId> 64 <configuration> 65 <mainClass>com.example.App</mainClass> 66 <layout>JAR</layout> 67 </configuration> 68 <executions> 69 <execution> 70 <goals> 71 <goal>repackage</goal> 72 </goals> 73 </execution> 74 </executions> 75 </plugin> 76 </plugins> 77 </build> 78 79 </project>
3) 创建 src/main/resources/application.yml 文件
1 server: 2 port: 7001 # 端口 3 spring: 4 application: 5 name: service-sentinel # 服务名 6 cloud: 7 nacos: 8 discovery: 9 # Nacos 服务注册中心(集群)地址 10 server-addr: 127.0.0.1:8848 11 sentinel: 12 transport: 13 # 配置 Sentinel dashboard 地址 14 dashboard: 127.0.0.1:8080 15 # 默认 8719 端口,假如被占用会自动从 8719 开始依次+1扫描, 直至找到未被占用的端口 16 port: 8719 17 management: 18 endpoints: 19 web: 20 exposure: 21 include: '*'
4) 创建 src/main/java/com/example/controller/IndexController.java 文件
1 package com.example.controller; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.PathVariable; 6 import org.springframework.web.bind.annotation.RestController; 7 8 @RestController 9 public class IndexController { 10 @Value("${server.port}") 11 private String serverPort; 12 @Value("${spring.application.name}") 13 private String appName; 14 15 @GetMapping(value = "/service/{id}") 16 public String getService(@PathVariable("id") Integer id) { 17 18 String str = "<h2>Nacos - Service Sentinel</h2>"; 19 str += "<p>Service Name: " + appName + "<br>"; 20 str += "Port: " + serverPort + "<br>"; 21 str += "Id: " + id + "</p>"; 22 23 return str; 24 } 25 }
5) 修改 src/main/java/com/example/App.java 文件
1 package com.example; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 7 @SpringBootApplication 8 @EnableDiscoveryClient 9 public class App { 10 public static void main(String[] args) { 11 SpringApplication.run(App.class, args); 12 } 13 }
6) 运行
依次启动 Nacos Server 或集群、 ServiceSentinel 模块,浏览器访问 http://localhost:7001/service/1,显示结果如下。
Nacos - Service Sentinel
Service Name: service-sentinel
Port: 7001
Id: 1
浏览器访问 http://localhost:8080/,发现在 “首页” 下方新增了一个 “service-sentinel” 的菜单,而这正是 ServiceSentinel 模块的服务名(spring.application.name),说明 Sentinel 已经监控到这个服务。
点击 “实时监控”,查看 service-sentinel 下各请求的实时监控数据。
3. 定义资源
资源是 Sentinel 中的核心概念之一。在项目开发时,只需要考虑这个服务、方法或代码是否需要保护,如果需要保护,就可以将它定义为一个资源。
Sentinel 提供了多种定义资源的方式:
(1) 适配主流框架自动定义资源
(2) 通过 SphU 手动定义资源
(3) 通过 SphO 手动定义资源
(4) 注解方式定义资源
1) 适配主流框架自动定义资源
Sentinel 对大部分的主流框架都进行了适配,只要引入相关的适配模块(例如 spring-cloud-starter-alibaba-sentinel),Snetinel 就会自动将项目中的服务(包括调用端和服务端)定义为资源,资源名就是服务的请求路径。此时,只要再定义一些规则,这些资源就可以享受到 Sentinel 的保护。
可以在 Sentinel 控制台的 “簇点链路” 中,直接查看被 Sentinel 监控的资源。
2) 通过 SphU 手动定义资源
Sentinel 提供了一个名为 SphU 的类,它包含的 try-catch 风格的 API ,可以帮助我们手动定义资源。
示例,在 ServiceSentinel 模块上修改如下。
(1) 修改 src/main/java/com/example/controller/IndexController.java 文件
1 package com.example.controller; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.PathVariable; 6 import org.springframework.web.bind.annotation.RestController; 7 8 import com.alibaba.csp.sentinel.Entry; 9 import com.alibaba.csp.sentinel.SphU; 10 import com.alibaba.csp.sentinel.slots.block.BlockException; 11 12 @RestController 13 public class IndexController { 14 @Value("${server.port}") 15 private String serverPort; 16 @Value("${spring.application.name}") 17 private String appName; 18 19 @GetMapping(value = "/service/{id}") 20 public String getService(@PathVariable("id") Integer id) { 21 22 String str = "<h2>Nacos - Service Sentinel</h2>"; 23 str += "<p>Service Name: " + appName + "<br>"; 24 str += "Port: " + serverPort + "<br>"; 25 str += "Id: " + id + "</p>"; 26 27 return str; 28 } 29 30 @GetMapping(value = "/service/sphu") 31 public String sphu() { 32 return sphuRes(); 33 } 34 35 public String sphuRes() { 36 37 String str = "<h2>Service Sentinel - SphU Resource</h2>"; 38 str += "<p>Service Name: " + appName + "<br>"; 39 str += "Port: " + serverPort + "</p>"; 40 Entry entry = null; 41 42 try { 43 entry = SphU.entry("sphuRes"); 44 str += "<p>Status: success</p>"; 45 } catch (BlockException e) { 46 str += "<p>Status: service restricted</p>"; 47 } finally { 48 if (entry != null) { 49 entry.exit(); 50 } 51 } 52 53 return str; 54 } 55 }
(2) 访问资源
重启 ServiceSentinel,浏览器访问 http://localhost:7001/service/sphu,结果如下:
Service Sentinel - SphU Resource
Service Name: service-sentinel
Port: 7001
Status: success
3) 通过 SphO 手动定义资源
Sentinel 还提供了一个名为 SphO 的类,它包含了 if-else 风格的 API,能帮助我们手动定义资源。通过这种方式定义的资源,发生了限流之后会返回 false,此时我们可以根据返回值,进行限流之后的逻辑处理。
示例,在 ServiceSentinel 模块上修改如下。
(1) 修改 src/main/java/com/example/controller/IndexController.java 文件
1 package com.example.controller; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.PathVariable; 6 import org.springframework.web.bind.annotation.RestController; 7 8 import com.alibaba.csp.sentinel.Entry; 9 import com.alibaba.csp.sentinel.SphU; 10 import com.alibaba.csp.sentinel.SphO; 11 import com.alibaba.csp.sentinel.slots.block.BlockException; 12 13 @RestController 14 public class IndexController { 15 @Value("${server.port}") 16 private String serverPort; 17 @Value("${spring.application.name}") 18 private String appName; 19 20 @GetMapping(value = "/service/{id}") 21 public String getService(@PathVariable("id") Integer id) { 22 23 String str = "<h2>Nacos - Service Sentinel</h2>"; 24 str += "<p>Service Name: " + appName + "<br>"; 25 str += "Port: " + serverPort + "<br>"; 26 str += "Id: " + id + "</p>"; 27 28 return str; 29 } 30 31 @GetMapping(value = "/service/sphu") 32 public String sphu() { 33 return sphuRes(); 34 } 35 36 public String sphuRes() { 37 38 String str = "<h2>Service Sentinel - SphU Resource</h2>"; 39 str += "<p>Service Name: " + appName + "<br>"; 40 str += "Port: " + serverPort + "</p>"; 41 Entry entry = null; 42 43 try { 44 entry = SphU.entry("sphuRes"); 45 str += "<p>Status: success</p>"; 46 } catch (BlockException e) { 47 str += "<p>Status: service restricted</p>"; 48 } finally { 49 if (entry != null) { 50 entry.exit(); 51 } 52 } 53 54 return str; 55 } 56 57 @GetMapping(value = "/service/spho") 58 public String spho() { 59 return sphoRes(); 60 } 61 62 public String sphoRes() { 63 64 String str = "<h2>Service Sentinel - SphO Resource</h2>"; 65 str += "<p>Service Name: " + appName + "<br>"; 66 str += "Port: " + serverPort + "</p>"; 67 68 if (SphO.entry("sphoRes")) { 69 try { 70 str += "<p>Status: success</p>"; 71 } finally { 72 SphO.exit(); 73 } 74 } else { 75 str += "<p>Status: service restricted</p>"; 76 } 77 78 return str; 79 } 80 }
(2) 访问资源
重启 ServiceSentinel,浏览器访问 http://localhost:7001/service/spho,结果如下:
Service Sentinel - SphO Resource
Service Name: service-sentinel
Port: 7001
Status: success
4) 注解方式定义资源(推荐)
还可以通过 Sentinel 提供的 @SentinelResource 注解定义资源。
示例,在 ServiceSentinel 模块上修改如下。
(1) 修改 src/main/java/com/example/controller/IndexController.java 文件
1 package com.example.controller; 2 3 import com.alibaba.csp.sentinel.annotation.SentinelResource; 4 import org.springframework.beans.factory.annotation.Value; 5 import org.springframework.web.bind.annotation.GetMapping; 6 import org.springframework.web.bind.annotation.PathVariable; 7 import org.springframework.web.bind.annotation.RestController; 8 9 import com.alibaba.csp.sentinel.Entry; 10 import com.alibaba.csp.sentinel.SphU; 11 import com.alibaba.csp.sentinel.SphO; 12 import com.alibaba.csp.sentinel.slots.block.BlockException; 13 14 @RestController 15 public class IndexController { 16 @Value("${server.port}") 17 private String serverPort; 18 @Value("${spring.application.name}") 19 private String appName; 20 21 @GetMapping(value = "/service/{id}") 22 public String getService(@PathVariable("id") Integer id) { 23 24 String str = "<h2>Nacos - Service Sentinel</h2>"; 25 str += "<p>Service Name: " + appName + "<br>"; 26 str += "Port: " + serverPort + "<br>"; 27 str += "Id: " + id + "</p>"; 28 29 return str; 30 } 31 32 @GetMapping(value = "/service/sphu") 33 public String sphu() { 34 return sphuRes(); 35 } 36 37 public String sphuRes() { 38 39 String str = "<h2>Service Sentinel - SphU Resource</h2>"; 40 str += "<p>Service Name: " + appName + "<br>"; 41 str += "Port: " + serverPort + "</p>"; 42 Entry entry = null; 43 44 try { 45 entry = SphU.entry("sphuRes"); 46 str += "<p>Status: success</p>"; 47 } catch (BlockException e) { 48 str += "<p>Status: service restricted</p>"; 49 } finally { 50 if (entry != null) { 51 entry.exit(); 52 } 53 } 54 55 return str; 56 } 57 58 @GetMapping(value = "/service/spho") 59 public String spho() { 60 return sphoRes(); 61 } 62 63 public String sphoRes() { 64 65 String str = "<h2>Service Sentinel - SphO Resource</h2>"; 66 str += "<p>Service Name: " + appName + "<br>"; 67 str += "Port: " + serverPort + "</p>"; 68 69 if (SphO.entry("sphoRes")) { 70 try { 71 str += "<p>Status: success</p>"; 72 } finally { 73 SphO.exit(); 74 } 75 } else { 76 str += "<p>Status: service restricted</p>"; 77 } 78 79 return str; 80 } 81 82 @GetMapping(value = "/service/anno") 83 @SentinelResource(value = "annoRes") 84 public String annoRes() { 85 String str = "<h2>Service Sentinel - Annotation Resource</h2>"; 86 str += "<p>Service Name: " + appName + "<br>"; 87 str += "Port: " + serverPort + "</p>"; 88 89 return str; 90 } 91 92 }
(2) 访问资源
重启 ServiceSentinel,浏览器访问 http://localhost:7001/service/anno,结果如下:
Service Sentinel - Annotation Resource
Service Name: service-sentinel
Port: 7001
标签:13,String,Springcloud,sentinel,str,Sentinel,import,com 来源: https://www.cnblogs.com/tkuang/p/16504296.html