上篇我们讨论了静态数据源(Static Source, snapshot)。这种方式只能在预知数据规模有限的情况下使用,对于超大型的数据库表也可以说是不安全的资源使用方式。Slick3.x已经增加了支持Reactive-Streams功能,可以通过Reactive-Streams API来实现有限内存空间内的无限规模数据读取,这正符合了FunDA的设计理念:高效、便捷、安全的后台数据处理工具库。我们在前面几篇讨论里介绍了Iteratee模式,play-iteratees支持Reactive-Streams并且提供与Slick3.x的接口API,我们就在这篇讨论里介绍如何把Slick-Reactive-Streams转换成fs2-Streams。根据Slick官方文档:Slick可以通过db.stream函数用Reactive-Stream方式来读取后台数据,具体的配置如下:

  val disableAutocommit = SimpleDBIO(_.connection.setAutoCommit(false))
val action = queryAction.withStatementParameters(fetchSize = )
val publisher = db.stream(disableAutocommit andThen action)

首先,我们需要取消自动提交(disableAutocommit)。fetchSize是缓存数据页长度(每批次读取数据字数),然后用db.stream来构成一个Reactive-Streams标准的数据源publisher。Slick官方网页只提供了下面这个使用publisher的例子:

  val fut = publisher.foreach(s => println(s))
Await.ready(fut,Duration.Inf)

除了数据枚举外就没什么用处,也无法提供更细节点的示范。FunDA的具体解决方案是把publisher转换成play-iteratee的Enumerator。play-iteratee支持Reactive-Streams,所以这个Enumerator应该具备协调后台数据和内存缓冲之间关系(back-pressure)的功能。play-iteratee是如下构建Enumerator的;

import play.api.libs.iteratee._
val enumerator = streams.IterateeStreams.publisherToEnumerator(publisher)

enumerator从后台数据库表中产生的数据源通过Iteratee把数据元素enqueue推送给一个fs2的queue:

    private def pushData[R](q: async.mutable.Queue[Task,Option[R]]): Iteratee[R,Unit] = Cont {
case Input.EOF => {
q.enqueue1(None).unsafeRun
Done((), Input.Empty)
}
case Input.Empty => pushData(q)
case Input.El(e) => {
q.enqueue1(Some(e)).unsafeRun
pushData(q)
}
}

然后fs2进行dequeue后生成fs2的Stream:

      Stream.eval(async.boundedQueue[Task,Option[SOURCE]](queSize)).flatMap { q =>
Task { Iteratee.flatten(enumerator |>> pushData(q)).run }.unsafeRunAsyncFuture()
pipe.unNoneTerminate(q.dequeue)
}

整个构建Stream的过程在FunDA的fdasources包是这样定义的:

package com.bayakala.funda.fdasources
import fs2._
import play.api.libs.iteratee._
import com.bayakala.funda.fdapipes._
import slick.driver.JdbcProfile object FDADataStream { class FDAStreamLoader[SOURCE, TARGET](slickProfile: JdbcProfile, convert: SOURCE => TARGET) { import slickProfile.api._ def fda_typedStream(action: DBIOAction[Iterable[SOURCE],Streaming[SOURCE],Effect.Read])(slickDB: Database)(fetchSize: Int, queSize: Int): FDAPipeLine[TARGET] = {
val disableAutocommit = SimpleDBIO(_.connection.setAutoCommit(false))
val action_ = action.withStatementParameters(fetchSize = fetchSize)
val publisher = slickDB.stream(disableAutocommit andThen action)
val enumerator = streams.IterateeStreams.publisherToEnumerator(publisher) Stream.eval(async.boundedQueue[Task,Option[SOURCE]](queSize)).flatMap { q =>
Task { Iteratee.flatten(enumerator |>> pushData(q)).run }.unsafeRunAsyncFuture()
pipe.unNoneTerminate(q.dequeue).map {row => convert(row)}
} }
def fda_plainStream(action: DBIOAction[Iterable[SOURCE],Streaming[SOURCE],Effect.Read])(slickDB: Database)(fetchSize: Int, queSize: Int): FDAPipeLine[SOURCE] = {
val disableAutocommit = SimpleDBIO(_.connection.setAutoCommit(false))
val action_ = action.withStatementParameters(fetchSize = fetchSize)
val publisher = slickDB.stream(disableAutocommit andThen action)
val enumerator = streams.IterateeStreams.publisherToEnumerator(publisher) Stream.eval(async.boundedQueue[Task,Option[SOURCE]](queSize)).flatMap { q =>
Task { Iteratee.flatten(enumerator |>> pushData(q)).run }.unsafeRunAsyncFuture()
pipe.unNoneTerminate(q.dequeue)
} }
private def pushData[R](q: async.mutable.Queue[Task,Option[R]]): Iteratee[R,Unit] = Cont {
case Input.EOF => {
q.enqueue1(None).unsafeRun
Done((), Input.Empty)
}
case Input.Empty => pushData(q)
case Input.El(e) => {
q.enqueue1(Some(e)).unsafeRun
pushData(q)
}
} }
object FDAStreamLoader {
def apply[SOURCE, TARGET](slickProfile: JdbcProfile, converter: SOURCE => TARGET): FDAStreamLoader[SOURCE, TARGET] =
new FDAStreamLoader[SOURCE, TARGET](slickProfile, converter)
}
}

FDADataStream对象内主要实现了fda_typedStream和fda_plainStream。fda_typedStream提供了SOURCE=>TARGET的转换。从Enumerator转换到Stream整个过程和原理我们在FunDA(7)里已经详细介绍过了。下面我们看看FunDA-Example中fda_typedStream的具体应用例子:

package com.bayakala.funda.fdasources.examples
import slick.driver.H2Driver.api._
import com.bayakala.funda.fdasources.FDADataStream._
import com.bayakala.funda.samples._
import com.bayakala.funda.fdarows._
import com.bayakala.funda.fdapipes._
import FDANodes._
import FDAValves._
object Example2 extends App {
val albums = SlickModels.albums
val companies = SlickModels.companies //数据源query
val albumsInfo = for {
(a,c) <- albums join companies on (_.company === _.id)
} yield (a.title,a.artist,a.year,c.name) //query结果强类型(用户提供)
case class Album(title: String, artist: String, year: Int, publisher: String) extends FDAROW
//转换函数(用户提供)
def toTypedRow(row: (String, String, Option[Int], String)): Album =
Album(row._1, row._2, row._3.getOrElse(), row._4) val db = Database.forConfig("h2db") val streamLoader = FDAStreamLoader(slick.driver.H2Driver, toTypedRow _)
val albumStream = streamLoader.fda_typedStream(albumsInfo.result)(db)(,) //定义一个用户作业函数:列印数据内容
def printAlbums: FDATask[FDAROW] = row => {
row match {
case album: Album =>
println("____________________")
println(s"品名:${album.title}")
println(s"演唱:${album.artist}")
println(s"年份:${album.year}")
println(s"发行:${album.publisher}")
fda_next(album)
case _ => fda_skip
}
} albumStream.through(fda_execUserTask(printAlbums)).run.unsafeRun }

运算结果:

品名:Keyboard Cat's Greatest Hits
演唱:Keyboard Cat
年份:
发行:Sony Music Inc
____________________
品名:Spice
演唱:Spice Girls
年份:
发行:Columbia Records
____________________
品名:Whenever You Need Somebody
演唱:Rick Astley
年份:
发行:Sony Music Inc
____________________
品名:The Triumph of Steel
演唱:Manowar
年份:
发行:The K-Pops Singers
____________________
品名:Believe
演唱:Justin Bieber
年份:
发行:Columbia Records Process finished with exit code

