服务容错加餐:Sentinel核心技术

开发 架构
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。所以,Sentinel的核心功能包括:流量控制、熔断降级、系统负载保护。

在《SpringCloud Alibaba实战》专栏前面的文章中,我们实现了用户微服务、商品微服务和订单微服务之间的远程调用,并且实现了服务调用的负载均衡。也基于阿里开源的Sentinel实现了服务的限流与容错。今天,就和大家一起来聊聊Sentinel的核心技术与配置规则,这应该是全网最全的Sentinel使用教程了吧。

本章总览

文章有点长呀,小伙伴们耐心看完,并跟着实操每一个案例,相信你一定会对Sentinel有一个全新的认识。

Sentinel核心功能

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。所以,Sentinel的核心功能包括:流量控制、熔断降级、系统负载保护。

流量控制

在高并发、大流量场景下,进入系统的流量如果不加控制的话,系统就很有可能会被流量压垮。所以,在流量正式进入系统之前,需要对流量进行控制,以便使流量均匀、可控的方式进入系统。

Sentinel作为一个非常出色的容错组件,能够将不可控的流量经过处理转化成均匀、可控的流量。

熔断降级

如果检测到系统中的某个调用链路中某个节点出现故障,比如请求超时、服务宕机或者异常比超出一定阈值时,就会对出现故障的节点的调用频率进行限制,甚至不调用出现故障的节点,让请求能够快速失败并返回,以最大程度避免影响到其他节点的服务而导致系统的级联故障。

Sentinel主要通过 限制并发线程数和响应时间 对资源的访问进行降级。

1、限制并发线程数进行降级

Sentinel可以通过限制服务节点的并发线程数量,来减少对其他服务节点的影响。例如,当某个服务节点出现故障,例如响应时间变长,或者直接宕机。此时,对服务的直接影响就是会造成请求线程数的不断堆积。如果这些堆积的线程数达到一定的数量后,对当前服务节点的后续请求就会被拒绝,等到堆积的线程完成任务后再开始继续接收新的请求。

2、通过响应时间进行降级

Sentinel除了可以通过限制并发线程数进行降级外,也能够通过响应时间进行降级。如果依赖的服务出现响应时间过长的情况,则所有对该服务的请求都会被拒绝,直到过了指定的时间窗口之后才能再次访问该服务。

系统负载保护

Sentinel提供了系统维度的自适应保护能力。当系统的压力和负载比较高的时候,如果还持续让大量的请求进入系统,此时就有可能将系统压垮,进而导致系统宕机。Sentinel会在集群环境下,将本应服务器A承载的流量转发到其他服务器上,比如转发到服务器B上。如果此时服务器B也处于高负载的状态,则Sentinel会提供相应的保护机制,让系统的入口流量和系统的整体负载达到平衡,让系统整体可用,并且能够最大限度的处理请求。

Sentinel核心规则

Sentinel的核心规则包括流控规则、熔断规则、热点规则、授权规则和系统规则,每种规则的配置方式不同。接下来,就详细介绍下Sentinel中的每种规则的作用与效果。

流控规则

Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。

簇点链路规则

(1)点击簇点链路菜单,可以看到之前访问过的接口,如下所示。

(2)点击右侧的流控按钮,会弹出新增流控规则的提示框,如下所示。

这里,每个配置项的说明如下所示。

  • 资源名:资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
  • 针对来源:具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。
  • 阈值类型:QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。
  • 单机阈值:与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。
  • 是否集群:选中则表示集群环境,不选中则表示非集群环境。

配置简单限流

这里,针对http://localhost:8080/order/test_sentinel接口进行简单的配置,在新增流控规则里阈值类型选择QPS,单机阈值输入3,表示每秒钟的请求量如果超过3,则会触发Sentinel的限流操作。

点击新增按钮后,会为http://localhost:8080/order/test_sentinel接口新增一条限流规则,如下所示。

接下来,在浏览器上快速刷新http://localhost:8080/order/test_sentinel接口,当每秒钟的刷新频率超过3次时,会出现如下所示的提示信息。

配置流控模式

点击http://localhost:8080/order/test_sentinel接口流控规则后面的编辑按钮,打开编辑流控规则弹出框(如果是首次配置的话,就是新增流控规则弹出框),点击高级选项配置。

