菜鸟的Spring Cloud Alibaba学习总结(二):Sentinel
说明
更新时间:2020/10/08 18:10,更新到了sentinel持久化
更新时间:2020/10/08 02:22,更新到了@SentinelResource
本文主要对Spring Cloud Alibaba中的Sentinel进行学习与记录,偏向于实战,本文会持续更新,不断地扩充
注意:本文仅为记录学习轨迹,如有侵权,联系删除
一、概述

以上面的技术结构图为例,学到这里,上面的大多组件基本已经学过,现在是Sentinel
这里给出它的官方链接:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
如果打不开可以访问这个链接:https://www.oschina.net/p/sentinel?hmsr=aladdin1e1

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
二、Sentinel Dashboard
Sentinel Dashboard可以看一下官网:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
Sentinel本来想在服务器上用docker进行拉取部署,结果,经过一天的尝试,发现如果用docker拉取部署Sentinel,即Sentinel在云服务器上面,而项目如果在本地的话,Sentinel无法监控接口,网上的解决方案大多一样,像是配置文件加个client-ip,还有docker中的Sentinel时间与本地时间要一致等,都没法解决,之后在网上看到这样的一句话

也不知道是真假,最后无奈之下只能自己下一个sentinel的jar包,在本地运行

访问页面,账户密码都是sentinel

创建子模块cloud-alibaba-sentiel-server8401,引入pom
<dependencies>
<!-- SpringCloud ailibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-server8401
cloud:
nacos:
discovery:
server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群
namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id
#sentinel配置
sentinel:
transport:
#client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP)
dashboard: 127.0.0.1:8080 #sentinel的地址
port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口
management:
endpoints:
web:
exposure:
include: '*'
随便创建几个接口
@RestController
public class SentinelController {
@GetMapping("/sentinel01")
public String sentinel01(){
return "---------------------------this is sentinel01";
}
@GetMapping("/sentinel02")
public String sentinel02(){
return "---------------------------this is sentinel02";
}
}
主启动类,注意注解的添加

启动该项目,sentinel默认采用懒加载,所以点击实时监控等子菜单都空白的,需要先加载接口后,才会显示数据


sentinel可以用来做服务限流、服务降级和服务熔断等,它跟之前的Hystrix完全不同,sentinel更加简单更加友好,同时功能更加强大。下面开始一一介绍它的功能。
(1)实时监控
同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。

这里以http://localhost:8401/sentinel02建接口为例,任何对该接口的访问,都会在实时监控上面展示,包括访问了多少次,时间、失败或成功等详细信息,但是实时监控仅存储 5 分钟以内的数据,之后变为了空白。
(2)簇点链路
"簇点链路"中显示刚刚调用的资源(单机实时),即刚刚访问过的接口,都会显示在簇点链路上,簇点链路(单机调用链路)页面实时的去拉取指定客户端资源的运行情况。它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种则不区分调用链路展示资源的实时情况。

上面显示访问过接口,关于这个模块主要学习上面的4种操作,流控、降级、热点和授权,直接可视化操作,注意: 簇点链路监控是内存态的信息,它仅展示启动后调用过的资源。
(3)流控
流量控制
即流量控制,控制某一时刻的流量,对于流控可以在"簇点链路”那里直接根据资源进行流控规则的添加,也可以在子菜单“流控规则”那里进行添加,所有添加的流控信息,都可以在流控规则菜单那里看到

流控规则
主要以下面这张图进行流控规则的解读

对应的解读如下

实战
例子1:QPS + 直接 + 快速失败
下面先直接上例子,对sentinel01接口进行流量控制

上面的例子表示,当请求该接口时,每秒中请求的数量达到自己设置的阈值后,会进行限流,返回限流信息

例子1:QPS + 关联 + 快速失败
流控模式是关联的情况下,举个例子,两个模块,订单支付和订单打印两个模块,前者调用后者,当订单打印的业务太多处理不过来的情况下,订单支付就可以暂时先别去调用订单打印模块了,这个功能就可以用关联的流控模式来做,这两个模块关联后,当订单打印模块QPS达到阈值后,限流订单支付模块,这就是关联的流控模式
应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单
下面以两个接口(sentinel03和sentinel04)来实现这种关联

当接口关联的资源sentinel04达到阈值后,自己sentinel03会被限流

例子1:QPS + 直接 + Warm up

