scala akka
Scala是功能性和面向对象的语言,可在JVM上运行。 对于并发和/或并行编程, Akka框架是一个合适的选择, Akka框架为各种并发任务提供了丰富的工具集。 在这篇文章中,我想展示一个小例子,说明如何使用Futures和Actors在多个文件/服务器上安排日志文件搜索作业。
建立
我使用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
}
在执行线程Hibernate超时持续时间后,将返回备用将来。 现在,调用代码如下所示。
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结果调用。
链接
- https://gist.github.com/muuki88/6099946
- http://doc.akka.io/docs/akka/2.1.0/scala/futures.html
- http://stackoverflow.com/questions/17672786/scala-future-sequence-and-timeout-handling
- http://stackoverflow.com/questions/16304471/scala-futures-built-in-timeout
翻译自: https://www.javacodegeeks.com/2013/08/future-composition-with-scala-and-akka.html
scala akka