会显示出如下所示的界面。

可以看到,Sentinel主要提供了三种流控模式,分别为直接、关联和链路。

  • 直接:默认的流控模式,当接口达到限流条件时,直接开启限流功能。
  • 关联:当关联的资源达到限流条件时,开启限流功能。
  • 链路:当从某个接口请求过来的资源达到限流条件时,开启限流功能。

演示直接流控模式

Sentinel默认就是使用的直接流控模式,我们之前在订单微服务中集成的就是Sentinel的直接流控模式,在本文的流控规则-配置简单限流中,也是使用的直接流控模式,这里不再赘述。

演示关联流控模式

(1)在订单微服务的io.binghe.shop.order.controller.OrderController 类中新增 /test_sentinel2接口,如下所示。

@GetMapping(value = "/test_sentinel2")
public String testSentinel2(){
log.info("测试Sentinel2");
return "sentinel2";
}

(2)在浏览器上访问下http://localhost:8080/order/test_sentinel2接口,以便使Sentinel检测到http://localhost:8080/order/test_sentinel2接口。

注意:如果想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel检测出相应的接口,这里一定要注意,在后续的文章中,不再单独说明。

(3)回到为http://localhost:8080/order/test_sentinel接口配置流控规则的页面,如下所示。

(4)在流控模式中选择关联,在关联资源中输入/test_sentinel2,如下所示。

点击保存按钮保存配置。

(5)打开JMeter,对http://localhost:8080/order/test_sentinel2接口进行测试,使其每秒钟的访问次数大于3,JMeter的具体配置如下所示。

在线程组中配置每秒访问4次。

配置完毕后,使用JMeter开始访问http://localhost:8080/order/test_sentinel2接口。

(6)在浏览器上刷新http://localhost:8080/order/test_sentinel接口,发现已经触发了Sentinel的限流功能。

至此,关联流控模式演示完毕。

演示链路流控模式

(1)在订单微服务的io.binghe.shop.order.service包中新增SentinelService接口,用于测试流控模式,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 测试Sentinel
*/
public interface SentinelService {

/**
* 测试方法
*/
void sendMessage();
}

(2)在订单微服务的io.binghe.shop.order.service.impl包中新增SentinelServiceImpl类,实现SentinelService接口,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 测试类
*/
@Service
public class SentinelServiceImpl implements SentinelService {
@Override
@SentinelResource("sendMessage")
public void sendMessage() {
System.out.println("测试Sentinel的链路流控模式");
}
}

这里,我们在sendMessage()方法上使用了@SentinelResource注解, @SentinelResource注解会在后续文章中介绍,这里不再赘述。

(3)在订单微服务的io.binghe.shop.order.controller包下新建SentinelController类,用于测试接口,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 测试Sentinel
*/
@Slf4j
@RestController
public class SentinelController {
@Autowired
private SentinelService sentinelService;

@GetMapping(value = "/request_sentinel1")
public String requestSentinel1(){
log.info("测试Sentinel1");
sentinelService.sendMessage();
return "sentinel1";
}
@GetMapping(value = "/request_sentinel2")
public String requestSentinel2(){
log.info("测试Sentinel2");
sentinelService.sendMessage();
return "sentinel2";
}
}

(4)升级SpringCloud Alibaba的依赖版本到2.2.7.RELEASE,升级SpringCloud版本到Hoxton.SR12,并且加入SpringBoot的管理依赖。主要的修改的是shop-springcloud-alibaba父工程的pom.xml配置,修改后的配置文件如下所示。

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
<spring.boot.version>2.3.12.RELEASE</spring.boot.version>
<logback.version>1.1.7</logback.version>
<slf4j.version>1.7.21</slf4j.version>
<common.logging>1.2</common.logging>
<fastjson.version>1.2.51</fastjson.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis.plus.version>3.4.1</mybatis.plus.version>
<mysql.jdbc.version>8.0.19</mysql.jdbc.version>
<druid.version>1.1.10</druid.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>

(5)升级Nacos,将Nacos注册中心由1.4.3版本升级为2.1.0版本,并进入Nacos的bin目录,输入如下命令启动Nacos。

