多数据中心路由

简介

在部署集群时,利用局部性来减少延迟并提高性能通常是理想的选择。例如,最好在本地数据中心以局域网(LAN)延迟执行图密集型工作负载,而不是在遥远的数据中心以广域网(WAN)延迟执行。Neo4j 针对多数据中心场景提供的负载均衡追赶策略插件(catch-up strategy plugins)正是为此而设计的。

Neo4j 的负载均衡是一个协作系统,驱动程序会定期向集群询问应将其不同类型的工作负载(例如写操作和读操作)引导至何处。这使得驱动程序可以在很长一段时间内独立工作,同时不时进行回查以适应变化,例如为了增加容量而添加了新服务器。此外,在某些故障情况下,例如当驱动程序无法使用其分配的任何服务器时,它会立即再次发起询问。

从客户端的角度来看,这是基本透明的。在服务器端,负载均衡行为通过简单的领域特定语言(DSL)进行配置,并以指定的负载均衡策略(load balancing policy)形式公开,驱动程序可以绑定到该策略。所有服务器端配置均在主服务器(Primary servers)上执行。

追赶策略插件是一组规则,定义了辅助服务器(secondary servers)如何连接集群中的上游服务器以同步事务日志。Neo4j 提供了一套预定义的策略,用户也可以使用相同的 DSL 创建自定义策略。此外,Neo4j 还支持一个 API,高级用户可以使用该 API 来增强上游推荐。

一旦追赶策略插件解析出一个满意的上游服务器,它就会被用于拉取事务,以更新本地辅助服务器进行单次同步。对于后续更新,该过程会重复进行,从而确保始终解析出最优选的可用上游服务器。

先决条件配置

服务器标签

跨多数据中心的负载均衡和用户定义的追赶策略都基于服务器标签(Server Tag)概念。

为了根据特定需求优化集群服务器的使用,它们会使用服务器标签进行分类。服务器标签可以映射到数据中心、可用区或操作员领域中的任何其他重要拓扑元素,例如 usus-east。将相同的标签应用于多台服务器可以在逻辑上将它们组合在一起。请注意,服务器可以拥有多个标签。

服务器标签定义为一个映射到集群中一组服务器的键。服务器标签在每台服务器上使用 neo4j.conf 中的 initial.server.tags 参数定义。集群中的每台服务器都可以标记为零个或多个服务器标签。服务器标签可以在运行时通过 ALTER SERVER 命令进行更改。有关更多详细信息,请参阅更改服务器选项

示例 1. 使用服务器标签定义服务器分组

使用服务器标签对服务器进行分组是在 neo4j.conf 中实现的,如下例所示

使用 usus-east 标记当前服务器
initial.server.tags=us,us-east
使用 london 标记当前服务器
initial.server.tags=london
使用 eu 标记当前服务器
initial.server.tags=eu

请注意,服务器标签所暗示的组成员身份是显式的。例如,标记为 gb-london 的服务器不会自动成为标记为 gbeu 的服务器所在的同一个标签组的一部分,除非该服务器也被显式标记了这些标签。

配置设置

根据部署情况和集群中可用的数据库分配数量,关于读工作负载是否应路由到主分配(primary allocations),不同的策略具有不同的合理性。

以下布尔配置设置允许将读工作负载路由到主数据库

策略定义

负载均衡策略的配置对于客户端应用程序是透明的,并通过 DSL 表示。语法由一组按顺序考虑的规则组成。产生非空结果的第一条规则即为最终结果。

rule1; rule2; rule3

每个规则依次由一组限制所考虑服务器的过滤器组成,从完整的服务器集开始。请注意,每个规则的评估都从完整的可用服务器集开始。

有一组固定的过滤器构成一条规则,它们使用箭头链接在一起。

filter1 -> filter2 -> filter3

如果最后一个过滤器之后还有剩余的服务器,则规则评估产生了结果,该结果将返回给驱动程序。然而,如果没有剩余服务器,则考虑下一条规则。如果没有任何规则能够产生可用的结果,则驱动程序会收到失败信号。

过滤器

