Session存储策略

作者:金融级分布式架构 查看原文open in new window

SOFAStack (Scalable Open Financial Architecture Stack)是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

SOFARegistry 是蚂蚁金服开源的具有承载海量服务注册和订阅能力的、高可用的服务注册中心,在支付宝/蚂蚁金服的业务发展驱动下,近十年间已经演进至第五代。

本文为《剖析 | SOFARegistry 框架》第五篇,本篇作者力鲲,来自蚂蚁金服。《剖析 | SOFARegistry 框架》系列由 SOFA 团队和源码爱好者们出品,项目代号:SOFA:RegistryLab:/

GitHub 地址:

https://github.com/sofastack/sofa-registryopen in new window

回顾:服务注册

SOFARegistry 作为服务注册中心,面临的一个很重要的挑战就是如何解决海量的客户端连接问题,这也是本文要剖析的内容。不过作为一篇完整的文章,我们还是会先花一点时间介绍 SOFARegistry 的相关信息,以便读者了解其背景。

服务注册中心在服务调用的场景中,扮演一个“中介”的角色,服务发布者 (Publisher) 将服务发布到服务注册中心,服务调用方 (Subscriber) 通过访问服务注册中心就能够获取到服务信息,进而实现调用。

图1 - 服务的“中介”

流程:订阅/发布

在《海量数据下的注册中心 - SOFARegistry 架构介绍open in new window》一文中,我们提到了一个典型的 “RPC 调用的服务寻址” 应用场景,服务的提供方通过如下两个步骤完成服务发布:

  • 注册,将自己以 Publisher 的角色注册到 SOFARegistry;
  • 发布,将需要发布的数据 (通常是IP 地址、端口、调用方式等) 发布到 SOFARegistry;

与此相对应的,服务的调用方通过如下步骤实现服务调用:

  • 注册,将自己以 Subscriber 的角色注册到 SOFARegistry;
  • 订阅,收到 SOFARegistry 推送的服务数据;

从上面我们可以看到,整个流程中很重要的一个步骤就是注册,不管是 Publisher 还是 Subscriber 都只能在注册成功后才能实现发布订阅的需求。因此 SOFARegistry 要解决的一个问题就是如何维护与 Client 连接而产生的 Session,尤其是当 Client 数量众多的时候。

图2 - 海量啊海量

设计:分层隔离

在 SOFARegistry 的应用场景中,体量庞大的数据主要有两类:Session 数据、服务信息数据。两类数据的相同之处在于其数据量都会不断扩展,而不同的是其扩展的原因并不相同:Session 是对应于 Client 的连接,其数据量是随着业务机器规模的扩展而增长,而服务信息数据量的增长是由 Publisher 的发布所决定。所以 SOFARegistry 通过分层设计,将两种数据隔离,从而使二者的扩容互不影响。

图3 - 分层,扩容互不影响

当然,对于分层设计的概念介绍,在《海量数据下的注册中心 - SOFARegistry 架构介绍open in new window》的 “如何支持海量客户端” 章节已经有了很完整的介绍,这里不再赘述。本文是想从代码层面来看看其设计实现的方式。

通信 Exchange

Exchange 作为 Client / Server 连接的抽象,负责节点之间的连接。在建立连接中,可以设置一系列应对不同任务的 handler (称之为 ChannelHandler),这些 ChannelHandler 有的作为 Listener 用来处理连接事件,有的作为 Processor 用来处理各种指定的事件,比如服务信息数据变化、Subscriber 注册等事件。

图4 - 每一层各司其职,协同实现节点通信

Session 节点在启动的时候,利用 Exchange 设置了一系列 ChannelHandler:

  • PublisherHandler
  • SubscriberHandler
  • WatcherHandler
  • ClientNodeConnectionHandler
  • CancelAddressRequestHandler
  • SyncConfigHandler

其中 SubscriberHandler 和 PublisherHandler 主要是与服务发布方 (Publisher) 以及服务调用方 (Subscriber) 的行为相关,我们在下面说明。

任务处理

由于 SubscriberHandler 在 Session 节点启动时就已经初始化并设置,所以当有 Subscriber 注册时,就由 SubscriberHandler 负责后续一系列的处理逻辑。

图5 - Subscriber 的注册过程

上面的流程图展示了 Subscriber 注册的处理过程,SessionSever 在处理注册请求时,除了保存 Subscriber 的会话信息,还要为新注册的 Subscriber 提供其所订阅的服务信息数据,最后通过推送的方式将数据发送 Subscriber。

