Neo4j系列博客-集群

简介

集群技术是一项在较低成本下能有效提高系统整体性能、可靠性、灵活性和可扩展性,并广泛应用于生产系统的必备技术,同样 Neo4j 企业版也提供了集群技术,主要分为:高可用性集群(High Availablity, HA)和因果集群(Causal Clustering)。


因果集群

因果集群技术基于 Raft 协议开发,可支持数据中心和云所需要的大规模和多拓扑环境,其中内置了由 Neo4j Bolt 驱动处理的负载均衡,还支持同样由 Bolt 驱动管理的集群可感知会话,以此帮助开发人员解决架构上的问题。
因果集群的安全性改进还包括:多用户、基于角色的访问控制(读取者、发布者、架构者和管理者)、查询安全事件日志、列出并终止运行中的查询以及细粒度的访问控制等特性。

Neo4j 因果集群主要提供以下两个特点:

  • 安全性:核心服务器为事务提供处理提供容错平台,仅有一部分核心服务器正常运行时,整个系统仍然是可用的。
  • 可扩展性:只读副本为图查询提供了一个大规模高可扩展平台,可以在分布广泛的拓扑中执行非常大的图查询工作。

以上两个特点结合起来就是允许最终用户拥有完整数据库的全部功能,并在可以在发生多个硬件或网络故障的情况下也可以正常的对数据库进行读写操作。

操作视图

从操作视图的角度来看,可以认为集群主要包括两个角色,一个是核心服务器,另一个就是只读副本。
neo4j-8-1.jpg

  1. 核心服务器
    核心服务器的主要职责就是保护数据,使用 Raft 协议复制所有事务来实现。Raft 协议能确保数据在最终用户应用程序提交事务之前是安全的。然而在实际中意味者集群中的大多数(即 N/2+1)核心服务器接收事务,就可以安全地提交事务到最终用户应用程序。
    但是这种安全性对写操作有延迟影响,写操作由多数核心服务器进行确认,然而随着核心服务器数量的增长,必定会影响写操作的效率。实际上,只需要少量的核心服务器就可以为特定部署提供足够的容错能力,可以使用公式 M = 2F + 1,其中 M 是允许 F 个核心服务器发生故障所需的核心服务器总数。
    如果核心服务器集群发生故障而无法再处理写入操作时,则核心服务器将转化为只读状态,以确保整个集群的数据安全。

  2. 只读副本
    只读副本的主要职责是扩展图操作(例如 Cypher 查询、过程处理等)的工作负载,类似于核心服务器中受数据保护的高速缓存,当并不是简单的键值高速缓存。然而事实上,只读副本是能够完成任意只读图查询和过程处理的全功能的 Neo4j 数据库。
    只读副本通过事务日志以异步的方式从核心服务器上复制数据,周期性(通常在毫秒内)地轮询核心服务器,以查找自上次轮询之后处理的新事务,然后核心服务器将新事务发送到只读副本。大量的只读副本可以从相对较少的核心服务器复制数据,从而确保大量的图查询工作负载得以均衡。
    只读副本与核心服务器不同的在于不参与集群拓扑的决策,丢失只读副本不影响集群的可用性,除了影响图查询性能吞吐的能力之外,也不会影响集群的容错能力。

应用视图

在应用程序中,通常从图数据库中读写数据,但是根据工作负载需要考虑读取时之前的写入,以确保因果一致性。因果一致性可以保证数据很容易写入到核心服务器,并可以从只读副本中读到这些写入的数据。
neo4j-8-2.jpg
在实行事务时客户端可以请求书签,然后作为下一次事务的参数,使用书签功能集群就可以确保其中的服务器只处理了客户端的书签事务之后才可以处理下一个事务,这提供了一个因果链,从客户的角度来确保行为的正确性。
除了书签之外剩余的工作都有集群来处理,其中主要是由数据库驱动程序与集群拓扑管理器一起完成,以选择最合适的核心服务器和只读副本来提高服务的质量。

创建新的因果集群

