akka-stream的数据流可以由一些组件组合而成。这些组件统称数据流图Graph,它描述了数据流向和处理环节。Source,Flow,Sink是最基础的Graph。用基础Graph又可以组合更复杂的复合Graph。如果一个Graph的所有端口(输入、输出)都是连接的话就是一个闭合流图RunnableGraph,否则就属于·开放流图PartialGraph。一个完整的(可运算的)数据流就是一个RunnableGraph。Graph的输出出入端口可以用Shape来描述:

/**
* A Shape describes the inlets and outlets of a [[Graph]]. In keeping with the
* philosophy that a Graph is a freely reusable blueprint, everything that
* matters from the outside are the connections that can be made with it,
* otherwise it is just a black box.
*/
abstract class Shape {
/**
* Scala API: get a list of all input ports
*/
def inlets: immutable.Seq[Inlet[_]] /**
* Scala API: get a list of all output ports
*/
def outlets: immutable.Seq[Outlet[_]] ...

Shape类型的抽象函数inlets,outlets分别代表Graph形状的输入、输出端口。下面列出了aka-stream提供的几个现有形状Shape:

final case class SourceShape[+T](out: Outlet[T @uncheckedVariance]) extends Shape {...}
final case class FlowShape[-I, +O](in: Inlet[I @uncheckedVariance], out: Outlet[O @uncheckedVariance]) extends Shape {...}
final case class SinkShape[-T](in: Inlet[T @uncheckedVariance]) extends Shape {...}
sealed abstract class ClosedShape extends Shape
/**
* A bidirectional flow of elements that consequently has two inputs and two
* outputs, arranged like this:
*
* {{{
* +------+
* In1 ~>| |~> Out1
* | bidi |
* Out2 <~| |<~ In2
* +------+
* }}}
*/
final case class BidiShape[-In1, +Out1, -In2, +Out2](
in1: Inlet[In1 @uncheckedVariance],
out1: Outlet[Out1 @uncheckedVariance],
in2: Inlet[In2 @uncheckedVariance],
out2: Outlet[Out2 @uncheckedVariance]) extends Shape {...}
object UniformFanInShape {
def apply[I, O](outlet: Outlet[O], inlets: Inlet[I]*): UniformFanInShape[I, O] =
new UniformFanInShape(inlets.size, FanInShape.Ports(outlet, inlets.toList))
}
object UniformFanOutShape {
def apply[I, O](inlet: Inlet[I], outlets: Outlet[O]*): UniformFanOutShape[I, O] =
new UniformFanOutShape(outlets.size, FanOutShape.Ports(inlet, outlets.toList))
}

Shape是Graph类型的一个参数:

trait Graph[+S <: Shape, +M] {
/**
* Type-level accessor for the shape parameter of this graph.
*/
type Shape = S @uncheckedVariance
/**
* The shape of a graph is all that is externally visible: its inlets and outlets.
*/
def shape: S
...

RunnableGraph类型的Shape是ClosedShape:

/**
* Flow with attached input and output, can be executed.
*/
final case class RunnableGraph[+Mat](override val traversalBuilder: TraversalBuilder) extends Graph[ClosedShape, Mat] {
override def shape = ClosedShape /**
* Transform only the materialized value of this RunnableGraph, leaving all other properties as they were.
*/
def mapMaterializedValue[Mat2](f: Mat ⇒ Mat2): RunnableGraph[Mat2] =
copy(traversalBuilder.transformMat(f.asInstanceOf[Any ⇒ Any])) /**
* Run this flow and return the materialized instance from the flow.
*/
def run()(implicit materializer: Materializer): Mat = materializer.materialize(this)
...

我们可以用akka-stream提供的GraphDSL来构建Graph。GraphDSL继承了GraphApply的create方法,GraphDSL.create(...)就是构建Graph的方法:

object GraphDSL extends GraphApply {...}
trait GraphApply {
/**
* Creates a new [[Graph]] by passing a [[GraphDSL.Builder]] to the given create function.
*/
def create[S <: Shape]()(buildBlock: GraphDSL.Builder[NotUsed] ⇒ S): Graph[S, NotUsed] = {
val builder = new GraphDSL.Builder
val s = buildBlock(builder) createGraph(s, builder)
}
...
def create[S <: Shape, Mat](g1: Graph[Shape, Mat])(buildBlock: GraphDSL.Builder[Mat] ⇒ (g1.Shape) ⇒ S): Graph[S, Mat] = {...}
def create[S <: Shape, Mat, M1, M2](g1: Graph[Shape, M1], g2: Graph[Shape, M2])(combineMat: (M1, M2) ⇒ Mat)(buildBlock: GraphDSL.Builder[Mat] ⇒ (g1.Shape, g2.Shape) ⇒ S): Graph[S, Mat] = {...}
...
def create[S <: Shape, Mat, M1, M2, M3, M4, M5](g1: Graph[Shape, M1], g2: Graph[Shape, M2], g3: Graph[Shape, M3], g4: Graph[Shape, M4], g5: Graph[Shape, M5])(combineMat: (M1, M2, M3, M4, M5) ⇒ Mat)(buildBlock: GraphDSL.Builder[Mat] ⇒ (g1.Shape, g2.Shape, g3.Shape, g4.Shape, g5.Shape) ⇒ S): Graph[S, Mat] = {
...}

buildBlock函数类型:buildBlock: GraphDSL.Builder[Mat] ⇒ (g1.Shape, g2.Shape,...,g5.Shape) ⇒ S,g?代表合并处理后的开放型流图。下面是几个最基本的Graph构建试例:

import akka.actor._
import akka.stream._
import akka.stream.scaladsl._ object SimpleGraphs extends App{ implicit val sys = ActorSystem("streamSys")
implicit val ec = sys.dispatcher
implicit val mat = ActorMaterializer() val source = Source( to )
val flow = Flow[Int].map(_ * )
val sink = Sink.foreach(println) val sourceGraph = GraphDSL.create(){implicit builder =>
import GraphDSL.Implicits._
val src = source.filter(_ % == )
val pipe = builder.add(Flow[Int])
src ~> pipe.in
SourceShape(pipe.out)
} Source.fromGraph(sourceGraph).runWith(sink).andThen{case _ => } // sys.terminate()} val flowGraph = GraphDSL.create(){implicit builder =>
import GraphDSL.Implicits._ val pipe = builder.add(Flow[Int])
FlowShape(pipe.in,pipe.out)
} val (_,fut) = Flow.fromGraph(flowGraph).runWith(source,sink)
fut.andThen{case _ => } //sys.terminate()} val sinkGraph = GraphDSL.create(){implicit builder =>
import GraphDSL.Implicits._
val pipe = builder.add(Flow[Int])
pipe.out.map(_ * ) ~> Sink.foreach(println)
SinkShape(pipe.in)
} val fut1 = Sink.fromGraph(sinkGraph).runWith(source) Thread.sleep()
sys.terminate()

上面我们示范了Source,Flow,Sink的Graph编写,我们使用了Flow[Int]作为共同基础组件。我们知道:akka-stream的Graph可以用更简单的Partial-Graph来组合,而所有Graph最终都是用基础流图Core-Graph如Source,Flow,Sink组合而成的。上面例子里我们是用builder.add(...)把一个Flow Graph加入到一个空的Graph模版里,builder.add返回Shape pipe用于揭露这个被加入的Graph的输入输出端口。然后我们按目标Graph的功能要求把pipe的端口连接起来就完成了这个数据流图的设计了。测试使用证明这几个Graph的功能符合预想。下面我们还可以试着自定义一种类似的Pipe类型Graph来更细致的了解Graph组合的过程。所有基础组件Core-Graph都必须定义Shape来描述它的输入输出端口,定义GraphStage中的GraphStateLogic来描述对数据流元素具体的读写方式。

import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import scala.collection.immutable case class PipeShape[In,Out](
in: Inlet[In],
out: Outlet[Out]) extends Shape { override def inlets: immutable.Seq[Inlet[_]] = in :: Nil override def outlets: immutable.Seq[Outlet[_]] = out :: Nil override def deepCopy(): Shape =
PipeShape(
in = in.carbonCopy(),
out = out.carbonCopy()
)
}

PipeShape有一个输入端口和一个输出端口。因为继承了Shape类所以必须实现Shape类的抽象函数。假设我们设计一个Graph,能把用户提供的一个函数用来对输入元素进行施用,如:source.via(ApplyPipe(myFunc)).runWith(sink)。当然,我们可以直接使用source.map(r => myFunc).runWith(sink),不过我们需要的是:ApplyPipe里可能涉及到许多预设定的共用功能,然后myFunc是其中的一部分代码。如果用map(...)的话用户就必须提供所有的代码了。ApplyPipe的形状是PipeShape,下面是它的GraphState设计:

  class Pipe[In, Out](f: In => Out) extends GraphStage[PipeShape[In, Out]] {
val in = Inlet[In]("Pipe.in")
val out = Outlet[Out]("Pipe.out") override def shape = PipeShape(in, out) override def initialAttributes: Attributes = Attributes.none override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with InHandler with OutHandler { private def decider =
inheritedAttributes.get[SupervisionStrategy].map(_.decider).getOrElse(Supervision.stoppingDecider) override def onPull(): Unit = pull(in) override def onPush(): Unit = {
try {
push(out, f(grab(in)))
}
catch {
case NonFatal(ex) ⇒ decider(ex) match {
case Supervision.Stop ⇒ failStage(ex)
case _ ⇒ pull(in)
}
}
} setHandlers(in,out, this)
}
}

在这个Pipe GraphStage定义里首先定义了输入输出端口in,out,然后通过createLogic来定义GraphStageLogic,InHandler,outHandler。InHandler和OutHandler分别对应输入输出端口上数据元素的活动处理方式:

/**
* Collection of callbacks for an input port of a [[GraphStage]]
*/
trait InHandler {
/**
* Called when the input port has a new element available. The actual element can be retrieved via the
* [[GraphStageLogic.grab()]] method.
*/
@throws(classOf[Exception])
def onPush(): Unit /**
* Called when the input port is finished. After this callback no other callbacks will be called for this port.
*/
@throws(classOf[Exception])
def onUpstreamFinish(): Unit = GraphInterpreter.currentInterpreter.activeStage.completeStage() /**
* Called when the input port has failed. After this callback no other callbacks will be called for this port.
*/
@throws(classOf[Exception])
def onUpstreamFailure(ex: Throwable): Unit = GraphInterpreter.currentInterpreter.activeStage.failStage(ex)
} /**
* Collection of callbacks for an output port of a [[GraphStage]]
*/
trait OutHandler {
/**
* Called when the output port has received a pull, and therefore ready to emit an element, i.e. [[GraphStageLogic.push()]]
* is now allowed to be called on this port.
*/
@throws(classOf[Exception])
def onPull(): Unit /**
* Called when the output port will no longer accept any new elements. After this callback no other callbacks will
* be called for this port.
*/
@throws(classOf[Exception])
def onDownstreamFinish(): Unit = {
GraphInterpreter
.currentInterpreter
.activeStage
.completeStage()
}
}

akka-stream Graph的输入输出处理实现了Reactive-Stream协议。所以我们最好使用akka-stream提供现成的pull,push来重写抽象函数onPull,onPush。然后用setHandlers来设定这个GraphStage的输入输出及处理函数handler:

  /**
* Assign callbacks for linear stage for both [[Inlet]] and [[Outlet]]
*/
final protected def setHandlers(in: Inlet[_], out: Outlet[_], handler: InHandler with OutHandler): Unit = {
setHandler(in, handler)
setHandler(out, handler)
}
/**
* Assigns callbacks for the events for an [[Inlet]]
*/
final protected def setHandler(in: Inlet[_], handler: InHandler): Unit = {
handlers(in.id) = handler
if (_interpreter != null) _interpreter.setHandler(conn(in), handler)
}
/**
* Assigns callbacks for the events for an [[Outlet]]
*/
final protected def setHandler(out: Outlet[_], handler: OutHandler): Unit = {
handlers(out.id + inCount) = handler
if (_interpreter != null) _interpreter.setHandler(conn(out), handler)
}

有了Shape和GraphStage后我们就可以构建一个Graph:

def applyPipe[In,Out](f: In => Out) = GraphDSL.create() {implicit builder =>
val pipe = builder.add(new Pipe(f))
FlowShape(pipe.in,pipe.out)
}

也可以直接用来组合一个复合Graph:

  RunnableGraph.fromGraph(
GraphDSL.create(){implicit builder =>
import GraphDSL.Implicits._ val source = Source( to )
val sink = Sink.foreach(println)
val f: Int => Int = _ *
val pipeShape = builder.add(new Pipe[Int,Int](f))
source ~> pipeShape.in
pipeShape.out~> sink
ClosedShape }
).run()

整个例子源代码如下:

import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.stream.ActorAttributes._
import akka.stream.stage._ import scala.collection.immutable
import scala.util.control.NonFatal object PipeOps { case class PipeShape[In, Out](
in: Inlet[In],
out: Outlet[Out]) extends Shape { override def inlets: immutable.Seq[Inlet[_]] = in :: Nil override def outlets: immutable.Seq[Outlet[_]] = out :: Nil override def deepCopy(): Shape =
PipeShape(
in = in.carbonCopy(),
out = out.carbonCopy()
)
} class Pipe[In, Out](f: In => Out) extends GraphStage[PipeShape[In, Out]] {
val in = Inlet[In]("Pipe.in")
val out = Outlet[Out]("Pipe.out") override def shape = PipeShape(in, out) override def initialAttributes: Attributes = Attributes.none override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with InHandler with OutHandler { private def decider =
inheritedAttributes.get[SupervisionStrategy].map(_.decider).getOrElse(Supervision.stoppingDecider) override def onPull(): Unit = pull(in) override def onPush(): Unit = {
try {
push(out, f(grab(in)))
}
catch {
case NonFatal(ex) ⇒ decider(ex) match {
case Supervision.Stop ⇒ failStage(ex)
case _ ⇒ pull(in)
}
}
} setHandlers(in,out, this)
}
} def applyPipe[In,Out](f: In => Out) = GraphDSL.create() {implicit builder =>
val pipe = builder.add(new Pipe(f))
FlowShape(pipe.in,pipe.out)
} } object ShapeDemo1 extends App {
import PipeOps._
implicit val sys = ActorSystem("streamSys")
implicit val ec = sys.dispatcher
implicit val mat = ActorMaterializer() RunnableGraph.fromGraph(
GraphDSL.create(){implicit builder =>
import GraphDSL.Implicits._ val source = Source( to )
val sink = Sink.foreach(println)
val f: Int => Int = _ *
val pipeShape = builder.add(new Pipe[Int,Int](f))
source ~> pipeShape.in
pipeShape.out~> sink
ClosedShape }
).run() val fut = Source( to ).via(applyPipe[Int,Int](_ * )).runForeach(println) scala.io.StdIn.readLine() sys.terminate() }

Akka(18): Stream:组合数据流,组件-Graph components的更多相关文章

  1. Akka(19): Stream:组合数据流,组合共用-Graph modular composition

    akka-stream的Graph是一种运算方案,它可能代表某种简单的线性数据流图如:Source/Flow/Sink,也可能是由更基础的流图组合而成相对复杂点的某种复合流图,而这个复合流图本身又可以 ...

  2. Akka(17): Stream:数据流基础组件-Source,Flow,Sink简介

    在大数据程序流行的今天,许多程序都面临着共同的难题:程序输入数据趋于无限大,抵达时间又不确定.一般的解决方法是采用回调函数(callback-function)来实现的,但这样的解决方案很容易造成“回 ...

  3. SSIS自定义数据流组件开发(血路)

    由于特殊的原因(怎么特殊不解释),需要开发自定义数据流组件处理. 查了很多资料,用了不同的版本,发现各种各样的问题没有找到最终的解决方案. 遇到的问题如下: 用VS2015编译出来的插件,在SSDTB ...

  4. Flutter学习笔记(18)--Drawer抽屉组件

    如需转载,请注明出处:Flutter学习笔记(18)--Drawer抽屉组件 Drawer(抽屉组件)可以实现类似抽屉拉出和推入的效果,可以从侧边栏拉出导航面板.通常Drawer是和ListView组 ...

  5. 第18讲- UI常用组件之EditText

    第18讲UI常用组件之EditText 三.文本输入框EditText EditTex类继承自TextView.EditText是接受用户输入信息的最重要控件.在html当中,相当于<input ...

  6. Django学习---组合搜索组件

    组合搜索组件 我们都会写博客,写文章之后我们要给文章设置目录,设置类型.之后我们在浏览文章的时候就能够按类别进行精确定位到文章,那这个组合搜索我们应该怎么做呢? 首先我们先创建3张表,分别存放文章,文 ...

  7. Django之组合搜索组件(二)--另附simple_tag的创建使用方法

    这次的代码为Django之组合搜索组件(一)的改版,实现的结果和(一)相同,不同的是,这次运用simple_tag方式,使.html程序简化 所以现在就开始编程吧! 首先想使用simple_tag方法 ...

  8. Vue 中数据流组件

    好久不见呀,这两年写了很多很多东西,也学到很多很多东西,没有时常分享是因为大多都是我独自思考.明年我想出去与更多的大神交流,再修筑自己构建的内容. 有时候我会想:我们遇到的问题,碰到的界限,是别人给的 ...

  9. spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...

随机推荐

  1. FileOutputStreamTest

    package JBJADV003; import java.io.FileOutputStream;import java.io.OutputStream;import java.io.IOExce ...

  2. usaco training 4.1.1 麦香牛块 题解

    Beef McNuggets题解 Hubert Chen Farmer Brown's cows are up in arms, having heard that McDonalds is cons ...

  3. springmvc(一) springmvc框架原理分析和简单入门程序

    springmvc这个框架真的非常简单,感觉比struts2还更简单,好好沉淀下来学习~ --WH 一.什么是springmvc? 我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻的理解 ...

  4. Python的元类

    1.用元类验证子类 每当我们定义新类的时候,元类就会运行雅正代码,以确保这个新类符合规定的规范. Python系统把子类的class语句处理完毕,就会调用元类的 __new__ 方法.元类可以通过 _ ...

  5. CSS 选择器优先级问题

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  6. C#面试题记录

    最怕的就是面试,每每到找工作的时候感觉压力山大,每次都要提前刷点题目去面对.所以这里自己对以后需要了解的知识做一个记录,方便自己随时的补充和深入了解,也给现在的自己留下一点点的印记,给未来留下一点回忆 ...

  7. java用户界面—创建一个面板

    先从基础学起 创建一个面板 代码如下: package Day08; import java.awt.FlowLayout; import javax.swing.JButton;import jav ...

  8. linux UART

    #include <stdio.h> #include <string.h> #include <sys/types.h> #include <errno.h ...

  9. angularjs-1.3代码学习 模块

    花了点时间,阅读了下angularjs的源码.本次先从模块化开始. angular可以通过module的api来实现前端代码的模块化管理.跟define类似.但不具备异步加载脚本的功能.先从最基本的m ...

  10. JSON风格指南

    中文版:https://github.com/darcyliu/google-styleguide/blob/master/JSONStyleGuide.md 英文版:https://google.g ...