Distributed Data

模块信息

要使用Akka群集分布式数据,必须在项目中添加以下依赖项:

介绍

当您需要在Akka群集中的节点之间共享数据时,Akka分布式数据很有用。使用提供键值存储(例如API)的actor访问数据。键是数据值的类型信息的唯一标识符。这些值是无冲突的复制数据类型(CRDT)。

通过直接复制和基于gossip的分发,所有数据条目都将散布到群集中的所有节点,或具有特定角色的节点。您可以很好地控制读写一致性级别。

CRDT的性质使得无需协调就可从任何节点执行更新。来自所有节点的并发更新,将由单调合并功能自动解决,所有数据类型必须提供。状态变化总是收敛的。提供了一些有用的数据类型,如计数器,集合,maps和寄存器的数据类型,您还可以实现自定义的数据类型。

它是最终一致的,旨在提供高读写可用性(分区容错)和低延迟。请注意,在最终一致的系统中,读取操作可能会返回过期的值。

使用复制器

您可以通过DistributedData扩展,通过复制器actor与数据进行交互。

复制程序的消息(例如Replicator.Update)被定义为Replicator.Command的子类,而实际的CRDT定义在akka.cluster.ddata包(例如GCounter)。它需要akka.cluster.ddata.SelfUniqueAddress,可从以下获得:

复制器可以包含多个条目,每个条目都包含一个复制的数据类型,因此,我们需要创建一个key来标识该条目,帮助我们知道具体类型,然后每次与复制器交互,都使用该key。每个复制的数据类型均包含一个用于定义该key的工厂。

状态为WeaklyUp的群集成员将参与分布式数据。这意味着将使用后台gossip协议,将数据复制到WeaklyUp节点。请注意,在所有节点或大多数节点读取/写入的情况下,它将不参与任何操作保证一致性。 WeaklyUp节点不算作集群的一部分。因此,就一致性操作而言,3节点+5 WeaklyUp本质上是3节点集群。

本示例使用复制的数据类型GCounter,实现一个可在群集的任何节点上写入的计数器:

尽管您可以使用DistributedData(ctx.getSystem()).replicator()中的ActorRef与Replicator进行交互,但如上例所示,使用ReplicatorMessageAdapter通常更为方便。

更新

若要修改和复制数据值,请向本地Replicator发送Replicator.Update消息。

在上面的示例中,对于传入的Increment命令,我们向复制器发送一个Replicator.Update请求,该请求包含五个值:

1.我们要更新的KEY
2.如果复制器之前未看到KEY,将数据置为空状态
3.我们想要更新的写入一致性级别
4.当更新完成时,响应一个ActorRef>
5.一个修改函数,该函数根据先前的状态对其进行更新,在我们的示例中,将其递增1

Update的key的当前数据值,作为参数传递给Update的Modify函数。该函数应该返回数据的新值,然后将根据给定的写入一致性级别对其进行复制。

Modify函数由Replicator actor调用,因此必须是纯函数,仅使用数据参数、封闭范围中的稳定字段。例如,它不能访问ActorContext或actor内部的可变状态。由于修改功能通常不可序列化,因此只能从与复制器在同一本地ActorSystem中运行的actor发送更新。

您将始终看到自己的写入。例如,如果您发送两条更改同一key的值的更新消息,则第二条消息的修改函数将看到第一条更新消息执行的更改。

如果在超时时间内,根据提供的一致性级别,成功复制,则发送Replicator.UpdateSuccess作为Update的回复到Update的replyTo。否则,将发送一个Replicator.UpdateFailure子类。请注意,Replicator.UpdateTimeout答复并不意味着更新完全失败或已回滚。它可能仍已复制到某些节点,并且使用gossip协议最终复制到所有节点。

检查传递给modify函数的状态参数,抛异常时,可以中止更新。这是在执行更新并将Replicator.ModifyFailure作为答复发送回之前发生的。

Get

若要检索数据的当前值,请向Replicator发送Replicator.Get消息。

该示例具有GetValue命令,该命令向复制器询问当前值。请注意,当收到来自复制器的GetSuccess响应时,如何使用来自传入消息的ReplyTo。

对于Get,您需要提供读取一致性级别。

您将始终读到自己的写入。例如,如果您发送更新消息,然后发送具有相同key的Get,则Get将检索前一条Update消息执行的更改。但是,回复消息的顺序未定义,即在前面的示例中,您可能在UpdateSuccess之前收到GetSuccess。

