在上篇文章中,我们介绍了服务调用会产生的网络故障导致服务崩掉的问题;以及随之带来的服务容错的概念。那这篇文章就介绍下,什么是服务容错?Sentinel有哪些概念和重要功能?它又有哪些降级规则和流控规则?如何对接口实现限流?Feign如何整合Sentinel?如何利用Sentinel
实现服务容错等问题,在这篇文章中,你都能有所了解,有所收获。
首先,什么是Sentinel
?相信初学微服务的小伙伴,心里肯定一阵懵。
Sentinel
(分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel
具有以下特征:
SpringCloud、 Dubbo、gRPC
的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。Sentinel分为两个部分:
从上面Sentinel的特性中可以了解到,为微服务集成Sentinel
非常简单, 只需要加入Sentinel的依赖即可。
1、在pom.xml中加入下面依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、编写一个Controller测试使用:
@RestController
@Slf4j
public class OrderController3 {
@RequestMapping("/order/message1")
public String message1() {
return "message1";
}
@RequestMapping("/order/message2")
public String message2() {
return "message2";
}
}
Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。
1、下载jar包,解压到文件夹:
https://github.com/alibaba/Sentinel/releases
2、启动控制台
\# 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 - Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.0.jar
3、修改 shop-order ,在里面加入有关控制台的配置:
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可 dashboard: localhost:8080 # 指定控制台服务的地址
4、 通过浏览器访问localhost:8080 进入控制台 ( 默认用户名密码是 sentinel/sentinel )
注:
Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上,即在微服务中指定控制台的地址,并且还要开启一个跟控制台传递数据的端口,控制台也可以通过此端口调用微服务中的监控程序获取微服务的各种信息。
接下来就利用Sentinel实现接口的限流:
1、通过控制台为message1添加一个流控规则:
资源是 Sentinel
的关键概念。它可以是 Java 应用程序中的任何内容,可以是一个服务,也可以是一个 方法,甚至可以是一段代码。
通俗的讲,资源就是Sentinel要保护的东西。
作用在资源之上, 定义以什么样的方式保护资源,主要包括流量控制规则、熔断降级规则以及系统保护 规则。
通俗的讲,规则就是用来定义如何进行保护资源的。
Sentinel
的主要功能就是容错,主要体现为下面这三个:
任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为 一个调配器,可以根据需要把随机的请求调整成合适的形状。这就是流量控制。
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel
对这个问题采取了两种手段:
当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。
这时候我们可以利用Sentinel
通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。
当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。
当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流 量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
新增流控规则界面如下:
资源名: 唯一名称,默认是请求路径,可自定义
针对来源: 指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制
阈值类型单机阈值:
QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流
线程数: 当调用该接口的线程数达到阈值的时候,进行限流
是否集群:暂不需要集群
接下来我们以QPS为例来研究限流规则的配置:
我们先做一个简单配置,设置阈值类型为QPS,单机阈值为3。即每秒请求量大于3的时候开始限流。接下来,在流控规则页面就可以看到这个配置。
点击上面设置流控规则的编辑按钮,然后在编辑页面点击高级选项,会看到有流控模式一栏:
sentinel
共有三种流控模式,分别是:
下面呢分别演示三种模式:
直接流控模式:
直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控模式。
关联流控模式:
关联流控模式指的是,当指定接口关联的接口达到限流条件时,开启对指定接口开启限流。
第1步:配置限流规则,将流控模式设置为关联,关联资源设置为的 /order/message2。
第3步:通过postman软件向/order/message2连续发送请求,注意QPS一定要大于3
第4步:访问/order/message1,会发现已经被限流
链路流控模式:
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项。
区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度 更细。
第1步: 编写一个service,在里面添加一个方法message
@Service
public class OrderServiceImpl3 {
@SentinelResource("message")
public void message() {
System.out.println("message");
}
}
第2步: 在Controller中声明两个方法,分别调用service中的方法m
@RestController
@Slf4j
public class OrderController3 {
@Autowired
private OrderServiceImpl3 orderServiceImpl3;
@RequestMapping("/order/message1")
public String message1() {
orderServiceImpl3.message();
return "message1";
}
@RequestMapping("/order/message2")
public String message2() {
orderServiceImpl3.message();
return "message2";
}
}
第3步: 禁止收敛URL的入口 context
添加一个配置类,自己构建CommonFilter实例:
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口资源关闭聚合
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
第4步: 控制台配置限流规则
第5步: 分别通过 /order/message1 和 /order/message2 访问, 发现2没问题, 1的被限流了
降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:
平均响应时间:
当资源的平均响应时间超过阈值(以 ms 为单位)之后,资源进入准降级状态。 如果接下来 1s 内持续进入 5 个请求,它们的 RT都持续超过这个阈值,那么在接下的时间窗口 (以 s 为单位)之内,就会对这个方法进行服务降级。
异常比例:
当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口(以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0,1.0]。
第1步: 首先模拟一个异常:
int i = 0;
@RequestMapping("/order/message2")
public String message2() {
i++;
//异常比例为0.333
if (i % 3 == 0){
throw new RuntimeException();
}
return "message2";
}
第2步: 设置异常比例为0.25
异常数 :
当资源近 1 分钟的异常数目超过阈值之后会进行服务降级。注意由于统计时间窗口是分钟级别的,若时间窗口小于 60s,则结束熔断状态后仍可能再进入熔断状态。
热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。
热点规则简单使用:
第1步: 编写代码:
@RequestMapping("/order/message3")
@SentinelResource("message3")
//注意这里必须使用这个注解标识,热点规则不生效
public String message3(String name, Integer age) {
return name + age;
}
第2步: 配置热点规则
第3步: 分别用两个参数访问,会发现只对第一个参数限流了
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel
的来源访问控制的功能。
上面的资源名和授权类型不难理解,但是流控应用怎么填写呢?
第1步: 自定义来源处理规则:
@Component
public class RequestOriginParserDefinition implements RequestOriginParser{
@Override
public String parseOrigin(HttpServletRequest request) {
String serviceName = request.getParameter("serviceName");
return serviceName;
}
}
第2步: 授权规则配置:
这个配置的意思是只有serviceName=pc
不能访问(黑名单):
第3步: 访问 http://localhost:8091/order/message1?serviceName=pc观察结果
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU使用 率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统 保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
第1步: 引入sentinel的依赖:
<!--sentinel客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
第2步: 在配置文件中开启Feign对Sentinel的支持:
feign:
sentinel:
enabled: true
第3步: 创建容错类
//容错类要求必须实现被容错的接口,并为每个方法实现容错方案
@Component
@Slf4j
public class ProductServiceFallBack implements ProductService {
@Override
public Product findByPid(Integer pid) {
Product product = new Product();
product.setPid(-1);
return product;
}
}
第4步: 为被容错的接口指定容错类
//value用于指定调用nacos下哪个微服务
//fallback用于指定容错类
@FeignClient(value = "service-product", fallback = ProductServiceFallBack.class)
public interface ProductService {
@RequestMapping("/product/{pid}")
//指定请求的URI部分
Product findByPid(@PathVariable Integer pid);
}
第5步: 修改controller
@RestController
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private ProductService productService;
//下单--fegin
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
//调用商品微服务,查询商品信息
Product product = productService.findByPid(pid);
if (product.getPid() == -1) {
Order order = new Order();
order.setPname("下单失败");
return order;
}
log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.createOrder(order);
log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return order;
}
}
第6步: 停止所有 shop-product 服务,重启 shop-order 服务,访问请求,观察容错效果
本篇文章,主要介绍了Sentinel的基本概念和重要功能有哪些。也介绍了如何实现一个接口的限流以及Sentinel的流控规则,以及最重要的Feign如何整合Sentinel。