响应式数据库编程_响应式数据库访问–第2部分–参与者

响应式数据库编程

很高兴继续在Manuel BernhardtjOOQ博客上做客串文章。 在本博客系列中,Manuel将解释所谓的React技术背后的动机,并在介绍期货和参与者的概念后使用Actor来与jOOQ一起访问关系数据库。

曼努埃尔·伯恩哈特 Manuel Bernhardt是一位独立软件顾问,热衷于构建基于Web的后端和前端系统。 他是“响应式Web应用程序” (Manning)的作者,在Java花费了很长时间之后,他于2010年开始与Scala,Akka和Play Framework合作。 他住在维也纳,他是当地Scala用户组的联合组织者。 他对基于Scala的技术和充满活力的社区充满热情,并正在寻找在行业中推广其使用的方法。 自6岁起,他就开始潜水,不能完全适应奥地利缺少海的情况。

本系列分为三个部分,我们将在下个月发布:

介绍

在我们的上一篇文章中,我们介绍了React式应用程序的概念 ,解释了异步编程的优点,并介绍了Futures(一种用于表达和操作异步值的工具)。

在本文中,我们将研究基于消息驱动的通信概念构建异步程序的另一个工具:参与者。

基于Actor的并发模型已被Erlang编程语言推广,其在JVM上最流行的实现是Akka并发工具包

在某种程度上,Actor模型是“正确”的面向对象的:actor的状态可以是可变的,但永远不会直接暴露给外界。 相反,参与者在异步消息传递的基础上彼此通信,在异步消息传递中消息本身是不可变的。 演员只能做以下三件事之一:

  • 发送和接收任意数量的消息
  • 更改其行为或状态以响应消息到达
  • 开始新的儿童演员

演员通常要决定准备共享哪种状态以及何时对其进行突变。 因此,此模型使我们的人更容易编写并发程序,而这些程序不会因意外读取或写入过时状态或使用锁作为避免后者的手段而引入的竞争条件或死锁。

接下来,我们将看到Actor的工作方式以及如何将它们与Future相结合。

演员基础

角色是轻量级的对象,它们通过发送和接收消息相互通信。 每个参与者都有一个邮箱 ,传入的消息在得到处理之前会在其中排队。

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f434830362d4163746f72732e706e67

角色具有不同的状态:它们可以启动,恢复,停止和重新启动。 当actor崩溃时,恢复或重启actor很有用,我们将在后面看到。

演员也有演员参考 ,这是一个演员接触另一个演员的一种手段。 就像电话号码一样,actor引用是指向actor的指针,如果在崩溃的情况下重新启动actor并用新的化身替换actor,则与其他尝试向其发送消息的actor一样没有任何区别。他们所知道的关于演员的一件事是它的参考,而不是一个特定化身的身份。

发送和接收消息

让我们从创建一个简单的actor开始:

import akka.actor._

class Luke extends Actor {
  def receive = {
    case _ => // do nothing
  }
}

这实际上就是创建一个演员所需要的全部。 但这不是很有趣。 让我们加一点香料,并定义对给定消息的React:

import akka.actor._

case object RevelationOfFathership

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      System.err.println("Noooooooooo")
  }
}

开始了! RevelationOfFathership是一个案例对象,即不可变的消息。 最后一个细节非常重要:您的消息应该始终是自包含的,并且不要引用任何参与者的内部状态,因为这会将这种状态有效地泄漏到外部,从而破坏了只有参与者可以更改其内部状态的保证。 最后一点对于参与者提供更好,更人性化的并发模型并且不会引起任何意外是至关重要的。

既然卢克(Luke)知道如何恰当地回应黑暗维德(Dark Vader)是他父亲的不便事实,那么我们所需要的就是黑暗主人。

import akka.actor._

class Vader extends Actor {

  override def preStart(): Unit =
    context.actorSelection("akka://application/user/luke") ! RevelationOfFathership

  def receive = {
    case _ => // ...
  }
}

维达(Vader)演员使用preStart生命周期方法,以便在儿子上手时触发将消息发送给儿子。 我们使用演员的上下文来向Luke发送消息。

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f434830362d4163746f72496e74726f2e706e67

运行此示例的整个序列如下所示:

import akka.actor._

val system = ActorSystem("application")
val luke = system.actorOf(Props[Luke], name = "luke")
val vader = system.actorOf(Props[Vader], name = "vader")

Props是描述如何获取演员实例的一种手段。 由于它们是不可变的,因此可以自由共享它们,例如跨不同机器上运行的不同JVM(例如,在操作Akka群集时,这很有用)。