下面是上述流程在代码模块上的实现,我们依然用图的方式展示出来,大家按图索骥也便于查阅相关源码中的细节。

图6 - 代码流转:Subscriber 注册

可以看到,SOFARegistry 采用了 Handler - Task & Strategy - Listener 的方式来应对服务注册中的各种场景和任务,这样的处理模型能够尽可能的让代码和架构清晰整洁。

Publisher 的注册过程和 Subscriber 基本一致,略有不同的是 Publisher 在注册完毕之后将要发布的数据写到 DataServer 上。

图7 - Publisher 的注册过程

这个过程也是采用了 Handler - Task & Strategy - Listener 的方式来处理,任务在代码内部的处理流程和订阅过程基本一致。

图8 - 代码流转:Publisher 注册

会话缓存

在二层架构中 (即 Client 直接连接 DataServer),连接数是一个很难收敛的指标,因为当一个 Subscriber 订阅的服务位于不同 DataServer 上时,他就会与多个 DataServer 同时保持连接,这样“每台 DataServer 承载的连接数会随 Client 数量的增长而增长,每台 Client 极端的情况下需要与每台 DataServer 都建连,因此通过 DataServer 的扩容并不能线性的分摊 Client 连接数”。

图9 - 两层结构中,扩容无法减少连接数

这也是 SOFARegistry 设计三层模型的原因,通过 SessionServer 来负责与 Client 的连接,将每个 Client 的连接数收敛到 1,这样当 Client 数量增长时,只需要扩容 SessionServer 集群就可以了。 所以从设计初衷上我们就能够看出来 SessionServer 必须要满足的两个主要能力:从 DataServer 获取服务信息数据;以及保存与 Client 的会话。下面我们分开来谈:

1. 从 DataServer 获取服务信息数据;

其实很好理解,既然 SessionServer 扮演了三层结构中的“中间商”角色,那它也就理应替 Subscriber 从 DataServer 获取数据。但是这里有一个考量:SessionServer 要不要缓存数据,以及如何缓存从 DataServer 获取的数据。

  • SessionServer 要不要缓存数据?

服务信息数据从 SessionServer 被推送到 Subscriber 主要有两种触发场景:一是 DataServer 上的数据有变化,二是有新的 Subscriber 注册到 SessionServer。两种场景的出现频率是不一样的,在实际应用中第二种场景才是数据推送的主要原因,所以 SessionServer 上缓存数据可以对 DataServer 层屏蔽 Client 的变化,从而有效减轻 DataServer 的压力。

图10 - 两种场景导致的数据推送

  • 如何缓存从 DataServer 获取的数据?

这里 SOFARegistry 采用了 LoadingCache的数据结构,通过给 cache 中的 entry 设置过期时间的方式,使得 cache 定期从 DataServer 中拉取数据以替换过期的 entry。同时,当 DataServer 中有数据更新时,也会主动向 SessionServer 发请求使对应 entry 失效,从而促使 SessionServer 去更新失效 entry。具体细节如图所示:

图11 - SessionServer 从 DataServer 更新数据

在代码实现中,依然是采用 Handler - Task & Strategy - Listener 的方式来处理,感兴趣的读者可以实际翻阅代码查看具体细节。

2. 保存与 Client 的会话

每个 SessionServer 都负责与若干 Client 的连接,所以在 SessionServer 中也会有专门的数据结构来保存这些会话。随着 Client 数量增加,我们可以通过扩容 SessionServer 集群的方式来解决 SessionServer 连接数增加的。

图12 - SessionInterests 维护 Client 的连接

如图中所示,SessionServer 会分别用不同的数据结构来保存 Subscriber 和 Publisher 的会话,这种设计的一个重要原因是 SessionServer 会定期与 DataServer 进行 Publisher 的数据比对,以保证数据的一致性。这方面的细节我们会在后续的文章中继续讨论。

总结

SOFARegistry 作为服务注册中心,需要面对的业务场景以及要处理的任务种类都比较多,其代码也涉及到分布式存储、一致性算法、会话连接等多个方面,可以算是架构设计方面一本很好的“教材”。本文从 Session 存储的角度出发,剖析了 SessionServer 在实现过程中的一些设计思路和代码抽象,希望可以对读者的学习有所帮助和启发。

Last Updated: