akka-stream的Graph是一种运算方案,它可能代表某种简单的线性数据流图如:Source/Flow/Sink,也可能是由更基础的流图组合而成相对复杂点的某种复合流图,而这个复合流图本身又可以被当作组件来组合更大的Graph。因为Graph只是对数据流运算的描述,所以它是可以被重复利用的。所以我们应该尽量地按照业务流程需要来设计构建Graph。在更高的功能层面上实现Graph的模块化(modular)。按上回讨论,Graph又可以被描述成一种黑盒子,它的入口和出口就是Shape,而内部的作用即处理步骤Stage则是用GraphStage来形容的。下面是akka-stream预设的一些基础数据流图:

上面Source,Sink,Flow代表具备线性步骤linear-stage的流图,属于最基础的组件,可以用来构建数据处理链条。而Fan-In合并型,Fan-Out扩散型则具备多个输入或输出端口,可以用来构建更复杂的数据流图。我们可以用以上这些基础Graph来构建更复杂的复合流图,而这些复合流图又可以被重复利用去构建更复杂的复合流图。下面就是一些常见的复合流图:

注意上面的Composite Flow(from Sink and Source)可以用Flow.fromSinkAndSource函数构建:

def fromSinkAndSource[I, O](sink: Graph[SinkShape[I], _], source: Graph[SourceShape[O], _]): Flow[I, O, NotUsed] =
fromSinkAndSourceMat(sink, source)(Keep.none)

这个Flow从流向来说先Sink再Source是反的,形成的Flow上下游间无法协调,即Source端终结信号无法到达Sink端,因为这两端是相互独立的。我们必须用CoupledTermination对象中的fromSinkAndSource函数构建的Flow来解决这个问题:

/**
* Allows coupling termination (cancellation, completion, erroring) of Sinks and Sources while creating a Flow them them.
* Similar to `Flow.fromSinkAndSource` however that API does not connect the completion signals of the wrapped stages.
*/
object CoupledTerminationFlow {
@deprecated("Use `Flow.fromSinkAndSourceCoupledMat(..., ...)(Keep.both)` instead", "2.5.2")
def fromSinkAndSource[I, O, M1, M2](in: Sink[I, M1], out: Source[O, M2]): Flow[I, O, (M1, M2)] =
Flow.fromSinkAndSourceCoupledMat(in, out)(Keep.both)
 

从上面图列里的Composite BidiFlow可以看出:一个复合Graph的内部可以是很复杂的,但从外面看到的只是简单的几个输入输出端口。不过Graph内部构件之间的端口必须按照功能逻辑进行正确的连接,剩下的就变成直接向外公开的界面端口了。这种机制支持了层级式的模块化组合方式,如下面的图示:

最后变成:

在DSL里我们可以用name("???")来分割模块:

val nestedFlow =
Flow[Int].filter(_ != ) // an atomic processing stage
.map(_ - ) // another atomic processing stage
.named("nestedFlow") // wraps up the Flow, and gives it a name val nestedSink =
nestedFlow.to(Sink.fold()(_ + _)) // wire an atomic sink to the nestedFlow
.named("nestedSink") // wrap it up // Create a RunnableGraph
val runnableGraph = nestedSource.to(nestedSink)

在下面这个示范里我们自定义一个某种功能的流图模块:它有2个输入和3个输出。然后我们再使用这个自定义流图模块组建一个完整的闭合流图:

import akka.actor._
import akka.stream._
import akka.stream.scaladsl._ import scala.collection.immutable object GraphModules {
def someProcess[I, O]: I => O = i => i.asInstanceOf[O] case class TwoThreeShape[I, I2, O, O2, O3](
in1: Inlet[I],
in2: Inlet[I2],
out1: Outlet[O],
out2: Outlet[O2],
out3: Outlet[O3]) extends Shape { override def inlets: immutable.Seq[Inlet[_]] = in1 :: in2 :: Nil override def outlets: immutable.Seq[Outlet[_]] = out1 :: out2 :: out3 :: Nil override def deepCopy(): Shape = TwoThreeShape(
in1.carbonCopy(),
in2.carbonCopy(),
out1.carbonCopy(),
out2.carbonCopy(),
out3.carbonCopy()
)
}
//a functional module with 2 input 3 output
def TwoThreeGraph[I, I2, O, O2, O3] = GraphDSL.create() { implicit builder =>
val balancer = builder.add(Balance[I]())
val flow = builder.add(Flow[I2].map(someProcess[I2, O2])) TwoThreeShape(balancer.in, flow.in, balancer.out(), balancer.out(), flow.out)
} val closedGraph = GraphDSL.create() {implicit builder =>
import GraphDSL.Implicits._
val inp1 = builder.add(Source(List(,,))).out
val inp2 = builder.add(Source(List(,,))).out
val merge = builder.add(Merge[Int]())
val mod23 = builder.add(TwoThreeGraph[Int,Int,Int,Int,Int]) inp1 ~> mod23.in1
inp2 ~> mod23.in2
mod23.out1 ~> merge.in()
mod23.out2 ~> merge.in()
mod23.out3 ~> Sink.foreach(println)
merge ~> Sink.foreach(println)
ClosedShape }
} object TailorGraph extends App {
import GraphModules._ implicit val sys = ActorSystem("streamSys")
implicit val ec = sys.dispatcher
implicit val mat = ActorMaterializer() RunnableGraph.fromGraph(closedGraph).run() scala.io.StdIn.readLine()
sys.terminate() }

这个自定义的TwoThreeGraph是一个复合的流图模块,是可以重复使用的。注意这个~>符合的使用:akka-stream只提供了对预设定Shape作为连接对象的支持如:

      def ~>[Out](junction: UniformFanInShape[T, Out])(implicit b: Builder[_]): PortOps[Out] = {...}
def ~>[Out](junction: UniformFanOutShape[T, Out])(implicit b: Builder[_]): PortOps[Out] = {...}
def ~>[Out](flow: FlowShape[T, Out])(implicit b: Builder[_]): PortOps[Out] = {...}
def ~>(to: Graph[SinkShape[T], _])(implicit b: Builder[_]): Unit =
b.addEdge(importAndGetPort(b), b.add(to).in) def ~>(to: SinkShape[T])(implicit b: Builder[_]): Unit =
b.addEdge(importAndGetPort(b), to.in)
...

所以对于我们自定义的TwoThreeShape就只能使用直接的端口连接了:

   def ~>[U >: T](to: Inlet[U])(implicit b: Builder[_]): Unit =
b.addEdge(importAndGetPort(b), to)

以上的过程显示:通过akka的GraphDSL,对复合型Graph的构建可以实现形象化,大部分工作都在如何对组件之间的端口进行连接。我们再来看个较复杂复合流图的构建过程,下面是这个流图的图示:

可以说这是一个相对复杂的数据处理方案,里面甚至包括了数据流回路(feedback)。无法想象如果用纯函数数据流如scalaz-stream应该怎样去实现这么复杂的流程,也可能根本是没有解决方案的。但用akka GraphDSL可以很形象的组合这个数据流图;

  import GraphDSL.Implicits._
RunnableGraph.fromGraph(GraphDSL.create() { implicit builder =>
val A: Outlet[Int] = builder.add(Source.single()).out
val B: UniformFanOutShape[Int, Int] = builder.add(Broadcast[Int]())
val C: UniformFanInShape[Int, Int] = builder.add(Merge[Int]())
val D: FlowShape[Int, Int] = builder.add(Flow[Int].map(_ + ))
val E: UniformFanOutShape[Int, Int] = builder.add(Balance[Int]())
val F: UniformFanInShape[Int, Int] = builder.add(Merge[Int]())
val G: Inlet[Any] = builder.add(Sink.foreach(println)).in C <~ F
A ~> B ~> C ~> F
B ~> D ~> E ~> F
E ~> G ClosedShape
})

另一个端口连接方式的版本如下:

RunnableGraph.fromGraph(GraphDSL.create() { implicit builder =>
val B = builder.add(Broadcast[Int]())
val C = builder.add(Merge[Int]())
val E = builder.add(Balance[Int]())
val F = builder.add(Merge[Int]()) Source.single() ~> B.in; B.out() ~> C.in(); C.out ~> F.in()
C.in() <~ F.out B.out().map(_ + ) ~> E.in; E.out() ~> F.in()
E.out() ~> Sink.foreach(println)
ClosedShape
})

如果把上面这个复杂的Graph切分成模块的话,其中一部分是这样的:

这个开放数据流复合图可以用GraphDSL这样构建:
val partial = GraphDSL.create() { implicit builder =>
val B = builder.add(Broadcast[Int]())
val C = builder.add(Merge[Int]())
val E = builder.add(Balance[Int]())
val F = builder.add(Merge[Int]()) C <~ F
B ~> C ~> F
B ~> Flow[Int].map(_ + ) ~> E ~> F
FlowShape(B.in, E.out())
}.named("partial")
模块化的完整Graph图示如下:
这部分可以用下面的代码来实现:
// Convert the partial graph of FlowShape to a Flow to get
// access to the fluid DSL (for example to be able to call .filter())
val flow = Flow.fromGraph(partial) // Simple way to create a graph backed Source
val source = Source.fromGraph( GraphDSL.create() { implicit builder =>
val merge = builder.add(Merge[Int]())
Source.single() ~> merge
Source(List(, , )) ~> merge // Exposing exactly one output port
SourceShape(merge.out)
}) // Building a Sink with a nested Flow, using the fluid DSL
val sink = {
val nestedFlow = Flow[Int].map(_ * ).drop().named("nestedFlow")
nestedFlow.to(Sink.head)
} // Putting all together
val closed = source.via(flow.filter(_ > )).to(sink)
和scalaz-stream不同的还有akka-stream的运算是在actor上进行的,除了大家都能对数据流元素进行处理之外,akka-stream还可以通过actor的内部状态来维护和返回运算结果。这个运算结果在复合流图中传播的过程是可控的,如下图示:

返回运算结果是通过viaMat, toMat来实现的。简写的via,to默认选择流图左边运算产生的结果。

 

Akka(19): Stream:组合数据流,组合共用-Graph modular composition的更多相关文章