作为获取复制器的答复,如果在提供的超时时间内根据提供的一致性级别成功检索到值,则将GetSuccess发送到Get的replyTo。否则,将发送Replicator.GetFailure。如果key不存在,则答复为Replicator.NotFound。

Subscribe

每当示例中的分布式计数器被更新时,我们都会缓存该值,以便我们可以使用GetCachedValue命令,在不与复制器进行额外交互的情况下回答有关该值的请求。

当启动actor时,我们为它订阅key的更改,这意味着只要复制器观察到计数器的更改,我们的actor就会收到Replicator.Changed。由于这不是我们协议中的消息,因此我们使用消息转换功能将其包装在内部InternalSubscribeResponse消息中,然后在behavior的常规消息处理中对其进行处理,如上面的示例所示。如有更改,将根据可配置的akka​​.cluster.distributed-data.notify-subscribers-interval通知订户。

如果订户终止,则该订户将自动退订。订阅者也可以通过plicatortorAdapter.unsubscribe(key)函数注销。

Delete

可以通过向本地Replicator发送Replicator.Delete消息来删除数据条目。作为删除的回复,如果在提供的超时时间内提供的一致性级别成功删除了该值,则会将Replicator.DeleteSuccess发送到Delete的replyTo。否则,将发送Replicator.ReplicationDeleteFailure。请注意,ReplicationDeleteFailure并不意味着删除完全失败或已回滚。它可能仍已复制到某些节点,并且最终可能会复制到所有节点。

删除的key不能再次使用,但是仍然建议删除未使用的数据条目,因为这样可以减少新节点加入群集时的复制开销。随后的Delete,Update和Get请求将通过Replicator.DataDeleted进行回复。订阅者将收到Replicator.Deleted。

警告
随着删除的key继续包含在每个节点上存储的数据以及gossip消息中,顶层实体的一系列连续更新和删除将导致内存使用量不断增长,直到ActorSystem内存不足。在需要频繁添加和删除的情况下使用Akka分布式数据,应使用固定数量的顶级数据类型,同时支持更新和删除,例如ORMap或ORSet。

一致性

更新和获取中提供的一致性级别指定每个请求必须有多少个副本,成功响应写入和读取请求。

WriteAll和ReadAll是最强的一致性级别,但也最慢、可用性最低。例如,这种级别就足够了:一个节点对于Get请求不可用,您将不会收到该值。

对于低延迟读取,您可以使用readLocal来获取,但有获取过时数据的风险,即来自其他节点的更新可能尚不可见。

写一致性

使用writeLocal时,更新仅写入本地副本,然后使用gossip协议在后台分发,这可能需要几秒钟的时间才能传播到所有节点。

对于更新,提供了写一致性级别,其含义如下:

  • writeLocal,值将立即仅被写入本地副本,并随后用gossip传播
  • WriteTo(n),值将立即写入至少n个副本中,包括本地副本
  • WriteMajority,值将立即写入大多数副本,即至少N/2 + 1个副本,其中N是群集(或群集角色组)中节点的数量
  • WriteMajorityPlus类似于WriteMajority,在majority count基础上增加给定数量的附加节点数,最多所有节点。这样可以更好地容忍写入和读取之间的成员关系变化。
  • WriteAll,值将立即写入群集中的所有节点(或群集角色组中的所有节点)

当您指定要写入x个节点中的n个时,更新将首先复制到n个节点。如果在1/5超时时间后没有足够的Acks,更新将被复制到n个其他节点。如果剩余少于n个可用节点,则将使用所有剩余节点。可到达节点比不可到达节点优先。

请注意,WriteMajority和WriteMajorityPlus具有minCap参数,该参数对于指定为小型集群实现更好的安全性很有用。

读取一致性

如果优先考虑一致性,则可以使用以下公式确保读取始终反映最新的写入:

其中N是群集中节点的总数,或具有用于Replicator的角色的节点数。

您提供的一致性级别具有以下含义:

  • readLocal,值仅从本地副本中读取
  • ReadFrom(n),从n个副本(包括本地副本)读取并合并该值
  • ReadMajority,从大多数副本(即至少N / 2 + 1个副本)中读取和合并,其中N是群集(或群集角色组)中节点的数量
  • ReadMajorityPlus与ReadMajority相似,但是将给定数量的附加节点添加到majority count。最多所有节点。这样可以更好地容忍写入和读取之间的成员关系变化。
  • ReadAll,从集群中的所有节点(或集群角色组中的所有节点)读取并合并值