应用场景:当秒杀系统开启的时候,瞬间会有大量的请求打进来,容易将系统打垮,这个时候就可以用这种方法,慢慢将请求放进来,慢慢的将阈值升到设置的阈值
例子1:QPS + 直接 + 排队等待

(4)降级
即服务降级,对于降级可以在"簇点链路”那里直接根据资源进行降级规则的添加,也可以在子菜单“降级规则”那里进行添加,所有添加的降级信息,都可以在降级规则菜单那里看到

降级规则

RT(平均响应时间)
编写测试接口

这里故意暂停1秒,添加降级规则

默认当1秒内请求的数量超过5个的时候,并且平均响应的时间超过200毫秒,此时再有请求进来的时候,就会进行降级熔断,并且在时间窗口这个时间内该服务不可访问,这里的降级带有熔断的效果

此时,每秒都有10个请求发送出去,在访问浏览器,就会发现已经触发了降级规则,而且在时间窗口期内无法访问

异常比例

编写测试接口

配置降级规则

同样的用压力测试工具进行测试,每秒发送10个请求,每次都是触发错误,达到降级规则后触发降级熔断

异常数

测试接口


(5)热点参数限流
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
编写测试接口
@GetMapping("/sentinel07")
@SentinelResource("hotKeySentinel07")
public String sentinel07(
@RequestParam(value = "id",required = false) String id,
@RequestParam(value = "name",required = false) String name,
@RequestParam(value = "age", required = false) String age
){
System.out.println("---------------------------this is sentinel07");
StringBuilder sb = new StringBuilder();
if (!StringUtils.isBlank(id)) {
sb = sb.append("id = " + id +"\t");
}
if(!StringUtils.isBlank(name)){
sb = sb.append("name = " + name +"\t");
}
if(!StringUtils.isBlank(age)){
sb = sb.append("age = " + age);
}
System.out.println("sb = " + sb);
return "---------------------------this is sentinel07,"+sb;
}
注意在热点接口上面添加注解@SentinelResource,作唯一标志,然后是热点参数的限流


默认参与QPS模式,参数索引对应接口参数,这里对该接口的第一个参数id做了限流,只要参数里面带有id参数,并且在1秒内请求的数量达到设置的单机阈值,就会抛出异常,注意,这次是抛出异常,而不是限流信息

三、@SentinelResource
这个是重点注解,也是必须要掌握的重点,下面列出所有的该注解的属性
| 属性名 | 是否必填 | 说明 |
|---|---|---|
| value | 是 | 资源名称 。(必填项,需要通过 value 值找到对应的规则进行配置) |
| entryType | 否 | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
| blockHandler | 否 | 处理BlockException的函数名称(可以理解为对Sentinel的配置进行方法兜底)。函数要求: 1.必须是 public 修饰 2.返回类型与原方法一致 3. 参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。 4. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
| blockHandlerClass | 否 | 存放blockHandler的类。 对应的处理函数必须 public static 修饰,否则无法解析,其他要求:同blockHandler。 |
| fallback | 否 | 用于在抛出异常的时候提供fallback处理逻辑(可以理解为对Java异常情况方法兜底)。 fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求: 1.返回类型与原方法一致 2.参数类型需要和原方法相匹配,Sentinel 1.6开始,也可在方法最后加 Throwable 类型的参数。 3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。 |
| fallbackClass | 否 | 存放fallback的类。 对应的处理函数必须static修饰,否则无法解析,其他要求:同fallback。 |
| defaultFallback | 否 | 用于通用的 fallback 逻辑。 默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求: 1.返回类型与原方法一致 2.方法参数列表为空,或者有一个 Throwable 类型的参数。 3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。 |
| exceptionsToIgnore | 否 | 指定排除掉哪些异常。 排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。 |
| exceptionsToTrace | 否 | 需要trace的异常 |
上面所做的一切,像是限流、降级、热点等一旦遇到异常会直接将异常信息直接打回页面,而且,限流信息都是默认的限流信息:Blocked by Sentinel (flow limiting),为了解决这些问题,就需要用到@SentinelResource,下面开始实战
创建一个handler包,里面存放遇到异常时的自定义异常类(CustomerFallbackHandler)的兜底方法和自定义限流信息类(CustomerBlockHandler)
public class CustomerFallbackHandler {
/**
* sentinel08异常的兜底方法,注意参数必须一致
* @param e
* @return
*/
public static String sentinel08FallbackMethod(Throwable e) {
return "sentinel08异常的兜底方法:" + e.getMessage();
}
/**
* sentinel09异常的兜底方法,注意参数必须一致
* @param e
* @return
*/
public static String sentinel09FallbackMethod(Long id,Throwable e) {
return "id = "+id+",sentinel09异常的兜底方法:" + e.getMessage();
}
}
public class CustomerBlockHandler {
/**
* Sentinel08的自定义限流信息,注意参数必须一致
* @param e
* @return
*/
public static String flowLimitBlockExceptionSentinel08(BlockException e){
return "Sentinel08的自定义限流信息";
}
/**
* Sentinel09的自定义限流信息,注意参数必须一致
* @param id
* @param e
* @return
*/
public static String flowLimitBlockExceptionSentinel09(Long id,BlockException e){
return "id = "+id+",Sentinel09的自定义限流信息";
}
}
创建接口
@GetMapping("/sentinel08")
@SentinelResource(
value = "sentinel08", //唯一标识
fallback = "sentinel08FallbackMethod", //遇到异常的兜底方法
fallbackClass = CustomerFallbackHandler.class, //遇到异常的兜底方法对应的类
blockHandler = "flowLimitBlockExceptionSentinel08", //自定义限流信息的方法
blockHandlerClass = CustomerBlockHandler.class) //自定义限流信息的方法对应的类
public String sentinel08(){
int a = 10/0;
return sentinelServer.sentinel08();
}
@GetMapping("/sentinel09")
@SentinelResource(
value = "sentinel09", //唯一标识
fallback = "sentinel09FallbackMethod", //遇到异常的兜底方法
fallbackClass = CustomerFallbackHandler.class, //遇到异常的兜底方法对应的类
blockHandler = "flowLimitBlockExceptionSentinel09",//自定义限流信息的方法
blockHandlerClass = CustomerBlockHandler.class)//自定义限流信息的方法对应的类
public String sentinel09(@RequestParam(value = "id",required = false) Long id){
int a = 10/id.intValue();
return sentinelServer.sentinel09(id);
}
注意,上面的接口和对应的fallback、blockHandler 的方法参数必须一致
测试


