Akka Essentials - 2

简介:

Actors

Defining an actor

class MyActor extends Actor {
    def receive = {
    
    }
}

In Scala, the receive block is actually a partial function, which allows the usage of pattern matching syntax.

Creating actors

Actor with default constructor

使用actorOf创建actor,两个参数Props中是actor class对象,name是actor name

val system = ActorSystem("MyActorSystem")
val myActor = system.actorOf(Props[MyActor], name = "myActor")

To create an Actor, we created the ActorSystem class and invoked the actorOf() method on the same. 
The actorOf() method accepted two arguments—first was the Props object and second was the actor's name passed as a String object. 
The Props object accepted the Actor class object, which needed to instantiated and started. 
The actor is started after the object has been instantiated.

The instantiated actor is held using ActorRef. ActorRef provides an immutable and serial able handle to the underlying actor.

Actor with non-default constructor

class MyActor(initialise:Int) extends Actor {
    def receive = {
    }
}
val system = ActorSystem("MyActorSystem")
val myActor = system.actorOf(Props(new MyActor(10)), name = "myActor") //call-by-name block

Creating an actor within an actor hierarchy

class SupervisorActor extends Actor {
    val myWorkerActor = context.actorOf(Props[MyWorkerActor],"myWorkerActor")
}

In this case, within the Actor object, we created the child using the parent context.

 

Messaging model

Fire and forget messages – tell()

单向发送

actor ! msg
//or
actor.tell(msg)
//or
actor tell msg

两个参数,一个是msg, 一个是sender actor's reference 
By default, the caller's actor reference is passed implicitly if nothing is specified, 第二个参数是implicitly包含sender的, 用于receiver的reply-back

DeadLetterActor用于接受所有发送给dead或不存在的actor的message 
But if the caller is not an actor or if the actor that invoked no longer exists, then the reply is sent to the dead letter actor. 
In Akka, DeadLetterActorRef is the default implementation of the dead letters' service, where all messages are rerouted whose callers are shut down or nonexistent.

当然你也可以传入一个其他的actor

//explicit passing of another actor reference
actor.tell(msg, anotherActorRef)

Send and receive messages – ask()

the message is sent asynchronously and future is returned, which represents a potential reply. 
In the case of send and receive message mode, actors use the ask() method for sending a message and wait on future for the reply.

val result: Future[Order] = ask(orderActor, userId).mapTo[Order] //用userId去orderActor获取这个user的order

Replying to messages

One of the key aspects of communicating via messages is the ability to reply back to the actors. 
When an actor receives a message and a reply is expected back, the actor can make use of the sender available within the actor to reply back.

def receive = {
    case message:String =>
        sender ! (message + "world") //从message中获取sender
}

In case, no sender is specified or available, the reply is sent back to the DeadLetterActorRef.

Forwarding messages

Forward的不同在于会保留original sender reference 
When writing an actor that provides functionality such as routing or load balancing or replication, the incoming messages will get forwarded to the target actors. In this case, it is very important that the original sender reference is maintained and passed on to the target actors. This makes sure that the replies go to the original sender and not the mediator actor.

actor.forward(message)

Stopping actors

Actor termination involves multiple steps. Once the STOP signal is received by the actor, the following actions take place: 
1. Actor stops processing the mailbox messages. 
2. Actor sends the STOP signal to all the children. 
3. Actor waits for termination message from all its children. 
4. Next, Actor starts the self-termination process that involves the following: 
    -Invoking the postStop() method 
    -Dumping the attached mailbox 
    -Publishing the terminated message on DeathWatch 
    -Informing the supervisor about self-termination

用以下方法去stop actor:

//first option of shutting down the actors by shutting down
//the ActorSystem
system.shutdown()
//second option of shutting down the actor by sending a
//poisonPill message
actor ! PoisonPill
//third option of shutting down itself
context.stop(self)
//or stop the child actors
context.stop(childActorRef)

Killing actors 
An actor can be killed when a kill() message is sent to it. Unlike PoisonPill, which is an asynchronous way to shut down the actor, kill() is a synchronous 
way. The killed actor sends ActorKilledException to its parent.