请注意,ReadMajority和ReadMajorityPlus具有minCap参数,该参数对于指定为小型集群实现更好的安全性很有用。

一致性和响应类型

使用ReadLocal时,您永远不会收到GetFailure响应,因为本地副本始终可供本地读取使用。但是,如果Modify函数引发异常,或者它无法持久存化到持久存储中,WriteLocal可能仍会用UpdateFailure消息答复。

例子

在7节点群集中,通过写入4个节点并从4个节点读取数据,或写入5个节点并从3个节点读取数据,来实现这些一致性属性。

通过结合WriteMajority和ReadMajority级别,始终可以读取最新的写入。复制器写入和读取大多数副本,即N/2 + 1。例如,在5节点群集中,它写入3个节点并从3个节点读取。在6节点群集中,它写入4个节点并从4个节点读取。

您可以为WriteMajority和ReadMajority定义最小数量的节点,这将最大程度地减少读取脏数据的风险。最小上限由WriteMajority和ReadMajority的minCap属性提供,并定义所需的majority。如果minCap较大,minCap=(N/2 + 1)。

例如,如果minCap为5,则3个节点的群集的WriteMajority和ReadMajority将为3,6个节点的群集将为5,12个节点的群集将为7(N/2 +1)。

对于小型群集(节点数<7),WriteMajority和ReadMajority之间的成员g关系发生变化导致的风险非常高,因此无法保证将多数读写操作组合在一起。因此,ReadMajority和WriteMajority具有minCap参数,该参数可用于指定为小型集群实现更好的安全性。这意味着如果群集大小小于majority 大小,它将使用minCap节点数,但最多使用群集的总大小。

在极少数情况下,执行更新时需要首先尝试从其他节点获取最新数据。这可以通过以下方式完成:首先发送带有ReadMajority的Get,然后在收到GetSuccess,GetFailure或NotFound答复时继续进行更新。当您需要根据最新信息做出决定,或从ORSet或ORMap中删除数据时,可能需要这样做。如果将数据从一个节点添加到ORSet或ORMap,并从另一节点删除,则只有在执行删除的节点上观察到添加的数据条目时,该数据才会被删除(因此,称为观察删除集)。

警告
警告:即使您使用WriteMajority和ReadMajority,如果群集成员在Update和Get时发生了更改,您仍可能读取脏数据。例如,在5个节点的群集中,当您进行Update时,该更改将写入3个节点:n1,n2,n3。然后再添加2个节点,并从4个节点读取一个Get请求,恰好是n4,n5,n6,n7,即在Get请求的响应中看不到n1,n2,n3的值。要在写入和读取时,增加对成员更改的容忍,可以使用WriteMajorityPlus和ReadMajorityPlus。

运行单独的复制器实例

对于某些用例,例如,当将复制器限制为某些角色,或在不同角色上使用不同的子集时,启动单独的复制器是有意义的,这需要在所有节点或标记有特定角色的节点组上完成。为此,基于分布式数据,您首先必须启动经典的Replicator,并将其传递给采用经典actor ref的Replicator.behavior方法。所有这些复制器必须在经典actor层次结构中的同一路径上运行。

也可以为给定的Replicator创建一个独立的ReplicatorMessageAdapter,而不是通过DistributedData扩展名创建一个。

复制的数据类型

Akka包含一组有用的复制数据类型,完全有可能实现自定义复制数据类型。

数据类型必须是收敛的(有状态的)CRDT,并且必须实现AbstractReplicatedData接口,即它们提供单调合并功能,并且状态更改始终会收敛。

您可以使用自己的自定义AbstractReplicatedData或AbstractDeltaReplicatedData类型,此包提供了几种类型,例如:

  • 计数器:GCounter,PNCounter
  • 集合:GSet,ORSet
  • Maps:ORMap,ORMultiMap,LWWMap,PNCounterMap
  • 寄存器:LWWRegister,Flag

计数器

GCounter是“仅增长计数器”。它仅支持增加,不支持减小。

它的工作方式与矢量时钟类似。它跟踪每个节点一个计数器,总值是这些计数器的总和。通过获取每个节点的最大数量来实现合并。

如果同时需要递增和递减,则可以使用PNCounter(正/负计数器)。

它正在分别跟踪increments(N)分、decrements(P)。 P和N都表示为两个内部GCounter。合并是通过合并内部P和N计数器来处理的。计数器的值是P计数器的值减去N计数器的值。

GCounter和PNCounter支持增量CRDT,不需要因此传递增量。

