Sentinel的线程隔离与Hystix的线程隔离有什么差别?
线程隔离有两种方式实现:
线程池隔离(Hystix默认采用)
信号量隔离(Sentinel默认采用)
Sentinel的限流与Gateway的限流有什么差别?
限流 含义:对应用服务器的请求做限制,避免因过多请求而导致服务器过载甚至宕机。
限流算法常见的包括三种:
计数器算法,又包括窗口计数器算法、滑动窗口计数器算法
令牌桶算法(Token Bucket)
漏桶算法(Leaky Bucket)
1. 固定窗口计数器算法
将时间划分为多个窗口,窗口时间跨度称为Interval,本例中为1000ms;
每个窗口维护一个计数器,每有一次请求就将计数器加一,限流就是设置计数器阈值,本例为3
如果计数器超过了限流阈值,则超出阈值的请求都被丢弃。
(把时间窗口固定了,但是时间是没有界限的,在4500-5500其实一共有6次请求,但是最高阈值是3,超过了上限,可能会给服务器带来压力,甚至使服务器宕机)
2. 滑动窗口计数器算法
会将一个窗口划分为n个更小的区间,例如
窗口时间跨度Interval为1秒;区间数量 n = 2 ,则每个小区间时间跨度为500ms
限流阈值依然为3,时间窗口(1秒)内请求超过阈值时,超出的请求被限流
窗口会根据当前请求所在时间(currentTime)移动,窗口范围是从(currentTime-Interval)之后的第一个时区开始,到currentTime所在时区结束。
(1250-2100之间差850ms在1s之内,说明1s之内有4个请求,这个时候就需要划分更小的区间,2100-1000为1100,这个时间段内有1250,1300,1600三个了,这个时候2100就为有问题的请求,超出阈值,窗口划分的越细,限流越准确 )
3.令牌桶算法
以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
如果令牌桶中没有令牌,则请求等待或丢弃
4.漏桶算法
将每个请求视作"水滴"放入"漏桶"进行存储;
"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水”;
如果"漏桶"满了则多余的"水滴"会被直接丢弃。
可以理解成请求在桶内排队等待
Sentinel在实现漏桶时,采用了排队等待模式:
让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。并发的多个请求必须等待,预期的等待时长 =最近一次请求的预期等待时间 + 允许的间隔。如果请求预期的等待时间超出最大时长,则会被拒绝。
例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常
限流算法对比
一、Sentinel的基本概念
Sentinel实现限流、隔离、降级、熔断等功能,本质要做的就是两件事情:
统计数据:统计某个资源的访问数据(QPS、RT等信息)
规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足
这里的 资源 就是希望被Sentinel保护的业务,例如项目中定义的controller方法就是默认被Sentinel保护的资源。
1.ProcessorSlotChain
实现上述功能的核心骨架是一个叫做ProcessorSlotChain(处理器插槽列)的类。这个类基于责任链模式来设计,将不同的功能(限流、降级、系统保护)封装为一个个的Slot,请求进入后逐个执行即可。
其工作流如图:
责任链中的Slot也分为两大类:
1)统计数据构建部分(statistic)
NodeSelectorSlot:负责构建簇点链路中的节点(DefaultNode),将这些节点形成链路树
ClusterBuilderSlot:负责构建某个资源的ClusterNode,ClusterNode可以保存资源的运行信息(响应时间、QPS、block 数目、线程数、异常数等)以及来源信息(origin名称)
StatisticSlot:负责统计实时调用数据,包括运行信息、来源信息等
2)规则判断部分(rule checking)
AuthoritySlot:负责授权规则(来源控制)
SystemSlot:负责系统保护规则
ParamFlowSlot:负责热点参数限流规则
FlowSlot:负责限流规则
DegradeSlot:负责降级规则
2.Node
Sentinel中的簇点链路是由一个个的Node组成的,Node是一个接口,包括下面的实现:
所有的节点都可以记录对资源的访问统计数据,所以都是StatisticNode的子类。
按照作用分为两类Node:
DefaultNode:代表链路树中的每一个资源,一个资源出现在不同链路中时,会创建不同的DefaultNode节点。而树的入口节点叫EntranceNode,是一种特殊的DefaultNode
ClusterNode:代表资源,一个资源不管出现在多少链路中,只会有一个ClusterNode。记录的是当前资源被访问的所有统计数据之和。
DefaultNode记录的是资源在当前链路中的访问数据,用来实现基于链路模式的限流规则。ClusterNode记录的是资源在所有链路中的访问数据,实现默认模式、关联模式的限流规则。
例如:我们在一个SpringMVC项目中,有两个业务:
业务1:controller中的资源`/order/query`访问了service中的资源`/goods`
业务2:controller中的资源`/order/save`访问了service中的资源`/goods`
创建的链路图如下:
3.Entry
默认情况下,Sentinel会将controller中的方法作为被保护资源,那么问题来了,我们该如何将自己的一段代码标记为一个Sentinel的资源呢?
Sentinel中的资源用Entry来表示。声明Entry的API示例:
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
// 被保护的业务逻辑
// do something here...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
}
1)自定义资源
例如,我们在order-service服务中,将`OrderService`的`queryOrderById()`方法标记为一个资源。
1] 首先在order-service中引入sentinel依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2] 然后配置Sentinel地址
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8089 # 这里我的sentinel用了8089的端口
3] 修改OrderService类的queryOrderById方法
代码这样来实现:
public Order queryOrderById(Long orderId) {
// 创建Entry,标记资源,资源名为resource1
try (Entry entry = SphU.entry("resource1")) {
// 1.查询订单,这里是假数据
Order order = Order.build(101L, 4999L, "小米 MIX4", 1, 1L, null);
// 2.查询用户,基于Feign的远程调用
User user = userClient.findById(order.getUserId());
// 3.设置
order.setUser(user);
// 4.返回
return order;
}catch (BlockException e){
log.error("被限流或降级", e);
return null;
}
}
4] 访问
打开浏览器,访问order服务:http://localhost:8080/order/101
然后打开sentinel控制台,查看簇点链路:
2)基于注解标记资源
在之前学习Sentinel的时候,我们知道可以通过给方法添加@SentinelResource注解的形式来标记资源。
这个是怎么实现的呢?
来看下我们引入的Sentinel依赖包:
其中的spring.factories声明需要就是自动装配的配置类,内容如下:
我们来看下`SentinelAutoConfiguration`这个类:
可以看到,在这里声明了一个Bean,`SentinelResourceAspect`:
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
// 切点是添加了 @SentinelResource注解的类
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
// 环绕增强
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// 获取受保护的方法
Method originMethod = resolveMethod(pjp);
// 获取 @SentinelResource注解
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
// 获取注解上的资源名称
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
// 创建资源 Entry
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
// 执行受保护的方法
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
简单来说,@SentinelResource注解就是一个标记,而Sentinel基于AOP思想,对被标记的方法做环绕增强,完成资源(`Entry`)的创建。