actor ! Kill

HotSwap

其实就是可以动态的切换,或暂时改变,消息处理逻辑

Another key functionality of Akka is the ability to HotSwap an actor's message loop functionality at runtime. 
The functionality is provided via the getContext().become() and getContext().unbecome() methods. 
The HotSwapped code is kept in a stack, which can be pushed and popped when the become() or unbecome() methods are invoked.

case class PING
case class PONG
class PingPongActor extends Actor {
    import context._
    var count = 0
    def receive: Receive = {
            case PING =>
                println("PING")
                count = count + 1
                Thread.sleep(100)
                self ! PONG
                    become {  //切换到pong的逻辑,ping的逻辑会暂时放在stack里面
                        case PONG =>
                        println("PONG")
                        count = count + 1
                        Thread.sleep(100)
                        self ! PING
                        unbecome() //恢复ping的逻辑
                    }
        if(count > 10) context.stop(self)
    }
}

虽然我觉得这个例子没必要一定要这样实现,但反应出message loop functionality可以runtime的在ping和pong直接切换

 

Typed Actors

Untyped actors respond to messages sent, while typed actors respond to method calls.

A typed actor has two parts—a publicly defined interface, and secondly, an implementation of the interface.

In effect, the public interface provides the service contract that bridges the Actor Model to the object-oriented paradigm.

The explicit public interface makes the actors more clear and concise, lending an object-oriented design paradigm to the Actor Model, as opposed to an event-driven design paradigm.

In Akka, typed actors have been implemented using the Active Object pattern. (参考Active Object pattern)

理解了Active Object, 就能明白其实是给actor封装一个method call的proxy, 将message send的方式转化为method call 
为什么需要这个? 比如将现有的Java对象加到Actor系统来

直接看例子, 体会和message方式的不同

//定义actor CalculatorInt的接口
//有4个method, 对于actor可以理解为, 4种message
trait CalculatorInt {
    def add(first: Int, second: Int): Future[Int] //异步方式, 类似ask
    def subtract(first: Int, second: Int): Future[Int]
    def incrementCount(): Unit  //类似tell,不需要返回
    def incrementAndReturn(): Option[Int] //同步方式,会blocking等待
}
//实现actor CalculatorInt 
class Calculator extends CalculatorInt with PreStart with PostStop {
    var counter: Int = 0
    import TypedActor.dispatcher
    def add(first: Int, second: Int): Future[Int] =
        Promise successful first + second
    def subtract(first: Int, second: Int): Future[Int] =
        Promise successful first - second
    def incrementCount(): Unit = counter += 1
    def incrementAndReturn(): Option[Int] = {
        counter += 1
        Some(counter)
    }
    def preStart(): Unit = { //初始化
        log.info ("Actor Started")
    }
    def postStop(): Unit = {
        log.info ("Actor Stopped")
    }
}
//创建TypedActor
val _system = ActorSystem("TypedActorsExample")
val calculator: CalculatorInt = TypedActor(_system).typedActorOf(TypedProps[Calculator]())
//Fire and forget, 类似tell
calculator.incrementCount()
//Send and receive
val future = calculator.add(14,6);
val result = Await.result(future, 5 second);
//Method invocation in a blocking way
val result = calculator.incrementAndReturn()
//To shut down the typed actor
TypedActor(system).stop(calculator)
//Other way to stop the actor is invoke the Poisonpill method
TypedActor(system).poisonPill(calculator)

 

Dispatchers and Routers

Dispatchers

In the real world, dispatchers are the communication coordinators that are responsible for receiving and passing messages.

比较形象的比喻, 接线员, 需要同时接多个电话, 这里就有策略问题, 并且需要将不同的电话不同的需要转到不同的分机

For Akka, the dispatchers, actors, mailbox, and threads look like the following diagram:

在Akka中, 有很多不同的actor(带有各自的mailbox, message queue), 线程池(根据cpu核数, 分配不同数量的线程) 
Dispatcher的责任就是从不同的actor的mailbox中取出messages, 然后分配到不同的thread上面去执行

image

