Grid 架构

Grid 由一组各司其职的组件构成,共同维护整个 Grid。其架构看起来可能比较复杂, 希望本文能帮助你理清其中的概念。

主要组件

Grid 的主要组件如下:

事件总线(Event Bus)
用于在其他组件之间发送消息,接收方可以异步处理这些消息。
新会话队列(New Session Queue)
保存尚未由分发器分配给节点的新会话请求。
分发器(Distributor)
负责维护 Grid 中可运行会话的位置模型(称为“插槽”),并接收新会话请求, 将请求分配给一个插槽。
节点(Node)
运行WebDriver 会话。每个会话都会分配到一个插槽,每个节点至少有一个插槽。
会话映射(Session Map)
维护会话 ID 与运行该会话的节点地址之间的映射。
路由器(Router)
作为 Grid 的前端。它是 Grid 中唯一可能暴露给更广泛 Web 的部分, 但我们强烈不建议这样做。路由器会将收到的请求转发到新会话队列, 或者转发到正在运行相应会话的节点。

讨论 Grid 时,还需要了解以下几个概念:

  • **插槽(slot)**是可运行一个会话的位置。
  • 每个插槽都有一个特征(stereotype),即一组最小的能力。 新会话请求必须满足这些能力要求,分发器才会将请求发送到 拥有该插槽的节点。
  • **Grid 模型(Grid Model)**是分发器跟踪 Grid 状态的模型。顾名思义, 模型有时可能与实际状态不同步(例如,分发器刚刚启动时)。 分发器使用该模型而不是逐一查询节点,从而能快速为新会话请求分配插槽。

同步调用与异步调用

Grid 内部主要使用两种通信机制:

  1. 通过 HTTP 发送同步的、近似 REST 风格的 JSON 请求。
  2. 向事件总线发送异步事件。

应该如何选择通信机制?毕竟,我们也可以将整个 Grid 设计成事件驱动架构, 而且这种设计同样可行。

如果要执行的操作本身是同步的(例如,大多数 WebDriver 调用),或者遗漏响应会造成问题, Grid 就会使用同步调用。相反,如果要向所有感兴趣的接收方广播信息, 或者遗漏响应也无关紧要,则优先使用事件总线。

值得注意的是,与同步调用相比,异步调用与其监听方的耦合更松散。

启动顺序与组件依赖关系

Grid 允许组件按任意顺序启动,但从概念上讲,组件的启动顺序如下:

  1. 首先启动事件总线和会话映射。它们不依赖其他组件,彼此之间也没有依赖关系, 因此可以并行启动。
  2. 接下来启动会话队列。
  3. 此时可以启动分发器。分发器会定期连接会话队列并轮询任务; 轮询既可以由事件触发(有新会话加入队列时),也可以按固定时间间隔执行。
  4. 然后可以启动路由器。新会话请求会被转发到会话队列, 分发器则会尝试找到可运行会话的插槽。
  5. 此时可以启动节点。关于节点如何注册到 Grid,请参见下文。 注册完成后,Grid 即可处理流量。

下表展示了组件之间的依赖关系。“✅”表示组件之间存在同步依赖关系。

事件总线分发器节点路由器会话映射会话队列
事件总线X
分发器✅X✅✅
节点✅X
路由器✅X✅
会话映射X
会话队列✅X

节点注册

将新节点注册到 Grid 的过程很轻量。

  1. 节点启动后,应定期发送“心跳”事件。心跳中包含节点状态。
  2. 分发器会监听心跳事件。收到心跳后,分发器会尝试向节点的 /status 端点发送 GET 请求,并根据返回的信息配置 Grid。

分发器也会定期使用同一个 /status 端点检查节点状态。节点启动后仍应持续发送心跳事件, 这样,即使分发器没有持久化保存 Grid 状态,也能在重启后通过心跳逐步恢复并更新状态。

节点状态对象

节点状态是一个包含以下字段的 JSON 对象:

名称类型描述
availabilitystring可取 up、draining 或 down。其中,draining 表示不应再向该节点分配新会话;该节点上的最后一个会话结束后,节点进程将退出或重启。
externalUrlstringGrid 中其他组件连接此节点时使用的 URI。
lastSessionCreatedinteger此节点上最近一次创建会话时的 Unix 纪元时间戳。在其他条件相同的情况下,分发器会优先向空闲时间最长的节点分配新会话。
maxSessionCountinteger虽然可以通过统计可用插槽数推算会话数,但此整数值用于确定节点被视为“已满”之前可同时运行的最大会话数。
nodeIdstring用于标识此节点实例的 UUID。
osInfoobject包含 arch、name 和 version 字段的对象,供 Grid UI 和 GraphQL 查询使用。
slotsarray插槽对象数组(见下文)。
versionstring节点的版本号(对于 Selenium,此版本号与 Selenium 版本号一致)。

建议为所有字段都设置值。

插槽对象

插槽对象表示节点中的一个插槽。一个插槽可运行一个会话。 节点的插槽数可能多于其可同时运行的会话数。例如,某个节点最多可以运行 10 个会话, 但会话可以使用 Chrome、Edge 或 Firefox 的任意组合。此时,该节点会将“最大会话数” 设为 10,同时声明有 10 个 Chrome 插槽、10 个 Edge 插槽和 10 个 Firefox 插槽。

名称类型描述
idstring用于标识插槽的 UUID。
lastStartedstring插槽最近一次启动会话的时间,采用 ISO-8601 格式。
stereotypeobject此插槽用于匹配的最小能力集合。例如:{"browserName": "firefox"}。
sessionobject会话对象(见下文)。

会话对象

此对象表示插槽中正在运行的会话。

名称类型描述
capabilitiesobject会话实际提供的能力;其值与新会话命令的返回值一致。
startTimestring会话的启动时间,采用 ISO-8601 格式。
stereotypeobject此插槽用于匹配的最小能力集合。例如:{"browserName": "firefox"}。
uristring节点用于与会话通信的 URI。