四、服务与调用
前面已经创建了这么多的模块,还有关于Sentinel的学习等,下面直接整合Ribbon和openfeign进行服务的调用
(1)服务模块
首先创建两个服务模块cloud-alibaba-provider-payment9003和cloud-alibaba-provider-payment9004,这里以cloud-alibaba-provider-payment9003为例,9004项目内容基本一致,首先引入pom
<dependencies>
<!-- 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>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 9003
spring:
application:
name: cloud-alibaba-provider-server
cloud:
nacos:
discovery:
server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群
namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id
#sentinel配置
sentinel:
transport:
#client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP)
dashboard: 127.0.0.1:8080 #sentinel的地址
port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口
management:
endpoints:
web:
exposure:
include: '*'
主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class NacosPayment9003 {
public static void main(String[] args) {
SpringApplication.run(NacosPayment9003.class,args);
}
}
服务层
@Service
public class PaymentServer {
@Value("${server.port}")
private String port;
public String payment01(){
return "port:"+port+", this is PaymentServer---->payment01";
}
public String payment02(Long id) {
return "port:"+port+", this is PaymentServer---->payment02,id = "+id;
}
}
自定义限流方法
public class CustomerBlockHandler {
public static String payment02Block(Long id,Throwable e){
return "id = "+id+",this is payment02自定义限流方法,异常:"+e.getMessage();
}
public static String payment01Block(Throwable e){
return "this is payment01自定义限流方法,异常:"+e.getMessage();
}
}
异常的兜底方法
public class CustomerFallbackHandler {
public static String payment02Fallback(Long id,Throwable e){
return "id = "+id+",this is payment02异常的兜底方法,异常:"+e.getMessage();
}
public static String payment01Fallback(Throwable e){
return "this is payment01异常的兜底方法,异常:"+e.getMessage();
}
}
编写接口
@RestController
public class PaymentController {
@Autowired
private PaymentServer paymentServer;
@GetMapping("/payment01")
@SentinelResource(
value = "sentinelPayment01",
fallback = "payment01Fallback",
fallbackClass = CustomerFallbackHandler.class,
blockHandler = "payment01Block",
blockHandlerClass = CustomerBlockHandler.class
)
public String Payment01(){
return paymentServer.payment01();
}
@GetMapping("/payment02/{id}")
@SentinelResource(
value = "sentinelPayment02",
fallback = "payment02Fallback",
fallbackClass = CustomerFallbackHandler.class,
blockHandler = "payment02Block",
blockHandlerClass = CustomerBlockHandler.class
)
public String payment02(@PathVariable("id") Long id){
if(id == null){
throw new NullPointerException("id 不可以为null");
}else if(id < 0){
throw new IllegalArgumentException("id 不可以为负数");
}else {
return paymentServer.payment02(id);
}
}
}
上面注意接口的@SentinelResource注解,里面配置了异常的兜底方法和自定义限流方法
同样的,9004项目也是如此,启动9003项目,测试接口