startup.cmd -m standalone

(6)在订单微服务的application.yml文件中新增链路配置,如下所示。

spring:
cloud:
sentinel:
web-context-unify: false

(7)在浏览器中分别访问http://localhost:8080/order/request_sentinel1和http://localhost:8080/order/request_sentinel2,查看Sentinel中的簇点链路,如下所示。

(8)点击sendMessage后面的流控按钮,如下所示。

(9)在弹出的新增流控规则编辑框中阈值类型选择QPS,单机阈值输入3,在打开的高级选项中的流控模式选择链路,入口资源输入/request_sentinel1,如下所示。

点击新增按钮保存配置,此时,如果是通过http://localhost:8080/order/request_sentinel1接口调用io.binghe.shop.order.service.SentinelService#sendMessage()方法时,如果调用的频率每秒钟超过3次,就会触发Sentinel的限流操作,而通过http://localhost:8080/order/request_sentinel2接口调用io.binghe.shop.order.service.SentinelService#sendMessage()方法时,则不受限制。

(10)在浏览器中不断访问http://localhost:8080/order/request_sentinel1,使得每秒的访问次数超过3,则会触发Sentinel的限流操作,如下所示。

访问http://localhost:8080/order/request_sentinel2接口则不会被限流。

至此,链路流控模式演示完毕。

附加说明

在流控规则的高级选项中还有三个流控效果,如下所示。

接下来,就对这三个选项进行简单的说明。

快速失败:会直接失败,抛出异常,期间不会做任何其他的处理操作。

Warm Up:从开始阈值到最大QPS阈值会有一个缓冲,可以设置一个预热时长,这个选项比较适合突发瞬时高并发流量的场景,能够将突发的高并发流量转换为均匀、缓慢增长的场景。

排队等待:能够使请求均匀的通过,单机的阈值为每秒通过的请求数量,其余的请求会排队等待。另外,还会设置一个超时时间,当请求超过超时时间未处理时,会被丢弃。

熔断规则

降级规则一般情况下,指的是满足某些条件时,对服务进行降级操作。

熔断规则概述

Sentinel主要提供了三个熔断策略,分别为:慢调用比例、异常比例和异常数。

演示基于慢调用比例熔断

(1)首先在浏览器中访问:http://localhost:8080/order/request_sentinel2,在Sentinel的簇点链路里找到/request_sentinel2。

(2)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

上述配置表示最大响应时长为1ms,比例阈值达到0.1时,会触发熔断操作,并且熔断的时长为2,最小请求数未5,统计的时长为1000毫秒。

(3)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel2,会发现触发了Sentinel的熔断操作,如下所示。

演示基于异常比例熔断

(1)在订单微服务的io.binghe.shop.order.controller.SentinelController类中定义一个成员变量count,用来记录访问计数,同时,新增一个requestSentinel4()方法用来测试基于异常比例的熔断,如下所示。

private int count = 0;
@GetMapping(value = "/request_sentinel4")
@SentinelResource("request_sentinel4")
public String requestSentinel4(){
log.info("测试Sentinel4");
count++;
//模拟异常,比例为50%
if (count % 2 == 0){
throw new RuntimeException("演示基于异常比例熔断");
}
return "sentinel4";
}

(2)首先在浏览器中访问

http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

(3)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

在io.binghe.shop.order.controller.SentinelController#requestSentinel4()方法中,设置的异常比例为50%,这里在Sentinel的比例阈值中设置的异常比例为0.3,也就是30%,所以,在访问http://localhost:8080/order/request_sentinel4接口时,会触发Sentinel的熔断操作。

(4)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4,会发现触发了Sentinel的熔断操作,如下所示。

演示基于异常数熔断

(1)首先在浏览器中访问

http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

(2)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

上述配置表示,在1秒钟内最少请求2次,当异常数大于1时,会触发熔断操作,熔断的时长为5秒。

(3)点击保存按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4,会发现触发了Sentinel的熔断操作,如下所示。

热点规则

Sentinel的热点规则可以将流量规则控制到具体的参数上。

热点规则概述

Sentinel的热点规则可以根据具体的参数来控制流量规则,适用于根据不同参数进行流量控制的场景。