接下来配置具有三个核心实例的集群,三个核心实例是形成一个安全的因果集群所需的最少的服务器数量,并配备三个只读副本来提供一定的横向扩展的能力。

  1. 下载配置
    从官网下载企业版,然后解压到对应的机器上,接着配置每台机器的配置文件,主要配置 neo4j.conf
    在三台独立的机器上运行 Neo4j 时,实现一个因果集群的最基本配置就是两个网络配置和三个集群配置。

    • dbms.connection.default_listen_address:本机用于监听传入消息的地址或网络接口。
    • dbms.connection.default_advertised_address:告知其他机器需要连接的地址。
    • dbms.mode:数据库实例的运行模式。
    • causal_clustering.excepted_core_cluster_size:启动时的初始集群大小。建立集群时需要在早期就实现稳定的核心服务器资格认证,以确保集群数据的安全读写操作。
    • causal_clustering.initial_discovery_members:用于引导核心服务器或只读副本去初始发现核心集群成员,该值是以逗号为分隔的地址/端口列表。
  2. 启动服务器

    1
    ./bin/neo4j start

    启动成功之后,从 Neo4j-shell 中使用 call dbms.cluster.oerview() 命令来显式集群的状态和每个集群成员的信息。

  3. 添加新的核心服务器
    添加新的核心服务器与前面的配置一样,只需要配置新添加的核心服务器的 neo4j.conf 配置文件并修改其他已存在的集群中的核心服务器的 neo4j.conf 配置文件即可。一旦重新配置完成就可以重启核心服务器,新的核心服务器也会自动加入到集群中。

  4. 设置集群领导者偏好
    因果集群中的核心服务器使用 Raft 协议来保证一致性和安全性。Raft 协议的实现细节是它使用其他领导者角色对基础日子加以排序,其他的实例作为追随者,复制领导者的状态。在具体的实现中,这意味着对数据库的写操作是由当前为领导者角色的核心服务器来排序的。
    相关的配置参数是 neo4j.conf 配置文件中的 causal_clustering.refuse_to_be_leader,该配置项默认为 false,设置为 true 之后表示在领导者的选举中弃权。

  5. 添加只读副本
    添加只读副本与核心服务器类似,因为只读副本不参与集群的决策,因此配置项更少,只读副本只需要知道核心服务器的地址,以便于绑定到这些地址上正常运行发现协议,一旦完成了发现过程,只读副本就会知道核心服务器的状态,并且选择一个合适的核心服务器进行数据复制。

  • 数据库的操作模式,必须取消注释并且设置为 dbms.mode = READ_REPLICA
  • 集群中核心实例的地址 causal_clustering.initial_discovery_members

生命周期

