Sentinel 提供了@SentinelResource注解用于定义资源,并提供了AspectJ的扩展用于自定义资源,处理BlockException、fallback等。
Sentinel服务熔断环境搭建
服务熔断:应对微服务雪崩效应的一种链路保护机制,类似保险丝。
需要完成Sentinel整合Ribbon+openFeign,所以我们先要搭建环境,那么先从整合Ribbon开始
为了演示操作,所以在这里我们需要利用Ribbon进行负载均衡的调用,所以我们需要创建一个服务消费者cloudalibaba-consumer8084和两个服务提供者cloudalibaba-provider9003和cloudalibaba-provider9004,以下是结构图
新建项目alibaba-sentinel
pom:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.jms</groupId> <artifactId>alibaba-sentinel</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <!-- 指定SpringBoot版本 --> <spring-boot.version>2.4.2</spring-boot.version> <!-- 指定SpringCloud版本 --> <spring-cloud.version>2020.0.4</spring-cloud.version> <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version> </properties> <packaging>pom</packaging> <modules> <module>sentinel-demo01</module> <module>cloudalibaba-provider-9003</module> <module>cloudalibaba-provider-9004</module> <module>cloudalibaba-commons</module> </modules> <dependencyManagement> <dependencies> <!-- 声明依赖 springBoot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 声明依赖 springCloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 声明依赖 springCloudAlibaba --> <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> </project>
新建cloudalibaba-provider-9003/9004模块
在建立9003和9004之前,先建立一个共享项目cloudalibaba-commons,在其中新建一个类型JsonResult,这个类型用于返回JSON数据类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult<T> {
private Integer code;
private T data;
}
创建服务提供者9003,9004基本上是一样的,所以我们建立9003复制就可以得到9004
子类pom:
cloudalibaba-provider-9003/9004
<?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>org.jms</groupId> <artifactId>alibaba-sentinel</artifactId> <version>1.0-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.jms</groupId> <artifactId>cloudalibaba-provider-9003</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cloudalibaba-provider-9003</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.jms</groupId> <artifactId>cloudalibaba-commons</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </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>
properties
server.port=9003 spring.application.name=nacos-provider #配置Nacos地址 spring.cloud.nacos.discovery.server-addr=localhost:8848 management.endpoints.web.exposure.include='*'
控制器
package com.jms.cloudalibabaprovider9003.controller;
import com.jms.JsonResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* @Auther: jms
* @Date: 2022-11-16-17:20
* @Description: com.jms.cloudalibabaprovider9003.controller
* @version: 1.0
*/
@RestController
public class DataController {
@Value("${server.port}")
private String serverPort;
//模仿数据库存储数据
public static HashMap<Long,String> hashMap = new HashMap<Long,String>();
static {
hashMap.put(1l,"鼠标");
hashMap.put(2l,"键盘");
hashMap.put(3l,"耳机");
}
@GetMapping("info/{id}")
public JsonResult<String> msbSql(@PathVariable("id") Long id){
JsonResult<String> result = new JsonResult(200,"端口号:"+serverPort+hashMap.get(id));
return result;
}
}
注意:9004和9003 一致,但是要注意修改properties文件端口号
新建cloudalibaba-consumer8084
pom
<?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>org.jms</groupId> <artifactId>alibaba-sentinel</artifactId> <version>1.0-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.jms</groupId> <artifactId>cloudalibaba-consumer8084</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cloudalibaba-consumer8084</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringCloud ailibaba sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.jms</groupId> <artifactId>cloudalibaba-commons</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </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>
properties
server.port=8084 spring.application.name=nacos-consumer-8084 spring.cloud.nacos.discovery.server-addr=localhost:8848 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 spring.cloud.sentinel.transport.port=8719 #配置Sentinel dashboard地址 spring.cloud.sentinel.transport.dashboard=localhost:8080 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url.nacos-user-service=http://nacos-provider management.endpoints.web.exposure.include='*'
控制器
@RestController
public class DataController {
@Value("${server.port}")
private String serverPort;
//模仿数据库存储数据
public static HashMap<Long,String> hashMap = new HashMap<>();
static {
hashMap.put(1l,"鼠标");
hashMap.put(2l,"键盘");
hashMap.put(3l,"耳机");
}
@GetMapping("info/{id}")
public JsonResult<String> msbSql(@PathVariable("id") Long id){
JsonResult<String> result = new JsonResult(1L,200,hashMap.get(id));
return result;
}
}
注意:利用Ribbon进行负载均衡的调用,需要在RestTemplate加上@LoadBalanced注解,新建一个java类,代码如下:
package com.jms.cloudalibabaconsumer8084.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @Auther: jms
* @Date: 2022-11-16-17:56
* @Description: com.jms.cloudalibabaconsumer8084.config
* @version: 1.0
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
RestTemplate initRestTemplate() {
return new RestTemplate();
}
}
最后测试
访问http://localhost:8084/consumer/fallback/1
查看最后结果是否为9003/9004切换调用,如图:
如果我们访问http://localhost:8084/consumer/fallback/4(id非法)地址时,就会出现对应的显示效果:
明显此时显示效果非常不好,我们就可以通过@SentinelResource注解的fallback属性来解决这种java异常,给出友好提示
package com.jms.cloudalibabaconsumer8084.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jms.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @Auther: jms
* @Date: 2022-11-16-17:39
* @Description: com.jms.cloudalibabaconsumer8084.controller
* @version: 1.0
*/
@RestController
public class DemoController {
@Autowired
private RestTemplate restTemplate;
//服务提供者URL
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",
fallback = "fallbackHandler",
blockHandler = "blockHandler",
exceptionsToIgnore = NullPointerException.class
)
public JsonResult<String> fallback(@PathVariable Long id){
System.out.println("id:"+id);
if (id<=3){
//通过Ribbon发起远程访问,访问9003/9004
JsonResult<String> result = restTemplate.getForObject(SERVICE_URL+"/info/"+id,JsonResult.class);
System.err.println(result.getData());
return result;
}else {
throw new NullPointerException("没有产品记录");
}
}
//处理java异常
public JsonResult<String> fallbackHandler(Long id,Throwable throwabl){
System.out.println(throwabl.getMessage());
return new JsonResult<String>(400,"查询无该产品id:"+id);
}
//处理Sentinel限流
public JsonResult<String> blockHandler(Long id, BlockException e){
return new JsonResult<String>(445,"BlockException限流");
}
}
SentinelResource的fallback属性和blockHandler属性:
概念:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
-
返回值类型必须与原函数返回值类型一致;
-
方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 -
fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
其实通过官网上提供的概念,我们不难看出这个属性类似于blockHandler,但是各位一定要注意他们有本质的不同。
注意:fallback属性和blockHandler属性的本质不同在于他们作用的异常不同:
-
blockHandler:针对违反Sentinel控制台配置规则时触发BlockException异常时对应处理的属性
-
fallback:针对Java本身出现的异常进行处理的对应属性。
此时我们来设置Sentinel配置,我们通过熔断规则中的异常数来演示(当然也可以用其他的)
规则:在一秒内超过最小访问次数5次,并且异常数超过2的时候,就会触发熔断规则。
此时我们来访问http://localhost:8084/consumer/fallback/6看效果:
- 在没有触发熔断之前的异常交给fallback来处理
- 但是一旦触发熔断规则就变成了blockHandler来处理
exceptionsToIgnore属性
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
总结:
-
blockHandler:针对违反Sentinel控制台配置规则时触发BlockException异常时对应处理的属性
-
fallback:针对Java本身出现的异常进行处理的对应属性。
-
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。