理论教育 Scala语言:Actor的事务

Scala语言:Actor的事务

时间:2023-11-24 理论教育 版权反馈
【摘要】:在这一小节中,将介绍Actor中的事务,Actor中要完成事务有两种方法,第一种方法是使用协作事务,顾名思义,协作事务是经过多方协作共同完成事务,其思路是将事务分发到每一个Actor中,每个Actor完成事务的一部分。要创建协作事务,需要将协作消息发送到每一个Actor上,通过把协作事务发送到每一个Actor,使得参与事务的每一个Actor自动包含到这个协作事务中来。Akka中的Transactor就可解决这个问题,提供了一个统一的方式来处理协作事务。

Scala语言:Actor的事务

在这一小节中,将介绍Actor中的事务,Actor中要完成事务有两种方法,第一种方法是使用协作事务,顾名思义,协作事务是经过多方协作共同完成事务,其思路是将事务分发到每一个Actor中,每个Actor完成事务的一部分。另外一种方法就是使用transactor。为了说明协作事务和transactor的使用,这里借助账户间转账案例来说明。

为了完成转账,需要从一个账户中取出钱,然后将钱存入另外一个账户。如果这其中的任何一步出现问题,都应该放弃之前的操作,只有所有操作都成功,才提交事务。在Akka中,为了完成这样的操作,需要使用协作事务。它的原理是:创建一个原子事务,并将他们分发到多个Actor上,所有的Actor都将同时开始并完成事务,这使得它在整体上看起来就像一个大的原子块,每一个Actor上的事务都会提交,如果有一个Actor上的事务失败,整体失败。

这里我们借助Coordinated类来完成协作事务。这里我们分步描述。

步骤一:创建Coordinated

创建Coordinated需要引入akka.transactor.Coordinated类,创建代码如下所示。

要创建协作事务,需要将协作消息发送到每一个Actor上,通过把协作事务发送到每一个Actor,使得参与事务的每一个Actor自动包含到这个协作事务中来。

步骤二:创建Account账户

Account继承自Actor类,在receive方法中根据消息类型,处理存钱和取钱的操作。同时定义了三个消息样本类,GetoutMoney类表示取钱操作,PutinMoney表示存钱操作,Get⁃Balance表示查询余额。代码如下所示。

上述的代码中,序号的解释如下:

①balance账户必须被STM引用包装起来。

②匹配协作事务中的取款消息。

③事务原子块的开始。

④在原子块中更新账户。

⑤匹配协作事务中的存款消息。

⑥查询账户余额信息,并将余额信息返回给sender。

账户信息是一个共享变量,因为要在该变量上面进行读和写操作,因此使用STM的Ref包装。在receive方法中,匹配消息,这些消息要么是Coordinated(GetoutMoney(amont))类型,要么是Coordinated(PutinMoney(amont))类型,由于这些消息类型都很长,于是使用@符号给它们取了别名,取别名的好处是名称短小且易记。(www.daowen.com)

在Coordinated(GetoutMoney(amont))和Coordinated(PutinMoney(amont))两种类型消息对应的case分支中,都有atomic原子块,在Coordinated(GetoutMoney(amont))分支的atomic原子块中,首先检查账户余额是否满足取款要求,如果余额不足,抛出一个Error的异常,这将导致事务的失败,事务失败将会导致Actor的重启,以恢复Actor内部的一致性。Actor在重启之前,自动调用preRestart方法,在preRestart方法中,将失败的原因及账户的余额发送给查询方。

Account中会处理协作事务,那协作事务是从哪里发送出去的呢?协作事务就是一个被视为整体的任务的集合,从整体上看就跟一个指令一样,既然有指令,那谁发送指令呢?当然要单独实现,下面就实现发送协作事务到Account Actor完成存款。

步骤三:发送协作事务到Account Actor,完成存款操作

发送协作事务到Account,Account是一个Actor,因此在向它发送消息之前,需要启动Account Actor,并得到Account Actor的引用。下面是向Acount发送协作事务,完成存款的代码。

上面代码中,通过Coordinated类,创建transaction对象,在transaction的actomic原子块中,向account1发送transaction(PutinMoney(amount=100)),发送的transaction(PutinMoney(amount=100))消息在Account Actor的receive方法中匹配,并且匹配到case coordinated@Coordinated(PutinMoney(amount))分支,在该分支中,balance()=balance()+amount,账户余额变成balance()的值加上存入的值amount,完成存款操作。

因为发送消息的操作本来就是原子性的,因此可以省略上面代码中的atomic原子块,上面的代码可以改成如下所示,以使代码看起来更简洁、可读。

另外的不同就是,由于发送事务没有放入atomic原子块,因此线程不需要等待事务完成之后才执行,这提高了调用线程的性能。

现在已经可以创建协作消息,也能够分发协作事务。接下来专门创建一个名为Transfer的Actor去从一个账户转账到另外一个账户,最后给请求返回消息。

步骤四:创建Transfer Actor负责转账处理

创建Transfer的目的是使存款、取款和转账操作得到统一的处理,而不需要单独分开编写代码。首先需要定义一个TransferMoney的样本类,该类中有转账账户from:ActorRef,目标账户to:ActorRef和每次发生转移的金额3个属性。

Transfer也是一个Actor,因此必须继承Actor,在其receive方法中,匹配消息,若收到的消息为TransferMoney(amount,from,to)消息,则完成转账任务。代码如下所示。

创建的Transfer继承自Actor,提供了一个通用的转账的方法。当账户里面有足够的钱时,可以得到下面的结果。

当账户中没有足够的余额时,得到如下结果。

从上面的示例中可以看到,需要在不同地方的代码中处理协作事务,但是大多数时候,这些处理的结构都是相似的,因此可以使用一个设计模式来避免代码的重复。Akka中的Transactor就可解决这个问题,提供了一个统一的方式来处理协作事务。接下来就看看Akka中的Transactor。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