写点什么

责任链模式在 Sentinel 中的应用

  • 2023-06-10
    湖南
  • 本文字数:2212 字

    阅读完需:约 7 分钟

在前面,我们将 Sentinel 提供的所有 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 实例。

用户头像

加VX:bjmsb02 凭截图即可获取 2020-06-14 加入

公众号:程序员高级码农

评论

发布
暂无评论
责任链模式在 Sentinel 中的应用_互联网架构师小马_InfoQ写作社区