实际上很早就写了一系列关于akka-streams的博客。但那个时候纯粹是为了了解akka而去学习的,主要是从了解akka-streams的原理为出发点。因为akka-streams是akka系列工具的基础,如:akka-http, persistence-query等都是基于akka-streams的,其实没有真正把akka-streams用起来。这段时间所遇到的一些需求也是通过集合来解决的。不过,现在所处的环境还是逼迫着去真正了解akka-streams的应用场景。现状是这样的:跨入大数据时代,已经有大量的现代IT系统从传统关系数据库转到分布式数据库(非关系数据库)了。不难想象,这些应用的数据操作编程不说截然不同吧,肯定也会有巨大改变。特别是在传统SQL编程中依赖数据关系的join已经不复存在了,groupby、disctict等操作方法也不是所有的分布式数据库都能支持的。而这些操作在具体的数据呈现和数据处理中又是不可缺少的。当然,有很多需求可以通过集合来满足,但涉及到大数据处理我想最好还是通过流处理来实现,因为流处理stream-processing的其中一项特点就是能够在有限的内存空间里处理无限量的数据。所以流处理应该是分布式数据处理的理想方式了。这是这次写akka-streams的初衷:希望能通过akka-streams来实现分布式数据处理编程。

先从基本流部件basic stream parts开始,即source,flow,sink。这几个部件可以组合成一个所谓线性流linear-stream。一个流对数据的处理包括两部分:1、对流中元素进行转变,如:source:Source[Int,NotUsed] = Source(1 to 10).map(i => i.toString),把流里的所有Int转变成String、2、对流内元素进行计算得出运算结果,如:sink: Sink[Int,Future[Int]] = Sink.fold(0)(_ + _)。当我们run这个sink后得出Future[Int],如:res: Future[Int] = src.runWith(sink)。这两项对流元素的操作所产生的结果不同:元素转换得到动态流动的一串元素、运算元素得到一个静态值,这个运算值materialized-value只能在Sink里获取。即使有这样的表示方式:Source[Int,Future[Int]],这是个迷惑,这个运算值只能通过自定义的graph才能得到,也就是说基本组件是没这个功能的。举个具体的例子吧:val source: Source[Int, Promise[Option[Int]]] = Source.maybe[Int] 这个表达式貌似可以在Source方获取运算值,再看看Source.maybe[Int]:

  def maybe[T]: Source[T, Promise[Option[T]]] =
Source.fromGraph(MaybeSource.asInstanceOf[Graph[SourceShape[T], Promise[Option[T]]]])

可以看出这个Source.maybe是从graph构建的。

上面这个例子里用一个Source对接一个Sink已经组成了一个完整的流,那么Flow是用来干什么的呢?由于运算值是无法当作流元素传递的,Flow只能是用来对Source传下来的元素进行转换后再传递给Sink,也就是说Flow是由一个或多个处理环节构成的。用Flow来分步实现功能是流处理实现并行运算的基本方式,如:

Source( to ).async.via(Flow[Int].map(i => i + )).async.runWith(sink)

用async把这个流分割成3个运算发送给3个actor去同时运算。乍看之下map好像是个Flow,它们的作用也似乎相同,也可以对接Source。如:Source(1 to 10).map(_ + 1)。但map和Flow还是有分别的,从类型款式来看Flow[In,Out,M]比起map[A,B]多出来了M,运算值。所以via(map(_.toString))无法匹配类型。那么对于定义带有预先处理环节的Sink就必须用Flow来实现了:ex_sink = Flow[Int].map(_ + 1).to(sink)。

虽然运算值不能像流元素一样流动,但akka-streams提供了机制让用户选择是否返回某个节点的运算值M。系统默认只选择最最左边节点的M,如:

// A source that can be signalled explicitly from the outside
val source: Source[Int, Promise[Option[Int]]] = Source.maybe[Int] // A flow that internally throttles elements to 1/second, and returns a Cancellable
// which can be used to shut down the stream
val flow: Flow[Int, Int, Cancellable] = throttler // A sink that returns the first element of a stream in the returned Future
val sink: Sink[Int, Future[Int]] = Sink.head[Int] val stream: RunnableGraph[(Cancellable, Future[Int])] =
source.viaMat(flow)(Keep.right).toMat(sink)(Keep.both) val stream1: RunnableGraph[(Promise[Option[Int]], Cancellable, Future[Int])] =
source.viaMat(flow)(Keep.both).toMat(sink)(Keep.both)

运算值M可以通过viaMat,toMat选择,然后stream.run()获取。akka-streams提供了简便一点的运算方式runWith:指定runWith参数流组件的M为最终运算值。如:

// Using runWith will always give the materialized values of the stages added
// by runWith() itself
val r4: Future[Int] = source.via(flow).runWith(sink)
val r5: Promise[Option[Int]] = flow.to(sink).runWith(source)
val r6: (Promise[Option[Int]], Future[Int]) = flow.runWith(source, sink)

值得注意的是:我们可以分别从Source,Sink,Flow开始针对Source runWith(Sink), Sink runWith(Source)及Flow runWith (Source,Sink)。用基础流组件Source,Flow,Sink构成的流是直线型的。也就是说从Source流出的元素会一个不漏的经过Flow进入Sink,不能多也不能少。可能Source.filter会产生疑惑,不过看看filter函数定义就明白了:

def filter(p: Out => Boolean): Repr[Out] = via(Filter(p))

@InternalApi private[akka] final case class Filter[T](p: T => Boolean) extends SimpleLinearGraphStage[T] {
override def initialAttributes: Attributes = DefaultAttributes.filter override def toString: String = "Filter" override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with OutHandler with InHandler {
def decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider private var buffer: OptionVal[T] = OptionVal.none override def preStart(): Unit = pull(in)
override def onPush(): Unit =
try {
val elem = grab(in)
if (p(elem))
if (isAvailable(out)) {
push(out, elem)
pull(in)
} else
buffer = OptionVal.Some(elem)
else pull(in)
} catch {
case NonFatal(ex) =>
decider(ex) match {
case Supervision.Stop => failStage(ex)
case _ => pull(in)
}
} override def onPull(): Unit =
buffer match {
case OptionVal.Some(value) =>
push(out, value)
buffer = OptionVal.none
if (!isClosed(in)) pull(in)
else completeStage()
case _ => // already pulled
} override def onUpstreamFinish(): Unit =
if (buffer.isEmpty) super.onUpstreamFinish()
// else onPull will complete setHandlers(in, out, this)
}
}

怎样?够复杂的了吧。很明显,复杂点的流处理需要根据上游元素内容来维护内部状态从而重新构建向下游发送元素的机制。如果想实现join,groupby,distict这些功能就必然对流动元素除转换之外还需要进行增减操作。这项需求可能还必须留在后面的sream-graph章节中讨论解决方案了。不过临时解决方法可以通过运算值M来实现。因为M可以是一个集合,在构建这个M集合时是可以对集合元素进行增减的,下面这段代码示范了一种cassandra数据表groupby的效果:

 def getVouchers(terminalid: String, susp: Boolean)(implicit classicSystem: ActorSystem) = {
implicit val session = CassandraSessionRegistry(classicSystem).sessionFor("alpakka.cassandra")
implicit val ec = classicSystem.dispatcher
var stmt = "select * from pos_on_cloud.txn_log where terminal = ? and txndate = ?"
if (susp) stmt = "select * from pos_on_cloud.txn_hold where terminal = ? and txndate = ?"
val source = session.select(stmt,terminalid,LocalDate.now.format(DateTimeFormatter.ofPattern("yyyyMMdd")))
val sink = Sink.fold[List[TxnItem],TxnItem](List[TxnItem]()){(acc,txn) =>
if (acc.isEmpty) txn.copy(price = ) :: acc
else
if (acc.head.num == txn.num) {
if (txn.salestype == SALESTYPE.itm &&
txn.txntype == TXNTYPE.sales) {
val nacc = acc.head.copy(
price = acc.head.price + ,
qty = acc.head.qty + txn.qty,
amount = acc.head.amount + txn.amount,
dscamt = acc.head.dscamt + txn.dscamt
)
nacc :: acc.drop()
} else acc
}
else txn :: acc
}
for {
vchs <- source.map(TxnItem.fromCqlRow).toMat(sink)(Keep.right).run()
_ <- session.close(ec)
} yield vchs
}

当然,基本流组件在流模式数据库读写方面还是比较高效的,如:

    def futTxns(items: Seq[TxnItem]): Future[Seq[TxnItem]] = Source(items.toSeq)
.via(
CassandraFlow.create(CassandraWriteSettings.defaults,
CQLScripts.insertTxns,
statementBinder)
)
.runWith(Sink.seq)

