基本概念 | 描述 |
---|---|
资源 |
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如由应用程序提供的服务或者是服务里的方法,甚至可以是一段代码。 我们可以通过 Sentinel 提供的 API 来定义一个资源,使其能够被 Sentinel 保护起来。通常情况下,我们可以使用方法名、URL 甚至是服务名来作为资源名来描述某个资源。 |
规则 | 围绕资源而设定的规则。Sentinel 支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则,所有这些规则都可以动态实时调整。 |
属性 | 说明 | 必填与否 | 使用要求 |
---|---|---|---|
value | 用于指定资源的名称 | 必填 | - |
entryType | entry 类型 | 可选项(默认为 EntryType.OUT) | - |
blockHandler |
服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。 简单点说,该属性用于指定服务限流后的后续处理逻辑。 |
可选项 |
|
blockHandlerClass | 若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 | 可选项 |
|
fallback |
用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 |
可选项 |
|
fallbackClass | 若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 | 可选项 |
|
defaultFallback |
默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。 默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 |
可选项 |
|
exceptionsToIgnore | 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 | 可选项 |
- |
图1:Sentinel 控制台下载
java -jar sentinel-dashboard-1.8.2.jar
图2:Sentinel 控制台登录页
图3:Sentinel 控制台主页
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>net.biancheng.c</groupId> <version>1.0-SNAPSHOT</version> <artifactId>spring-cloud-alibaba-demo</artifactId> </parent> <groupId>net.biancheng.c</groupId> <artifactId>spring-cloud-alibaba-sentinel-service-8401</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-alibaba-sentinel-service-8401</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--Nacos 服务发现依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--Snetinel 依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
server: port: 8401 #端口 spring: application: name: sentinel-service #服务名 cloud: nacos: discovery: #Nacos服务注册中心(集群)地址 server-addr: localhost:1111 sentinel: transport: #配置 Sentinel dashboard 地址 dashboard: localhost:8080 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 management: endpoints: web: exposure: include: '*'
package net.biancheng.c.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class SentinelFlowLimitController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/testA")
public String testA() {
return "c语言中文网提醒您,服务访问成功------testA";
}
@GetMapping("/testB")
public String testB() {
return "c语言中文网提醒您,服务访问成功------testB"
}
}
package net.biancheng.c; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudAlibabaSentinelService8401Application { public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaSentinelService8401Application.class, args); } }
图4:Sentinel 示例 1
图5:Sentinel 控制台主页
7. 点击“实时监控”,查看 sentinel-service 下各请求的实时监控数据,如下图所示。
图7:Sentinel 控制台-簇点链路
package net.biancheng.c.controller; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphO; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController @Slf4j public class SentinelFlowLimitController { @Value("${server.port}") private String serverPort; @GetMapping("/testA") public String testA() { return testAbySphU(); } @GetMapping("/testB") public String testB() { return "c语言中文网提醒您,服务访问成功------testB"; } /** * 通过 SphU 手动定义资源 * @return */ public String testAbySphU() { Entry entry = null; try { entry = SphU.entry("testAbySphU"); //您的业务逻辑 - 开始 log.info("c语言中文网提醒您,服务访问成功------testA:"+serverPort); return "c语言中文网提醒您,服务访问成功------testA:"+serverPort; //您的业务逻辑 - 结束 } catch (BlockException e1) { //流控逻辑处理 - 开始 log.info("c语言中文网提醒您,testA 服务被限流"); return "c语言中文网提醒您,testA 服务被限流"; //流控逻辑处理 - 结束 } finally { if (entry != null) { entry.exit(); } } } }
c语言中文网提醒您,服务访问成功------testA:8401
图8:Sentinel 通过 SphU 定义资源
package net.biancheng.c.controller; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphO; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController @Slf4j public class SentinelFlowLimitController { @Value("${server.port}") private String serverPort; @GetMapping("/testA") public String testA() { return testAbySphU(); } @GetMapping("/testB") public String testB() { return testBbySphO(); } /** * 通过 SphU 手动定义资源 * * @return */ public String testAbySphU() { Entry entry = null; try { entry = SphU.entry("testAbySphU"); //您的业务逻辑 - 开始 log.info("c语言中文网提醒您,服务访问成功------testA:" + serverPort); return "c语言中文网提醒您,服务访问成功------testA:" + serverPort; //您的业务逻辑 - 结束 } catch (BlockException e1) { //流控逻辑处理 - 开始 log.info("c语言中文网提醒您,testA 服务被限流"); return "c语言中文网提醒您,testA 服务被限流"; //流控逻辑处理 - 结束 } finally { if (entry != null) { entry.exit(); } } } /** * 通过 SphO 手动定义资源 * * @return */ public String testBbySphO() { if (SphO.entry("testBbySphO")) { // 务必保证finally会被执行 try { log.info("c语言中文网提醒您,服务访问成功------testB:" + serverPort); return "c语言中文网提醒您,服务访问成功------testB:" + serverPort; } finally { SphO.exit(); } } else { // 资源访问阻止,被限流或被降级 //流控逻辑处理 - 开始 log.info("c语言中文网提醒您,testB 服务被限流"); return "c语言中文网提醒您,testB 服务被限流"; //流控逻辑处理 - 结束 } } }
c语言中文网提醒您,服务访问成功------testB:8401
图9:Sentinel 通过 SphO 定义资源
@GetMapping("/testC") @SentinelResource(value = "testCbyAnnotation") //通过注解定义资源 public String testC() { log.info("c语言中文网提醒您,服务访问成功------testC:" + serverPort); return "c语言中文网提醒您,服务访问成功------testC:" + serverPort; }
c语言中文网提醒您,服务访问成功------testC:8401
图10:Sentinel 注解方式定义资源
属性 | 说明 | 默认值 |
---|---|---|
资源名 | 流控规则的作用对象。 | - |
阈值 | 流控的阈值。 | - |
阈值类型 | 流控阈值的类型,包括 QPS 或并发线程数。 | QPS |
针对来源 | 流控针对的调用来源。 | default,表示不区分调用来源 |
流控模式 | 调用关系限流策略,包括直接、链路和关联。 | 直接 |
流控效果 | 流控效果(直接拒绝、Warm Up、匀速排队),不支持按调用关系限流。 | 直接拒绝 |
/** * 通过 Sentinel 控制台定义流控规则 * * @return */ @GetMapping("/testD") public String testD() { log.info("c语言中文网提醒您,服务访问成功------testD:" + serverPort); return "c语言中文网提醒您,服务访问成功------testD:" + serverPort; }
c语言中文网提醒您,服务访问成功------testD:8401
图11:Sentinel 控制台定义流控规则
图12:Sentinel 控制台定义流控规则
图13:Sentinel 控制台流控规则列表
6. 快速连续(频率大于每秒钟 2 次)访问“http://localhost:8401/testD”,结果如下。
Blocked by Sentinel (flow limiting)
/** * 通过 Sentinel 控制台定义流控规则 * */ @GetMapping("/testD") @SentinelResource(value = "testD-resource", blockHandler = "blockHandlerTestD") //通过注解定义资源 public String testD() { log.info("c语言中文网提醒您,服务访问成功------testD:" + serverPort); return "c语言中文网提醒您,服务访问成功------testD:" + serverPort; } /** * 限流之后的逻辑 * @param exception * @return */ public String blockHandlerTestD(BlockException exception) { log.info(Thread.currentThread().getName() + "c语言中文网提醒您,TestD服务访问失败! 您已被限流,请稍后重试"); return "c语言中文网提醒您,TestD服务访问失败! 您已被限流,请稍后重试"; }
在以上代码中,我们通过 @SentinelResource 注解的 blockHandler 属性指定了一个 blockHandler 函数,进行限流之后的后续处理。
使用 @SentinelResource 注解的 blockHandler 属性时,需要注意以下事项:
图14:Sentinel 控制台-簇点链路
图15:Sentinel 控制台新增流控规则
c语言中文网提醒您,TestD服务访问失败! 您已被限流,请稍后重试
public static void loadRules(List<FlowRule> rules) { currentProperty.updateValue(rules); }
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,即流控规则的作用对象 | - |
count | 限流的阈值。 | - |
grade | 流控阈值的类型,包括 QPS 或并发线程数 | QPS |
limitApp | 流控针对的调用来源 | default,表示不区分调用来源 |
strategy | 调用关系限流策略,包括直接、链路和关联 | 直接 |
controlBehavior | 流控效果(直接拒绝、Warm Up、匀速排队),不支持按调用关系限流 |
直接拒绝 |
/** * 通过代码定义流量控制规则 */ private static void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); //定义一个限流规则对象 FlowRule rule = new FlowRule(); //资源名称 rule.setResource("testD-resource"); //限流阈值的类型 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 设置 QPS 的阈值为 2 rule.setCount(2); rules.add(rule); //定义限流规则 FlowRuleManager.loadRules(rules); }
@GetMapping("/testD") @SentinelResource(value = "testD-resource", blockHandler = "blockHandlerTestD") //通过注解定义资源 public String testD() { initFlowRules(); //调用初始化流控规则的方法 log.info("c语言中文网提醒您,服务访问成功------testD:" + serverPort); return "c语言中文网提醒您,服务访问成功------testD:" + serverPort; }
c语言中文网提醒您,服务访问成功------testD:8401
c语言中文网提醒您,TestD服务访问失败! 您已被限流,请稍后重试
curl http://localhost:8719/cnode?id=testD-resource
idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exceptio 2 testD-resource 0 0.0 0.0 0.0 0.0 0.0 10 16 26 0.0
熔断策略 | 说明 |
---|---|
慢调用比例 (SLOW_REQUEST_RATIO) |
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大响应时间),若请求的响应时间大于该值则统计为慢调用。 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则再次被熔断。 |
异常比例 (ERROR_RATIO) |
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目且异常的比例大于阈值,则在接下来的熔断时长内请求会自动被熔断。 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。 |
异常数 (ERROR_COUNT) |
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。 |
图16:Sentinel 熔断状态转换
状态 | 说明 | 触发条件 |
---|---|---|
熔断关闭状态 (CLOSED) |
处于关闭状态时,请求可以正常调用资源。 |
满足以下任意条件,Sentinel 熔断器进入熔断关闭状态:
|
熔断开启状态 (OPEN) |
处于熔断开启状态时,熔断器会一定的时间(规定的熔断时长)内,暂时切断所有请求对该资源的调用,并调用相应的降级逻辑使请求快速失败避免系统崩溃。 |
满足以下任意条件,Sentinel 熔断器进入熔断开启状态:
|
探测恢复状态 (HALF-OPEN) |
处于探测恢复状态时,Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成(没有错误)则结束熔断,熔断器进入熔断关闭(CLOSED)状态;否则会再次被熔断,熔断器进入熔断开启(OPEN)状态。 | 在熔断开启一段时间(降级窗口时间或熔断时长,单位为 s)后,Sentinel 熔断器自动会进入探测恢复状态。 |
属性 | 说明 | 默认值 | 使用范围 |
---|---|---|---|
资源名 | 规则的作用对象。 | - | 所有熔断策略 |
熔断策略 | Sentinel 支持3 中熔断策略:慢调用比例、异常比例、异常数策略。 | 慢调用比例 | 所有熔断策略 |
最大 RT | 请求的最大相应时间,请求的响应时间大于该值则统计为慢调用。 | - | 慢调用比例 |
熔断时长 | 熔断开启状态持续的时间,超过该时间熔断器会切换为探测恢复状态(HALF-OPEN),单位为 s。 | - | 所有熔断策略 |
最小请求数 | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)。 | 5 | 所有熔断策略 |
统计时长 | 熔断触发需要统计的时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)。 | 1000 ms | 所有熔断策略 |
比例阈值 | 分为慢调用比例阈值和异常比例阈值,即慢调用或异常调用占所有请求的百分比,取值范围 [0.0,1.0]。 | - | 慢调用比例 、异常比例 |
异常数 | 请求或调用发生的异常的数量。 | - |
异常数 |
DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `dept_no` int NOT NULL AUTO_INCREMENT, `dept_name` varchar(255) DEFAULT NULL, `db_source` varchar(255) DEFAULT NULL, PRIMARY KEY (`dept_no`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; INSERT INTO `dept` VALUES ('1', '开发部', 'bianchengbang_jdbc'); INSERT INTO `dept` VALUES ('2', '人事部', 'bianchengbang_jdbc'); INSERT INTO `dept` VALUES ('3', '财务部', 'bianchengbang_jdbc'); INSERT INTO `dept` VALUES ('4', '市场部', 'bianchengbang_jdbc'); INSERT INTO `dept` VALUES ('5', '运维部', 'bianchengbang_jdbc');
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>net.biancheng.c</groupId> <version>1.0-SNAPSHOT</version> <artifactId>spring-cloud-alibaba-demo</artifactId> </parent> <groupId>net.biancheng.c</groupId> <artifactId>spring-cloud-alibaba-provider-mysql-8003</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-alibaba-provider-mysql-8003</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>net.biancheng.c</groupId> <artifactId>spring-cloud-alibaba-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--添加 Spring Boot 的监控模块--> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
server: port: 8003 #端口 spring: application: name: spring-cloud-alibaba-provider-mysql cloud: nacos: discovery: server-addr: localhost:1111 ######################### 数据库连接 ################################# datasource: username: root #数据库登陆用户名 password: root #数据库登陆密码 url: jdbc:mysql://127.0.0.1:3306/spring_cloud_db2 #数据库url driver-class-name: com.mysql.jdbc.Driver management: endpoints: web: exposure: include: "*" # * 在yaml 文件属于关键字,所以需要加引号 ###################################### MyBatis 配置 ###################################### mybatis: # 指定 mapper.xml 的位置 mapper-locations: classpath:mybatis/mapper/*.xml #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名 type-aliases-package: net.biancheng.c.entity configuration: #默认开启驼峰命名法,可以不用设置该属性 map-underscore-to-camel-case: true
package net.biancheng.c.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; @AllArgsConstructor @NoArgsConstructor //无参构造函数 @Data // 提供类的get、set、equals、hashCode、canEqual、toString 方法 @Accessors(chain = true) public class Dept implements Serializable { private Integer deptNo; private String deptName; private String dbSource; }
package net.biancheng.c.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code, message, null); } }
package net.biancheng.c.mapper; import net.biancheng.c.entity.Dept; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface DeptMapper { //根据主键获取数据 Dept selectByPrimaryKey(Integer deptNo); //获取表中的全部数据 List<Dept> GetAll(); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="net.biancheng.c.mapper.DeptMapper"> <resultMap id="BaseResultMap" type="net.biancheng.c.entity.Dept"> <id column="dept_no" jdbcType="INTEGER" property="deptNo"/> <result column="dept_name" jdbcType="VARCHAR" property="deptName"/> <result column="db_source" jdbcType="VARCHAR" property="dbSource"/> </resultMap> <sql id="Base_Column_List"> dept_no , dept_name, db_source </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from dept where dept_no = #{deptNo,jdbcType=INTEGER} </select> <select id="GetAll" resultType="net.biancheng.c.entity.Dept"> select * from dept; </select> </mapper>
package net.biancheng.c.service; import net.biancheng.c.entity.Dept; import java.util.List; public interface DeptService { Dept get(Integer deptNo); List<Dept> selectAll(); }
package net.biancheng.c.service.impl; import net.biancheng.c.entity.Dept; import net.biancheng.c.mapper.DeptMapper; import net.biancheng.c.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service("deptService") public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Override public Dept get(Integer deptNo) { return deptMapper.selectByPrimaryKey(deptNo); } @Override public List<Dept> selectAll() { return deptMapper.GetAll(); } }
package net.biancheng.c.controller; import lombok.extern.slf4j.Slf4j; import net.biancheng.c.entity.CommonResult; import net.biancheng.c.entity.Dept; import net.biancheng.c.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.concurrent.TimeUnit; @RestController @Slf4j public class DeptController { @Autowired private DeptService deptService; @Value("${server.port}") private String serverPort; @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public CommonResult<Dept> get(@PathVariable("id") int id) { log.info("端口:" + serverPort + "\t+ dept/get/"); try { TimeUnit.SECONDS.sleep(1); log.info("休眠 1秒"); } catch (InterruptedException e) { e.printStackTrace(); } Dept dept = deptService.get(id); CommonResult<Dept> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, dept); return result; } @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public CommonResult<List<Dept>> list() { log.info("端口:" + serverPort + "\t+ dept/list/"); List<Dept> depts = deptService.selectAll(); CommonResult<List<Dept>> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, depts); return result; } }
package net.biancheng.c; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudAlibabaProviderMysql8003Application { public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaProviderMysql8003Application.class, args); } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>net.biancheng.c</groupId> <version>1.0-SNAPSHOT</version> <artifactId>spring-cloud-alibaba-demo</artifactId> </parent> <groupId>net.biancheng.c</groupId> <artifactId>spring-cloud-alibaba-consumer-mysql-8803</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-alibaba-consumer-mysql-8803</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入 OpenFeign 的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--SpringCloud ailibaba sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>net.biancheng.c</groupId> <artifactId>spring-cloud-alibaba-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
server: port: 8803 spring: application: name: spring-cloud-alibaba-consumer-mysql-feign cloud: nacos: discovery: server-addr: localhost:1111 sentinel: transport: dashboard: localhost:8080 port: 8719 # 以下配置信息并不是默认配置,而是我们自定义的配置,目的是不在 Controller 内硬编码 服务提供者的服务名 service-url: nacos-user-service: http://spring-cloud-alibaba-provider-mysql #消费者要方位的微服务名称 # 激活Sentinel对Feign的支持 feign: sentinel: enabled: true
package net.biancheng.c.service; import net.biancheng.c.entity.CommonResult; import net.biancheng.c.entity.Dept; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.util.List; @Component @FeignClient(value = "spring-cloud-alibaba-provider-mysql", fallback = DeptFallbackService.class) public interface DeptFeignService { @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public CommonResult<Dept> get(@PathVariable("id") int id); @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public CommonResult<List<Dept>> list(); }
package net.biancheng.c.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; import com.alibaba.csp.sentinel.util.TimeUtil; import lombok.extern.slf4j.Slf4j; import net.biancheng.c.entity.CommonResult; import net.biancheng.c.entity.Dept; import net.biancheng.c.service.DeptFeignService; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; @RestController @Slf4j public class DeptFeignController { @Resource DeptFeignService deptFeignService; @RequestMapping(value = "consumer/feign/dept/get/{id}", method = RequestMethod.GET) @SentinelResource(value = "fallback", fallback = "handlerFallback") public CommonResult<Dept> get(@PathVariable("id") int id) { monitor(); System.out.println("--------->>>>主业务逻辑"); CommonResult<Dept> result = deptFeignService.get(id); if (id == 6) { System.err.println("--------->>>>主业务逻辑,抛出非法参数异常"); throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...."); //如果查到的记录也是 null 也控制正异常 } else if (result.getData() == null) { System.err.println("--------->>>>主业务逻辑,抛出空指针异常"); throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } @RequestMapping(value = "consumer/feign/dept/list", method = RequestMethod.GET) public CommonResult<List<Dept>> list() { return deptFeignService.list(); } //处理异常的回退方法(服务降级) public CommonResult handlerFallback(@PathVariable int id, Throwable e) { System.err.println("--------->>>>服务降级逻辑"); Dept dept = new Dept(id, "null", "null"); return new CommonResult(444, "C语言中文网提醒您,服务被降级!异常信息为:" + e.getMessage(), dept); } /** * 自定义事件监听器,监听熔断器状态转换 */ public void monitor() { EventObserverRegistry.getInstance().addStateChangeObserver("logging", (prevState, newState, rule, snapshotValue) -> { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if (newState == CircuitBreaker.State.OPEN) { // 变换至 OPEN state 时会携带触发时的值 System.err.println(String.format("%s -> OPEN at %s, 发送请求次数=%.2f", prevState.name(), format.format(new Date(TimeUtil.currentTimeMillis())), snapshotValue)); } else { System.err.println(String.format("%s -> %s at %s", prevState.name(), newState.name(), format.format(new Date(TimeUtil.currentTimeMillis())))); } }); } }
package net.biancheng.c; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class SpringCloudAlibabaConsumerMysql8803Application { public static void main(String[] args) { SpringApplication.run(SpringCloudAlibabaConsumerMysql8803Application.class, args); } }
{"code":200,"message":"from mysql,serverPort: 8003","data":{"deptNo":3,"deptName":"财务部","dbSource":"spring_cloud_db2"}}
{"code":444,"message":"C语言中文网提醒您,服务被降级!异常信息为:NullPointerException,该ID没有对应记录,空指针异常","data":{"deptNo":7,"deptName":"null","dbSource":"null"}}
--------->>>>主业务逻辑
--------->>>>主业务逻辑
--------->>>>主业务逻辑,抛出空指针异常
--------->>>>服务熔断降级逻辑
图17:Sentinel 熔断降级
图18:Sentinel 控制台定义熔断规则
{"code":444,"message":"C语言中文网提醒您,服务被降级!异常信息为:null","data":{"deptNo":7,"deptName":"null","dbSource":"null"}}
--------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 --------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 CLOSED -> OPEN at 2021-11-17 14:06:47, 发送请求次数=2.00
{"code":444,"message":"C语言中文网提醒您,服务被降级!异常信息为:null","data":{"deptNo":4,"deptName":"null","dbSource":"null"}}
--------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 --------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 CLOSED -> OPEN at 2021-11-17 14:09:19, 发送请求次数=2.00 --------->>>>服务降级逻辑 --------->>>>服务降级逻辑 --------->>>>服务降级逻辑
{"code":200,"message":"from mysql,serverPort: 8003","data":{"deptNo":4,"deptName":"市场部","dbSource":"spring_cloud_db2"}}
--------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 --------->>>>主业务逻辑 --------->>>>主业务逻辑,抛出空指针异常 --------->>>>服务降级逻辑 CLOSED -> OPEN at 2021-11-17 14:09:19, 发送请求次数=2.00 --——--->>>>服务降级逻辑 --------->>>>服务降级逻辑 --------->>>>服务降级逻辑 OPEN -> HALF_OPEN at 2021-11-17 14:09:24 --——--->>>>主业务逻辑 HALF_OPEN -> CLOSED at 2021-11-17 14:09:24 --------->>>>主业务逻辑 --------->>>>主业务逻辑
public static void loadRules(List<DegradeRule> rules) { try { currentProperty.updateValue(rules); } catch (Throwable var2) { RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", var2); } }
属性 | 说明 | 默认值 |
---|---|---|
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("fallback"); //熔断策略为异常比例 rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType()); //异常比例阈值 rule.setCount(0.7); //最小请求数 rule.setMinRequestAmount(100); //统计市场,单位毫秒 rule.setStatIntervalMs(30000); //熔断市场,单位秒 rule.setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); }
@RequestMapping(value = "consumer/feign/dept/get/{id}", method = RequestMethod.GET) @SentinelResource(value = "fallback", fallback = "handlerFallback") public CommonResult<Dept> get(@PathVariable("id") int id) { initDegradeRule(); monitor(); System.out.println("--------->>>>主业务逻辑"); CommonResult<Dept> result = deptFeignService.get(id); if (id == 6) { System.err.println("--------->>>>主业务逻辑,抛出非法参数异常"); throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...."); //如果查到的记录也是 null 也控制正异常 } else if (result.getData() == null) { System.err.println("--------->>>>主业务逻辑,抛出空指针异常"); throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; }
"code":200,"message":"from mysql,serverPort: 8003","data":{"deptNo":1,"deptName":"开发部","dbSource":"spring_cloud_db2"}}
图19:Sentinel 代码定义熔断规则
本文链接:http://task.lmcjl.com/news/15843.html