实战Alibaba Sentinel:深度解析微服务高并发流量治理
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2 责任链模式在Sentinel中的应用

在第2章,我们将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实例。