有多种过滤器可用于指定路由策略的规则。语法类似于带有参数的方法调用。

  • tags(name1, name2, …​)

    • 只有带有指定标签中任意一个的服务器才能通过此过滤器。

    • 定义的名称必须与服务器标签匹配。

    • 在 5.4 版本之前,tags() 被称为 groups(),后者虽然仍能工作但现已被弃用。

  • min(count)

    • 只允许最少数量的服务器通过(或者不通过)。

    • 允许管理过载情况。

  • all()

    • 无需指定,因为它在每条规则的开头是隐式的。

    • 隐式作为最后一条规则(使用 halt 可以覆盖此行为)。

  • halt()

    • 仅在最后一条规则中的最后一个过滤器中使用才有意义。

    • 停止处理任何更多规则。

  • 引入于 2025.12 roles()

    • 该过滤器应用于数据库副本。

    • 可能的值为 writer(写入者)、follower(跟随者)和 secondary(辅助者)。writer 是主领导者。它同步地将写操作复制到其他主节点。writer 是从数据库的主节点中自动选举出来的,并可能随时间变化。followers 是其他主节点。

tags 过滤器本质上是一个 OR 过滤器,例如 tags(A,B) 会通过带有标签 A、标签 B 或两者兼有的任何服务器(即服务器标签的并集)。通过链接两个过滤器也可以创建 AND 过滤器,例如 tags(A) -> tags(B),它仅通过同时带有两个标签的服务器(即服务器标签的交集)。

负载均衡框架

可以通过多种方式配置集群的负载均衡,以便客户端应用程序能够将其工作负载引导至最合适的集群成员,例如附近的成员。

负载均衡系统基于插件架构,以实现未来的可扩展性并允许用户自定义。当前版本随附一个名为服务器策略(server policies)插件的单一预装插件。

通过指定以下设置来选择服务器策略插件

dbms.routing.load_balancing.plugin=server_policies

在服务器策略插件下,可以在服务器端配置多个负载均衡策略,并以唯一的名称向驱动程序公开。驱动程序在实例化时必须通过指定其名称来选择适当的策略。命名策略的常用模式是根据地理区域或预期的应用程序组进行命名。

至关重要的是,必须在所有服务器上定义完全相同的策略,因为这被视为集群范围的配置,如果不这样做会导致不可预测的行为。同样,不应删除或重命名正在使用的策略,因为这会破坏试图使用这些策略的应用程序。但是,以相同的名称修改策略是完全可以接受且符合预期的。

如果驱动程序请求的策略名称不可用,则驱动程序无法使用该集群。完全不指定名称的驱动程序将获得已配置的默认策略的行为。如果未更改,默认策略会在所有服务器之间分配负载。可以将默认策略更改为命名策略可以具备的任何行为。

配置错误的驱动程序或负载均衡策略会导致次优的路由选择,甚至可能完全阻止与集群的成功交互。

此处未记录如何编写自定义插件的详细信息。如果您认为需要自定义插件,请联系 Neo4j 专业服务团队。

使用 Neo4j 驱动程序的负载均衡

一旦启用并配置完成,自定义负载均衡功能就会被驱动程序用于按预期路由流量。有关如何配置驱动程序以使用自定义负载均衡的说明,请参阅 Neo4j 驱动程序手册

策略名称

策略在 server_policies 插件的命名空间下配置,并根据需要进行命名。您可以在 neo4j.conf 文件中找到它们。

策略名称可以包含字母数字字符和下划线,并且区分大小写。以下是名称为 mypolicy 的策略的属性键

dbms.routing.load_balancing.config.server_policies.mypolicy=

实际策略定义在值部分,使用 DSL。

default 策略名称保留用于默认策略。可以像配置任何其他策略一样配置此策略,未指定策略的驱动程序客户端将使用它。

此外,可以使用唯一策略名称创建任意数量的策略。策略名称可以暗示其预期的特定区域或应用程序。

负载均衡示例

关于多数据中心集群的讨论介绍了一个四区域、多数据中心设置。那里使用了区域的基准指南针方位和这些区域内的编号数据中心,这里也使用了同样的假设设置。

nesw regions and dcs
图 1. 将区域和数据中心映射到服务器标签

负载均衡器的行为是在属性 dbms.routing.load_balancing.config.server_policies.<policy-name> 中配置的。指定的规则允许对集群在负载下如何路由请求进行微调。

示例中为了更好的可读性使用了续行符 \。这在 neo4j.conf 中也是有效的语法,建议使用它来分解复杂的规则定义,并在每行放置一条新规则。

最严格的策略是坚持使用特定的数据中心,而排除所有其他数据中心

示例 2. 仅限特定数据中心
dbms.routing.load_balancing.config.server_policies.north1_only=\
tags(north1)->min(2); halt();

此情况表明意图是将查询发送到标记为 north1 的服务器(映射到特定的物理数据中心),前提是有两台这样的服务器可用。如果无法提供至少两台标记为 north1 的服务器,则该操作应 halt(),即不尝试任何其他数据中心。