akka-streams - 从应用角度学习:basic stream parts的更多相关文章

  1. java8学习之Stream源码分析

    上一次已经将Collectors类中的各种系统收集器的源代码进行了完整的学习,而在之前咱们已经花了大量的篇幅对其Stream进行了详细的示例学习,如: 那接下来则通过源代码的角度来对Stream的运作 ...

  2. 五 akka streams kafka

    (转载 https://doc.akka.io/docs/akka-stream-kafka/current/home.html) 一: Akka Streams Kafka, also known ...

  3. Discretized Streams: A Fault-Tolerant Model for Scalable Stream Processing

    https://www2.eecs.berkeley.edu/Pubs/TechRpts/2012/EECS-2012-259.pdf Discretized Streams: A Fault-Tol ...

  4. java8学习之Stream实例剖析

    继续操练Stream,直接上代码: 而咱们要返回ArrayList,显示可以用构造引用来传递到里面,因为它刚好符合Supplier函数式接口的特性:不接收参数返回一个值,所以: 接下来试着将Strea ...

  5. SpringCloud学习之Stream消息驱动【自定义通道】(十一)

    如果不清楚本篇内容的,请务必先去看完上一篇再看本篇,否则阅读起来可能会有部分障碍和困难: 上一篇文章<SpringCloud学习之Stream消息驱动[默认通道](十)>我们简单用自定义通 ...

  6. 从编程实现角度学习Faster R-CNN(附极简实现)

    https://www.jianshu.com/p/9da1f0756813 从编程实现角度学习Faster R-CNN(附极简实现) GoDeep 关注 2018.03.11 15:51* 字数 5 ...

  7. Java8学习(4)-Stream流

    Stream和Collection的区别是什么 流和集合的区别是什么? 粗略地说, 集合和流之间的差异就在于什么时候进行计算.集合是一个内存中的数据结构,它包含数据结构中目前所有的值--集合中的每个元 ...

  8. java8学习之Stream介绍与操作方式详解

    关于默认方法[default method]的思考: 在上一次[http://www.cnblogs.com/webor2006/p/8259057.html]中对接口的默认方法进行了学习,那在Jav ...

  9. java8新特性学习:stream与lambda

    Streams api 对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect). 流的操作类型分为两种: Int ...

随机推荐

  1. SqlServer 多表连接、聚合函数、模糊查询、分组查询应用总结(回归基础)

    --exists 结合 if else 以及 where 条件来使用判断是否有数据满足条件 select * from Class where Name like '%[1-3]班' if (not ...

  2. Github获8300星!用Python开发的一个命令行的网易云音乐

    最近在逛Github发现了一个非常有趣的库musicbox,是用纯Python打造的,收获了8300颗星.Python语言简单易学,好玩有趣,身边越来越多的小伙伴都开始学习Python.她的魅力非常大 ...

  3. NoisyStudent:

  4. Assembly.LoadFrom() 方法加载程序集,无法转换类型

    有些情况在开发一个C#框架的时候,要用到反射加载另外程序集,这是必然考虑的事情.这样做的好处就是多个工程同时作业的时候,可以互不干扰,替换DLL文件即可. Assembly.Load();这个方法只能 ...

  5. 《Java从入门到失业》第一章:计算机基础知识(一):二进制和十六进制

    0 前言 最近7年来的高强度工作和不规律的饮食作息,压得我有些喘不过气,身体也陆续报警.2018年下半年的一场病,让我意识到了这个问题的严重性,于是开始强制自己有规律饮食和作息,并辅以健身锻炼,不到2 ...

  6. 关于Differentiated Services Field (DS Field)的诸多疑问

    Differentiated Services Field (DS Field) 先上疑问截图: 这是用wireshark抓包时协议树的某一项的展开结果:IPV4 header.其中有一项如下: 大家 ...

  7. GAN量化评估方法——IS(Inception Score)和FID(Frechet Inception Distance score)

    生成模型产生的是高维的复杂结构数据,它们不同于判别模型,很难用简单的指标来评估模型的好坏.下面介绍两种当前比较流行的评估生成模型的指标(仅判别图像):IS(Inception Score)和FID(F ...

  8. .Net Core3.1 + EF Core + LayUI 封装的MVC版后台管理系统

    项目名称:学生信息管理系统1.0 后台框架:.Net Core 3.1 + EF Core    yrjw.ORM.Chimp 前端框架:ASP.NET Core MVC  +  LayUI + Bo ...

  9. 解决VS Code安装golang相关插件失败问题

    解决VS Code安装golang相关插件失败问题 VSCode作为一款优秀的编辑器,基本上前端开发,Python,markdown都可以用,真正是开发过程中的好帮手,所以学习go也当仁不让的要用vs ...

  10. Jmeter 常用函数(15)- 详解 __StringFromFile

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 从文本文件读取字符串,每次一行 需要注意 ...