Scala 如何开始使用Akka Streams?
Akka Streams图书馆已经提供了相当多的功能。然而,对我来说,主要的问题是它提供了太多的材料——我觉得我必须学习的概念太多了。这里显示的很多示例都很重,不能很容易地转化为现实世界的用例,因此非常深奥。我认为它给出了太多的细节,没有解释如何将所有构建块构建在一起,以及它如何帮助解决具体问题 有源、汇、流、图阶段、部分图、物化、图DSL等等,我不知道从哪里开始。这本应该是一个起点,但我不明白。它只是把上面提到的概念放进去,没有解释它们。此外,代码示例无法执行-缺少的部分使我或多或少无法理解文本Scala 如何开始使用Akka Streams?,scala,akka-stream,Scala,Akka Stream,Akka Streams图书馆已经提供了相当多的功能。然而,对我来说,主要的问题是它提供了太多的材料——我觉得我必须学习的概念太多了。这里显示的很多示例都很重,不能很容易地转化为现实世界的用例,因此非常深奥。我认为它给出了太多的细节,没有解释如何将所有构建块构建在一起,以及它如何帮助解决具体问题 有源、汇、流、图阶段、部分图、物化、图DSL等等,我不知道从哪里开始。这本应该是一个起点,但我不明白。它只是把上面提到的概念放进去,没有解释它们。此外,代码示例无法执行-缺少的部分使我或多或少无法理解文
有人能解释一下源、汇、流、图阶段、局部图、物化以及其他一些我用简单的语言和简单的例子遗漏的概念吗?这些例子并不能解释每一个细节(在开始时可能并不需要)?此答案基于
akka stream
版本2.4.2
。其他版本中的API可能略有不同。依赖项可由以下人员使用:
好的,让我们开始吧。Akka Streams的API包括三种主要类型。与之相反,这些类型功能更强大,因此更复杂。假设对于所有代码示例,已经存在以下定义:
import scala.concurrent._
import akka._
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.util._
implicit val system = ActorSystem("TestSystem")
implicit val materializer = ActorMaterializer()
import system.dispatcher
类型声明需要import
语句system
表示Akka的参与者系统,materializer
表示流的评估上下文。在我们的例子中,我们使用了Actormatarializer
,这意味着流是在actor之上进行评估的。这两个值都标记为implicit
,这使Scala编译器能够在需要时自动注入这两个依赖项。我们还导入了system.dispatcher
,这是的执行上下文
一种新的API
Akka流具有以下关键特性:
- 他们实现了,其三个主要目标:背压、异步和非阻塞边界以及不同实现之间的互操作性也完全适用于Akka流
- 它们为流的评估引擎提供了一个抽象,称为
Materializer
- 程序被描述为可重用的构建块,表示为三种主要类型
源
、
接收器
和
流
。构建块形成一个图形,其计算基于
,需要显式触发物化器
Source
是数据创建者,它充当流的输入源。每个源
都有一个输出通道,没有输入通道。所有数据通过输出通道流向连接到源的任何对象
这张照片是从中国拍摄的
源代码可以通过多种方式创建:
scala> val s = Source.empty
s: akka.stream.scaladsl.Source[Nothing,akka.NotUsed] = ...
scala> val s = Source.single("single element")
s: akka.stream.scaladsl.Source[String,akka.NotUsed] = ...
scala> val s = Source(1 to 3)
s: akka.stream.scaladsl.Source[Int,akka.NotUsed] = ...
scala> val s = Source(Future("single value from a Future"))
s: akka.stream.scaladsl.Source[String,akka.NotUsed] = ...
scala> s runForeach println
res0: scala.concurrent.Future[akka.Done] = ...
single value from a Future
在上述情况下,我们向源提供了有限的数据,这意味着它们最终将终止。人们不应该忘记,在默认情况下,反应流是惰性和异步的。这意味着必须显式地请求对流进行求值。在Akka流中,这可以通过run*
方法完成。runForeach
与众所周知的foreach
函数没有什么不同-通过run
添加,我们明确要求对流进行评估。由于有限数据很无聊,我们继续使用无限数据:
scala> val s = Source.repeat(5)
s: akka.stream.scaladsl.Source[Int,akka.NotUsed] = ...
scala> s take 3 runForeach println
res1: scala.concurrent.Future[akka.Done] = ...
5
5
5
使用take
方法,我们可以创建一个人工停止点,防止我们无限期地进行评估。由于actor支持是内置的,我们还可以轻松地向流提供发送给actor的消息:
def run(actor: ActorRef) = {
Future { Thread.sleep(300); actor ! 1 }
Future { Thread.sleep(200); actor ! 2 }
Future { Thread.sleep(100); actor ! 3 }
}
val s = Source
.actorRef[Int](bufferSize = 0, OverflowStrategy.fail)
.mapMaterializedValue(run)
scala> s runForeach println
res1: scala.concurrent.Future[akka.Done] = ...
3
2
1
val actor = system.actorOf(Props(new Actor {
override def receive = {
case msg => println(s"actor received: $msg")
}
}))
scala> val sink = Sink.actorRef[Int](actor, onCompleteMessage = "stream completed")
sink: akka.stream.scaladsl.Sink[Int,akka.NotUsed] = ...
scala> val runnable = Source(1 to 3) to sink
runnable: akka.stream.scaladsl.RunnableGraph[akka.NotUsed] = ...
scala> runnable.run()
res3: akka.NotUsed = NotUsed
actor received: 1
actor received: 2
actor received: 3
actor received: stream completed
我们可以看到,期货
在不同的线程上异步执行,这解释了结果。在上面的示例中,传入元素不需要缓冲区,因此使用OverflowStrategy.fail
我们可以配置流在缓冲区溢出时失败。特别是通过这个actor接口,我们可以通过任何数据源为流提供信息。数据是由同一线程创建的、由不同线程创建的、由另一个进程创建的,还是来自Internet上的远程系统,这些都无关紧要
下沉
接收器
基本上与源
相反。它是流的端点,因此使用数据。接收器
只有一个输入通道,没有输出通道<当我们希望以可重用的方式指定数据采集器的行为而不评估流时,特别需要代码>接收器。已知的run*
方法不允许我们使用这些属性,因此最好使用Sink
这张照片是从中国拍摄的
接收器
运行的一个简短示例:
scala> val source = Source(1 to 3)
source: akka.stream.scaladsl.Source[Int,akka.NotUsed] = ...
scala> val sink = Sink.foreach[Int](elem => println(s"sink received: $elem"))
sink: akka.stream.scaladsl.Sink[Int,scala.concurrent.Future[akka.Done]] = ...
scala> val flow = source to sink
flow: akka.stream.scaladsl.RunnableGraph[akka.NotUsed] = ...
scala> flow.run()
res3: akka.NotUsed = NotUsed
sink received: 1
sink received: 2
sink received: 3
可以使用to
方法将源
连接到接收器
。它返回一个所谓的RunnableFlow
,我们稍后将看到一种特殊形式的流
——一种只需调用其run()
方法即可执行的流
这张照片是从中国拍摄的
当然,可以将到达接收器的所有值转发给参与者:
def run(actor: ActorRef) = {
Future { Thread.sleep(300); actor ! 1 }
Future { Thread.sleep(200); actor ! 2 }
Future { Thread.sleep(100); actor ! 3 }
}
val s = Source
.actorRef[Int](bufferSize = 0, OverflowStrategy.fail)
.mapMaterializedValue(run)
scala> s runForeach println
res1: scala.concurrent.Future[akka.Done] = ...
3
2
1
val actor = system.actorOf(Props(new Actor {
override def receive = {
case msg => println(s"actor received: $msg")
}
}))
scala> val sink = Sink.actorRef[Int](actor, onCompleteMessage = "stream completed")
sink: akka.stream.scaladsl.Sink[Int,akka.NotUsed] = ...
scala> val runnable = Source(1 to 3) to sink
runnable: akka.stream.scaladsl.RunnableGraph[akka.NotUsed] = ...
scala> runnable.run()
res3: akka.NotUsed = NotUsed
actor received: 1
actor received: 2
actor received: 3
actor received: stream completed
流动
如果您需要Akka streams和现有系统之间的连接,那么数据源和接收器是非常好的,但是使用t
scala> val s1 = Source(1 to 3) via invert to sink
s1: akka.stream.scaladsl.RunnableGraph[akka.NotUsed] = ...
scala> val s2 = Source(-3 to -1) via invert to sink
s2: akka.stream.scaladsl.RunnableGraph[akka.NotUsed] = ...
scala> s1.run()
res10: akka.NotUsed = NotUsed
-1
-2
-3
scala> s2.run()
res11: akka.NotUsed = NotUsed
3
2
1
val source: Source[Int, NotUsed] = Source(1 to 3)
val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)
val flow: Flow[Int, Int, NotUsed] = Flow[Int].map(x => x)
val multiClickStream = clickStream
.throttle(250.millis)
.map(clickEvents => clickEvents.length)
.filter(numberOfClicks => numberOfClicks >= 2)
val multiClickStream = clickStream.throttle(250.millis).map(_.length).filter(_ >= 2)
def mkServer(address: String, port: Int)(implicit system: ActorSystem, materializer: Materializer): Unit = {
import system.dispatcher
val connectionHandler: Sink[Tcp.IncomingConnection, Future[Unit]] =
Sink.foreach[Tcp.IncomingConnection] { conn =>
println(s"Incoming connection from: ${conn.remoteAddress}")
conn.handleWith(serverLogic)
}
val incomingCnnections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] =
Tcp().bind(address, port)
val binding: Future[Tcp.ServerBinding] =
incomingCnnections.to(connectionHandler).run()
binding onComplete {
case Success(b) =>
println(s"Server started, listening on: ${b.localAddress}")
case Failure(e) =>
println(s"Server could not be bound to $address:$port: ${e.getMessage}")
}
}
val serverLogic: Flow[ByteString, ByteString, Unit] = {
val delimiter = Framing.delimiter(
ByteString("\n"),
maximumFrameLength = 256,
allowTruncation = true)
val receiver = Flow[ByteString].map { bytes =>
val message = bytes.utf8String
println(s"Server received: $message")
message
}
val responder = Flow[String].map { message =>
val answer = s"Server hereby responds to message: $message\n"
ByteString(answer)
}
Flow[ByteString]
.via(delimiter)
.via(receiver)
.via(responder)
}
val serverLogic = Flow[ByteString]
.via(Framing.delimiter(
ByteString("\n"),
maximumFrameLength = 256,
allowTruncation = true))
.map(_.utf8String)
.map(msg => s"Server hereby responds to message: $msg\n")
.map(ByteString(_))
$ # Client
$ echo "Hello World\nHow are you?" | netcat 127.0.0.1 6666
Server hereby responds to message: Hello World
Server hereby responds to message: How are you?
$ # Server
$ ./startServer 127.0.0.1 6666
[DEBUG] Server started, listening on: /127.0.0.1:6666
[DEBUG] Incoming connection from: /127.0.0.1:37972
[DEBUG] Server received: Hello World
[DEBUG] Server received: How are you?
val connection = Tcp().outgoingConnection(address, port)
val flow = Flow[ByteString]
.via(Framing.delimiter(
ByteString("\n"),
maximumFrameLength = 256,
allowTruncation = true))
.map(_.utf8String)
.map(println)
.map(_ ⇒ StdIn.readLine("> "))
.map(_+"\n")
.map(ByteString(_))
connection.join(flow).run()
val closeConnection = new GraphStage[FlowShape[String, String]] {
val in = Inlet[String]("closeConnection.in")
val out = Outlet[String]("closeConnection.out")
override val shape = FlowShape(in, out)
override def createLogic(inheritedAttributes: Attributes) = new GraphStageLogic(shape) {
setHandler(in, new InHandler {
override def onPush() = grab(in) match {
case "q" ⇒
push(out, "BYE")
completeStage()
case msg ⇒
push(out, s"Server hereby responds to message: $msg\n")
}
})
setHandler(out, new OutHandler {
override def onPull() = pull(in)
})
}
}
def serverLogic
(conn: Tcp.IncomingConnection)
(implicit system: ActorSystem)
: Flow[ByteString, ByteString, NotUsed]
= Flow.fromGraph(GraphDSL.create() { implicit b ⇒
import GraphDSL.Implicits._
val welcome = Source.single(ByteString(s"Welcome port ${conn.remoteAddress}!\n"))
val logic = b.add(internalLogic)
val concat = b.add(Concat[ByteString]())
welcome ~> concat.in(0)
logic.outlet ~> concat.in(1)
FlowShape(logic.in, concat.out)
})