之前我们有提到服务降级、熔断等概念,而在SpringCloud和Netflix生态中,需要打组合拳,并且Netflix甚至停止更新,于是在SpringCloud Alibaba生态中出现了Sentinel(最早是拿来做流量监控的),可以做到流量控制、实时监控、限流、熔断降级等功能
Sentinel具有如下特性:
- 丰富的应用场景:承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀,可以实时熔断下游不可用应用;
- 完备的实时监控:同时提供实时的监控功能。可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况;
- 广泛的开源生态:提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合;
- 完善的 SPI 扩展点:提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。
总体框架设计(官网:https://sentinelguard.io/zh-cn/docs/basic-implementation.html)

我们来快速安装一下,我们不使用官网的安装方式,太繁琐,我们直接使用docker run的方式
使用docker hub里打包好的镜像,版本号是1.8.0(当前最新是1.8.5)
docker pull bladex/sentinel-dashboard:latest
docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard
如果使用官方注意启动端口是8080,此处是8858,同样如果是使用云服务器记得开放端口,账号/密码 sentinel/sentinel

引用nacos和sentinel
<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>
server:
port: 8401
spring:
application:
name: sentinel-service
cloud:
nacos:
server-addr: 127.0.0.1:8848 #配置Nacos地址
sentinel:
transport:
dashboard: 127.0.0.1:8858 #配置sentinel dashboard地址
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
启动项目后,查看nacos能看到服务已经注册成功了,接下来我们测试一下功能
1、流量控制
现在到sentinel中添加流量规则(1)根据名称注解@SentinelResource配置的value;(2)根据访问路径;


编写一下测试接口
@RestController
@RequestMapping("/rate-limit")
public class RateLimitController {
/**
* 按资源名称限流,需要指定限流处理逻辑
*/
@GetMapping("/by-resource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public String byResource() {
return "按资源名称限流";
}
/**
* 按URL限流,有默认的限流处理逻辑
*/
@GetMapping("/by-url")
@SentinelResource(value = "by-url", blockHandler = "handleException")
public String byUrl() {
return "按url限流";
}
public String handleException(BlockException exception) {
return "错误异常" + exception.getClass().getCanonicalName();
}
}
多次访问会提示下面的错误

或者可以自定义异常

注意:我们是直接访问项目的,而流量控制是项目与sentinel通信,所以sentinel需要可以连通项目,application.yml中可以配置客户端ip和端口,默认端口为8719,所以在本地测试的情况下,如果你的sentinel是部署云服务器上是无法对本地进行监控的,因此如果需要本地运行项目就需要映射到外网,当然这对于没弄过的同学来说很麻烦,所以最简单的办法就是在本地运行sentinel,或者在内网可以互通的情况下使用,连通的情况下,在机器列表里会有对应的客户端信息

2、熔断
增加一个biz-service模块,我这边使用之前的nacos-biz-service,添加一个接口用作测试
@RestController
@RequestMapping("/biz")
public class BizController {
@GetMapping("/{id}")
public String getDataById(@PathVariable Long id) {
System.out.println("获取数据" + id);
return "获取信息";
}
}
启动服务查看nacos是否注册成功
然后给sentinel-server增加一个RestTemplate的配置
@Configuration
public class RestTemplateConfig {
@Bean
@SentinelRestTemplate
@LoadBalanced//配合nacos发现服务名进行调用
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在application.yml增加配置
service-url:
biz-service: http://nacos-biz-service
添加测试代码,测试断路器
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {
private Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);
@Autowired
private RestTemplate restTemplate;
@Value("${service-url.biz-service}")
private String bizServiceUrl;
@RequestMapping("/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handleFallback")
public String fallback(@PathVariable Long id) {
return restTemplate.getForObject(bizServiceUrl + "/biz/{1}", String.class, id);
}
@RequestMapping("/fallbackException/{id}")
@SentinelResource(value = "fallbackException", fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
public String fallbackException(@PathVariable Long id) {
if (id == 1) {
throw new IndexOutOfBoundsException();
} else if (id == 2) {
throw new NullPointerException();
}
return restTemplate.getForObject(bizServiceUrl + "/biz/{1}", String.class, id);
}
public String handleFallback(Long id) {
return "服务降级返回";
}
public String handleFallback2(@PathVariable Long id, Throwable e) {
LOGGER.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
return "服务降级返回2";
}
}
(1)启动服务,查看nacos,注册成功后,测试正常服务下调用

(2)关闭nacos-biz-service,再调用一次,此时因为biz-service服务异常,应触发服务降级

(3)重新打开nacos-biz-service,我们写了一个手工路由,用参数1调用,说明:1的情况下抛IndexOutOfBoundsException()异常,因此被降级处理

用参数2调用,说明:2的情况因为配置了exceptionsToIgnore,忽略这种情况下的异常直接返回给客户端,因此没有被降级处理

用任意1和2之外的参数调用,说明:此时服务正常调用到nacos-biz-service

关闭掉nacos-biz-service再调用一次,说明:此时跟参数1情况一致,都被服务降级处理

3、结合openFeign
引入openfeign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
application.yml新增参数
feign:
sentinel:
enabled: true
启动类增加@EnableFeignClients,和之前openFeign一样
//打开Feign
@EnableFeignClients
@SpringBootApplication
public class SentinelServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelServiceApplication.class, args);
}
}
@FeignClient(value = "nacos-biz-service", fallback = BizFallbackService.class)
public interface BizService {
@PostMapping("/biz/create")
String create();
@GetMapping("/biz/{id}")
String getDataById(@PathVariable Long id);
@GetMapping("/biz/getByUsername")
String getByUsername(@RequestParam String username);
@PostMapping("/biz/update")
String update();
@PostMapping("/biz/delete/{id}")
String delete(@PathVariable Long id);
}
@Component
public class BizFallbackService implements BizService {
@Override
public String create() {
return "服务降级处理create";
}
@Override
public String getDataById(Long id) {
return "服务降级处理getDataById";
}
@Override
public String getByUsername(String username) {
return "服务降级处理getByUsername";
}
@Override
public String update() {
return "服务降级处理update";
}
@Override
public String delete(Long id) {
return "服务降级处理delete";
}
}
@RestController
@RequestMapping("/biz")
public class BizFeignController {
@Qualifier("com.jay.cloud.service.BizService")
@Autowired
private BizService bizService;
@GetMapping("/{id}")
public String getDataById(@PathVariable Long id) {
return bizService.getDataById(id);
}
@GetMapping("/getByUsername")
public String getByUsername(@RequestParam String username) {
return bizService.getByUsername(username);
}
@PostMapping("/create")
public String create() {
return bizService.create();
}
@PostMapping("/update")
public String update() {
return bizService.update();
}
@PostMapping("/delete/{id}")
public String delete(@PathVariable Long id) {
return bizService.delete(id);
}
}
如果服务不能启动可能是包兼容的问题,除了之前引用的,加上以下这些
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.netflix.archaius/archaius-core -->
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>2.0.0-rc.7</version>
</dependency>
最后启动服务成功后,调用一下接口

4、系统保护规则
从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

系统规则支持以下的阈值类型:
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的
maxQps * minRt
计算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
5、热点流量
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>
6、动态规则扩展
默认情况下,当我们在Sentinel控制台中配置规则时,控制台推送规则方式是通过API将规则推送至客户端并直接更新到内存中。一旦我们重启应用,规则将消失。下面我们介绍下如何将配置规则进行持久化

此处我们把规则配置到nacos中
先引入包
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
增加配置
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-sentinel
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
启动项目,然后到nacos上增加路由规则

调用一下接口让sentinel接收服务的心跳,此时去查看流控规则,里面多了一个规则,之前我们是手动添加的,并且关闭项目后规则就消失了,达到我们的预期

我们再测试一下规则是否生效