Types of dispatcher 
In the case of Akka, the framework provides the following four types of dispatchers out of the box: 
- Dispatcher 
Default, event-based, dispatcher被所有actors sharing, 如上图, optimized for non-blocking code 
- Pinned dispatcher 
Provides a single, dedicated thread (pinned) for each actor, optimized for blocking operations, 比如I/O 
- Balancing dispatcher 
前提是, 所有actor都是同一种类型, 所以可以共享一个mailbox, 这样dispatcher可以balance忙或闲的actors 
- Calling thread dispatcher 
Primarily used for testing, this dispatcher runs the task execution on the current thread only 

Types of mailbox 

Similarly, there are four default mailbox implementations provided as follows: 
- Unbounded mailbox 
- Bounded mailbox 
- Unbounded priority mailbox 
- Bounded priority mailbox

看看这个mailbox的实现就懂了...

image

 

关键参数

Choice of dispatcher:

blocking versus non-blocking operations, homogeneity versus heterogeneity of actors, to determine the right choice of dispatcher

Choice of executor:

Choosing between thread pool or fork join depends upon the characteristics of your application logic. 
For most cases, fork join is excellent when large numbers of tasks can be forked (started).

Number of threads (min/max) factored to the CPU cores:

Throughput factor:

This determines the number of messages that are processed by one actor as a batch or in one go. 
就是一次处理多少条message, 肯定1是最公平的, 处理一条释放给别的actor, 但这样效率比较低, 所以可以选择处理一个batch, 然后再释放, 但这样就有可能block其他的actor, 需要balance

 

Routers

In Akka, a router is also a type of actor, which routes the incoming messages to the outbound actors. For the router, the outbound actors are also called routees. 
The router employs a different set of algorithms to route the messages to the routee actors:

Router本身也是一种actor, 负责把incoming的message发送到各个actor routees 
只有在actor存在并发的时候, router才有用, 即A1…A5应该是同一种类型的actor

image


By default, the Akka router supports the following router mechanisms: 
• Round robin router (轮询遍历): It routes the incoming messages in a circular order to all its routees 
• Random router (随机): It randomly selects a routee and routes the message to the same 
• Smallest mailbox router (优先发给负担轻的): It identifies the actor with the least number of messages in its mailbox and routes the message to the same   
• Broadcast router (发给所有): It forwards the same message to all the routees 
• Scatter gather first completed router (发给所有, 取最先返回的): It forwards the message to all its routees as a future, then whichever routee actor responds back, it takes the results and sends them back to the caller

使用的例子, 使用5个MyActor的instance, 并且创建RoundRobin类型的myRouterActor, 来分发消息给所有routee actors

val router = system.actorOf(Props[MyActor].withRouter(RoundRobinRouter (nrOfInstances = 5)) , name = "myRouterActor")

使用remote actor的例子,

//创建remote actor的列表
val addresses = Seq( Address("akka", "remotesys", "host1", 1234),Address("akka", "remotesys", "host2", 1234))
val routerRemote = system.actorOf(Props[MyEchoActor].withRouter(RemoteRouterConfig(RoundRobinRouter(5), addresses)))

 

Supervision and Monitoring

Let It Crash

这个策略其实简单, 失败了自己不处理, let it crash, 但是不是说没人处理, 交给上层supervisor取处理 
这样的好处, 底层的worker尽可能的pure和简单, 否则每个worker都要加上一大堆的fault-tolerant的代码 
现在统一由上层supervisor统一处理, 分工明确, 职能专一

这样就会形成下面这样的actor hierarchy,

image

Supervision

这是一种直接的管理方式, supervisor作为父actor创建一系列的subordinate actor, 当子actor发生异常时会报告给supervisor, 由他来决定如果处理

有4种处理方式,

1. Restart the Subordinate actor – means kill the current actor instance and instantiate a new actor instance. 丢失当前状态, 从新开始执行 
2. Resume the Subordinate actor – means the actor keeps its current state and goes back to its current state as though nothing has happened. 保持当前状态继续执行 
3. Terminate the Subordinate actor permanently. 直接关闭 
4. Escalate the failure to its own supervisor. 自己处理不了, 发给上层的supervisor

