akka 在处理并发内存状态时,Actor模型是否最有意义

f0brbegy  于 2022-11-23  发布在  其他
关注(0)|答案(1)|浏览(87)

我最近一直在研究Actor Model和Akka。作为一个OOP程序员,我尝试坚持松散耦合和“告诉不要问”。我还使用消息和事件在有界上下文之间进行通信。所以,Actor的模式对我来说很有意义。
我的理解是,使用Actor的主要动机是在并发环境中管理状态,我的大部分日常工作涉及编写后端业务应用程序,其中典型的模式是从DB加载实体、检查不变式、处理命令、持久化如果我有一个高负载和大量的并发更改,我可能会选择使用事件来源,而不是更新数据库。因此,我的并发挑战实际上是由我的数据库层来解决的。
因此,我的问题是,在处理快速变化、并发和内存中的状态管理(如在线多玩家游戏)时,Actor模型主要是一个优势吗?

du7egjpx

du7egjpx1#

演员模型确实有助于管理内存中的状态,并通过并发性方面大大简化了模式/技术,如数字孪生和内存映像。
还有其他好处:模型中明确的通信和延迟促进了分发,这是一个主要原因......应用程序越接近于纯参与者模型,就越能够将多个副本作为一个应用程序呈现,并带来相应的弹性优势。此外,还有一个故障处理问题(与错误处理不同:它们是相当不同的),它在构建可靠的系统方面有着悠久的历史。
但是对于您正在谈论的"后端业务应用程序",在那里(从有界上下文等的讨论中),您似乎至少遵循了一些DDD模式,能够在内存中保持状态是一个相当大的胜利。
考虑一个DDD聚合,注意到对该聚合中实体的所有访问都是通过其根,并注意到聚合形成了一个一致性边界。这两者都很好地符合参与者模型:该模型的并发性保证仅在参与者的内部状态只能通过参与者访问时保持,并且通过将聚合上的操作编码为给参与者的消息/命令并依赖于一次一条消息的处理,可以很容易地满足一致性边界。
为了简单起见,我将假设(不失一般性,但一个聚合多个实体的情况会在一次一条消息之外保留一个假象方面引入一些复杂性:在Akka术语中的广泛方法是将聚合Map到分片实体(存在术语不匹配...),并且将聚合中的实体Map到该实体的(可能持久的)子参与者),即聚合包含单个实体。
收到一个请求,我们解析该请求的聚合根,得到一个Map<AggregateRoot, ActorRef<AggregateCommand>>(我在这里使用的是类型化的API,我们可以假设Map是并发的)跟踪聚合的活动内存示例。我们将请求转换为AggregateCommand并将其发送给参与者(我们可能会使用ask模式,这样我们就可以返回一个不同的响应,以确定命令是被接受还是被拒绝);如果内存中还没有示例,我们将生成一个示例并将其保存在map中,然后再像前面一样继续。
作为旧逻辑的直接转换,我们的聚合参与者将在每个命令上从DB加载其实体的状态,验证某些不变式是否成立,处理命令以更新内存中的状态,保持更新后的状态并引发事件(可能是使用了某种事务发件箱模式,否则会出现即使状态已更新也没有实际引发事件的情况...)。
目前还不清楚您在并发命令执行方面采取了什么策略(我暂时忽略事件来源)。(让数据库处理并发),这很好(我们都应该这么幸运!),尽管在代码和DB模式之间传播域规则会引入一个潜在的冲突,并且意味着对这些规则的某些类别的更改需要模式迁移和所有这些痛苦。或者,某些乐观并发控制可能正在起作用:如果并发更新已经完成,我们可以返回到从DB重新加载。
因为actor和并发Map结合起来可以防止并发修改(我们基本上已经介绍了悲观并发控制),所以我们可以在没有并发的假设下持久化,这样就不用支付并发负担。同样,我们可以省去从DB加载实体并验证不变量对每个消息都成立的步骤:只要在任何时间对于聚合的这个示例最多有一个参与者处于活动状态,并且只有这样的参与者更新DB(并且命令处理支持不变式,您真的希望以前是这种情况),它足以在参与者启动时将实体加载到内存中,并在命令之间使用相同的内存中状态。数据库表示仅用于持久性,并且您还为域逻辑和约束创建了单一的真值源。作为附带的好处,您消除了并发下的大部分数据库负载(读取、数据库并发解析、并发下的重试)。
这听起来很复杂,但是这个模式已经有了预先存在的实现:并发Map由AkkaClusterSharding提供(注意,1个节点的集群是完全可以接受的),我基本上已经描述了AkkaPersistence的DurableStateBehaviorAPI。

请注意,我们已经显著地转移了数据库的工作负载:它从基本的1读:1写变成了1读:n 写(n〉= 1)。您现在可以为写调整DB,特别是如果您正在执行CQRS(如果您正在为其他绑定上下文发布域事件,您至少已经执行了一些CQRS)。转移到事件源(例如Akka Persistence的EventSourcedBehavior),如果使用细粒度事件,则会以读取为代价降低写入成本(因为事件只涉及实体的一部分,并且在读取快照时可能很小,那么许多事件几乎肯定比一次读取整个状态更昂贵),这种优化放大了我们新的写入繁重工作负载的优势。
(免责声明:我受雇于Lightbend,它是Akka的主要开发者,但这是我甚至在受雇之前给予的相同答案)

相关问题