因果集群的生命周期主要包括:发现协议、加入集群、核心服务器和只读副本成员资格获取以及用于轮询、捕获和备份的协议到最后的关闭集群。

  1. 发现协议
    发现协议是构建集群的第一步,他提供了现有核心集群服务器的一些提示,并使用这些提示初始化网络连接协议。而在这些提示中,服务器会加入到现有集群或者形成自己的集群,但是发现协议仅支持从核心服务器到核心服务器和从只读副本到核心服务器的发现过程。
    这个提示的具象化就是 neo4j.conf 配置文件中的 initial_discovery_members 配置项,该配置项通常为点分十进制 IP 地址和端口,且在执行过程中当前服务器尝试与其他列出的服务器连接,一旦连接成功即可成功加入集群的拓扑结构中。同时发现服务也会持续运行在因果集群的全生命周期,用以维持可用服务器的当前状态,还可以帮助客户端通过驱动转发请求到合适的服务器。
    发现协议只针对核心服务器,而不管执行发现的是只读副本还是核心服务器。

  2. 核心服务器成员资格
    若此时为核心服务器执行发现协议,则一旦与核心服务器集群建立连接,就会加入到 Raft 协议中去。
    Raft 协议处理集群成员资格的方式就是通过使其成为分布式日志同步的一部分。要想加入到 Raft 集群则必须将集群成员资格插入到 Raft 日志中,然后复制到集群的其他成员目录中,一旦该目录被应用于 Raft 共识组(运行 Raft 算法的服务器)中的大部分成员,则集群成员更新自己的集群视图以添加新加入的成员。新加入的核心服务器还必须在初始化其内部 Raft 实例时,从其他核心服务器获取到自己的 Raft 日志。
    当一个实例想要加入到一个集群时,取决于集群的当前状态和它本身是否有资格加入。其资格的判定在于是否与集群成员保存有相同的数据库存储(可以是过时的数据)。

  3. 只读副本成员资格
    若只读副本执行发现协议,则一旦与任何核心服务器建立连接,就会将自己加入到共享白板(Shared Whiteboard)中。其中共享白板实时提供所有只读副本的视图,并用于最终用户应用程序的数据库驱动程序中的请求路由和集群状态的监控。
    只读副本不涉及 Raft 协议,也不影响集群拓扑。

  4. Raft 协议实现事务
    一旦集群建立成功,就意味着每个核心服务器都会花时间处理数据库事务。而更新操作会通过 Raft 协议在核心服务器之间可靠复制,更新操作以包含事务命令的 Raft 日志条目的形式呈现,再作用到图数据库上。
    因果集群中仅在核心服务器之间采用了 Raft 协议,并对集群中的核心服务器划分为三类角色:Leader(领导者,只有一个)、 Follower(通常多个)、 Candidate(候选人,通常多个)。
    选举流程
    Raft 协议的事务执行过程就是在某时刻 Raft Leader 将事务附加到其本地日志的头部,并要求其他实例执行相同的操作。当领导者看到大多数的实例都已经附加上此条目之后,就可以将这个事务安全地提交到 Raft 日志中,至此就可以通知客户端应用程序事务已经安全地提交。
    在任何一个 Raft 协议实例中,只有一个领导者能够在给定的任期内驱动协议的正常运转,领导者承担强制 Raft 日志排序和管理日志的责任。
    追随者根据领导者的日志变化来更新自身的日志,如果集群中的任何参与者怀疑领导者出现故障,那么就可以通过进入候选者状态来发起领导者候选。其中最佳领导者的最佳状态由最长运行时间、最长日志、最高提交条目来决定。

  5. 跟踪协议
    只读副本一方面处理图查询,另一方面处理与核心服务器事务更新的一致性。而从核心服务器到只读副本的更新是通过事务发送来传递的,事务发送由只读副本频繁轮询任意一个核心服务器,得到其接收和处理的最后一个事务编号来启动,其中轮询的频率是可以设置的。
    Neo4j 事务编号是严格递增的整数值。只读副本通过自身最后处理的事务编号与核心服务器的事务编号比较来判断该事务是否在只读副本上执行过。
    如果有时候核心服务器与只读副本之间的数据差异较大时,就会直接执行回滚操作并直接将数据库内容从核心服务器复制到只读副本中。

  6. 备份协议
    参考上一节内容中的备份。

  7. 关闭只读副本
    只读副本正常关闭时会调用发现协议从集群的共享白板视图中删除自身,这样可以确保数据库的完全关闭和集群数据的一致性。
    非常规关机之后,只读副本可能不再具有完全一致的存储文件和事务日志,因此在后续重新启动时,只读副本会回滚这段时间的事务,以保证数据的一致性。

  8. 关闭核心服务器
    正常关闭核心服务器需要通过 Raft 协议来处理,当一个核心服务器关闭时,协议会将成员资格条目附加到 Raft 日志上,然后其他的核心服务器重复同样的操作,一旦大多数核心服务器都提交了成员资格条目,在逻辑上该核心服务器就可以离开集群并安全关闭。
    而非正常关闭核心服务器不会通知集群已经离开集群,此时集群中的核心服务器数量与之前记录的一致,这样在同步事务时会更加的困难,因此在收到告警时,请在必要的时候进行人工干预。

灾备恢复