演员监督

演员不仅在野外存在,而且是演员层次结构的一部分,每个演员都有一个父级。 我们创建的Actor由应用程序ActorSystem的User Guardian进行监督,ActorSystem是Akka提供的特殊actor,负责监督用户空间中的所有actor。 监督演员的角色是决定如何处理儿童演员的失败并采取相应的行动。

用户守护者本身由Root Guardian监督(该守护者还监督Akka内部的另一个特殊角色),并由特殊角色引用对其进行监督。 传奇人物说,这个参考文献是在所有其他演员参考文献出现之前就存在的,被称为“走时空泡沫的那个人”(如果您不相信我,请查看Akka官方文档 )。

在层次结构中组织参与者提供了将错误处理编码到层次结构中的优势。 每个父母都应对自己子女的行为负责。 万一出问题了并且孩子崩溃了,父母将有机会重新启动它。

例如,维达(Vader)有一些突击部队:

import akka.actor._
import akka.routing._

class Vader extends Actor {

  val troopers: ActorRef = context.actorOf(
    RoundRobinPool(8).props(Props[StromTrooper])
  )
}

RoundRobinPool是一种表达以下事实的方式:发送到troopers消息将一个接一个地发送给每个部队的孩子。 路由器对将消息一次发送给多个参与者的策略进行编码,Akka提供了许多预定义的路由器。

687474703a2f2f6d616e75656c2e6265726e68617264742e696f2f77702d636f6e74656e742f4b696c6c656453746f726d74726f6f706572732e6a7067

最终,演员可能会崩溃,然后由主管决定要做什么。 决策机制以所谓的监督策略为代表。 例如,维达(Vader)可以决定在放弃并停止风暴突击队3次之前,重新尝试重新启动它:

import akka.actor._

class Vader extends Actor {

  val troopers: ActorRef = context.actorOf(
    RoundRobinPool(8).props(Props[StromTrooper])
  )

  override def supervisorStrategy =
    OneForOneStrategy(maxNrOfRetries = 3) {
      case t: Throwable =>
        log.error("StormTrooper down!", t)
        SupervisorStrategy.Restart
    }
}

这种监视策略相当粗糙,因为它以相同的方式处理所有类型的Throwable 。 在下一篇文章中,我们将看到监督策略是一种以不同方式应对不同类型故障的有效手段。

结合期货和参与者

与参与者合作有一个黄金法则:您不应执行任何阻止操作,例如阻止网络呼叫。 原因很简单:如果actor阻塞,它将无法处理可能导致邮箱已满的传入消息(或者,由于actor使用的默认邮箱不受限制,因此会OutOfMemoryException

这就是为什么在参与者中使用Futures可能有用的原因。 管道模式旨在实现这一目的:将Future的结果发送给actor:

import akka.actor._
import akka.pattern.pipe

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      sendTweet("Nooooooo") pipeTo self
    case tsr: TweetSendingResult =>
      // ...
  }

  def sendTweet(msg: String): Future[TweetSendingResult] = ...
}

在此示例中,我们在接收到RevelationOfFathership时调用sendTweet Future,并使用pipeTo方法表示我们希望将Future的结果发送给自己。

上面的代码只有一个问题:如果Future失败了,我们将以相当不方便的格式接收失败的throwable,将其包装在akka.actor.Status.Failure类型的消息中,而没有任何有用的上下文。 这就是为什么在通过管道传递结果之前恢复故障可能更合适的原因:

import akka.actor._
import akka.pattern.pipe
import scala.control.NonFatal

class Luke extends Actor {
  def receive = {
    case RevelationOfFathership =>
      val message = "Nooooooo"
      sendTweet(message) recover {
        case NonFatal(t) => TweetSendFailure(message, t)
      } pipeTo self
    case tsr: TweetSendingResult =>
      // ...
    case tsf: TweetSendingFailure =>
      // ...
  }

  def sendTweet(msg: String): Future[TweetSendingResult] = ...
}

通过此故障处理,我们现在知道哪些消息未能在Twitter上发送,并且可以采取适当的措施(例如,重试发送)。

简短介绍Actors就是这样。 在本系列的下一篇和最后一篇文章中,我们将看到如何结合使用Futures和Actors进行React式数据库访问。

继续阅读

请继续关注,我们将很快发布第3部分,作为本系列的一部分:

翻译自: https://www.javacodegeeks.com/2015/12/reactive-database-access-part-2-actors.html

响应式数据库编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值