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上面去执行
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
Similarly, there are four default mailbox implementations provided as follows:
- Unbounded mailbox
- Bounded mailbox
- Unbounded priority mailbox
- Bounded priority mailbox
看看这个mailbox的实现就懂了...
关键参数
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
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,
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
以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
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.
直接看下代码例子,
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.
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.
ScalaSTM, http://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会失败, 可以基于新的值重新提交
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
现在就上面给出的银行转账的例子, 给出代码
//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
这个就不多说了, 总觉得有点怪