灾备恢复是指把少量幸存实例从只读状态转换到可读写状态的实例组成的集群。

  • 确保已经发生数据中心丢失的风险,并且可以访问正在运行的集群中幸存的成员,然后对集群中的每个实例进行以下操作。
  • 运行命令 ./bin/neo4j stop 停止数据库实例。
  • 修改 neo4j.conf 配置文件中的 causal_clustering.initial_discovery_members 参数为当前可用实例的 IP 地址。
  • 更改 neo4j.conf 配置文件中的 causal+clustering.expected_core_cluster_size = 2,即有两个实例幸存。
  • 重新启动实例 ./bin/neo4j start

其他设置

  1. causal_clustering.raft_advertised_address
    该配置项为地址/端口对,是本机 Neo4j 实例向集群的其他成员通知他在核心集群内监听 Raft 消息的位置。

  2. causal_clustering.transaction_advertised_address
    值为地址/端口对,指定本机 Neo4j 实例在哪里监听事务传输捕获协议中的事务请求。

  3. causal_clustering.discover_listen_address
    发现协议所使用的地址/端口对。

  4. causal_clustering.raft_listen_address
    值为地址/端口对,指定 Neo4j 实例将绑定到哪个网络地址和端口用以进行集群通信。此配置必须与 causal_clustering.raft_advertised_address 配置相配合,因为本机也会监听自己的 causal_clustering.raft_advertised_address 配置。

  5. causal_clustering.transaction_listen_address
    值为地址/端口对,指定 Neo4j 实例将绑定到哪个网络地址和端口上进行集群通信。此配置必须与 causal_clustering.transaction_advertised_address 配置相配合,因为本机也会监听自己的 causal_clustering.transaction_advertised_address 配置。


高性能集群

高可用性 Neo4j 集群采用了主从复制结构,优点在于硬件故障时的应变能力和容错能力和扩展 Neo4j 读密集型数据场景的能力。
弹性和容错是指即使在网络或硬件发生故障时,Neo4j 集群仍然可以继续提供服务的能力。这就意味着集群中的一个节点坏了,服务依旧是可用的。主从结构的另外一个好处就是能够在读密集型场景下的横向扩展能力,另外还支持缓存分区,使得集群相比单实例具有更大的负载处理能力。

概述

一个高可用性集群由一个主实例和零个或多个从实例组成,集群中所有的实例都在其本地数据库文件中保存有集群数据的完整副本。
neo4j-8-3.jpg
横线箭头代表协调每个实例之间的数据复制和选举管理。斜线箭头代表集群中主实例与从实例之间保持数据库最新。

  1. 仲裁实例
    仲裁实例是一个仅在仲裁模式下运行的 Neo4j 实例,但是它只参与集群的通信,用于在选举出现僵局时进行仲裁,并不参与集群的其他操作(也不复制集群中的数据)。

  2. 事务传播
    在主机上的写操作与在非集群模式下的方式一样,在主机上执行成功之后,会将该事务推送到从机上,然而如果推送中间发生异常,主机也是默认推送成功。
    但是在从机上执行写操作时,每个写操作都是首先与主机同步,此时主机和从机都会被锁定,当事务提交时,首先在主机上提交,在主机上提交成功后才会在从机上提交。同时为了保证一致性,在执行写操作时从机和主机的状态必须完全一致,从机的自动更新协议包含在与主机的通过协议中。(写操作尽量在主机上完成)

  3. 故障恢复
    当集群中的一个实例不可用时,集群中的其他实例将会检测到此问题并将其标记为临时故障,在该实例发生故障之后如果可以重新实现和集群同步,那么会重新加入到集群中。如果一个主机发生故障,那么稍后在集群中就会进行主机选举,如果一个从机被选中,他的角色也会由从机转变为主机,之后就会向集群中的其他成员通报自己的身份。在选举过程中的几秒钟是不能进行写操作的。

  4. 分支
    数据分支可能由以下两种方式引起:

  • 从机落后主机很多,然后离开或重新加入集群,这类型的分支是无害的。
  • 重新发生了主机选举,旧的主机还有一个或多个事务未推送,这类型的分支是有害的,需要采取措施处理。
  1. 总结
    • 写入事务是可以在集群中的任何实例中执行。
    • Neo4j 高可用性集群是具有容错功能的。
    • 从机的写操作将先在主机上同步。
    • 如果主机发生故障,着自动选出新的主机。
    • 集群会自动处理一个实例发生故障的情况,并且在它再次可用时还可以重新加入集群中。
    • 事务具有原子性、一致性、持久性,直到最后才会传递到从机。
    • 如果主机关闭,那么正在运行的事务将会回滚,新事务将会阻塞或者执行失败,直至主机可用为止。
    • 读操作的负载能力会随着集群中的数据库实例增加而增加。

