Sentinel限流和熔断机制深度解析
Sentinel限流和熔断机制深度解析
Sentinel是阿里巴巴开源的流量控制组件,主要用于微服务架构中的系统稳定性保障。本文将深入剖析Sentinel的核心功能——限流和熔断的实现原理,帮助读者理解其工作机制。
前言
在微服务架构中,一个请求往往需要经过多个服务的调用链路。为了保护每个服务不被过载,需要采取限流和熔断等措施。本文将通过源码分析,详细介绍Sentinel是如何实现这些功能的。
雪崩问题
雪崩问题通常发生在服务调用链路中,当某个下游服务发生故障时,会导致整个链路的性能下降甚至崩溃。例如:
a1服务——》b服务——》c服务
a2服务——》b服务——》c服务
a3服务——》b服务
d服务——》a3服务
如果服务c发生故障,会导致b服务的连接被占用至超时。随后a2服务的请求也会堆积在b服务上,最终可能导致b服务也发生故障。这种故障扩散现象就是雪崩问题。
市面上的解决方案
常见的解决方案包括:
- 流量控制(限流):提前对服务的QPS进行限制,防止过载。
- 熔断降级:当检测到下游服务故障时,暂时停止对该服务的调用,避免故障扩散。
- 舱壁模式:为每个服务调用分配独立的线程池,隔离故障影响。
限流与熔断的概念
- 限流:通过限制服务的访问频率,防止服务过载。
- 熔断:当检测到服务故障时,暂时停止对该服务的调用,避免故障扩散。
源码分析
在Sentinel中,每个资源都对应一个资源名称和一个Entry。Entry可以通过注解或API显式创建。每个Entry创建时,会同时创建一系列功能插槽(slot chain)。
例如,服务调用链路如下:
服务a——》服务b——》服务c
每个服务都可以作为一个保护资源,默认情况下,一个Controller对应一个保护资源。在服务内部,可以通过@SentinelResource
注解标识需要保护的service层方法。
Sentinel的调用链路主要包括以下8个slot:
每个slot负责不同的功能,例如统计访问QPS、判断是否达到限流或熔断阈值等。如果某个slot抛出BlockException
,则后续的slot将不再执行。
SentinelResource的切面
SentinelResource
的切面主要通过SphU.entry
方法实现:
// 查找slot链
com.alibaba.csp.sentinel.CtSph#entryWithPriority
ProcessorSlot<Object> chain = this.lookProcessChain(resourceWrapper);
// 第一次访问资源时,使用copyOnWrite的方式添加到map中,后面就从map获取即可
ProcessorSlotChain chain = (ProcessorSlotChain)chainMap.get(resourceWrapper);
if (chain == null) {
synchronized(LOCK) {
chain = (ProcessorSlotChain)chainMap.get(resourceWrapper);
if (chain == null) {
if (chainMap.size() >= 6000) {
return null;
}
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap(chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
// 创建的slotChain是通过spi的方式从METAINFO目录获取所有的slot
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
Iterator var3 = sortedSlotList.iterator();
while(var3.hasNext()) {
ProcessorSlot slot = (ProcessorSlot)var3.next();
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain", new Object[0]);
} else {
chain.addLast((AbstractLinkedProcessorSlot)slot);
}
}
return chain;
}
// 创建完slotChain后,就开始执行每个slot
chain.entry(context, resourceWrapper, (Object)null, count, prioritized, args);
public class DefaultProcessorSlotChain extends ProcessorSlotChain {
AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) throws Throwable {
super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
}
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
super.fireExit(context, resourceWrapper, count, args);
}
};
}
// 调用下一个slot是通过当前slot的next属性
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable {
if (this.next != null) {
this.next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
}
}
滑动窗口源码
滑动窗口用于统计访问资源的线程数和QPS。其原理是将一个时间窗口划分为多个时间样本,以解决固定时间窗口的缺陷。
// 统计线程数和QPS
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry
this.fireEntry(context, resourceWrapper, node, count, prioritized, args);
node.increaseThreadNum();
node.addPassRequest(count);
限流源码
限流规则主要在FlowSlot
中实现,通过检查静态流量规则来判断是否达到阈值。
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {
this.checkFlow(resourceWrapper, context, node, count, prioritized);
this.fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
if (ruleProvider != null && resource != null) {
Collection<FlowRule> rules = (Collection)ruleProvider.apply(resource.getName());
if (rules != null) {
Iterator var8 = rules.iterator();
while(var8.hasNext()) {
FlowRule rule = (FlowRule)var8.next();
if (!this.canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
}
熔断源码
熔断规则主要在DegradeSlot
中实现,通过检查熔断器状态来判断是否触发熔断。
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {
this.performChecking(context, resourceWrapper);
this.fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void performChecking(Context context, ResourceWrapper r) throws BlockException {
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers != null && !circuitBreakers.isEmpty()) {
Iterator var4 = circuitBreakers.iterator();
CircuitBreaker cb;
do {
if (!var4.hasNext()) {
return;
}
cb = (CircuitBreaker)var4.next();
} while(cb.tryPass(context));
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
}
}
}
总结
Sentinel通过限流和熔断机制,有效保护了微服务架构中的系统稳定性。其核心实现基于责任链模式,通过多个插槽(slot)协同工作,实现流量控制和故障隔离。本文详细介绍了Sentinel的源码实现,希望对读者理解其工作原理有所帮助。