scala中:::的用法
但是,与基于线程的并发或STM相比,不明智地使用actor可能带来更多的痛苦和样板。 缺乏基于角色的建模的深入文档会导致误解和误解,这些误解和误解可通过在Erlang中构建容错系统的最佳实践和设计指南来解决。
防御性编程迫使您付出巨大的努力来保护系统免受您可能想到的各种意外行为的影响,并尽可能检查输入和当前状态,并定义在更高级别上升级问题的方法。 作为一名防御性程序员,您应该始终保持积极主动,并寻找新的潜在问题来处理,因为即使是最小的问题也可以导致无关紧要的组件发生故障,也可能导致巨大的系统崩溃。
适当的容错系统不会倾向于预测所有可能的故障原因(以防止单个组件崩溃),而是将组件与系统的其余部分隔离开并重新启动它,以保持系统一致地工作。 如果重新启动组件没有帮助,则该问题可能会传播到更高级别,以便[可能]重新启动与该组件交换消息的各方。
就像乔·阿姆斯特朗 ( Joe Armstrong )在他的《 编程Erlang 》一书中所说的那样,当一个演员在充满演员的房间里死亡时,其他人可能应该注意到这一点并开始解决问题(清理尸体)。
在Erlang和Scala中,此行为是通过链接角色来实现的。 最基本的形式是,当两个角色链接在一起,而其中一个死亡时,它也会向另一个角色发送退出信号以终止它。
Actor链接是双向操作,因此,当将actor A链接到B时,B在链接到A的背景上,并且它们中的任何一个死亡都会导致向另一个发送退出信号。 在Erlang中,可以与监视器创建单向链接(当监视的进程死亡时,“ DOWN”消息发送到处理程序)。 Scala标准库中没有类似于监视器的监视器,但是按需实现它很容易。
链接的actor可以创建退出陷阱,以便将退出信号作为正常消息处理,而不导致actor终止。 退出信号通常包含对失败的actor的引用(可用于重新启动它)和失败原因。
在“对Erlang编程”中,用一个简单的示例表示了几种链接/陷阱退出方案:
-module(edemo1).
-export([start2]).
start(Bool, M) ->
A = spawn(fun() -> a() end),
B = spawn(fun() -> b(A, Bool) end),
C = spawn(fun() -> c(B, M) end),
sleep(1000),
status(b, B),
status(c, C).
在上面的代码中,创建了三个与睡眠同步的进程,以使它们有时间处理传递的消息(通常很丑,但仅用于简单的示例),然后检查其状态。 参与者定义如下:
a() ->
process_flag(trap_exit, true),
wait(a).
b(A, Bool) ->
process_flag(trap_exit, Bool),
link(A),
wait(b).
c(B, M) ->
link(B),
case M of
{die, Reason} ->
exit(Reason);
{divide, N} ->
1N,
wait(c);
normal ->
true
end.
进程A总是陷阱退出,进程B链接到A并根据函数输入陷阱退出,链接到B的进程C接收消息并进行计算或失败。
wait(Prog) ->
receive
Any ->
io:format('Process ~p received ~p~n' ,[Prog, Any]),
wait(Prog)
end.
方法等待以递归方式接收打印出消息内容的消息。
使用标准Actors库将其转换为Scala后,该示例如下所示:
object LinkedActors {
case class Die(reason:AnyRef)
case class Divide(number: Int)
def status(name: String, actor: Actor) = println('Actor %s is %s'
format(name, actor.getState))
def printMessage(actorName: String):PartialFunction[Any, Unit] =
{case msg => println('Actor %s received %s' format(actorName, msg))}
def start(isTrapExit: Boolean, message: Any) = {
val A = actor{
self.trapExit = true
loop(react(printMessage('a')))
}
val B = actor{
self.trapExit = isTrapExit
self link A
loop(react(printMessage('b')))
}
val C = actor{
self link B
loop{
react{
case Die(reason) => exit(reason)
case Divide(number) => 1number
}
}
}
C! message
Thread.sleep(1000)
status('b', B)
status('c', C)
}
}
本质上,代码是相同的,不同之处在于,参与者C接受的消息被分类为案例类,参与者B和参与者C的接收行为由部分函数表示。
让我们将一些输入传递给start方法,以了解链中的actor在其中一些死亡时的行为:
scala> start(false, Die('abc'))
Actor a received Exit(scala.actors.Actor$$anon$1@dc8f6d,abc)
Actor b is Terminated
Actor c is Terminated
演员C收到消息Die并以原因“ abc”存在。 链接到C的Actor B不会捕获出口,因此也终止了。 只要连接到B的陷阱的A退出,角色B终止时,它就会向A发送一条消息,说明其失败的原因(具有以下签名的案例类):
** An `Exit` message (an instance of this class) is sent to an actor
* with `trapExit` set to `true` whenever one of its linked actors
* terminates.
*
* @param from the actor that terminated
* @param reason the reason that caused the actor to terminate
*
case class Exit(from: AbstractActor, reason: AnyRef)
同时,当预计退出时(不是由于计算异常引起的),链接的actor不会受到影响:
scala> start(false, Die('normal))
Actor b is Suspended
Actor c is Terminated
在下面的代码段中,未处理的零除异常会导致C和B死亡:
scala> start(false, Divide(0))
Actor a received Exit(scala.actors.Actor$$anon$1@113eb9c,UncaughtException
(scala.actors.Actor$$anon$1@1a1446d,Some(Divide(0)),Some(scala.act
ors.ActorProxy@14f83d1),java.lang.ArithmeticException: by zero))
Actor b is Terminated
Actor c is Terminated
如果我们强制B阻止出口退出,则参与者在上述所有情况下都可以存活:
scala> start(true, Die('abc'))
Actor b received Exit(scala.actors.Actor$$anon$1@13e49a8,abc)
Actor b is Suspended
Actor c is Terminated
与第一个代码段相比,现在B接收到来自C的退出消息。
未处理的错误也不会传播到A:
scala> start(true, Divide(0))
Actor b received Exit(scala.actors.Actor$$anon$1@119f779,UncaughtException
(scala.actors.Actor$$anon$1@119f779,Some(Divide(0)),Some(scala.act
ors.ActorProxy@14f83d1),java.lang.ArithmeticException: by zero))
Actor b is Suspended
Actor c is Terminated
退出消息包含对失败的actor的引用,该引用可用于重新启动它。 通过类似于Erlang中的主管行为,可以实现一个非常简单的演员主管。
case class ChildSpecification(worker: Worker, restartBehavior: Child
RestartBehavior.Value = permanent)
case class OneForOne(maxRestarts: Long = 3, maxTime: Long = 3000)
extends RestartStrategy
case class OneForAll(maxRestarts: Long = 3, maxTime: Long = 3000)
extends RestartStrategy
class Supervisor(supervisorId: String, strategy: RestartStrategy,
childSpecs: List[ChildSpecification]) extends Worker {
...
override def act = {
self.trapExit = true
linkWorkers
loop {
react{
case Exit(worker: Worker, reason) =>
println('Worker [%s] has failed due to [%s]' format(worker.id,
reason))
if(worker.restartsInPeriod(strategy.maxTime) >= strategy
.maxRestarts) exit('Maximum restart intensity for %s is reached!' format(worker.id))
strategy match {
case str:OneForOne => restartWorker(worker)
case str:OneForAll => childSpecs.foreach{spec =>
restartWorker(spec.worker)}
}
case Terminate(reason) => println('Supervisor terminated with
reason [%s]' format(reason))
exit(reason)
}
}
}
...
}
监督者本身是一个普通的Scala参与者,它会捕获来自与其链接的其他参与者(工作人员,根据监督术语)的消息,并仅重新启动一个失败的参与者或所有受监督的参与者。 当重新启动频率达到重新启动策略指定的限制时,主管将终止,以便位于较高层次结构位置的主管可以尝试解决该问题。
在最简单的情况下,主管重新启动由于未捕获的异常而终止的actor:
'Actor terminated due to uncaught exception is restarted by the supervisor' in {
val worker = new SimpleWorker('simple_worker')
Supervisor('basic_supervisor', OneForOne(),
List(ChildSpecification(worker))).start
worker !? (1000, Divide(0))
(worker !? (1000, Divide(1))).asInstanceOf[Option[Int]] must be equalTo Some(1)
}
规范的输出为:
Starting worker simple_worker
Worker [simple_worker] has failed due to [UncaughtException(com.vasilrem.linked.
SupervisorSpec$SimpleWorker@fd54ec,Some(Divide(0)),Some(scal
a.actors.Channel@16167ab),java.lang.ArithmeticException: by zero)]
Restarting worker [simple_worker]...
[info] + Actor terminated due to uncaught exception is restarted by the supervisor
在更复杂的场景中,当超级用户链接到树中时,高级超级用户会在死时重启低级别超级用户,这会导致与其链接的工作程序重新启动:
'High-level supervisor restarts low-level supervisor and the wrokers linked to it' in{
val worker = new SimpleWorker('simple_worker')
val lowLevel = Supervisor('lowlevel_supervisor', OneForOne(),
List(ChildSpecification(worker)))
val highLevel = Supervisor('lowlevel_supervisor', OneForOne(),
List(ChildSpecification(lowLevel))).start
worker.getState must not be equalTo(State.Terminated)
lowLevel ! Terminate('Kill lowlevel')
Thread.sleep(1000)
worker.getState must not be equalTo(State.Terminated)
}
测试输出如下:
Starting worker lowlevel_supervisor
Starting worker simple_worker
Supervisor terminated with reason [Kill lowlevel]
Worker [lowlevel_supervisor] has failed due to [Kill lowlevel]
Restarting worker [lowlevel_supervisor]...
Starting worker simple_worker
[info] + High-level supervisor restart low-level supervisor
祝您编程愉快,别忘了分享!
参考: Scala中的容错原语:来自我们的JCG合作伙伴 Vasil Remeniuk的链接和陷阱 ,位于Vasil Remeniuk博客博客上。
翻译自: https://www.javacodegeeks.com/2012/09/fault-tolerance-primitives-in-scala.html
scala中:::的用法