搭建高可用性集群

搭建高可用性集群步骤如下:

  • 每台机器上安装 Neo4j 企业版。
  • 若是有需要,可以配置仲裁实例。
  • 修改每台机器上 Neo4j 实例的配置文件。
  • 安装单个实例。
  • 修改具体的参数配置 dbms.modeha.server_idha.initial_hosts
  1. 重要配置项
    在启动 Neo4j 集群时,每个实例按照配置项来关联其他的实例,当一个实例需要建立到其他实例的连接时,能否加入集群取决于集群的当前状态和该实例是否有这个加入,为了保证实例具有资格加入集群,因此每个实例都必须与集群的其他成员具有相同的数据库存储,或者是没有数据库存储的全新部署。
    参与到集群中的实例应该配置 IP 地址或主机名。

    • dbms.mode 配置数据库的操作模式,需设置为 dbms.mode = HA
    • ha.server_id 是每个实例的集群标识符,他必须是整数,并且必须在集群中所有的实例中保持唯一,为 ha.server_id = 1
    • ha.host.coordination 是指实力监听集群的端口,默认端口为 5001
    • ha.ininial_hosts 是用逗号隔开的地址/端口对列表,该列表指定一个实力如何与其他的实例进行通信。当实例启动时,使用这些主机地址来查找和加入集群,当集群冷启动没有集群可用时,数据库也是不可用的,直到 ha.ininial_hosts 中列出的地址都在线且彼此通信才可以恢复使用。
    • ha.host.data 是一个地址/端口对设置,用来指定从机监听来自主机的事务,默认配置端口为 6001ha.host.data 必须使用与 ha.host.coordination 使用不同的端口。
  2. 配置文件示例

    1
    2
    3
    4
    5
    ha.server_id = 1
    ha.initial_hosts = neo4j-01.local:5001,neo4j-02.local:5001,neo4j-03.local:5001
    dbms.mode = HA
    dbms.connector.http.enabled = true
    dbms.connector.http.listen+address:7474
  3. 设置仲裁实例
    高可用性集群的典型部署就是三台机器来提供容错和读可扩展性。至此为了防止主机不可用,可以部署仲裁实例的 Neo4j 实例,他也会被视为集群的一员,主要作用是用于主机的选举,但不会参与到其他的操作。
    仲裁实例的 neo4j.conf 配置文件与其他实例的配置基本相同,只是将 dbms.mode 配置的值变为 ARBITER,启动和停止仲裁实例的方法也与其他实例一致。

状态信息端点

Neo4j 高可用性集群常见的操作就是将所有的写入请求定向到主机上,从机只负责读取操作,这样在集群中对读操作做到负载均衡,同时支持故障转移。
实现这个功能最常见的方法是在集群上加入一个负载均衡器,可以选用 HA Proxy(一个支持高可用性、负载均衡 TCPHTTP 开源应用程序代理软件),这里的负载均衡器使用 HTTP 端点来发现主节点和从节点。

  1. 状态端点信息
    每个高可用性集群的实例都有关于其在集群中状态的三个端点,每个端点都是可用的,具体的状态取决于负载均衡的需求和设置。三个端点分别为:
    • /db/manage/server/ha/master
    • /db/manage/server/ha/slave
    • /db/manage/server/ha/available