(2)客户端
创建客户端cloud-alibaba-nacos-consumer-order84用来调用上面的9003和9004服务,首先引入依赖
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</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>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 84
spring:
application:
name: nacos-consumer-order84
cloud:
nacos:
discovery:
server-addr: 39.96.22.34:8080 #配置Nacos地址
namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id
#sentinel配置
sentinel:
transport:
#client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP)
dashboard: 127.0.0.1:8080 #sentinel的地址
port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口
#激活sentinel对feign的支持
feign:
sentinel:
enabled: true
主启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerOrder84 {
public static void main(String[] args) {
SpringApplication.run(ConsumerOrder84.class,args);
}
}
服务层接口,用openfeign进行调用
@FeignClient(value = "cloud-alibaba-provider-server",fallback = PaymentServerFallback.class)
@Service
public interface PaymentServer {
@GetMapping("/payment01")
public String payment01();
@GetMapping("/payment02/{id}")
public String payment02(@PathVariable("id") Long id);
}
对应实现类用于调用失败的方法处理
@Component
public class PaymentServerFallback implements PaymentServer {
@Override
public String payment01() {
return "84---->Payment01:限流方法";
}
@Override
public String payment02(Long id) {
return "84---->Payment02:限流方法,id = "+id;
}
}
编写接口
@RestController
public class PaymentController {
@Resource
private PaymentServer paymentServer;
@GetMapping("/consumer/payment01")
public String Payment01(){
return paymentServer.payment01();
}
@GetMapping("/consumer/payment02/{id}")
public String payment02(@PathVariable("id") Long id){
return paymentServer.payment02(id);
}
}
最后启动9003项目、9004项目和84项目

客户端调用服务端

关闭9003和9004项目,再次调用走的是自定义限流方法
五、sentinel持久化
当Sentinel Dashboard中添加的规则是存储在内存中的,只要项目一重启规则就丢失了,所以需要将规则持久化到nacos中,在nacos中添加规则,然后同步到dashboard中
以8401为例,实现规则的持久化,加入下面的依赖
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
修改配置文件
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-server8401
cloud:
nacos:
discovery:
server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群
namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id
#sentinel配置
sentinel:
transport:
#client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP)
dashboard: 127.0.0.1:8080 #sentinel的地址
port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口
#sentinel持久化到nacos,存储在nacos配置文件中
datasource:
dsl:
nacos:
server-addr: 39.96.22.34:8080 #nacos的地址
groupId: DEV_GROUP # 配置文件的分组
namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id
dataId: sentinel-server8401 # 名字与在nacosc创建的持久化文件名一致
data-type: json #文件类型是json
rule-type: flow # 路由存储规则
# datasource: #添加Nacos数据源配置
# ds1:
# nacos:
# server-addr: 39.96.22.34:8080 #nacos的地址
# data-id: sentinel-server8401 # 名字与在nacosc创建的持久化文件名一致
# group-id: DEV_GROUP # 配置文件的分组
# data-type: json #文件类型是json
# rule-type: flow # 路由存储规则
management:
endpoints:
web:
exposure:
include: '*'
重点加入datasource

然后再nacos服务上对应配置文件进行相应文件的创建即可

json文件解读

启动项目即可直接看到sentinel配置了。
本文详细介绍了如何在SpringCloud Alibaba的Sentinel中实现服务限流、降级、流控规则实战及持久化配置。通过实例演示了Sentinel Dashboard的使用和配置Sentinel规则,包括自定义异常处理和持久化至Nacos。
:Sentinel&spm=1001.2101.3001.5002&articleId=108944773&d=1&t=3&u=8966885b248947e5ab0b768bc5a17889)
1232

被折叠的 条评论
为什么被折叠?