演示热点规则

(1)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel3()方法,其中会带有一个String类型的参数header和一个String类型的参数body,如下所示。

@GetMapping(value = "/request_sentinel3")
@SentinelResource("request_sentinel3")
public String requestSentinel3(String header, String body){
log.info("测试Sentinel3");
return "sentinel3";
}

(2)在浏览器中访问http://localhost:8080/order/request_sentinel3接口,在Sentinel的簇点链路中会显示/request_sentinel3接口,如下所示。

(3)点击热点按钮,如下所示。

(4)在弹出的热点规则配置框中的参数索引中输入0,单机阈值输入1,统计窗口时长输入1,如下所示。

表示对requestSentinel3()方法的第一个参数header进行限流,如果每秒钟访问的次数超过1次,则触发限流。

(5)保存配置后,在浏览器中不断访问http://localhost:8080/order/request_sentinel3?header=header,当每秒访问的频率超过1次时,会触发Sentinel的限流操作,如下所示。

不断访问http://localhost:8080/order/request_sentinel3?body=body,则不会触发限流操作。

演示热点高级选项规则

(1)在弹出的热点规则配置框中打开高级选项,在参数类型中选择java.lang.String,因为在参数索引中输入0,表示的是对header参数限流,而header参数是String类型的。在参数值里输入header,也就是为参数名为header的参数赋值为字符串header。限流阈值为1,如下所示。

(2)点击添加按钮后如下所示。

(3)点击保存按钮,在浏览器不断刷新http://localhost:8080/order/request_sentinel3?header=header,会触发Sentinel的限流操作。

授权规则

授权规则能够根据调用来源判断还否允许执行本次请求。

授权规则概述

在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。

在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。

演示授权规则

(1)在订单微服务shop-order中新建io.binghe.shop.order.parser包,并创建MyRequestOriginParser类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,用来处理请求的来源。代码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description Sentinel授权规则,用来处理请求的来源
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}

(2)首先在浏览器中访问

http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

(3)点击授权按钮,进入授权规则配置框,按照如下方式进行配置。

其中,流控应用填写的是test,授权类型为黑名单。这里要结合新建的MyRequestOriginParser类进行理解,MyRequestOriginParser类的parseOrigin()方法如下所示。

public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}

parseOrigin()方法中直接返回了从HttpServletRequest中获取的serverName参数,而在上图中的流控应用中输出的是test,授权类型为黑名单。

所以,如果我们访问http://localhost:8080/order/request_sentinel4?serverName=test的话,是处于黑名单的状态,无法访问。

(4)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4?serverName=test,会发现无法访问,被Sentinel限流了。

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、 RT、入口 QPS 、 CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则概述

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护

演示系统规则

(1)在订单微服务中新建io.binghe.shop.order.handler包,并创建MyUrlBlockHandler类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler接口,用来捕获系统级Sentinel异常,代码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 处理Sentinel系统规则,返回自定义异常
*/
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = null;
if (e instanceof FlowException) {
msg = "限流了";
} else if (e instanceof DegradeException) {
msg = "降级了";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (e instanceof SystemBlockException) {
msg = "系统规则(负载/...不满足要求)";
} else if (e instanceof AuthorityException) {
msg = "授权规则不通过";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 500);
jsonObject.put("codeMsg", msg);
response.getWriter().write(jsonObject.toJSONString());
}
}

(2)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel5()方法,如下所示。

@GetMapping(value = "/request_sentinel5")
@SentinelResource("request_sentinel5")
public String requestSentinel5(){
log.info("测试Sentinel5");
return "sentinel5";
}

(3)首先在浏览器中访问

http://localhost:8080/order/request_sentinel5,在Sentinel的簇点链路里找到/request_sentinel5。

(4)点击流控按钮,进入流控规则配置框,按照如下方式进行配置。

(5)在浏览器中不断刷新

http://localhost:8080/order/request_sentinel5,会显示如下信息。

返回的原始数据如下所示。

{"code":500,"codeMsg":"限流了"}

说明触发了系统规则,捕获到了Sentinel全局异常。

@SentinelResource注解

使用Sentinel时,可以使用@SentinelResource注解来指定异常处理策略。

