FunDA(9)- Stream Source:reactive data streams
上篇我们讨论了静态数据源(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的更多相关文章
- FunDA(8)- Static Source:保证资源使用安全 - Resource Safety
我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算.我们选定的解决方案是把后台数据转换成内存中的数据流.无论在打开数据库表或从数据库读取数据等环节都涉及到对数据库表这项 ...
- FunDA(4)- 数据流内容控制:Stream data element control
上节我们探讨了通过scalaz-stream-fs2来驱动一套数据处理流程,用fs2的Pipe类型来实现对数据流的逐行操作.本篇讨论准备在上节讨论的基础上对数据流的流动和元素操作进行优化完善.如数据流 ...
- FunDA(13)- 示范:用户自定义操作函数 - user defined tasks
FunDA是一种函数式的编程工具,它所产生的程序是由许多功能单一的细小函数组合而成,这些函数就是用户自定义操作函数了.我们在前面曾经提过FunDA的运作原理模拟了数据流管道.流元素在管道流动的过程中被 ...
- FunDA(12)- 示范:强类型数据源 - strong typed data sources
FunDA设计的主要目的是解决FRM(Functional Relation Mapping)如Slick这样的批次型操作工具库数据源行间游动操作的缺失问题.FRM产生的结果集就是一种静态集合,缺乏动 ...
- FunDA(11)- 数据库操作的并行运算:Parallel data processing
FunDA最重要的设计目标之一就是能够实现数据库操作的并行运算.我们先重温一下fs2是如何实现并行运算的.我们用interleave.merge.either这几种方式来同时处理两个Stream里的元 ...
- FunDA(5)- Reactive Streams:Play with Iteratees
FunDA的设计目标就是把后台数据库中的数据搬到内存里,然后进行包括并行运算的数据处理,最后可能再对后台数据库进行更新.如果需要把数据搬到内存的话,那我们就必须考虑内存是否能一次性容纳所有的数据,有必 ...
- FunDA(3)- 流动数据行操作:FDAPipeLine operations using scalaz-stream-fs2
在上节讨论里我们介绍了数据行流式操作的设想,主要目的是把后台数据库的数据载入前端内存再拆分为强类型的数据行,这样我们可以对每行数据进行使用和处理.形象点描述就是对内存里的一个数据流(data-stre ...
- FunDA(17)- 示范:异常处理与事后处理 - Exceptions handling and Finalizers
作为一个能安全运行的工具库,为了保证占用资源的安全性,对异常处理(exception handling)和事后处理(final clean-up)的支持是不可或缺的.FunDA的数据流FDAPipeL ...
- FunDA(15)- 示范:任务并行运算 - user task parallel execution
FunDA的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...
随机推荐
- Java 8 接口中的默认方法与静态方法
Java 8 接口中的默认方法与静态方法 1. 接口中的默认方法 允许接口中包含具有具体实现的方法,该方法称"默认方法",默认方法使用用 default 关键字修饰. public ...
- struts2 的 ServletActionContext 和 actionContext,服务器代码测试, redirect 、dispatcher、chain、redirectAction
一.ServletActionContext 和 actionContext HttpServletRequest request=ServletActionContext.getRequest() ...
- HTTP 500.21 处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”
问题:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler” IIS7下部署网站出现错误,原因是先安装的II ...
- py-函数进阶
名称空间 又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方 名称空间共3种, ...
- js 获取时间不能大于当前系统时间
var dataDate=$.trim($("#dataDate").val()); if(dataDate.length==0){ $("#dataDateTip&qu ...
- java.lang.String cannot be cast to java.util.Date
我这个是个新建的功能,然后在保存的时候出现了这个错误.然后就找到了新建的action,发现其上的list方法出了问题. 这样是正确的.之前list<Constract>写成这样了.
- 山东省第七届ACM竞赛 C题 Proxy (Dijkstra算法,单源路径最短问题)
题意:给定0-n+1个点,和m条边,让你找到一条从0到n+1的最短路,输出与0相连的结点... 析:很明显么,是Dijkstra算法,不过特殊的是要输出与0相连的边,所以我们倒着搜,也是从n+1找到0 ...
- 20155226 2016-2017-2 《Java程序设计》第7周学习总结
20155226 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 认识时间与日期 六个时间基准: 1.格林威治标准时间 2.世界时 3.国际原子时 4.世界协 ...
- SqlCommand和SqlDataAdapter的区别
SqlDataAdapter对象 一.特点介绍1.表示用于填充 DataSet 和更新 SQL Server 数据库的一组数据命令和一个数据库连接.2.在SqlDataAdapter和DataSet之 ...
- 让tableView的某行移动到tableView的某位置
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lineNumber inSection:0]; [lrcTableView selectR ...