  1. Akka(18): Stream:组合数据流,组件-Graph components

    akka-stream的数据流可以由一些组件组合而成.这些组件统称数据流图Graph,它描述了数据流向和处理环节.Source,Flow,Sink是最基础的Graph.用基础Graph又可以组合更复杂 ...

  2. [Python设计模式] 第19章 分公司=部门?——组合模式

    github地址:https://github.com/cheesezh/python_design_patterns 组合模式 组合模式,将对象组合成树形结构以表示"部分-整体" ...

  3. 【题解】数字组合(NTT+组合 滑稽)

    [题解]数字组合(NTT+组合 滑稽) 今天实践一下谢总讲的宰牛刀233,滑稽. \((1+x)(1+x)(1+x)\)的\(x^2\)系数就代表了有三个一快钱硬币构成的两块钱的方案数量. 很好理解, ...

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

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

  5. Leetcode刷题笔记(Python 找出所有相加之和为n的k个组合,组合中只允许含有1-9的正整数,并且每种组合中不存在重复的数字。)

    eg:输入:k=3,n=9 输出: [[1,2,6],[1,3,5],[2,3,4]] 输入:k=2,n=5 输出:[[1,4][2,3]] #!/usr/bin/env python # -*- c ...

  6. JVM垃圾收集器组合--各种组合对应的虚拟机参数实践

    前言 相信很多人都看过下面这张图,(来自<深入理解Java虚拟机:JVM高级特性与最佳实践>) 在学完几种垃圾收集器类型及组合后,打算看看实际中程序用到的垃圾收集器. 但是在jconsol ...

  7. 剑指offer-拓展训练-字符的所有组合-全组合

    /* 题目: 给定不含重复字符字符串的全组合. */ /* 思路: 递归法. 例给定abc,输出的组合长度为1,2,3. 对于长度为2的组合,分选择a(ab,ac)和不选择a的情况(bc). 选择a, ...

  8. [LeetCode] Moving Average from Data Stream 从数据流中移动平均值

    Given a stream of integers and a window size, calculate the moving average of all integers in the sl ...

  9. [LeetCode] 346. Moving Average from Data Stream 从数据流中移动平均值

    Given a stream of integers and a window size, calculate the moving average of all integers in the sl ...

随机推荐

  1. voa 2015.4.29

    Nepal has declared three days of mourning for the victims of Saturday's 7.8 magnitude earthquake tha ...

  2. OpenCV 之 网络摄像头

     1  RTSP RTSP (Real Time Streaming Protocol),是一种语法和操作类似 HTTP 协议,专门用于音频和视频的应用层协议. 和 HTTP 类似,RTSP 也使用 ...

  3. 是否使用安全模式启动word

          打开word,出现了一个提示,显示着“word遇到问题需要关闭.我们对此引起的不便表示抱歉.”下面有选项“恢复我的工作并重启word”,选中它.点下面的“不发送”.      在出现的提示 ...

  4. CSS 样式书写规范+特殊符号

    虽然我只是刚踏入web前端开发圈子.在一次次任务里头,我发觉每一次的css命名都有所不同和不知所措.脑海就诞生了一个想法--模仿大神的css命名样式. 毕竟日后工作上,是需要多个成员共同协作的.如果没 ...

  5. 关于"模块计算机类型与目标计算机类型冲突"的解决

    问题描述:我的64位工程包含32位静态库之后报错(模块计算机类型"x86"与目标计算机类型"x64"冲突),将工程修改为32位之后,又报错(若干个无法解析的外部 ...

  6. git创建版本库以及使用

    Git使用教程(摘自tugenhua0707) 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央 ...

  7. laravel数据库查询返回的数据形式

    版本:laravel5.4+ 问题描述:laravel数据库查询返回的数据不是单纯的数组形式,而是数组与类似stdClass Object这种对象的结合体,即使在查询构造器中调用了toArray(), ...

  8. spring AOP原理

    spring 实现AOP是依赖JDK动态代理和CGLIB代理实现的.以下是JDK动态代理和CGLIB代理简单介绍    JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的 ...

  9. ajax分页效果、分类联动、搜索功能

    一.使用smarty+ajax+php实现无刷新分页效果 效果图 <!DOCTYPE html> <html lang="en"> <head> ...

  10. centOS(redhat/oracle linux更换语言

    编辑/etc/sysconfig/i18n将LANG=”zh_CN.UTF-8″ 改为 LANG=”en_US.UTF-8″ 或其他语言中文乱码将LANG=”zh_CN.UTF-8″改为LANG=”z ...