Akka会默认提供一个parental supervisor – "user" 
Akka by default provides a parental supervisor – "user". This parental supervisor creates the rest of the actors and the actor hierarchy.

Supervision strategies

两种策略, 
One-For-One strategy, default的策略, 可以handle大部分的情况, 即一个actor fail, 就单独处理他, 不会涉及其他的actor 
All-For-One strategy, 用于children have tight interdependencies, 所以为了保证数据的一致性, 必须同时处理所有的actors

image

以One-For-One strategy为例子, 直接看看代码,

//Work Actor的定义,提高对state的更改和查询
case class Result
class WorkerActor extends Actor with ActorLogging {
    var state: Int = 0 //actor需要维护的状态
            
    override def preStart() {
        log.info("Starting WorkerActor instance hashcode # {}", this.hashCode())
    }
    override def postStop() {
        log.info("Stopping WorkerActor instance hashcode # {}", this.hashCode())
    }
    
    //对于正常的msg外,会抛出各种异常 
    def receive: Receive = {
        case value: Int =>
            if (value <= 0) //小于等于0的时候,抛出ArithmeticException
                throw new ArithmeticException("Number equal or less than zero")
            else
                state = value
        case result: Result => //对于结果查询,返回结果
            sender ! state
        case ex: NullPointerException =>
            throw new NullPointerException("Null Value Passed")
        case _ => //其他的输入抛出IllegalArgumentException
            throw new IllegalArgumentException("Wrong Argument")
    }
}

//Supervisor Actor的定义, 负责创建Work Actor和supervisorStrategy
class SupervisorActor extends Actor with ActorLogging {
    val childActor = context.actorOf(Props[WorkerActor],name = "workerActor") //创建子Actor
    
    //创建supervisorStrategy是Supervisor的关键
    //前两个参数, maxNrOfRetries:重试的次数
      //the number of times an actor is allowed to be restarted before it is assumed to be dead. 
      //A negative number implies no limits. 
    //withinTimeRange:重试的间隔
    //关键是第三个参数, 定义了异常处理的策略
      //下面针对worker抛出的不同的异常, 匹配了不同的处理方法
    override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
        case _: ArithmeticException => Resume 
        case _: NullPointerException => Restart
        case _: IllegalArgumentException => Stop
        case _: Exception => Escalate
    }
    
    //可以接收两种msg
    def receive = {
        case result: Result =>
            childActor.tell(result, sender)
        case msg: Object =>
            childActor ! msg
    }
}

下面实际运行看看效果, 
这里发送的是8, 正常的messge, 所以不会产生异常, 并且查询返回的结果确实是8

val system = ActorSystem("faultTolerance")
val log = system.log
val originalValue: Int = 0
val supervisor = system.actorOf(Props[SupervisorActor], name = "supervisor")
log.info("Sending value 8, no exceptions should be thrown! ")
var mesg: Int = 8
supervisor ! mesg
implicit val timeout = Timeout(5 seconds)
var future = (supervisor ? new Result).mapTo[Int]
var result = Await.result(future, timeout.duration)
log.info("Value Received-> {}", result)

下面通过几种不同的异常, 看看几种处理方式的不同

Resume

发送-8的时候, 会导致ArithmeticException, supervisor对这个异常的处理是resume, 即保持状态继续进行 
所以从worker读出的值, 仍然是之前的值, 8

mesg = -8
supervisor ! mesg

image  

Restart

如果发生NullPointerException, Superviosr的处理方式是restart, 会清空状态 
所以和resume不同的是, 从woker读到的值是0, 已经被清零

supervisor ! new NullPointerException

Stop

如果发送任意的message的话, supervisor的处理方式是stop

supervisor ? "Do Something"

 

Lifecycle monitoring

相对于supervision, monitoring是一种间接的方式, 即actor不会或来不及将异常report给我的情况下, 就需要使用monitoring来monitor

下面给出使用monitoring的3个主要场景, 
- The actor monitoring is usually used when the actors in question are not part of your hierarchy. So actors at the horizontal level are primarily the candidates for monitoring. 
  不属于我的hierarchy, 即水平level的actors 