PNCounterMap数据类型可以管理几个相关的计数器。当将计数器放置在PNCounterMap中,而不是将它们放置为单独的顶级值时,可以保证将它们作为一个单元一起复制,这有时对于相关数据是必需的。

集合

如果只需要向集合中添加元素而不删除元素,则GSet(仅增长集合)是要使用的数据类型。 元素可以是可以序列化的任何类型的值。 合并是两个集合的结合。

GSet支持增量CRDT,不需要增量的因果传递。

如果需要添加和删除操作,则应使用ORSet(观察删除集)。 可以多次添加和删除元素。 如果并行添加和删除元素,则执行添加操作。 您无法删除未看到的元素。

将元素添加到集合时,ORSet的版本向量会增加。 所谓的“出生点跟踪节点添加元素的版本。 合并功能使用版本向量和点来跟踪操作的因果关系、解决并发更新。

ORSet支持增量CRDT,并且需要增量的因果传递。

maps

ORMap(观察到删除的映射)是具有Any类型的keys的映射,其值本身就是ReplicatedData类型。它支持数据条目添加,更新和删除任意多次。

如果同时添加和删除条目,则将执行添加。您无法删除未看到的条目。这与ORSet的语义相同。

如果数据条目被同时更新为不同的值,则这些值将被合并,因此要求这些值必须是ReplicatedData类型。

尽管ORMap支持多次删除和重新添加keys,但是这对值的影响可能是不确定的。合并将始终尝试合并同一key的两个值,而不管该key是否同时已被删除,并重新添加,尝试用新的值替换旧值,可能不会达到预期的效果。这意味着如果同时看到删除和更新的节点与没有看到删除,更新的节点进行gossip,则可以有效地恢复旧值。其结果之一是,改变值的CRDT类型(例如,从GCounter更改为GSet)可能会导致CRDT的合并功能始终失败。对于节点来说,这可能是不可恢复的状态,因此,对于给定的key,ORMap值的类型一定不能改变。

直接使用ORMap并不方便,因为它不暴露特定类型的值。 ORMap旨在用作构建更具体的maps的低级工具,例如以下专用maps。

  • ORMultiMap(可观察到的移除多map)是一种多map实现,其中将ORMap与ORSet封装在一起以获取map的值。
  • PNCounterMap(正负计数器map)是命名计数器的map(名称可以是任何类型)。它是带有PNCounter值的专用ORMap。
  • LWWMap(最后一位写获胜map)是具有LWWRegister(最后一位写获胜寄存器)值的专用ORMap。

ORMap,ORMultiMap,PNCounterMap和LWWMap支持增量CRDT,并且它们需要增量的因果传递。这里对增量的支持意味着,所有的map使用ORSet作为底层的key值类型,使用增量传播来传递更新。实际上,map的更新是一对,由ORSet作为key的增量和map中保留的各个值(ORSet,PNCounter或LWWRegister)的完整更新组成。

更改数据条目时,该条目的完整状态将复制到其他节点,即,当您更新map时,整个map将被复制。因此,与其使用一个ORMap包含1000个元素,不如将其拆分成10个顶级ORMap条目(每个元素包含100个元素),效率更高。顶层条目是单独复制的,这是折衷方案,即可能无法同时复制不同的条目,并且您可能会看到相关条目之间的不一致。单独的顶级条目不能一起原子更新。

有一个特殊版本的ORMultiMap,它是通过使用单独的构造函数ORMultiMap.emptyWithValueDeltas [A,B]创建的,它也将用增量传播值的更新(ORSet类型)。这意味着ORMultiMap用ORMultiMap.emptyWithValueDeltas初始化,以key的增量和值的增量成对传播更新。就消耗的网络带宽而言,效率要高得多。

但是,ORMultiMap尚未将此行为设为默认行为,如果希望在代码中使用它,则需要将调用ORMultiMap.emptyA,B)替换为ORMultiMap.emptyWithValueDeltas[A,B],其中A和B分别是map中key和值的类型。

另请注意,尽管具有相同的Scala类型,但由于不同的复制机制,ORMultiMap.emptyWithValueDeltas与“vanilla” ORMultiMap不兼容。需要特别注意不要将两者混用,因为它们具有相同的类型,因此编译器将不会提示错误。尽管如此,ORMultiMap.emptyWithValueDeltas使用与“vanilla” ORMultiMap相同的ORMultiMapKey类型进行引用。