FunDA(9)- Stream Source:reactive data streams的更多相关文章

  1. FunDA(8)- Static Source:保证资源使用安全 - Resource Safety

    我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算.我们选定的解决方案是把后台数据转换成内存中的数据流.无论在打开数据库表或从数据库读取数据等环节都涉及到对数据库表这项 ...

  2. FunDA(4)- 数据流内容控制:Stream data element control

    上节我们探讨了通过scalaz-stream-fs2来驱动一套数据处理流程,用fs2的Pipe类型来实现对数据流的逐行操作.本篇讨论准备在上节讨论的基础上对数据流的流动和元素操作进行优化完善.如数据流 ...

  3. FunDA(13)- 示范:用户自定义操作函数 - user defined tasks

    FunDA是一种函数式的编程工具,它所产生的程序是由许多功能单一的细小函数组合而成,这些函数就是用户自定义操作函数了.我们在前面曾经提过FunDA的运作原理模拟了数据流管道.流元素在管道流动的过程中被 ...

  4. FunDA(12)- 示范:强类型数据源 - strong typed data sources

    FunDA设计的主要目的是解决FRM(Functional Relation Mapping)如Slick这样的批次型操作工具库数据源行间游动操作的缺失问题.FRM产生的结果集就是一种静态集合,缺乏动 ...

  5. FunDA(11)- 数据库操作的并行运算:Parallel data processing

    FunDA最重要的设计目标之一就是能够实现数据库操作的并行运算.我们先重温一下fs2是如何实现并行运算的.我们用interleave.merge.either这几种方式来同时处理两个Stream里的元 ...

  6. FunDA(5)- Reactive Streams:Play with Iteratees

    FunDA的设计目标就是把后台数据库中的数据搬到内存里,然后进行包括并行运算的数据处理,最后可能再对后台数据库进行更新.如果需要把数据搬到内存的话,那我们就必须考虑内存是否能一次性容纳所有的数据,有必 ...

  7. FunDA(3)- 流动数据行操作:FDAPipeLine operations using scalaz-stream-fs2

    在上节讨论里我们介绍了数据行流式操作的设想,主要目的是把后台数据库的数据载入前端内存再拆分为强类型的数据行,这样我们可以对每行数据进行使用和处理.形象点描述就是对内存里的一个数据流(data-stre ...

  8. FunDA(17)- 示范:异常处理与事后处理 - Exceptions handling and Finalizers

    作为一个能安全运行的工具库,为了保证占用资源的安全性,对异常处理(exception handling)和事后处理(final clean-up)的支持是不可或缺的.FunDA的数据流FDAPipeL ...

  9. FunDA(15)- 示范:任务并行运算 - user task parallel execution

    FunDA的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...

随机推荐

  1. Codeforces 599C. Day at the Beach 模拟

    Day at the Beach time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...

  2. CComSafeArray

    https://blog.csdn.net/tangaowen/article/details/6554640

  3. td里的英文字母不会自动换行的问题

    今天发现一个问题,限制了TD的宽度之后,汉字会自动换行,但是英文却不会,在网上搜索一下,发现在TD里面加上style='word-break:break-all'这个样式之后,换行成功 <tab ...

  4. 转录组差异表达分析工具Ballgown

    Ballgown是分析转录组差异表达的R包. 软件安装: 运行R, source(“http://bioconductor.org/biocLite.R”) biocLite(“ballgown”) ...

  5. [转载红鱼儿]delphi 实现微信开发(1)

    大体思路: 1.用户向服务号发消息,(这里可以是个菜单项,也可以是一个关键词,如:注册会员.) 2.kbmmw web server收到消息,生成一个图文消息给微信,在图文消息中做好自己的url,在u ...

  6. 2018.10.04 NOIP模拟 K进制(模拟)

    传送门 签到题,直接瞎模拟就行了. 代码

  7. 2018.06.29 洛谷P2890 [USACO07OPEN]便宜的回文(简单dp)

    P2890 [USACO07OPEN]便宜的回文Cheapest Palindrome 时空限制 1000ms / 128MB 题目描述 Keeping track of all the cows c ...

  8. CentOS里vim基本操作

    1.关于退出 :wq!  ----强制保存退出 :wq  ---- 保存退出 :x   ----- 作用和:wq 一样 ZZ  ---- 作用和:wq一样,(注意Z是大写的,并且不是在命令模式) :q ...

  9. hibernate hql where语句拼接工具类

    package com.zhaoshijie.tree.other; /** * hibernate HQL WHERE语句工具类 * * @author 赵士杰 * */public class H ...

  10. UVa 1572 Self-Assembly (构造+拓扑排序。。。。。)

    题意:给定n个带标号的正方形,标号要么是一个大写字母加一个+或-,要么是00, 当且仅当大写字母相同并且符号相反时可以连接,问你给定的能不能拼成一个无限大的的东西. 析:说实话,真心没有看出来是拓扑排 ...