scala中:::的用法_Scala中的容错原语:链接和陷阱

Scala容错机制解析
本文深入探讨了Scala中的容错机制,包括Actor模型的使用、链接和陷阱的概念,以及如何通过主管模式实现自动重启和故障隔离,确保系统的一致性和稳定性。

scala中:::的用法

在过去的十年中, Actor模型作为一种简单,有效且省钱的并发构建概念而积极推广。 Actors在90年代初被Erlang普及,并在Scala中用作主要的并发构造,Actor提供了无共享事件驱动模型,在该模型中创建和维护单个actor的成本很低(因此您可以继续运行数百万个actor),并且在分布式节点上工作的远程角色看起来和感觉都像本地角色。

但是,与基于线程的并发或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中:::的用法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值