Sentinel:万字详解微服务的哨兵机制,我跪了,mysql 编程入门教程
private static void initFlowRules(){List<FlowRule> rules = new ArrayList<>();FlowRule rule = new FlowRule();rule.setResource("HelloWorld");rule.setGrade(RuleConstant.FLOW_GRADE_QPS);// Set limit QPS to 20.rule.setCount(20);rules.add(rule);FlowRuleManager.loadRules(rules);}
完成!
完整的代码如下:
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">4 <modelVersion>4.0.0</modelVersion>5 <parent>6 <groupId>org.springframework.boot</groupId>7 <artifactId>spring-boot-starter-parent</artifactId>8 <version>2.2.2.RELEASE</version>9 <relativePath/> <!-- lookup parent from repository -->10 </parent>11 <groupId>com.cjs.example</groupId>12 <artifactId>sentinel-example</artifactId>13 <version>0.0.1-SNAPSHOT</version>14 <name>sentinel-example</name>1516 <properties>17 <java.version>1.8</java.version>18 <spring-cloud.version>Greenwich.SR4</spring-cloud.version>19 <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>20 </properties>2122 <dependencies>23 <dependency>24 <groupId>org.springframework.boot</groupId>25 <artifactId>spring-boot-starter-actuator</artifactId>26 </dependency>27 <dependency>28 <groupId>org.springframework.boot</groupId>29 <artifactId>spring-boot-starter-web</artifactId>30 </dependency>31 <dependency>32 <groupId>com.alibaba.cloud</groupId>33 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>34 </dependency>3536 </dependencies>3738 <dependencyManagement>39 <dependencies>40 <dependency>41 <groupId>org.springframework.cloud</groupId>42 <artifactId>spring-cloud-dependencies</artifactId>43 <version>{spring-cloud-alibaba.version}</version>52 <type>pom</type>53 <scope>import</scope>54 </dependency>55 </dependencies>56 </dependencyManagement>5758 <build>59 <plugins>60 <plugin>61 <groupId>org.springframework.boot</groupId>62 <artifactId>spring-boot-maven-plugin</artifactId>63 </plugin>64 </plugins>65 </build>6667 </project>
application.properties
server.port=8084spring.application.name=sentinel-examplespring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
SentinelExampleApplication.java
1 package com.cjs.example.sentinel;23 import com.alibaba.csp.sentinel.Entry;4 import com.alibaba.csp.sentinel.SphU;5 import com.alibaba.csp.sentinel.slots.block.BlockException;6 import com.alibaba.csp.sentinel.slots.block.RuleConstant;7 import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;8 import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;9 import org.springframework.boot.SpringApplication;10 import org.springframework.boot.autoconfigure.SpringBootApplication;1112 import java.util.ArrayList;13 import java.util.List;1415 @SpringBootApplication16 public class SentinelExampleApplication {1718 public static void main(String[] args) {19 SpringApplication.run(SentinelExampleApplication.class, args);202122 // 配置规则.23 initFlowRules();2425 while (true) {26 // 1.5.0 版本开始可以直接利用 try-with-resources 特性,自动 exit entry27 try (Entry entry = SphU.entry("HelloWorld")) {28 // 被保护的逻辑 29 System.out.println("hello world");30 } catch (BlockException ex) {31 // 处理被流控的逻辑 32 System.out.println("blocked!");33 }34 }35 }363738 private static void initFlowRules() {39 List<FlowRule> rules = new ArrayList<>();4041 FlowRule rule = new FlowRule();42 rule.setResource("HelloWorld");43 rule.setGrade(RuleConstant.FLOW_GRADE_QPS);44 // Set limit QPS to 20.45 rule.setCount(20);46 rules.add(rule);4748 FlowRuleManager.loadRules(rules);4950 }51 }
TestController.java
1 package com.cjs.example.sentinel;23 import com.alibaba.csp.sentinel.annotation.SentinelResource;4 import org.springframework.web.bind.annotation.GetMapping;5 import org.springframework.web.bind.annotation.RestController;67 @RestController8 public class TestController {910 @GetMapping("/hello")11 @SentinelResource("hello")12 public String hello() {13 return "hello";14 }1516 }
3. Sentinel 控制台
Sentinel 控制台最少应该包含如下功能:
查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
规则管理和推送:统一管理推送规则。
鉴权:生产环境中鉴权非常重要。
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
获取控制台:
方式一:下载已经打好的包
https://github.com/alibaba/Sentinel/releases?
wget https://github.com/alibaba/Sentinel/releases/download/1.7.1/sentinel-dashboard-1.7.1.jar
方式二:通过源码构件?
mvn clean package?
启动控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
默认用户名密码都是 sentinel?
4. Sentinel 注解支持
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项
@SentinelResource 注解的属性:
value :资源名称
entryType :the entry type (inbound or outbound)
blockHandler/blockHandlerClass :?blockHandler 是对应处理 BlockException 的函数名称。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass。blockHandlerClass 为对应函数所在类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallback :fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了?
exceptionsToIgnore
?里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:defaultFallback :默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑返回值类型必须与原函数返回值类型一致;
方法参数列表需要和原函数一致,或者可以额外多一个?
Throwable
?类型的参数用于接收对应的异常;fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定?
fallbackClass
?为对应的类的?Class
?对象,注意对应的函数必需为 static 函数,否则无法解析;exceptionsToIgnore :用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出?
需要注意的是,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。?
示例:
1 public class TestService {23 // 对应的 handleException
函数需要位于 ExceptionUtil
类中,并且必须为 static 函数.4 @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})5 public void test() {6 System.out.println("Test");7 }89 // 原函数 10 @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")11 public String hello(long s) {12 return String.format("Hello at %d", s);13 }14
15 // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.16 public String helloFallback(long s) {17 return String.format("Halooooo %d", s);18 }1920 // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.21 public String exceptionHandler(long s, BlockException ex) {22 // Do some log here.23 ex.printStackTrace();24 return "Oops, error occurred at " + s;25 }26 }
可以看到,blockHandler 和 fallback 必须与原方法在同一个类中。如果不想写在同一个类中,可以利用 blockHandlerClass 来指定类,然后通过 blockHandler 指定方法名。
如果同时配置了 blockHandler 和 fallback,则 BlockException 只会进到 blockHandler 处理逻辑中。
5. Sentinel 基本概念
资源
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。?
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。?
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:?
流量控制有以下几个角度:
资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
运行指标,例如 QPS、线程池、系统负载等;
控制的效果,例如直接限流、冷启动、排队等;?
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
熔断降级?
Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池隔离的方式,来对依赖(在 Sentinel 的概念中对应?资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。?如下图:
Sentinel 对这个问题采取了两种手段:
通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Senti
nel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
6. 如何使用 Sentinel
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
定义资源
定义规则
检验规则是否生效?
在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。
定义资源的常用方式
方式一:?抛出异常的方式定义资源
SphU
?包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出?BlockException
。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:
1 // 1.5.0 版本开始可以利用 try-with-resources 特性 2 // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。3 try (Entry entry = SphU.entry("resourceName")) {4 // 被保护的业务逻辑 5 // do something here...6 } catch (BlockException ex) {7 // 资源访问阻止,被限流或被降级 8 // 在此处进行相应的处理操作 9 }
特别地,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。
手动 exit 示例:
1 Entry entry = null;2 // 务必保证 finally 会被执行 3 try {4 // 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名 5 // EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效 6 entry = SphU.entry("自定义资源名");7 // 被保护的业务逻辑 8 // do something...9 } catch (BlockException ex) {10 // 资源访问阻止,被限流或被降级 11 // 进行相应的处理操作 12 } catch (Exception ex) {13 // 若需要配置降级规则,需要通过这种方式记录业务异常 14 Tracer.traceEntry(ex, entry);15 } finally {16 // 务必保证 exit,务必保证每个 entry 与 exit 配对 17 if (entry != null) {18 entry.exit();19 }20 }
热点参数埋点示例:
1 Entry entry = null;2 try {3 // 若需要配置例外项,则传入的参数只支持基本类型。4 // EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效 5 // count 大多数情况都填 1,代表统计为一次调用。6 entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);7 // Your logic here.8 } catch (BlockException ex) {9 // Handle request rejection.10 } finally {11 // 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。12 if (entry != null) {13 entry.exit(1, paramA, paramB);14 }15 }
SphU.entry()
?的参数描述:
方式二、注解方式定义资源
Sentinel 支持通过?@SentinelResource
?注解定义资源并配置?blockHandler
?和?fallback
?函数来进行限流之后的处理。示例:
1 // 原本的业务方法.2 @SentinelResource(blockHandler = "blockHandlerForGetUser")3 public User getUserById(String id) {4 throw new RuntimeException("getUserById command failed");5 }67 // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 8 public User blockHandlerForGetUser(String id, BlockException ex) {9 return new User("admin");10 }
评论