- When the supervisor wants to terminate the child actors instead of just restarting (in order to clear the mailbox attached to the actors), monitoring on the actor's termination is used. 
- When the child actor is terminated because of an external event (such as PoisonPill from another actor or a system.stop() request) 
  子actor被外部event强行terminated的情况

Monitoring只能局限在termination event, 而不象SupervisorActor可以处理各种fail的case. 
Monitoring同过ActorSystem的DeathWatch模块来实现和支持

The listener's events provided are only for actor's termination events, unlike supervisor's where the SupervisorActor reacts to the failures also. 
This service is provided by the DeathWatch component of the ActorSystem.

image

直接看下代码例子,

case class Result
case class DeadWorker
case class RegisterWorker(val worker: ActorRef, val supervisor: ActorRef)

class WorkerActor extends Actor with ActorLogging {
    import org.akka.essentials.supervisor.example1.Result
    var state: Int = 0
    override def preStart() {
        log.info("Starting WorkerActor instance hashcode # {}", this.hashCode())
    }
    override def postStop() {
        log.info("Stopping WorkerActor instance hashcode # {}", this.hashCode());
    }
    def receive: Receive = {
        case value: Int =>
            state = value
        case result: Result =>
            sender ! state
        case _ =>
            context.stop(self) //worker会terminate
    }
}

//定义MonitorActor
class MonitorActor extends Actor with ActorLogging {
    var monitoredActors = new HashMap[ActorRef, ActorRef]
    def receive: Receive = {
        case t: Terminated => //收到actor发来的terminated msg
            if (monitoredActors.contains(t.actor)) {
                log.info("Received Worker Actor Termination Message -> " + t.actor.path)
                log.info("Sending message to Supervisor")
                val value: Option[ActorRef] = monitoredActors.get(t.actor)
                value.get ! new DeadWorker() //通知注册的supervisor
            }
        case msg: RegisterWorker => //注册
            context.watch(msg.worker) //关键一步,调用context.watch来监控这个worker actor, 这样worker会在terminated时, 发送msg给monitor
            monitoredActors += msg.worker -> msg.supervisor //将actor和想监控他的supervisor放入map
    }
}

 

Fault tolerance

现在看如何实现Akka的fault tolerance, 应该比较清楚了, 就是通过supervision和monitoring来实现的

对于分布式应用, 需要考虑的fail的类型 
In a large, distributed application, actors can fail when: 
• There is a logic programming error when the message is received (for example, you received a message with incomplete data) 
• There is a failure of an external resource on which the actor is dependent (for example, database connection, socket connection, file connection, and so on) 
• The actor's internal state is corrupted over a period of time (especially when the cause of the corruption is not known)

看个例子, 右边的图显式如何设计fault tolerance 
Let's take an example of a Master–Slave kind of application where the Master node passes messages to the Slave nodes for processing.

image  image

 

Software Transactional Memory

看到STM第一个想到了clojure, Akka用的是Scala为啥需要这个, scala在并发上使用的是actor这种模型, 和clojure不是一个路数 
答案就是, Scala光靠Actor是不行的, Clojure对并发的处理方式还是很不错的 
Actor的方式问题: 会产生大量的message流量; 并且给不同的actor发送message是完全独立和异步的, 无法保证一致性或原子性; Actor的state是不公开的, 所以读state也需要发送message, 异步操作 
Scala的风格就是, 缺什么就补什么, 所以Scala也实现STM的功能, 而Akka的STM就是基于ScalaSTM实现的

什么是STM?

STM makes use of two concepts – optimism and transactions to manage the shared concurrency control.

ScalaSTMhttp://nbronson.github.io/scala-stm/

We’ve built a lightweight software transactional memory for Scala, inspired by the STMs in Haskell and Clojure while taking advantage of Scala’s power and performance.

直接看个例子, 在两个线程中同时运行swap和transfer

//首先定义成Ref,支持STM的首要条件, STM就是通过Ref的原子切换来实现transaction的
val (x, y) = (Ref(10), Ref(0)) 