虽然前面的示例演示了负载均衡规则的基本形式,但也可以进行一些扩展

示例 3. 首选特定数据中心
dbms.routing.load_balancing.config.server_policies.north1=\
tags(north1)->min(2);

在这种情况下,如果至少有两台服务器标记为 north1,则负载会在它们之间进行均衡。否则,将使用整个集群中的任何服务器,回退到隐式的最终 all() 规则。

上一个示例在求助于整个集群之前仅考虑了一个数据中心。如果通过服务器组暴露了层次结构或区域概念,回退可以更加平滑

示例 4. 平滑回退到邻居
dbms.routing.load_balancing.config.server_policies.north_app1=\
tags(north1,north2)->min(2);\
tags(north);\
all();

此示例表示集群应在带有 north1north2 标签的服务器之间进行负载均衡,前提是它们之间至少有两台机器可用。如果失败,可以使用 north 区域中的任何服务器;如果整个 north 区域都离线,则可以使用集群中的任何服务器。

内置路由策略

在 2025.12 版本中,引入了以下内置路由策略

  • builtin_default

  • builtin_all

  • builtin_prefer_secondaries

  • builtin_add_writer

作为用户定义的策略,内置策略在部署集群或向现有集群添加服务器时进行配置。默认策略是根据 neo4j.conf 文件中指定的配置设置确定的。有关详细信息,请参阅配置设置过滤器

内置路由策略源自配置设置

定义内置路由策略

当驱动程序作为客户端路由读事务的一部分调用 dbms.routing.getRoutingTable() 过程时,应用路由策略。

builtin_default 路由策略

builtin_default 路由表首选非写入主节点和辅助节点作为读取端点。如果没有其他端点可用,则回退到写入者。

您无需配置 neo4j.conf 文件,因为设置保持默认值。

dbms.routing.reads_on_primaries_enabled=true
dbms.routing.reads_on_writers_enabled=false
builtin_all 路由策略

builtin_all 路由表将写入者、非写入主节点和辅助节点作为读取端点包含在内。

配置 neo4j.conf 文件

dbms.routing.reads_on_primaries_enabled=true
dbms.routing.reads_on_writers_enabled=true
builtin_add_writer 路由策略

builtin_add_writer 路由表包含写入者和辅助节点作为读取端点。如果没有端点可用,该策略会回退到标记的辅助节点,最后回退到写入者和非写入主节点。

配置 neo4j.conf 文件

dbms.routing.reads_on_primaries_enabled=false
dbms.routing.reads_on_writers_enabled=true
builtin_prefer_secondaries 路由策略

builtin_prefer_secondaries 策略首选辅助节点,其次是非写入主节点,最后是写入者。

配置 neo4j.conf 文件

dbms.routing.reads_on_primaries_enabled=false

追赶策略插件

追赶策略插件是一组规则,定义了辅助节点如何联系集群中的上游数据库成员以同步事务日志。Neo4j 随附了一套预定义策略,也利用 DSL 灵活创建用户定义策略。最后,Neo4j 支持一个 API,高级用户可以使用该 API 来增强上游服务器推荐。

一旦追赶策略插件解析出一个满意的上游服务器,它就会被用于拉取事务,以更新本地辅助服务器进行单次同步。对于后续更新,该过程会重复进行,从而确保始终解析出最优选的可用上游服务器。

使用预定义追赶策略配置上游选择策略

Neo4j 随附以下预定义的追赶策略插件。它们提供了用于选择上游数据库成员的粗粒度算法

插件名称 结果行为

connect-to-random-primary-server

从当前可用的主数据库分配中随机选择并连接。

typically-connect-to-random-secondary

连接到任何可用的辅助分配,但大约 10% 的时间连接到任何随机的主分配。

connect-randomly-to-server-tags

随机连接到任何带有 server.cluster.catchup.connect_randomly_to_server_tags 逗号分隔列表中指定的任意服务器标签的辅助分配

leader-only

仅连接到主数据库当前的 Raft 领导者。

connect-randomly-within-server-tags

随机连接到带有此服务器所拥有的任意服务器标签的任何可用辅助数据库分配

预定义策略通过配置 server.cluster.catchup.upstream_strategy 设置来使用。这样做可以指定解析事务数据上游提供者的策略顺序偏好。策略插件名称的逗号分隔列表(按偏好顺序)应在该列表中提供。追赶策略的选择是通过询问列表顺序中的每个策略是否能够提供可以拉取事务的上游数据库成员来完成的。

示例 5. 定义上游数据库成员选择策略

