Scala和Akka的未来组合

Scala是功能性和面向对象的语言,可在JVM上运行。 对于并发和/或并行编程, Akka框架是一个合适的选择, Akka框架为各种并发任务提供了丰富的工具集。 在这篇文章中,我想展示一个小例子,说明如何使用FuturesActors在多个文件/服务器上安排日志文件搜索作业。

设定

我使用Typesafe Activator Hello-Akka模板创建了我的设置。 这将生成一个具有以下内容的build.sbt文件:

name := """hello-akka"""

version := "1.0"

scalaVersion := "2.10.2"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor" % "2.2.0",
  "com.typesafe.akka" %% "akka-testkit" % "2.2.0",
  "com.google.guava" % "guava" % "14.0.1",
  "org.scalatest" % "scalatest_2.10" % "1.9.1" % "test",
  "junit" % "junit" % "4.11" % "test",
  "com.novocode" % "junit-interface" % "0.7" % "test->default"
)

testOptions += Tests.Argument(TestFrameworks.JUnit, "-v")

Scala内置期货

Scala已经为期货提供了内置支持。 该实现基于java.util.concurrent 。 让我们实现一个运行日志搜索的Future。

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits._

object LogSearch extends App {

println("Starting log search")

val searchFuture = future {
  Thread sleep 1000
  "Found something"
}

println("Blocking for results")
  val result = Await result (searchFuture, 5 seconds)
  println(s"Found $result")
}

这就是我们在另一个线程中运行任务所需要的。 从ExecutionContext隐式导入提供了一个默认的ExecutionContext,用于处理将来运行的线程。 在创建未来之后,我们将等待阻塞结果等待结果。 到目前为止,没有什么太花哨的。

未来组成

有很多示例使用for-yield语法来组合将来的结果。 在我们的案例中,我们有一个动态的期货列表:每个服务器的日志搜索结果。

为了测试将来的功能,我们将从整数列表中创建一个未来列表,这些整数表示任务运行的时间。 类型只是为了澄清。

val tasks = List(3000, 1200, 1800, 600, 250, 1000, 1100, 8000, 550)
val taskFutures: List[Future[String]] = tasks map { ms =>
  future {
    Thread sleep ms
    s"Task with $ms ms"
  }
}

最后,我们想要一个List [String]作为结果。 这是通过“期货”伴随对象完成的。

val searchFuture: Future[List[String]] = Future sequence taskFutures

最后,我们可以等待我们的结果

val result = Await result (searchFuture, 2 seconds)

但是,这将引发TimeoutException ,因为我们的某些任务运行超过2秒。 当然,我们可以增加超时时间,但是当服务器关闭时,总是可能再次发生错误。 另一种方法是处理异常并返回错误。 但是所有其他结果将丢失。

未来–超时后备

没问题,我们生成一个后备,如果操作花费的时间太长,它将返回默认值。 一个非常幼稚的后备实现看起来像这样

def fallback[A](default: A, timeout: Duration): Future[A] = future {
  Thread sleep timeout.toMillis
  default
}

在执行线程进入超时时间后,回退的将来会返回。 现在,调用代码如下所示。

val timeout = 2 seconds
val tasks = List(3000, 1200, 1800, 600, 250, 1000, 1100, 8000, 550)
val taskFutures: List[Future[String]] = tasks map { ms =>
val search = future {
  Thread sleep ms
  s"Task with $ms ms"
}

Future firstCompletedOf Seq(search,
  fallback(s"timeout $ms", timeout))
}

val searchFuture: Future[List[String]] = Future sequence taskFutures

println("Blocking for results")
val result = Await result (searchFuture, timeout * tasks.length)
println(s"Found $result")

这里重要的调用是Future firstCompletedOf Seq(..) ,它产生一个Future,并返回第一个完成的Future的结果。

为讨论这个实现是非常糟糕这里 。 简而言之:我们通过使线程进入睡眠状态来浪费CPU时间。 同样,阻塞呼叫超时或多或少是一种猜测。 使用单线程调度程序实际上可能会花费更多时间。

期货和Akka

现在,让我们执行更高性能,更强大的操作。 我们的主要目标是摆脱糟糕的回退实现,因为它会阻塞一个完整的线程。 现在的想法是在给定的持续时间后安排回退功能。 这样,您所有的线程都可以在实际环境中工作,而回退的未来执行时间几乎为零。 Java本身具有ScheduledExecutorService ,或者您可以使用Netty的另一个实现HashedWheelTimer 。 Akka曾经使用HashWheelTimer,但现在拥有自己的实现

因此,让我们从演员开始。

import akka.actor._
import akka.pattern.{ after, ask, pipe }
import akka.util.Timeout

class LogSearchActor extends Actor {

  def receive = {
    case Search(worktimes, timeout) =>
      // Doing all the work in one actor using futures
      val searchFutures = worktimes map { worktime =>
      val searchFuture = search(worktime)
      val fallback = after(timeout, context.system.scheduler) {
          Future successful s"$worktime ms > $timeout" 
        }
        Future firstCompletedOf Seq(searchFuture, fallback)
      }

      // Pipe future results to sender
      (Future sequence searchFutures) pipeTo sender
    }

  def search(worktime: Int): Future[String] = future {
      Thread sleep worktime
      s"found something in $worktime ms"
  }
}

case class Search(worktime: List[Int], timeout: FiniteDuration)

重要的部分是after方法调用。 您给它一个持续时间,在该持续时间之后应执行future,并且将调度程序作为第二个参数,它是本例中actor系统的默认参数。 第三个参数是应该执行的未来。 我使用Future成功伴侣方法返回单个字符串。

其余代码几乎相同。 PipeTo是一种akka模式,可以将未来的结果返回给发送者。 这里没什么好看的。

现在如何称呼所有这些。 首先代码

object LogSearch extends App {

println("Starting actor system")
val system = ActorSystem("futures")

println("Starting log search")
try {
  // timeout for each search task
  val fallbackTimeout = 2 seconds

  // timeout use with akka.patterns.ask
  implicit val timeout = new Timeout(5 seconds)

  require(fallbackTimeout < timeout.duration) 

  // Create SearchActor 
  val search = system.actorOf(Props[LogSearchActor]) 

  // Test worktimes for search 
  val worktimes = List(1000, 1500, 1200, 800, 2000, 600, 3500, 8000, 250) 

  // Asking for results 
  val futureResults = (search ? Search(worktimes, fallbackTimeout)) 
    // Cast to correct type 
    .mapTo[List[String]] 
    // In case something went wrong 
    .recover { 
       case e: TimeoutException => List("timeout")
       case e: Exception => List(e getMessage)
  }
  // Callback (non-blocking)
  .onComplete {
      case Success(results) =>
         println(":: Results ::")
         results foreach (r => println(s" $r"))
         system shutdown ()
      case Failure(t) =>
         t printStackTrace ()
      system shutdown ()
  }

} catch {
  case t: Throwable =>
  t printStackTrace ()
  system shutdown ()
}

  // Await end of programm
  system awaitTermination (20 seconds)
}

评论应解释大部分内容。 此示例完全异步,并且可与回调一起使用。 当然,您可以像以前一样使用Await结果调用。

链接

参考: mukis.de博客上来自我们的JCG合作伙伴 Nepomuk Seiler的Scala和Akka的《 Future Composition》

翻译自: https://www.javacodegeeks.com/2013/08/future-composition-with-scala-and-akka.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值