@SentinelResource注解概述

在Sentinel中,指定发生异常时的处理策略非常简单,只需要使用@SentinelResource注解即可,@SentinelResource注解的源码如下所示。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

//资源名称
String value() default "";
//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
EntryType entryType() default EntryType.OUT;
int resourceType() default 0;
//处理BlockException的函数名称,函数要求:
//1. 必须是 public
//2.返回类型 参数与原方法一致
//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
//blockHandlerClass ,并指定blockHandlerClass里面的方法。
String blockHandler() default "";

//存放blockHandler的类,对应的处理函数必须static修饰。
Class<?>[] blockHandlerClass() default {};

//用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所
//有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
//1. 返回类型与原方法一致
//2. 参数类型需要和原方法相匹配
//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
String fallback() default "";

//存放fallback的类。对应的处理函数必须static修饰。
String defaultFallback() default "";

//用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
//行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
//1. 返回类型与原方法一致
//2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
//3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
Class<?>[] fallbackClass() default {};

//指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};

//需要trace的异常
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

演示@SentinelResource注解

1.定义限流和降级后的处理方法:

(1)在订单微服务的io.binghe.shop.order.service.SentinelService接口中新增sendMessage2()方法,如下所示。

String sendMessage2();

(2)在订单微服务的io.binghe.shop.order.service.impl.SentinelServiceImpl方法中,实现sendMessage2()方法,并且定义一个成员变量count,用来记录请求sendMessage2()方法的次数,同时定义25%的异常率。在sendMessage2()方法上使用@SentinelResource指定了资源的名称、发生BlockException时进入的方法和发生异常时进入的方法,代码如下所示。

private int count = 0;
@Override
@SentinelResource(
value = "sendMessage2",
blockHandler = "blockHandler",
fallback = "fallback")
public String sendMessage2() {
count ++;
//25%的异常率
if (count % 4 == 0){
throw new RuntimeException("25%的异常率");
}
return "sendMessage2";
}
public String blockHandler(BlockException e){
log.error("限流了:{}", e);
return "限流了";
}
public String fallback(Throwable e){
log.error("异常了:{}", e);
return "异常了";
}

(3)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel6()方法,在方法中调用io.binghe.shop.order.service.SentinelService接口中的sendMessage2()方法,如下所示。

@GetMapping(value = "/request_sentinel6")
public String requestSentinel6(){
log.info("测试Sentinel6");
return sentinelService.sendMessage2();
}

(4)首先在浏览器中访问

http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6,当刷新的频率超过每秒2次时,浏览器会显示如下信息。

当刷新的次数是4的倍数时,浏览器会显示如下信息。

2.在外部类中指定限流和异常调用的方法

(1)在订单微服务的io.binghe.shop.order.handler包下新建

MyBlockHandlerClass类,用于定义被Sentinel限流时的方法,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 定义被Sentinel限流时调用的方法
*/
@Slf4j
public class MyBlockHandlerClass {

public static String blockHandler(BlockException e){
log.error("限流了:{}", e);
return "限流了";
}
}

(2)在订单微服务的io.binghe.shop.order.handler包下新建MyFallbackClass类,用于定义抛出异常时调用的方法,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description 定义异常时调用的方法
*/
@Slf4j
public class MyFallbackClass {

public static String fallback(Throwable e){
log.error("异常了:{}", e);
return "异常了";
}
}

(3)修改io.binghe.shop.order.service.impl.SentinelServiceImpl#sendMessage2()方法上的注解,修改后的代码如下所示。

@Override
@SentinelResource(
value = "sendMessage2",
blockHandlerClass = MyBlockHandlerClass.class,
blockHandler = "blockHandler",
fallbackClass = MyFallbackClass.class,
fallback = "fallback")
public String sendMessage2() {
count ++;
System.out.println(count);
//25%的异常率
if (count % 4 == 0){
throw new RuntimeException("25%的异常率");
}
return "sendMessage2";
}

(4)首先在浏览器中访问

http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6,当刷新的频率超过每秒2次时,浏览器会显示如下信息。

当刷新的次数是4的倍数时,浏览器会显示如下信息。

Sentinel持久化