考虑以下配置示例

server.cluster.catchup.upstream_strategy=connect-randomly-to-server-tags,typically-connect-to-random-secondary

使用此配置,辅助节点首先尝试连接到带有 server.cluster.catchup.connect_randomly_to_server_tags 中指定标签的任何其他服务器。如果未能找到任何带有这些标签的在线服务器,则它会连接到随机的辅助分配。

pipeline of strategies
图 2. 将使用来自策略的第一个满意响应。

为确保下游数据库成员在上游故障时仍能访问实时数据,任何数据库分配的最后手段始终是联系随机主节点。这等同于以 connect-to-random-primary-server 结束 server.cluster.catchup.upstream_strategy 配置。

配置用户定义的追赶策略

Neo4j 集群支持一个小型 DSL,用于配置客户端-集群负载均衡。这在领域特定语言过滤器中有详细描述。相同的 DSL 也用于描述服务器如何绑定到另一台服务器以请求事务更新的偏好。

通过选择 user-defined 追赶策略,DSL 可用如下

server.cluster.catchup.upstream_strategy=user-defined

一旦指定了用户定义策略,您就可以根据为集群设置的服务器标签,向 server.cluster.catchup.user_defined_upstream_strategy 设置添加配置。

此功能通过两个示例进行说明

示例 6. 定义用户定义策略

为了说明目的,提出了四个区域:northsoutheastwest,在每个区域内有若干个数据中心,如 north1west2。配置服务器标签,以便每个数据中心映射到其自己的服务器标签。此外,假设每个数据中心独立于其他数据中心发生故障,并且一个区域可以作为其组成数据中心的超级组。因此,north 区域中的服务器可能有如 initial.server.tags=north2,north 的配置,这将其放入了两个组中,这些组匹配您的物理拓扑,如下图所示。

nesw regions and dcs
图 3. 将区域和数据中心映射到服务器标签

一旦标记了服务器,下一个任务就是基于它们定义一些上游选择规则。出于设计目的,假设 north 区域数据中心中的任何服务器如果可以的话,更愿意在数据中心内进行追赶,否则会求助于任何北部实例。要配置该行为,请添加

server.cluster.catchup.user_defined_upstream_strategy=tags(north2); tags(north); halt()

配置按从左到右的优先级顺序排列。tags() 运算符产生一个要追赶的服务器标签。在这种情况下,只有在没有标记为 north2 的服务器时,操作才会继续进行 tags(north) 规则,该规则产生任何标记为 north 的服务器。最后,如果无法解析出带有任何先前标签的服务器,则通过 halt() 停止规则链。

请注意,使用 halt() 会显式结束规则链。如果规则链末尾未使用 halt(),则隐式添加 all() 规则。all() 是广义的:它提供了所有服务器,因此增加了找到可用上游服务器的可能性。然而,all() 是不加区分的,它提供的服务器不能保证在拓扑或地理上是本地的,这可能会增加同步延迟。

上面的示例展示了通过使用服务器标签表达的简单偏好层次结构。但层次结构可以更加复杂。例如,可以对标记的追赶服务器施加条件。

示例 7. 带有条件的自定义策略

在此示例中,需要在选择追赶位置之前粗略评估集群健康状况。为此,按如下方式使用 min() 过滤器

server.cluster.catchup.user_defined_upstream_strategy=tags(north2)->min(3), tags(north)->min(3); all();

tags(north2)->min(3) 表示仅在有三台可用服务器时才执行来自标记为 north2 的服务器的追赶,这在这里被解释为良好健康的指标。如果 north2 无法满足该要求,则应根据 tags(north)->min(3) 尝试从标记为 north 的任何服务器进行追赶,前提是有至少三台可用服务器。最后,如果无法从足够健康的 north 区域进行追赶,则操作(显式)回退到整个集群并使用 all()

min() 过滤器是具有相同标签的一组服务器的简单但合理的健康指标。

偏好数据中心

在多数据中心场景中,虽然这种情况仍然很少见,但可以偏向于指定数据库的写操作应引导至何处。可以应用 db.cluster.raft.leader_transfer.priority_tag 来指定一组带有给定标签的服务器,这些服务器在为给定数据库选择领导者时应具有优先权。优先级标签可以设置在一个或多个数据库上,这意味着集群尝试将已配置数据库的领导权保留在标记有已配置服务器标签的服务器上。

对于已配置 db.cluster.raft.leader_transfer.priority_tag 的数据库,它将被排除在集群间领导权的自动平衡之外。因此,除非必要,否则建议不要使用此配置。