无冲突的复制数据类型

CloudEvent中的Event sourcing

备注:内容摘录自 https://cloudstate.io/docs/user/features/crdts.html

无冲突的复制数据类型(Conflict-free Replicated Data Types / CRDT)是可用于支持分布式系统中状态的高可用性和可伸缩性共享的数据结构。CRDT状态复制到系统中的每个节点。每个节点都可以读取和更新CRDT,而无需其他节点的任何协调。如果两个或更多节点同时修改了CRDT,则可以将修改合并在一起,并且CRDT保证最终,所有节点将就该CRDT的当前状态达成一致。

使CRDT成为CRDT的是其合并功能。对于给定的数据类型,您可以定义一个函数,该函数采用其保存的数据的多个版本,然后使用该函数将这些版本合并到一个版本中,这样就不会影响版本的合并顺序,您仍将始终获得相同的结果,然后可以将该数据类型用作CRDT。

要使用CRDT,应用程序必须找到一种使用可用的CRDT表示其数据的方法。重要的是要记住,CRDT并不是一种魔术技术,可以浇灌在任何任意数据模式上以使其高度可用和可扩展。必须仔细设计应用程序表示其数据的方式,以在可用CRDT的约束下工作。

何时使用CRDT

CRDT在不需要强一致性而只需要最终一致性的情况下很有用。CRDT不保证在一个节点上进行的更新将立即在所有节点上可见,而是保证此类更新最终将在所有节点上可见。

在需要非常低延迟的读写的情况下,它们很有用。读取CRDT不需要网络通信,因为只需从节点本地副本读取值即可。写CRDT也不需要网络通信,因为写CRDT仅需要更新节点本地副本。稍后将CRDT的更新复制到其他节点。

在需要高可用性的情况下,CRDT也很有用。如果一个或多个节点在群集中变得无响应,则丝毫不影响其他节点读取和更新其持有的CRDT的能力。对于网络分区,一旦解决了网络分区,对CRDT执行的所有更新都将复制到曾经无法访问的节点。

最后,在需要非常高吞吐量写入的某些情况下,CRDT很有用。确切地说,当此保留取决于所使用的特定CRDT时,例如计数器和投票可以支持非常高的吞吐量写入,因为它们不需要将每个更新都复制到每个节点,仅偶尔复制它们的更新就足够了。

Cloudstate中提供的CRDT

  • GCounter

    仅增长计数器(Grow-only Counter/GCounter)是只能递增的计数器。它的工作方式是为每个节点跟踪一个单独的计数器值,然后取所有节点的值之和以获得当前计数器值。由于每个节点仅更新其自己的计数器值,因此每个节点可以协调这些更新以确保它们是一致的。然后,如果合并函数看到同一个节点的两个不同值,则仅取最高值,因为该值必须是节点发布的最新值。

  • PN计数器

    正负计数器(PNCounter)是既可以递增也可以递减的计数器。它通过组合两个GCounter来工作,一个为正数,跟踪增量,一个为负数,跟踪增量。通过从正GCounter减去负GCounter来计算最终计数器值。

  • GSet

    仅增长集或GSet是只能添加项目的集合。GSet是非常简单的CRDT,其合并功能是通过合并两个合并的GSet来定义的。

  • 或设置

    观察删除集或ORSet是可以添加和删除项目的集合。它是通过为每个元素维护一组唯一的标记来实现的,这些标记在添加到集合中时会生成。删除元素时,该节点当前观察到的所有标签都将添加到删除集中,因此,只要没有任何新的添加,而该节点在删除元素时都没有看到该元素,则该元素将是删除。一个简单的实现会在删除元素时累积逻辑删除,但是Cloudstate参考实现提供了清理逻辑删除的实现。

  • Flag是一个以false开头的布尔值,可以将其设置为true。一旦设置为true,就不能再设置为false。标志是一个非常简单的CRDT,合并功能只是一个布尔值或两个正在合并的标志值。

  • LWW注册

    最后写入胜利寄存器或LWWRegister是一个CRDT,它可以保存任何值,以及时钟值和节点ID,以指示何时由哪个节点更新它。如果两个节点具有两个不同的值版本,则时钟值最高的一个将获胜。如果时钟值相等,则使用节点上的稳定函数确定时钟(例如,地址最低的节点)。请注意,LWWRegister不支持对其值的部分更新。如果寄存器包含一个人员对象,并且一个节点更新了age属性,而另一个节点同时更新了name属性,则这些更新中只有一个最终将获胜。默认情况下,LWWRegister容易受到节点之间时钟偏差的影响。如果有更值得信赖的更新顺序可用,Cloudstate支持选择提供自定义时钟值。

  • OR地图

    观察到的已移除地图或ORMap与ORSet相似,此外,该集合的值用作地图的键,而地图的值本身就是CRDT。当在两个不同的节点上同时修改ORMap中相同键的值时,两个节点中的值将合并在一起。

  • 投票

    投票是一种CRDT,它允许节点对条件进行投票。它类似于GCounter,每个节点都有自己的计数器,奇数被认为是条件的投票,而偶数被认为是条件的投票。投票的结果是通过获取当前是集群成员的所有节点的投票来决定的(当节点离开时,其投票将被丢弃)。可以使用多种决策策略来决定投票结果,例如至少一项,多数和全部。

Cloudstate中的CRDT方法

有状态服务可以管理多个CRDT,每个CRDT由Cloudstate实体密钥标识。

Cloudstate代理负责实施CRDT机制。这包括合并功能,复制状态更改并确保所有节点最终在每个CRDT状态上达成共识的机制。代理将告诉用户功能CRDT的状态,并具有用于推送更新并从用户功能接收更新的协议。代理服务器和用户功能通过依次轮流进行更新来保持其本地表示同步-当用户功能从代理服务器接收命令时,允许用户功能进行更新,并且允许代理服务器进行更新(用户功能上没有未执行的命令时)。这种方法使CRDT在用户功能中的实现非常简单,

流式CRDT呼叫

Cloudstate CRDT支持处理服务器流式调用,即CRDT的gRPC服务调用将返回类型标记为时streamed。当用户功能接收到流消息时,有两种情况允许更新CRDT:第一次收到呼叫时和客户端取消流时。如果希望在其他时间进行更新,则可以通过沿流发送的流消息发出效果来进行更新。

用户功能可以响应任何内容而向下游发送消息,但是Cloudstate提供的支持库仅允许响应CRDT更改发送消息。这样,可以实现需要监视CRDT状态的用例。

写一致性

默认情况下,更新在本地节点上执行,并异步复制到其他节点。更新也可以在其他写入一致性下执行,Cloudstate支持大多数。请注意,当使用非本地写入一致性时,CRDT的许多优点将不再可用,写入将需要网络通信,并且可能会受到其他节点故障的影响。因此,应谨慎使用非本地写入一致性,它们主要用于以下情况:在这种情况下,由于节点崩溃而导致的写入丢失(在将其复制到另一个节点之前,这种情况很少见)是不可接受的。

耐用性

尽管CRDT可以持久,但Cloudstate参考实施尚不支持持久CRDT。将来可能会提供此功能。

由于Cloudstate CRDT不持久,因此这意味着扩展到零,并且完全的群集崩溃将导致所有数据丢失。