请注意,LWWRegister以及LWWMap依赖于同步时钟,并且仅当值的选择对于在时钟偏斜内发生的并发更新不重要时才应使用。在下面的章节中,有关LWWRegister的更多信息。

Flags和寄存器

Flag是布尔值的类型,该布尔值已初始化为false,并且可以切换为true。此后不能更改。在合并中,true胜false。

LWWRegister(最后一位写赢得寄存器)可以保存任何(可序列化)值。

LWWRegister的合并采用时间戳最高的寄存器。 请注意,这依赖于同步时钟。 仅当值的选择对于时钟偏斜内发生的并发更新不重要时,才应使用LWWRegister。

如果时间戳完全相同,则合并采用地址最低的节点更新的寄存器(UniqueAddress是有序的)。

替代使用基于System.currentTimeMillis()的时间戳,可以使用基于其他的时间戳值,例如,用于乐观并发控制,数据库记录的递增的版本号。

对于首次写赢的语义,可以使用LWWRegister#reverseClock代替LWWRegister#defaultClock。

defaultClock使用System.currentTimeMillis()和currentTimestamp + 1的最大值。这意味着,同一节点上发生的更改会在同一毫秒内增加时间戳。 这也意味着在只有一个活跃的writer(例如,Cluster Singleton)的情况下,无需同步时钟使用LWWRegister是安全的。 然后,这样的单个写应首先使用ReadMajority(或更多)读取当前值,然后再使用WriteMajority(或更多)更改和写入值。 当LWWRegister与Cluster Singleton一起使用时,还建议启用:

增量CRDT

支持增量状态复制数据类型。增量CRDT是一种减少发送更新的完整状态的方法。例如,添加元素'c'和'd'以设置{'a','b'},只需发送增量{'c','d'},并将其与接收方的状态合并,从而设置{'a','b','c','d'}。

如果数据类型标记有RequiresCausalDeliveryOfDeltas,则复制增量的协议将支持因果一致性。否则,它只会最终保持一致。如果没有因果一致性,则意味着如果在两个单独的Update操作中添加元素“ c”和“ d”,则这些增量有时可能会传播到节点,与更新因果顺序不同。对于此示例,它可以导致在看到元素“ c”之前,可以看到集合{'a','b','d'}。最终它将是{'a','b','c','d'}。

请注意,有时增量CRDT也会复制完整状态,例如,当将新节点添加到群集中,或由于网络分区,或类似问题而无法传播增量时。

可以使用配置属性禁用增量传播:

自定义数据类型

您可以实现自己的数据类型。唯一的要求是,它必须实现AbstractReplicatedData接口的mergeData函数。

有状态CRDT的一个不错的特性是它们通常可以很好地组合,即,您可以组合几个较小的数据类型以构建更丰富的数据结构。例如,PNCounter由两个内部GCounter实例组成,以分别跟踪增加和减小。

这是自定义的TwoPhaseSet的简单实现,该方法使用两种内部GSet类型来跟踪添加和删除。 TwoPhaseSet是一个集合,可以在其中添加和删除元素,一旦添加后不再添加。

数据类型应该是不可变的,即“修改”方法应返回一个新实例。

如果AbstractDeltaReplicatedData支持增量CRDT复制,请实施其他方法。

序列化

数据类型必须使用Akka Serializer序列化。 强烈建议您使用Protobuf或类似的,对自定义数据类型实现有效的序列化。 内置数据类型用ReplicatedDataSerialization标记,并用akka.cluster.ddata.protobuf.ReplicatedDataSerializer序列化。

数据类型的序列化用于远程消息中,还用于创建消息摘要(SHA-1)以检测更改。 因此,重要的是,序列化必须高效,对于相同的内容产生相同的字节。 例如,set和map应在序列化时确定性地排序。

这是上述TwoPhaseSet的protobuf表示形式:

TwoPhaseSet的序列化器:

请注意,对集合中的元素进行了排序,因此对于相同的元素,SHA-1摘要是相同的。

您在配置中注册了序列化器:

有时使用压缩来减小数据大小可能是一个好主意。 Gzip压缩由akka.cluster.ddata.protobuf.AbstractSerializationSupport接口提供:

可以如上所述,对两个嵌入的GSet进行序列化,但是通常在从现有内置类型组成新数据类型时,最好将现有序列化器用于这些类型。 这可以通过在protobuf中将它们声明为bytes字段来完成:

并使用SerializationSupport特征提供的方法otherMessageToProto和otherMessageFromBinary来序列化和反序列化GSet实例。 这适用于注册Akka序列化任何类型。 TwoPhaseSet的序列化如下所示:

持久化存储

默认情况下,数据仅保存在内存中。因为它复制到群集中的其他节点,实现冗余,但是如果停止所有节点,则数据将丢失,除非您已将其保存在其他位置。

可以将数据条目配置为持久的,即存储在每个节点的本地磁盘上。下次启动复制器时,即在actor系统重新启动时,将加载存储的数据。这意味着只要旧群集中的至少一个节点参与新群集,数据将继续存在。持久数据条目的keys配置如下:

前缀匹配通过在key的末尾使用 * 来支持。

通过指定以下内容,可以使所有数据条目具有持久性:

LMDB是默认的存储实现。通过实现akka.cluster.ddata.DurableStore中描述的actor协议并为新的实现定义akka.cluster.distributed-data.durable.store-actor-class属性,可以用另一种实现替换该实现。

数据文件的位置配置有:

在生产环境中运行时,您可能需要将目录配置为特定路径(替代2),因为默认目录包含actor系统的远程端口以使名称唯一。如果使用动态分配的端口(0),则每次都将有所不同,并且不会加载先前存储的数据。

使数据具有持久性会降低性能。默认情况下,在发送UpdateSuccess答复之前,每个更新都会刷新到磁盘。为了获得更好的性能,但是如果JVM崩溃,则可能会丢失最后的写操作,因此可以启用写后写模式。然后,在将更改写入到LMDB并将其刷新到磁盘之前的一段时间内,将累积更改。当对同一key执行多次写入时,启用回写特别有效,因为这只是将序列化和存储的每个key的最后一个值。如果JVM崩溃,丢失写操作的风险很小,因为通常会根据给定的WriteConsistency将数据立即复制到其他节点。

请注意,如果由于某种原因而无法存储数据,则应该准备接收WriteFailure作为对持久数据条目更新的答复。启用write-behind-interval时,只会记录此类错误,而UpdateSuccess仍然是对Update的答复。

修剪CRDT垃圾以获得持久数据时有一个重要警告。如果在删除标记后删除了从未修剪过的旧数据条目并将其与现有数据合并,则该值将不正确。标记的生存时间由配置akka.cluster.distributed-data.durable.remove-pruning-marker-after定义,以天为单位。如果具有持久数据的节点不参与修剪(例如,它已关闭)并在此之后稍后启动,则可以这样做。具有持久数据的节点的停止时间不应长于此持续时间,如果在此持续时间之后再次加入,则应首先从lmdb目录中手动删除其数据。

局限性

您应该注意一些限制。

CRDTs不能用于所有类型的问题,并且最终的一致性并不适合所有领域。有时您需要强大的一致性。

它不适用于大数据。顶级条目的数量不应超过100000。将新节点添加到群集时,所有这些条目都将转移(指定)到新节点。条目被分成几块,所有现有节点都在gossip中协作,但是转移所有条目将需要一段时间(数十秒),这意味着您不能有太多顶级条目。当前建议的限制为100000。如果需要,我们将能够对此进行改进,但是该设计仍不适合数十亿个条目。

所有数据都保存在内存中,这是不打算将其用于大数据的另一个原因。

更改数据条目后,如果它不支持增量CRDT,则该条目的完整状态可能会复制到其他节点。例如,当新节点添加到群集中或由于网络分区或类似问题而无法传播增量时,也会为增量CRDT复制完整状态。这意味着您不能有太大的数据条目,因为这样远程消息的大小将太大。

CRDT垃圾

CRDT可能会引起问题的是某些数据类型会累积历史记录(垃圾)。 例如,一个GCounter跟踪每个节点一个计数器。 如果已从一个节点更新了GCounter,它将永久关联该节点的标识符。 对于长时间运行的系统,添加或删除许多群集节点,这可能会成为问题。 为了解决此问题,Replicator对从群集中删除的节点关联的数据进行修剪。 需要修剪的数据类型必须实现RemovedNodePruning接口。 有关详细信息,请参见复制器的API文档。

了解有关CRDT的更多信息

  • 强大的最终一致性和无冲突的复制数据类型(视频)Mark Shapiro的演讲
  • Mark Shapiro等人对收敛和交换复制数据类型进行的全面研究。 等

配置

可以使用以下属性配置DistributedData扩展:

示例项目
Distributed Data example project是一个示例项目,可以下载并带有如何运行的说明。

该项目包含几个示例,这些示例说明了如何使用分布式数据。

你可能感兴趣的:(cluster,akka)