mastersalve 可以将写入和非写入请求引导向特定的实例。available 端点一般情况下会将任意请求指向可用于事务处理的实例。查询端点的状态信息,执行 HTTP GET 操作。

  1. 状态信息端点详情
    端点如下:
    • /db/manage/server/ha/master
      实例状态 返回状态码 正文文本
      Master 200 OK true
      Slave 404 Not Found false
      Unknown 404 Not Found Unknown
    • /db/manage/server/ha/slave
      实例状态 返回状态码 正文文本
      Master 404 Not Found false
      Slave 200 OK true
      Unknown 404 Not Found Unknown
    • /db/manage/server/ha/available
      实例状态 返回状态码 正文文本
      Master 200 OK master
      Slave 200 OK salve
      Unknown 404 Not Found Unknown

UNKNOWN 状态表示当前 Neo4j 实例既不是主机也不是从机。

  1. 运行
    1
    2
    #> curl http://localhost:7474/db/manage/server/ha/master
    true

HAProxy 负载均衡

Neo4j 高可用性集群架构中,集群通常由负载均衡器管理。

Bolt 协议配置 HAProxy

Neo4j 高可用性集群的部署中,HAProxy 将配置两个端口其中一个用于写入操作路由到主机,另一个则用于从机读操作的负载均衡。每个应用程序都有两个驱动程序实例,一个连接到主机端口用于执行写操作,而另一个连接到从机端口用于执行读操作。

  1. 设置通信模式和空闲超时
    如果一个服务器或者客户端空闲超过两个小时,那么该客户端或服务器就会被断开连接。

    1
    2
    3
    4
    5
    defaults
    mode tcp
    timeout connect 30s
    timeout client 2h
    timeout server 2h
  2. 设置写操作

    1
    2
    3
    frontend neo4j-write
    bind *:7680
    default backend current-master
  3. 主机后端配置

    1
    2
    3
    4
    5
    backend current-master
    option httpchk HEAD /db/manage/server/ha/master HTTP/1.0
    server db01 10.0.1.10:7687 check port 7474
    server db02 10.0.1.11:7687 check port 7474
    server db03 10.0.1.12:7687 check port 7474
  4. 设置读连接

    1
    2
    3
    frontend neo4j-read
    bind *:7681
    default backend slaves
  5. 从机的后端配置

    1
    2
    3
    4
    5
    6
    backend slaves
    balance roundrobin
    option httpchk HEAD /db/manage/server/ha/slave HTTP/1.0
    server db01 10.0.1.10:7687 check port 7474
    server db02 10.0.1.11:7687 check port 7474
    server db03 10.0.1.12:7687 check port 7474

主机的后端配置与从机的后端配置相同。

然后将上述的所有配置放到一个文件中,就可以得到一个基本可运行的 HAProxy 配置文件,它采用 Bolt 协议为应用程序提供负载均衡功能。

HTTP API 配置 HAProxy

添加以下配置文件 haproxy.cfg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global
daemon
maxconn 256
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:80
default_backend neo4j
backend neo4j
option httochk GET /db/manage/server/ha/available
server s1 10.0.1.10:7474 maxconn 32
server s2 10.0.1.11:7474 maxconn 32
server s3 10.0.1.12:7474 maxconn 32
listen admin
bind *:8080
stats enable

使用以下方式启动

1
/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg

至此就可以通过 http://ip:8080/haproxy?stats 来查看状态信息。默认用户名密码为 admin

优化读写

HAProxy 支持使用 HTTP 响应码区分集群实例是否可用的功能,因此如果在应用程序中可以区分读请求和写请求,那么就可以使用两个独立的逻辑负载均衡器,其中一个用来将所有的写请求定向到主机,而另一个则将读请求定向到从机。在 HAProxy 中可以通过添加多个后端服务来构建逻辑负载均衡器。
下面将请求定向到从机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global
daemon
maxconn 256
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:80
default_backend neo4j-slaves
backend neo4j-slaves
option httochk GET /db/manage/server/ha/slave
server s1 10.0.1.10:7474 maxconn 32 check
server s2 10.0.1.11:7474 maxconn 32 check
server s3 10.0.1.12:7474 maxconn 32 check
listen admin
bind *:8080
stats enable

引用


个人备注

此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!