模块信息
要使用Akka群集分片,必须在项目中添加以下依赖项:
介绍
当您需要在群集中的多个节点之间分布actors,并希望能够使用其逻辑标识符与actors进行交互,而又不必关心actors在群集中的物理位置(随时间变化)时,群集分片将非常有用。
例如,它可以是代表领域驱动设计术语中的聚合根的actors。在这里,我们称这些actors为“实体”。这些actors通常具有持久(持久)状态,但是此功能并不限于具有持久状态的actors。
Akka Cluster Sharding简介视频是学习Cluster Sharding的一个很好的起点。
当您有许多有状态的actors一起消耗超过一台机器可以提供的资源(例如内存)时,通常使用集群分片。如果只有几个有状态的actors,则在“群集单例”节点上运行它们可能会更容易。
在这种情况下,分片意味着具有标识符(称为实体)的actors可以自动分布在集群中的多个节点上。每个实体actor仅在一个地方运行,并且可以将消息发送到实体,发送方无需知道目标actor的位置。这是通过扩展提供的ShardRegion actor发送消息来实现的,该actor知道如何将带有实体ID的消息路由到最终目的地。
如果启用了该功能,则群集分片将不会在状态为WeaklyUp的成员上处于活动状态。
警告
请确保不要使用“群集关闭”策略,以免在网络出现问题或系统过载(长时间的GC暂停)时将群集拆分为几个单独的群集,因为这将导致启动多个分片和实体,每个单独的群集中都有一个!请参阅Downing。
基本例子
通过ClusterSharding扩展访问分片:
分片常与持久性一起使用,但是任何行为都可以与分片一起使用,例如 基本计数器:
每个实体类型都有一个key,该key用于检索给定实体标识符的EntityRef。 请注意,在示例的Counter.create函数中,未调用entityId参数,它是用来演示如何将其传递给实体的。 另一种执行此操作的方法是,如果需要,将entityId作为消息的一部分发送。
然后,通过EntityRef将消息发送到特定实体。 也可以将方法包装在ShardingEnvelope中或定义提取器功能,并将消息直接发送到shard区域。
应在每种实体类型的每个节点上调用群集分片初始化。 可以使用角色来控制创建实体actors的节点。 init方法将根据节点的角色是否与实体的角色相匹配来创建ShardRegion或代理。
指定角色:
持久化示例
使用分片时,可以将实体移至群集中的不同节点。 持久性可用于在actor移动后恢复其状态。
Akka Persistence基于单写程序原理,对于特定的PersistenceId,仅一个持久性actor实例处于活动状态。 如果多个实例要同时保留事件,则事件将交错并且在重播时可能无法正确解释。 群集分片通常与持久性一起使用,以确保每个PersistenceId(entityId)只有一个活动实体。
这是用作分片实体的持久性actor的示例:
初始化和使用实体:
请注意,如何从Behavior的工厂函数中的EntityTypeKey和EntityContext提供的EntityId构造唯一的PersistenceId。这是定义PersistenceId的一种典型方法,但是其他格式也是可能的,如PersistenceId section所述。
向持久性实体发送消息与该实体不是持久性实体相同。唯一的区别是,当移动实体时,状态将被恢复。在上面的示例中,使用ask,但可以使用tell或其他任何交互模式。
有关更多详细信息,请参见persistence。
分片分配
分片是一起管理的一组实体。分组通常是由entityId的哈希函数定义的。对于特定的实体标识符,分片标识符必须始终相同。否则,实体actor可能会意外地同时在多个地方启动。
默认情况下,分片标识符是实体标识符的hashCode的绝对值,以分片总数取模。分片的数量通过以下方式配置:
根据经验,分片的数量应比计划的最大群集节点数量大十倍。不一定要确切。分片少于节点数会导致某些节点不会托管任何分片。分片过多会导致分片的管理效率降低,例如重新平衡开销,并增加了延迟,因为每个分片的第一条消息的路由中都包含了协调器。
集群中所有节点的number-of-shards
配置值必须相同,并在加入时通过配置检查进行验证。更改值需要停止集群中的所有节点。
分片被分配给集群中的节点。由分片分配策略决定在何处分配分片。默认实现ShardCoordinator.LeastShardAllocationStrategy将新的分片分配给ShardRegion(节点),并且之前分配的分片数量最少。此策略可以由应用程序指定的实现方式代替。
外部分片分配
另一种分配策略是ExternalShardAllocationStrategy,它允许通过ExternalShardAllocation扩展来显式控制分片在何处分配。例如,这可以用于将Kafka分区消费与分片位置相匹配。
要使用它,将其设置为您实体上的分配策略:
对于尚未分配的任何shardId,它将分配给请求节点。 进行明确分配:
任何新的或移动的分片分配都将在下一次重新平衡时移动。
从客户端到分片分配策略的通信是通过分布式数据进行的。它使用单个LWWMap,可以支持成千上万的分片。更高版本可以使用多个keys来支持更多的分片。
外部分配策略的示例项目
Kafka to Cluster Sharding是一个示例项目,可以下载并带有如何运行的说明,该示例演示了如何使用外部分片将Kafka分区消费对接分片。
自定义分片分配
初始化实体类型或显式使用withAllocationStrategy函数时,可以将可选的自定义分片分配策略传递到可选参数中。有关如何实现自定义ShardAllocationStrategy的详细信息,请参见akka.cluster.sharding.AbstractShardAllocationStrategy的API文档。
运行原理
钝化
如果实体的状态是持久性的,则可以停止不用于减少内存消耗的实体。这是通过实体actors的特定于应用程序的实现来完成的,例如,通过定义接收超时(context.setReceiveTimeout)。如果邮件在自身停止时已经入队,则该邮箱中的排队消息将被丢弃。为了支持优雅的钝化而不丢失此类消息,实体actor可以发送ClusterSharding.Passivate到创建实体时传递给工厂方法的ActorRef
初始化如下:
请注意,在上面的示例中,将stopMessage指定为GoodByeCounter。该消息会因重新平衡或钝化而自行停止发送给实体。如果未定义stopMessage,它将自动停止而不接收特定消息。如果实体需要在停止之前执行一些异步清除或交互,则定义自定义停止消息可能会很有用。
自动钝化
如果实体在akka.cluster.sharding.passivate-idle-entity-after中配置的持续时间内未收到消息,或者通过将ClusterShardingSettings上的passivateIdleEntityAfter标志显式设置为合适的时间以使actor保持活动状态,则这些实体将被自动钝化。请注意,仅对通过分片发送的消息进行计数,因此在此活动中不计入直接发送给ActorRef的消息或actor发送给自身的消息。可以通过设置akka.cluster.sharding.passivate-idle-entity-after = off来禁用钝化。如果启用了“记住实体”,它将自动禁用。
分片状态
有两种类型的状态管理:
- ShardCoordinator State-分片位置。这存储在状态存储中。
- 记住实体-活动分片和每个分片中的实体,这是可选的,默认情况下处于禁用状态。这存储在“记住实体存储”中。
状态存储
状态存储对于分片是必不可少的,它包含分片的位置。 ShardCoordinator在节点之间移动后需要加载此状态。
状态存储有两个选项:
- 分布式数据模式-使用Akka分布式数据(CRDT)(默认)
- 持久性模式-(不建议使用)使用Akka持久性(事件源)
警告
不建议使用状态存储模式的持久性。建议针对协调器状态迁移到ddata,如果使用复制的实体,则建议迁移到针对复制的实体状态的eventsourced
。
新的记住实体eventsourced
模式可以读取已过时的持久性状态存储模式写入的数据,对于记住的实体。
迁移后,您将无法返回到persistence
模式。
分布式数据模式
要启用分布式数据存储模式(默认):
ShardCoordinator的状态跨群集复制,但不存储到磁盘。分布式数据以WriteMajorityPlus/ReadMajorityPlus一致性处理ShardCoordinator的状态。当集群中的所有节点都停止时,不再需要该状态并将其删除。
群集分片在每个节点上使用其自己的分布式数据复制器。如果将角色与分片一起使用,则每个角色有一个Replicator,它为某些实体类型启用所有节点的子集,并为其他实体类型启用另一个子集。每个复制器都有一个包含节点角色的名称,因此,群集中所有节点上的角色配置都必须相同,例如,执行滚动升级时不能更改角色。更改角色需要重新启动整个群集。
akka.cluster.sharding.distributed-data config配置“分布式数据”的设置。对于不同的分片实体类型,不可能有不同的distributed-data
设置。
持久化模式
要启用持久性存储模式:
由于它在集群中运行,因此必须为持久性配置一个分布式日志。
警告
“记住实体”的持久性模式已由“记住实体”状态模式代替。它不应用于新项目,现有项目应尽快迁移。
记住实体
重新平衡或实体崩溃后,记住实体会自动重新启动实体。没有记住的实体,重新启动会在消息到达时发生。
启用记忆的实体会禁用 Automtic Passivation。
除非实体已被持久化(例如,使用事件来源),否则不会还原实体本身的状态。
要启用记住实体,当启动给定实体类型的分片区域(或其代理)时,在ClusterShardingSettings中将RememberEntities标志设置为true,或配置akka.cluster.sharding.remember-entities=on,。
启动和停止实体会产生开销,但是这受批处理操作限制在记忆实体存储中。
启用时的行为
启用rememberEntities
后,只要将分片重新平衡到另一个节点上或在崩溃后恢复,它将重新创建该分片中先前运行的所有实体。
要永久停止实体,请向创建实体时传递给工厂方法的ActorRef
记住实体状态
记住实体存储有两个选项:
ddata
eventsourced
记住实体分布式数据模式
启用ddata模式(默认启用):
为了支持在集群完全重新启动(非滚动)之后重新启动实体,记住实体存储将通过分布式数据持久保存到磁盘。如果不需要,可以将其禁用:
禁用的原因:
- 完全关闭集群后无需记住实体
- 在重新启动之间无法访问磁盘的环境中运行,例如没有持久卷的Kubernetes
为了在没有磁盘存储的环境中支持记住的实体,请改用事eventsourced
模式。
事件来源模式
通过以下方式启用事件源模式:
该模式使用事件源存储每个活动切片和每个切片的活动实体,因此必须配置持久性和快照插件。
从过时的持久性模式迁移
如果不使用记住的实体,则可以在完全重启集群后迁移到ddata。
如果使用记住的实体,则有两个迁移选项:
- ddata用于状态存储,而ddata用于记住实体。完全重启群集后,所有已记住的实体将丢失。
- ddata用于状态存储,
eventsourced
用于记忆实体。新的eventsourced
记忆实体存储读取旧持久性模式写入的数据。完整集群重新启动后,将记住您记住的实体。
为了迁移现有的记住的实体,需要在config中为您的application.conf中使用的日记配置事件适配器。在此示例中,cassandra是使用的日志:
迁移后,您将无法返回旧的持久性存储,因此无法进行滚动升级。
使用“分布式数据”模式时,实体的标识符存储在“分布式数据的持久存储”中。您可能要更改akka.cluster.sharding.distributed-data.durable.lmdb.dir的配置,因为默认目录包含actor系统的远程端口。如果使用动态分配的端口(0),则每次都将有所不同,并且不会加载先前存储的数据。
将活动实体的标识符存储在持久性存储(即存储到磁盘)中的原因是,在完全重启群集后也应启动相同的实体。如果不需要这样做,则可以使用以下配置来禁用持久存储并从更好的性能中受益:
最少成员数启动
建议对群集设置akka.cluster.min-nr-of-members或akka.cluster.role.
. min-nr-of-members
使用群集分片。 min-nr-of-members成员将推迟分片的分配,直到至少已经启动了一定数量的区域并将其注册到协调器。这样可以避免将许多分片分配给寄存器的第一个区域,而稍后分配给其他节点。
有关成员min-nr-of的更多信息,请参见How To Startup when Cluster Size Reached。
检查集群分片状态
有两个检查集群状态的请求:
GetShardRegionState,它将以ShardRegion.CurrentShardRegionState进行答复,该ShardRegion.CurrentShardRegionState包含在Region中运行的分片的标识符以及每个活着的实体。
GetClusterShardingStats,它将查询集群中的所有区域并使用ShardRegion.ClusterShardingStats进行响应,其中包含在每个区域中运行的分片的标识符以及每个分片中存在的实体数量。
如果任何分片查询失败,例如由于超时而导致,如果分片太忙而无法在配置的akka.cluster.sharding.shard-region-query-timeout内回复,则ShardRegion.CurrentShardRegionState和ShardRegion.ClusterShardingStats也将包括分片集群失败区域的标识符。
这些消息的目的是测试和监控,不允许它们直接向单个实体发送消息。
租约
可以将租约用作附加安全措施,以确保分片不在两个节点上运行。
发生这种情况的原因:
- 网络分区时,没有适当的中断提供程序
- 部署过程中的错误导致了两个单独的Akka集群
- 在从网络分区的一侧的群集中删除成员,在另一侧关闭它们导致的计时问题
租约可以作为最终备份,这意味着每个分片除非拥有租约,否则都不会创建子实体actors。
要将租约用于分片,请在配置中设置akka.cluster.sharding.use-lease。每个分片将尝试获取名称为
的租约,并将所有者设置为Cluster(system).selfAddress.hostPort
。
如果分片无法获得租约,它将保持未初始化状态,因此其拥有的实体的消息将被缓存在ShardRegion中。如果初始化后租约丢失,则分片将终止。
删除内部集群分片数据
删除内部群集分片数据仅与“持久模式”相关。群集分片ShardCoordinator存储分片的位置。重新启动整个Akka集群时,将安全删除这些数据。请注意,这不包括应用程序数据。
有一个工具程序akka.cluster.sharding.RemoveInternalClusterShardingData可以删除此数据。
警告
当正在运行使用群集分片的Akka集群节点时,切勿使用此程序。使用此程序之前,请停止所有群集节点。
如果由于数据损坏而导致集群分片协调器无法启动,则可能需要删除数据,如果意外地同时运行两个群集,例如在存在网络分区时,由无效的中断提供程序引起的。
将此程序用作独立的Java主程序:
该程序包含在akka-cluster-sharding jar文件中。 使用与普通应用程序相同的类路径和配置来运行它是最简单的。 可以从sbt或Maven以类似方式运行。
指定实体类型名称(与您在ClusterSharding的init方法中使用的名称相同)作为程序参数。
如果您将-2.3指定为第一个程序参数,它还将尝试使用不同的persistenceId删除Akka 2.3.x中的Cluster Sharding存储的数据。
配置
可以使用以下属性配置ClusterSharding扩展。 使用ActorSystem参数创建时,ClusterShardingSettings将读取这些配置属性。 也可以修改ClusterShardingSettings或从另一个配置部分使用以下相同的布局创建它。
一个重要的配置属性是分片数量,如分片分配中所述:
示例项目
Sharding example project是一个示例项目,可以下载并带有如何运行的说明。
该项目包含一个KillrWeather示例,该示例说明了如何使用群集分片。