责任链模式在Sentinel中的应用
在前面,我们将Sentinel提供的所有ProcessorSlot分为两类:一类是负责完成资源指标数据统计的ProcessorSlot;一类是实现限流、熔断等流量控制功能的ProcessorSlot。
Sentinel使用责任链模式将注册的所有ProcessorSlot按照一定的顺序串成一个单向链表。
实现资源指标数据统计的ProcessorSlot必须在实现流量控制功能的ProcessorSlot的前面,原因很简单,限流、熔断降级等都需要依赖资源的实时指标数据做判断。当然,如果ProcessorSlot不依赖资源的指标数据,那么这个ProcessorSlot的位置就不需要约束。
除按分类排序外,同一个分类下的每个ProcessorSlot可能也需要有严格的排序。例如,负责完成资源指标数据统计的ProcessorSlot的排序为NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot,如果排序乱了,就会抛出异常。
ProcessorSlotChain用于将ProcessorSlot串成一个单向链表,并且ProcessorSlotChain由SlotChainBuilder构造。
DefaultSlotChainBuilder是默认使用的SlotChainBuilder,其构造的ProcessorSlotChain所注册的ProcessorSlot及顺序如下:
ProcessorSlot接口的定义如下:
方法说明如下:
entry:入口方法。
fireEntry:调用下一个ProcessorSlot的entry方法。
exit:出口方法。
fireExit:调用下一个ProcessorSlot的exit方法。
参数说明如下:
context:当前调用链上下文,即Context实例。
resourceWrapper:资源ID。
param:泛型参数,一般用于传递资源的DefaultNode实例。
count:表示申请占用共享资源的数量,只有申请到足够的共享资源时才能继续执行。
prioritized:表示是否对请求进行优先级排序。
args:调用方法传递的参数,用于实现热点参数限流。
Sentinel将需要被保护的资源包装起来,这与锁的实现是一样的,即需要先获取锁才能继续执行。count与并发编程AQS中tryAcquire方法的参数作用一样,例如,线程池有200个线程,当前方法执行需要申请3个线程才能执行,那么count就是3。
count的值一般为1,当限流规则配置的限流阈值类型为Threads时,表示需要申请一个线程,当限流规则配置的限流阈值类型为QPS时,表示需要申请令牌(假设使用令牌桶算法)。
之所以能够将所有的ProcessorSlot构造成一个ProcessorSlotChain,是因为这些ProcessorSlot继承了AbstractLinkedProcessorSlot类。
AbstractLinkedProcessorSlot类有一个指向下一个ProcessorSlot的字段,正是这个字段实现了将所有注册的ProcessorSlot串成一条单向链表。AbstractLinkedProcessorSlot类的部分源码如下:
next:表示链表中当前节点的下一个节点。
实现责任链调用的方法:由前一个AbstractLinkedProcessorSlot实例调用fireEntry方法或fireExit方法,在fireEntry方法与fireExit方法中调用下一个AbstractLinkedProcessorSlot实例(next)的entry方法或exit方法。AbstractLinkedProcessorSlot实例的fireEntry方法与fireExit方法的实现源码如下:
fireEntry方法:如果next不为空,则调用下一个ProcessorSlot的entry方法。
fireExit方法:如果next不为空,则调用下一个ProcessorSlot的exit方法。
ProcessorSlotChain抽象类也继承了AbstractLinkedProcessorSlot类,只不过添加了两个方法:将一个ProcessorSlot添加到链表头节点的addFirst方法,以及将一个ProcessorSlot添加到链表末尾的addLast方法。
ProcessorSlotChain抽象类的默认实现子类是DefaultProcessorSlotChain,DefaultProcessorSlotChain类定义了一个指向链表头节点的first字段和一个指向链表尾节点的end字段。其中,first字段指向的是一个空实现的AbstractLinkedProcessorSlot实例。
DefaultProcessorSlotChain类的源码如下:
first字段:指向链表的头节点。
end字段:指向链表的尾节点。
addFirst方法:在链表头部添加一个节点。
addLast方法:在链表尾部添加一个节点。
entry方法:调用链表头节点的entry方法,由头节点调用下一节点的entry方法。
exit方法:调用链表头节点的exit方法,由头节点调用下一节点的exit方法。
责任链模式是非常常用的一种设计模式。在Shiro框架中,使用责任链模式实现资源访问权限过滤的骨架(过滤器链);在Netty框架中,使用责任链模式将处理请求的ChannelHandler包装为链表,实现局部串行处理请求。
在责任链的实现上,Sentinel与Netty有相似的地方。在Sentinel中,ProcessorSlot实例的entry方法是按ProcessorSlot实例在链表中的顺序被调用的,这一点与Netty中ChannelHandler链表的实现相同,不同的是,ProcessorSlot实例的exit方法被调用的顺序是从后往前的。
Netty可以实现局部串行以解决线程安全问题,即ChannelHandler链表只在收到数据包时被创建,在响应数据包后被销毁,而由于Sentinel是以资源为维度的,所以必然实现不了这种局部串行。
Sentinel会为每个资源创建且仅创建一个ProcessorSlotChain实例。ProcessorSlotChain实例被缓存在CtSph类的chainMap静态字段中,key为资源ID,每个资源的ProcessorSlotChain实例在CtSph#entryWithPriority方法中被创建,代码如下:
chainMap字段:缓存ProcessorSlotChain实例,key为资源ID,value为ProcessorSlotChain实例。
entryWithPriority方法:为资源创建ProcessorSlotChain实例(如果未创建)、为资源创建Entry实例,以及调用ProcessorSlotChain实例的entry方法。
CtSph类的chainMap静态字段最终会缓存整个应用中所有资源的ProcessorSlotChain实例。虽然chainMap的key类型为ResourceWrapper,但ResourceWrapper的equals方法只比较资源的名称,因此一个资源不会存在多个ProcessorSlotChain实例。
本文给大家讲解的内容是深度解析微服务高并发了解整体工作流程:责任链模式在Sentinel中的应用
下篇文章给大家讲解的内容是深度解析微服务高并发了解整体工作流程:Sentinel的整体工作流程分析
感谢大家的支持!
领取专属 10元无门槛券
私享最新 技术干货