def swap = atomic { implicit txn => //简写, 看transfer更清晰
    x = x + y
    y = x - y
    x = x - y
}

def transfer(n: Int) {
    atomic { implicit txn => //加上atomic
        x -= n
        y += n
    }
}

下面的图, 可以看出运行的结果, isolation的执行, 互不干涉, 提交的时候乐观锁, 所以thread1会失败, 可以基于新的值重新提交

image 

Coordinated transactions

The Actor Model is based on the premise of small independent processes working in isolation and where the state can be updated only via message passing.

上面这个情况是比较简单的情况, 一些场景下, 需要保证发给多个actor的msg形成的原子操作, 即你需要协调多个actor上的task形成一个transaction 
比如举个例子, 银行存款, 需要往一个accout actor发送扣款msg, 往另一个accout actor发送存款msg, 这样如果无法保证原子性和transaction, 确实是个问题 

为解决这个问题, 产生CommitBarrier, 基于Java的CountDownLatch实现的, 所以功能一致

To manage multiple transactions running on separate threads as a single atomic block, the concept of CommitBarrier is used.
CommitBarrier is a synchronization aid that is used as a single, common barrier point by all the transactions across multiple threads. Once the barrier is reached, all the transactions commit 
automatically. It is based on the Java's CountDownLatch.

Akka transactor就是基于CommitBarrier实现的, 会等transaction中的每个actor都ready后统一commit, 否则所有actor都会roll back

Akka transactors are based on CommitBarrier, where the atomic blocks of each actor (member) participating are treated as one, big single unit. Each actor will block until everyone participating in the transaction has completed.

其中CommitBarrier, 又是通过coordinated.coordinated来协调各个actor的操作 
看下面这个图, 就比较清楚了 
创建coordinated, 启动transaction 
通过coordinated.coordinate()把msg加到transaction中 
并且在每个actor中的操作通过atomic封装 
最终CommitBarrier就通过使用coordinated来进行barrier

image

现在就上面给出的银行转账的例子, 给出代码

image image

//Account Actor
class AccountActor(accountNumber: String, inBalance: Float) extends Actor {
    val balance = Ref(inBalance) //定义Ref
    def receive = {
        case value: AccountBalance =>
            sender ! new AccountBalance(accountNumber, balance.single.get)
            
        case coordinated @ Coordinated(message: AccountDebit) => //@变量绑定, 从TransferActor得到的coordinated对象
             //coordinated.atomic
           //通过atomic将下面的操作加到transaction
           //从TransferActor得到的coordinated对象, 一个coordinated对象代表一个transaction, 所以必须将所有transaction中的操作加到同一个coordinated对象中
            coordinated atomic { implicit t => 
                //check for funds availability
                if (balance.get(t) > message.amount)
                    balance.transform(_ - message.amount)
                else
                    throw new IllegalStateException("Insufficient Balance")
            }
        case coordinated @ Coordinated(message: AccountCredit) =>
            // coordinated atomic ...
            coordinated atomic { implicit t =>
                balance.transform(_ + message.amount)
            }
    }
}

//TransferActor
class TransferActor extends Actor {
    //创建子account actor
    val fromAccount = "XYZ";
    val toAccount = "ABC";
    val from = context.actorOf(Props(new AccountActor(fromAccount, 5000)), name = fromAccount)
    val to = context.actorOf(Props(new AccountActor(toAccount, 1000)), name = toAccount)
    
    //定义supervisorStrategy进行fault tolerant
    implicit val timeout = Timeout(5 seconds)
    override val supervisorStrategy =
        AllForOneStrategy(maxNrOfRetries = 10,
            withinTimeRange = 10 seconds) {
        case _: CoordinatedTransactionException => Resume
        case _: IllegalStateException => Resume
        case _: IllegalArgumentException => Stop
        case _: Exception => Escalate
    }
    