Sentinel中可以自定义配置的持久化来将Sentinel的配置规则持久化到服务器磁盘,使得重启应用或者Sentinel后,Sentinel的配置规则不丢失。

Sentinel持久化概述

细心的小伙伴会发现,我们之前配置的Sentinel规则在程序重启或者Sentinel重启后就会消失不见,此时就需要我们重新配置。如果这发生在高并发、大流量的场景下是不可接受的。那有没有什么办法让程序或Sentinel重启后配置不丢失呢?其实,Sentinel中可以自定义配置的持久化来解决这个问题。

实现Sentinel的持久化

(1)在订单微服务shop-order中新建io.binghe.shop.order.persistence包,并创建SentinelPersistenceRule类,实现com.alibaba.csp.sentinel.init.InitFunc接口,并在SentinelPersistenceRule类中获取应用的名称,覆写init()方法,源码如下所示。

/**
* @author binghe
* @version 1.0.0
* @description Sentinel规则持久化
*/
public class SentinelPersistenceRule implements InitFunc {
//实际可以从外部配置读取
private String appcationName = "server-order";
@Override
public void init() throws Exception {
String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + appcationName;
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);

// 流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

// 降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

// 授权规则
ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
authorityRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

// 热点参数规则
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}

private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
);

private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
);

private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
);

private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}

private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}

(2)在订单微服务的resources目录下新建META-INF目录,并在META-INF目录下新建services目录,在services目录下新建名称为com.alibaba.csp.sentinel.init.InitFunc的文件,如下所示。

(3)在com.alibaba.csp.sentinel.init.InitFunc文件中添加io.binghe.shop.order.persistence.SentinelPersistenceRule类的全类名,如下所示。

io.binghe.shop.order.persistence.SentinelPersistenceRule

(4)首先在浏览器中访问

http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

(6)点击新增按钮,此时打开电脑的user.home目录,我电脑的目录为C:\Users\binghe,可以发现C:\Users\binghe目录中多了一个sentinel-rules目录。

(7)打开sentinel-rules目录,发现里面存在一个server-order目录,如下所示。

(8)打开server-order目录后,会发现生成了Sentinel的配置文件,并持久化到了磁盘上,如下所示。

(9)打开flow-rule.json文件,内容如下所示。

[
{
"clusterConfig": {
"acquireRefuseStrategy": 0,
"clientOfflineTime": 2000,
"fallbackToLocalWhenFail": true,
"resourceTimeout": 2000,
"resourceTimeoutStrategy": 0,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false,
"controlBehavior": 0,
"count": 2,
"grade": 1,
"limitApp": "default",
"maxQueueingTimeMs": 500,
"resource": "/request_sentinel6",
"strategy": 0,
"warmUpPeriodSec": 10
}
]

可以看到,flow-rule.json文件中持久化了对于/request_sentinel6接口的配置。

至此,我们完成了Sentinel规则的持久化。

责任编辑:姜华 来源: 冰河技术
相关推荐

2022-05-03 19:38:15

限流微服务Sentinel

2022-05-16 08:22:11

网关过滤器路由

2021-03-16 08:31:59

微服务Sentinel雪崩效应

2022-05-07 14:31:46

物联网

2009-06-15 17:54:50

Java核心技术

2009-06-26 16:01:39

EJB组织开发EJB容器EJB

2017-03-08 10:06:11

Java技术点注解

2023-06-14 08:49:22

PodKubernetes

2016-11-15 14:33:05

Flink大数据

2021-07-28 07:53:20

C#.NET设计

2018-05-16 11:05:49

ApacheFlink数据流

2022-10-11 08:37:43

Servlet配置版本

2019-03-05 14:57:21

大数据Hadoop框架

2019-05-15 08:40:34

工业物联网MQTT物联网

2011-12-20 13:46:16

Facebook开源服务器数据中心

2017-05-08 13:37:32

IaaS核心虚拟化

2011-11-23 15:53:54

Java核心技术框架

2016-12-12 09:01:47

Amazon Go核心技术

2019-05-15 08:26:44

工业物联网MQTT物联网

2016-11-22 17:05:54

Apache Flin大数据Flink
点赞
收藏

51CTO技术栈公众号