Akka(18): Stream:组合数据流,组件-Graph components
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的更多相关文章
- Akka(19): Stream:组合数据流,组合共用-Graph modular composition
akka-stream的Graph是一种运算方案,它可能代表某种简单的线性数据流图如:Source/Flow/Sink,也可能是由更基础的流图组合而成相对复杂点的某种复合流图,而这个复合流图本身又可以 ...
- Akka(17): Stream:数据流基础组件-Source,Flow,Sink简介
在大数据程序流行的今天,许多程序都面临着共同的难题:程序输入数据趋于无限大,抵达时间又不确定.一般的解决方法是采用回调函数(callback-function)来实现的,但这样的解决方案很容易造成“回 ...
- SSIS自定义数据流组件开发(血路)
由于特殊的原因(怎么特殊不解释),需要开发自定义数据流组件处理. 查了很多资料,用了不同的版本,发现各种各样的问题没有找到最终的解决方案. 遇到的问题如下: 用VS2015编译出来的插件,在SSDTB ...
- Flutter学习笔记(18)--Drawer抽屉组件
如需转载,请注明出处:Flutter学习笔记(18)--Drawer抽屉组件 Drawer(抽屉组件)可以实现类似抽屉拉出和推入的效果,可以从侧边栏拉出导航面板.通常Drawer是和ListView组 ...
- 第18讲- UI常用组件之EditText
第18讲UI常用组件之EditText 三.文本输入框EditText EditTex类继承自TextView.EditText是接受用户输入信息的最重要控件.在html当中,相当于<input ...
- Django学习---组合搜索组件
组合搜索组件 我们都会写博客,写文章之后我们要给文章设置目录,设置类型.之后我们在浏览文章的时候就能够按类别进行精确定位到文章,那这个组合搜索我们应该怎么做呢? 首先我们先创建3张表,分别存放文章,文 ...
- Django之组合搜索组件(二)--另附simple_tag的创建使用方法
这次的代码为Django之组合搜索组件(一)的改版,实现的结果和(一)相同,不同的是,这次运用simple_tag方式,使.html程序简化 所以现在就开始编程吧! 首先想使用simple_tag方法 ...
- Vue 中数据流组件
好久不见呀,这两年写了很多很多东西,也学到很多很多东西,没有时常分享是因为大多都是我独自思考.明年我想出去与更多的大神交流,再修筑自己构建的内容. 有时候我会想:我们遇到的问题,碰到的界限,是别人给的 ...
- spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)
本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...
随机推荐
- maven仓库--搭建局域网私服(windows版)
使用nexus搭建局域网私服 一. 认识maven仓库 1.1 maven仓库的作用 回想之前不用maven的时候,我们用eclipse原始的项目骨架构建项目时,在工程目录下往往有一个lib文件夹 ...
- s2-048远程代码执行漏洞
在Struts 2.3.x 系列的 Showcase 应用中演示Struts2整合Struts 1 的插件中存在一处任意代码执行漏洞.当你的应用使用了Struts2 Struts1的插件时,可能导致不 ...
- 关于Mongo的一些坑
和大多数人一样,从Mysql转到Mongo的过程中,思维上经历了很大的转变.下面来说几点我遇到的坑: 1.单文档16M 这个是最多人碰到的,我也碰到过,当然,幸好是因为操作有误,导致这种不正常的大量数 ...
- JavaBean规范
JavaBean规范 (1)JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class user{ …}(2)JavaBean 类必须有一个空的构造函 ...
- Linux基础(七)
一.nfs服务 nfs(Network File System)即网络文件系统,它允许网络中的计算机之间通过TCP/IP网络共享资源.常用于Linux系统之间的文件共享. nfs在文件传送过程中依赖r ...
- guava缓存底层实现
摘要 guava的缓存相信很多人都有用到, Cache<String, String> cache = CacheBuilder.newBuilder() .expireAfterWrit ...
- (转)@ContextConfiguration注解说明
场景:学习spring实战中相关的单元测试 1 正常使用 @ContextConfiguration Spring整合JUnit4测试时,使用注解引入多个配置文件 1.1 单个文件 @ContextC ...
- Accord.NET_Naive Bayes Classifier
我们这个系列主要为了了解并会使用Accord.NET中机器学习有关算法,因此主要关注的是算法针对的的问题,算法的使用.所以主要以代码为主,通过代码来学习,在脑海中形成一个轮廓.下面就言归正传,开始贝叶 ...
- Tornado-StaticFileHandler参考
StaticFileHandler ====== tornado.web.StaticFileHandler 源代码中的解释 class StaticFileHandler(RequestHandle ...
- 36. leetcode 415. Add Strings
415. Add Strings Given two non-negative integers num1 and num2 represented as string, return the sum ...