文章目录
- 1. 简介
- 2. 安装
- 3. 使用
- 3.1 Nacos
- 3.2 sentinel-service
- 4. 测试
- 4.1 限流
- 4.2 熔断
- 4.2.1 user-service
- 4.2.2 sentinel-service
- 4.3 结合OpenFeign
- 4.4 使用Nacos存储规则
Spring Cloud Hoxton.SR4 Spring Cloud Alibaba 2.2.2.RELEASE Spring Boot 2.3.0.RELEASE
GitHub:shpunishment/spring-cloud-learning/spring-cloud-sentinel-test
1. 简介
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,Sentinel 作为其核心组件之一,具有熔断与限流等一系列服务保护功能。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
2. 安装
启动Sentinel控制台,访问http://ip:8400。默认账号密码为sentinel。
java -Dserver.port=8400 -jar sentinel-dashboard-1.8.0.jar
3. 使用
先用IDEA创建一个Spring Boot的项目,可以随意引用一个Spring Cloud的组件,之后也会删掉。
创建完,删掉除了pom.xml以外的其他文件,再修改pom.xml
<?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.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<groupId>com.shpun</groupId>
<artifactId>spring-cloud-sentinel-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-sentinel-test</name>
<description>spring cloud sentinel test</description>
<!--修改打包方式为pom-->
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<modules>
<!--后续添加子模块用-->
</modules>
<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>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>
3.1 Nacos
使用Windows启动时报错,修改startup.cmd的mode从cluster为standalone,还是启动失败。
所以这里使用Linux。
使用单体模式启动Nacos,访问http://ip:8848/nacos。默认账号密码为nacos。
./bin/startup.sh -m standalone
3.2 sentinel-service
创建子模块 sentinel-service
修改pom继承
<parent>
<groupId>com.shpun</groupId>
<artifactId>spring-cloud-sentinel-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
再添加依赖
<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>
修改application.yml
server:
port: 8200
spring:
application:
name: sentinel-service
cloud:
nacos:
# nacos地址
server-addr: http://192.168.110.40:8848
sentinel:
# 取消Sentinel控制台懒加载
eager: true
transport:
# Sentinel 控制台地址
dashboard: http://192.168.110.40:8400
# 当前服务运行的ip
client-ip: 192.168.4.23
port: 8719
在启动类上添加@EnableDiscoveryClient
注解表明是一个服务发现的客户端。
4. 测试
4.1 限流
Sentinel 默认为所有的 HTTP 服务提供了限流埋点,也可以通过使用@SentinelResource
自定义限流行为。
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {
/**
* 按url限流,有默认的限流处理逻辑
*
* @return
*/
@SentinelResource(value = "url")
@GetMapping("/url")
public ResultVo<?> url() {
return ResultVo.ok(200, "/url 请求成功");
}
/**
* 按资源名称限流,注意资源名称要相同。
* 需要指定限流处理逻辑。
*
* @return
*/
@SentinelResource(value = "resource", blockHandler = "handleException")
@GetMapping("/resource")
public ResultVo<?> resource() {
return ResultVo.ok(200, "/resource 请求成功");
}
/**
* 按资源名称限流处理,方法的访问修饰符需为public
* @param exception
* @return
*/
public ResultVo<?> handleException(BlockException exception) {
return ResultVo.failure(500, "handleException 资源:" + exception.getRule().getResource() + " 限流成功");
}
/**
* 按资源名称限流,注意资源名称要相同。
* 自定义限流处理逻辑,指定类CustomBlockHandler和其中的static方法handleException2。
*
* @return
*/
@SentinelResource(value = "resource2", blockHandlerClass = CustomBlockHandler.class, blockHandler = "handleException2")
@GetMapping("/resource2")
public ResultVo<?> resource2() {
return ResultVo.ok(200, "/resource2 请求成功");
}
}
自定义BlockHandler
public class CustomBlockHandler {
public static ResultVo<?> handleException2(BlockException exception) {
return ResultVo.failure(500, "handleException2 资源:" + exception.getRule().getResource() + " 限流成功");
}
}
测试
根据url限流:
添加url流控规则
限流成功,返回默认信息
根据资源名称限流:
添加资源名称流控规则
资源被限流,返回自定义信息
添加资源名称resource2的流控规则,使用自定义限流处理逻辑。
4.2 熔断
Sentinel 支持对服务间调用进行保护,对故障应用进行熔断操作。
这里使用RestTemplate来调用user-service服务所提供的接口。
4.2.1 user-service
创建子模块user-service
修改pom继承
<parent>
<groupId>com.shpun</groupId>
<artifactId>spring-cloud-sentinel-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
再添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
修改application.yml
server:
port: 8101
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:4306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
cloud:
nacos:
discovery:
# nacos地址
server-addr: http://192.168.110.40:8848
mybatis:
typeAliasesPackage: com.shpun.model
mapper-locations: classpath:mapper/**.xml
Model,Mapper,Service等省略。
UserController 完成对User的CURD接口。
@RequestMapping("/api/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/add")
public ResultVo<?> add(@RequestBody User user) {
userService.insertSelective(user);
return ResultVo.ok();
}
@GetMapping("/delete/{userId}")
public ResultVo<?> delete(@PathVariable("userId") Integer userId) {
userService.deleteByPrimaryKey(userId);
return ResultVo.ok();
}
@PostMapping("/update")
public ResultVo<?> update(@RequestBody User user) {
userService.updateByPrimaryKeySelective(user);
return ResultVo.ok();
}
@GetMapping("/{userId}")
public ResultVo<User> get(@PathVariable("userId") Integer userId) {
User user = userService.selectByPrimaryKey(userId);
return ResultVo.okData(user);
}
}
在启动类上添加@EnableDiscoveryClient
注解表明是一个服务发现的客户端。
4.2.2 sentinel-service
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
修改application.yml
service-url:
user-service: http://user-service
RibbonConfig
@Configuration
public class RibbonConfig {
@Bean
// 赋予RestTemplate负载均衡的能力
@LoadBalanced
// 添加Sentinel支持
@SentinelRestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
UserRibbonServiceImpl 使用RestTemplate请求user-service
@Service("userRibbonService")
public class UserRibbonServiceImpl implements UserRibbonService {
@Autowired
private RestTemplate restTemplate;
@Value("${service-url.user-service}")
private String userServiceUrl;
@Override
public ResultVo<?> add(User user) {
return restTemplate.postForObject(userServiceUrl + "/api/user/add", user, ResultVo.class);
}
@Override
public ResultVo<?> delete(Integer userId) {
return restTemplate.getForObject(userServiceUrl + "/api/user/delete/{0}", ResultVo.class, userId);
}
@Override
public ResultVo<?> update(User user) {
return restTemplate.postForObject(userServiceUrl + "/api/user/update", user, ResultVo.class);
}
@Override
public ResultVo<?> get(Integer userId) {
return restTemplate.getForObject(userServiceUrl + "/api/user/{0}", ResultVo.class, userId);
}
@SentinelResource(value = "fallback", fallback = "fallback")
@Override
public ResultVo<?> getForTestFallback(Integer userId) {
return this.get(userId);
}
/**
* fallback 方法访问修饰符需要为 public
* @param userId
* @return
*/
public ResultVo<?> fallback(Integer userId) {
return ResultVo.failure(500, "fallback(" + userId + ") 服务调用异常");
}
@SentinelResource(value = "fallbackException", fallback = "fallback2", exceptionsToIgnore = { NullPointerException.class })
@Override
public ResultVo<?> getForTestFallbackWithIgnoreExceptions(Integer userId) {
if (userId == 1) {
throw new IndexOutOfBoundsException();
} else if (userId == 2) {
throw new NullPointerException();
}
return this.get(userId);
}
/**
* fallback 方法访问修饰符需要为 public
* @param userId
* @param e
* @return
*/
public ResultVo<?> fallback2(Integer userId, Throwable e) {
return ResultVo.failure(500, "fallback2(" + userId + ") 服务调用异常。异常:" + e.getClass());
}
}
测试
@RequestMapping("/api/user")
@RestController
public class UserSentinelController {
@Autowired
private UserRibbonService userRibbonService;
@GetMapping("/fallback/{userId}")
public ResultVo<?> fallback(@PathVariable("userId") Integer userId) {
return userRibbonService.getForTestFallback(userId);
}
@GetMapping("/fallbackWithIgnore/{userId}")
public ResultVo<?> fallbackWithIgnoreExceptions(@PathVariable("userId") Integer userId) {
return userRibbonService.getForTestFallbackWithIgnoreExceptions(userId);
}
}
正常请求
关掉user-service,熔断了,走服务降级。
未在忽略异常中,走服务降级。
在忽略异常中,未走服务降级,直接出错。
4.3 结合OpenFeign
已下代码在sentinel-service中修改。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
修改application.yml
feign:
sentinel:
# 开启Sentinel
enabled: true
UserFeignService 注解@FeignClient
中value指定这是对user-service服务的接口调用客户端,fallback指定服务降级实现类
@FeignClient(value = "user-service", fallback = UserFeignServiceFallback.class)
public interface UserFeignService {
@PostMapping("/api/user/add")
ResultVo<?> add(@RequestBody User user);
@GetMapping("/api/user/delete/{userId}")
ResultVo<?> delete(@PathVariable("userId") Integer userId);
@PostMapping("/api/user/update")
ResultVo<?> update(@RequestBody User user);
@GetMapping("/api/user/{userId}")
ResultVo<User> get(@PathVariable("userId") Integer userId);
}
UserFeignServiceFallback 服务降级实现类
@Component
public class UserFeignServiceFallback implements UserFeignService {
@Override
public ResultVo<?> add(User user) {
return ResultVo.failure(500, "add 服务调用异常");
}
@Override
public ResultVo<?> delete(Integer userId) {
return ResultVo.failure(500, "delete 服务调用异常");
}
@Override
public ResultVo<?> update(User user) {
return ResultVo.failure(500, "update 服务调用异常");
}
@Override
public ResultVo<User> get(Integer userId) {
return ResultVo.build(500, null,"get 服务调用异常");
}
}
在启动类上添加@EnableFeignClients
注解启用Feign的客户端功能。
测试
@RequestMapping("/api/user")
@RestController
public class UserFeignController {
@Autowired
private UserFeignService userFeignService;
@PostMapping("/add")
public ResultVo<?> add(@RequestBody User user) {
return userFeignService.add(user);
}
@GetMapping("/delete/{userId}")
public ResultVo<?> delete(@PathVariable("userId") Integer userId) {
return userFeignService.delete(userId);
}
@PostMapping("/update")
public ResultVo<?> update(@RequestBody User user) {
return userFeignService.update(user);
}
@GetMapping("/{userId}")
public ResultVo<User> get(@PathVariable("userId") Integer userId) {
return userFeignService.get(userId);
}
}
关掉user-service,走服务降级。
4.4 使用Nacos存储规则
默认情况下,Sentinel推送为原始模式。在Sentinel控制台中配置的规则是通过API将规则推送到客户端的内存中,只要重启服务就会消失。
使用Push模式,将配置配置在配置中心中,由控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是:配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel。可使用Nacos将规则持久化,在配置中心创建规则,配置中心将规则推送到客户端。Sentinel控制台也从配置中心去获取配置信息。
已下代码在sentinel-service中修改。
添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
修改application.yml
spring:
application:
name: sentinel-service
cloud:
nacos:
# nacos地址
server-addr: http://192.168.110.40:8848
sentinel:
# 取消Sentinel控制台懒加载
eager: true
transport:
# Sentinel 控制台地址
dashboard: http://192.168.110.40:8400
# 当前服务运行的ip
client-ip: 192.168.4.23
port: 8719
# 添加Nacos数据源配置
datasource:
ds1-nacos:
nacos:
server-addr: 192.168.110.40:8848
username: nacos
password: nacos
namespace: public
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
在Nacos中添加配置
[
{
"resource": "resource",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
参数含义:resource
:资源名称;limitApp
:来源应用;grade
:阈值类型,0表示线程数,1表示QPS;count
:单机阈值;strategy
:流控模式,0表示直接,1表示关联,2表示链路;controlBehavior
:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;clusterMode
:是否集群。
重启sentinel-service,打开Sentinel控制台。Sentinel控制台已经有了限流规则:
对资源resource的限流也生效。
参考:
Spring Cloud Alibaba Sentinel 官方文档