    def receive: Receive = {
        case message: TransferMsg =>
            val coordinated = Coordinated() //创建Coordinated, 开始一个transaction
            coordinated atomic { implicit t => //使用atomic将下面两个msg加入transaction
                to ! coordinated(new AccountCredit(message.amtToBeTransferred)) //将coordinated对象发送给子actor
                from ! coordinated(new AccountDebit(message.amtToBeTransferred))
            }
        //正常的读取操作, 不需要transaction
        case message: AccountBalance =>
            if (message.accountNumber.equalsIgnoreCase(fromAccount)) {
                from.tell(message, sender)
            } else if (message.accountNumber.equalsIgnoreCase(toAccount)) {
                to.tell(message, sender)
            }
    }
}

//BankActor
class BankActor extends Actor with ActorLogging {
    //创建子transferActor
    val transferActor = context.actorOf(Props[TransferActor], name = "TransferActor")
    
    implicit val timeout = Timeout(5 seconds)
    def receive = {
        case transfer: TransferMsg =>
            transferActor ! transfer
        case balance: AccountBalance =>
            val future = ask(transferActor, balance).mapTo[AccountBalance]
            val account = Await.result(future, timeout.duration)
            log.info("Account #{} , Balance {}", account.accountNumber, account.accountBalance)
    }
    
    override val supervisorStrategy = AllForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
        case _: CoordinatedTransactionException => Resume
        case _: IllegalStateException => Stop
        case _: IllegalArgumentException => Stop
        case _: Exception => Escalate
    }
}

 

Transactor

这个是对coordinated的高层的抽象, 你不需要直接使用, 而且可以更加灵活 
比如上面转账的应用, 其中涉及扣款和存款操作, 在转账的场景下, 这些操作都是一个大的transaction的一部分, 所以每次都会把自己加到transferActor传入的coordinated对象中 
但实际中, 也有单纯的取钱和存钱的操作, 这样扣款和存款操作就成为独立的transaction, 不需要加到其他transaction中去

所以Transactor就提供这样的接口, 可以让操作在不同的条件场景下选择是独立transaction, 还是加入到一个大的transaction中去运行

上面的AccountActor可以改成这个样子,

class AccountActor(accountNumber: String, inBalance: Float) extends Transactor {
    val balance = Ref(inBalance)
    //atomatically, 原子操作, 可以同时接收普通msg和coordinated msg
    //对于coordinated msg对象, 会把自己加入到该transaction中去
    //对于普通msg对象, 直接起单独的transaction运行
    def atomically = implicit txn => {
        case message: AccountDebit =>
            if (balance.single.get < message.amount)
                throw new IllegalStateException("Insufficient Balance")
            else
                balance transform (_ - message.amount)
        case message: AccountCredit =>
            balance transform (_ + message.amount)
    }
    
    //普通操作, 无论收到什么msg, 都完全bypass coordinated transactions
    override def normally: Receive = {
        case value: AccountBalance =>
            sender ! new AccountBalance(accountNumber, balance.single.get)
    }
}

 

Agents

Akka agents are modeled on the Clojure agents. 
这个就完全模仿Clojure做的, 对于Clojure Agent可以提高一种异步并发的方案, 为什么scala已经有actor模型了还需要这个玩意?

Akka's Actor Model is based on the message-passing model made popular by Erlang. In an Actor Model, state is encapsulated within an Actor and can only be mutated via the passing of values. In the Actor Model, even for reading the actor state, you need to pass a message and wait before the Actor responds back. 
Agent is modeled on the premise that reading the state should not be a blocking call.

就是为了优化读状态操作, 不再成为一种blocking call 
这个就不多说了, 总觉得有点怪


本文章摘自博客园,原文发布日期:2013-11-17

目录
相关文章
|
5月前
177 Akka详细学习资料
177 Akka详细学习资料
25 0
|
XML 存储 SQL
Google Architecture Components应用框架初探
Google Architecture Components应用框架初探
257 0
Google Architecture Components应用框架初探
|
JavaScript 网络协议 Dubbo
Facebook分布式框架—Thrift介绍。
Thrift介绍 Thrift是一个分布式RPC框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml这些编程语言间无缝结合的、高效的服务。
345 0
Facebook分布式框架—Thrift介绍。
|
Web App开发 前端开发 关系型数据库
|
监控 数据格式 JSON
|
Java Scala 索引