在以前的博文中我们介绍了Slick,它是一种FRM(Functional Relation Mapper)。有别于ORM,FRM的特点是函数式的语法可以支持灵活的对象组合(Query Composition)实现大规模的代码重复利用,但同时这些特点又影响了编程人员群体对FRM的接受程度,阻碍了FRM成为广为流行的一种数据库编程方式。所以我们只能从小众心态来探讨如何改善Slick现状,希望通过与某些Stream库集成,在Slick FRM的基础上恢复一些人们熟悉的Recordset数据库光标(cursor)操作方式,希望如此可以降低FRM数据库编程对函数式编程水平要求,能够吸引更多的编程人员接受FRM。刚好,在这篇讨论里我们希望能介绍一些Akka-Stream和外部系统集成对接的实际用例,把Slick数据库数据载入连接到Akka-Stream形成streaming-dataset应该是一个挺好的想法。Slick和Akka-Stream可以说是自然匹配的一对,它们都是同一个公司产品,都支持Reactive-Specification。Reactive系统的集成对象之间是通过公共界面Publisher来实现对接的。Slick提供了个Dababase.stream函数可以构建这个Publisher:

 /** Create a `Publisher` for Reactive Streams which, when subscribed to, will run the specified
* `DBIOAction` and return the result directly as a stream without buffering everything first.
* This method is only supported for streaming actions.
*
* The Publisher itself is just a stub that holds a reference to the action and this Database.
* The action does not actually start to run until the call to `onSubscribe` returns, after
* which the Subscriber is responsible for reading the full response or cancelling the
* Subscription. The created Publisher can be reused to serve a multiple Subscribers,
* each time triggering a new execution of the action.
*
* For the purpose of combinators such as `cleanup` which can run after a stream has been
* produced, cancellation of a stream by the Subscriber is not considered an error. For
* example, there is no way for the Subscriber to cause a rollback when streaming the
* results of `someQuery.result.transactionally`.
*
* When using a JDBC back-end, all `onNext` calls are done synchronously and the ResultSet row
* is not advanced before `onNext` returns. This allows the Subscriber to access LOB pointers
* from within `onNext`. If streaming is interrupted due to back-pressure signaling, the next
* row will be prefetched (in order to buffer the next result page from the server when a page
* boundary has been reached). */
final def stream[T](a: DBIOAction[_, Streaming[T], Nothing]): DatabasePublisher[T] = streamInternal(a, false)

这个DatabasePublisher[T]就是一个Publisher[T]:

/** A Reactive Streams `Publisher` for database Actions. */
abstract class DatabasePublisher[T] extends Publisher[T] { self =>
...
}

然后Akka-Stream可以通过Source.fromPublisher(publisher)构建Akka Source构件:

  /**
* Helper to create [[Source]] from `Publisher`.
*
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
def fromPublisher[T](publisher: Publisher[T]): Source[T, NotUsed] =
fromGraph(new PublisherSource(publisher, DefaultAttributes.publisherSource, shape("PublisherSource")))

理论上Source.fromPublisher(db.stream(query))就可以构建一个Reactive-Stream-Source了。下面我们就建了例子来做示范:首先是Slick的铺垫代码boiler-code:

  val aqmraw = Models.AQMRawQuery
val db = Database.forConfig("h2db")
// aqmQuery.result returns Seq[(String,String,String,String)]
val aqmQuery = aqmraw.map {r => (r.year,r.state,r.county,r.value)}
// type alias
type RowType = (String,String,String,String)
// user designed strong typed resultset type. must extend FDAROW
case class TypedRow(year: String, state: String, county: String, value: String) extends FDAROW
// strong typed resultset conversion function. declared implicit to remind during compilation
implicit def toTypedRow(row: RowType): TypedRow =
TypedRow(row._1,row._2,row._3,row._4)

我们需要的其实就是aqmQuery,用它来构建DatabasePublisher:

  // construct DatabasePublisher from db.stream
val dbPublisher: DatabasePublisher[RowType] = db.stream[RowType](aqmQuery.result)
// construct akka source
val source: Source[RowType,NotUsed] = Source.fromPublisher[RowType](dbPublisher)

有了dbPublisher就可以用Source.fromPublisher函数构建source了。现在我们试着运算这个Akka-Stream:

  implicit val actorSys = ActorSystem("actor-system")
implicit val ec = actorSys.dispatcher
implicit val mat = ActorMaterializer() source.take().map{row => toTypedRow(row)}.runWith(
Sink.foreach(qmr => {
println(s"州名: ${qmr.state}")
println(s"县名:${qmr.county}")
println(s"年份:${qmr.year}")
println(s"取值:${qmr.value}")
println("-------------")
})) scala.io.StdIn.readLine()
actorSys.terminate()

下面是运算结果:

州名: Alabama
县名:Elmore
年份:
取值:
-------------
州名: Alabama
县名:Jefferson
年份:
取值:
-------------
州名: Alabama
县名:Lawrence
年份:
取值:
-------------
州名: Alabama
县名:Madison
年份:
取值:
-------------
州名: Alabama
县名:Mobile
年份:
取值:
-------------
州名: Alabama
县名:Montgomery
年份:
取值:
-------------

显示我们已经成功的连接了Slick和Akka-Stream。

现在我们有了Reactive stream source,它是个akka-stream,该如何对接处于下游的scalaz-stream-fs2呢?我们知道:akka-stream是Reactive stream,而scalaz-stream-fs2是纯“拖式”pull-model stream,也就是说上面这个Reactive stream source必须被动等待下游的scalaz-stream-fs2来读取数据。按照Reactive-Stream规范,下游必须通过backpressure信号来知会上游是否可以发送数据状态,也就是说我们需要scalaz-stream-fs2来产生backpressure。scalaz-stream-fs2 async包里有个Queue结构:

/**
* Asynchronous queue interface. Operations are all nonblocking in their
* implementations, but may be 'semantically' blocking. For instance,
* a queue may have a bound on its size, in which case enqueuing may
* block until there is an offsetting dequeue.
*/
trait Queue[F[_], A] { self =>
/**
* Enqueues one element in this `Queue`.
* If the queue is `full` this waits until queue is empty.
*
* This completes after `a` has been successfully enqueued to this `Queue`
*/
def enqueue1(a: A): F[Unit] /**
* Enqueues each element of the input stream to this `Queue` by
* calling `enqueue1` on each element.
*/
def enqueue: Sink[F, A] = _.evalMap(enqueue1)
/** Dequeues one `A` from this queue. Completes once one is ready. */
def dequeue1: F[A]
/** Repeatedly calls `dequeue1` forever. */
def dequeue: Stream[F, A] = Stream.bracket(cancellableDequeue1)(d => Stream.eval(d._1), d => d._2).repeat
...
}

这个结构支持多线程操作,也就是说enqueue和dequeue可以在不同的线程里操作。值得关注的是:enqueue会block,只有在完成了dequeue后才能继续。这个dequeue就变成了抵消backpressure的有效方法了。具体操作方法是:上游在一个线程里用enqueue发送一个数据元素,然后等待下游完成在另一个线程里的dequeue操作,完成这个循环后再进行下一个元素的enqueue。enqueue代表akka-stream向scalaz-stream-fs2发送数据,可以用akka-stream的Sink构件来实现:

 class FS2Gate[T](q: fs2.async.mutable.Queue[Task,Option[T]]) extends GraphStage[SinkShape[T]] {
val in = Inlet[T]("inport")
val shape = SinkShape.of(in) override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with InHandler {
override def preStart(): Unit = {
pull(in) //initiate stream elements movement
super.preStart()
} override def onPush(): Unit = {
q.enqueue1(Some(grab(in))).unsafeRun()
pull(in)
} override def onUpstreamFinish(): Unit = {
q.enqueue1(None).unsafeRun()
println("the end of stream !")
completeStage()
} override def onUpstreamFailure(ex: Throwable): Unit = {
q.enqueue1(None).unsafeRun()
completeStage()
} setHandler(in,this) }
}

以上这个akka-stream GraphStage描述了对上游每一个元素的enqueue动作。我们可以用scalaz-stream-fs2的flatMap来序列化运算两个线程里的enqueue和dequeue:

   val fs2Stream: Stream[Task,RowType] = Stream.eval(async.boundedQueue[Task,Option[RowType]]())
.flatMap { q =>
Task(source.to(new FS2Gate[RowType](q)).run).unsafeRunAsyncFuture //enqueue Task(new thread)
pipe.unNoneTerminate(q.dequeue) //dequeue in current thread
}

这个函数返回fs2.Stream[Task,RowType],是一种运算方案,我们必须run来实际运算:

  fs2Stream.map{row => toTypedRow(row)}
.map(qmr => {
println(s"州名: ${qmr.state}")
println(s"县名:${qmr.county}")
println(s"年份:${qmr.year}")
println(s"取值:${qmr.value}")
println("-------------")
}).run.unsafeRun

通过测试运行,我们成功的为scalaz-stream-fs2实现了data streaming。

下面是本次示范的源代码:

import slick.jdbc.H2Profile.api._
import com.bayakala.funda._
import api._ import scala.language.implicitConversions
import scala.concurrent.duration._
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.stream.stage._
import slick.basic.DatabasePublisher
import akka._
import fs2._
import akka.stream.stage.{GraphStage, GraphStageLogic} class FS2Gate[T](q: fs2.async.mutable.Queue[Task,Option[T]]) extends GraphStage[SinkShape[T]] {
val in = Inlet[T]("inport")
val shape = SinkShape.of(in) override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with InHandler {
override def preStart(): Unit = {
pull(in) //initiate stream elements movement
super.preStart()
} override def onPush(): Unit = {
q.enqueue1(Some(grab(in))).unsafeRun()
pull(in)
} override def onUpstreamFinish(): Unit = {
q.enqueue1(None).unsafeRun()
println("end of stream !!!!!!!")
completeStage()
} override def onUpstreamFailure(ex: Throwable): Unit = {
q.enqueue1(None).unsafeRun()
completeStage()
} setHandler(in,this) }
} object AkkaStreamSource extends App { val aqmraw = Models.AQMRawQuery
val db = Database.forConfig("h2db")
// aqmQuery.result returns Seq[(String,String,String,String)]
val aqmQuery = aqmraw.map {r => (r.year,r.state,r.county,r.value)}
// type alias
type RowType = (String,String,String,String)
// user designed strong typed resultset type. must extend FDAROW
case class TypedRow(year: String, state: String, county: String, value: String) extends FDAROW
// strong typed resultset conversion function. declared implicit to remind during compilation
implicit def toTypedRow(row: RowType): TypedRow =
TypedRow(row._1,row._2,row._3,row._4)
// construct DatabasePublisher from db.stream
val dbPublisher: DatabasePublisher[RowType] = db.stream[RowType](aqmQuery.result)
// construct akka source
val source: Source[RowType,NotUsed] = Source.fromPublisher[RowType](dbPublisher) implicit val actorSys = ActorSystem("actor-system")
implicit val ec = actorSys.dispatcher
implicit val mat = ActorMaterializer() /*
source.take(10).map{row => toTypedRow(row)}.runWith(
Sink.foreach(qmr => {
println(s"州名: ${qmr.state}")
println(s"县名:${qmr.county}")
println(s"年份:${qmr.year}")
println(s"取值:${qmr.value}")
println("-------------")
})) */ val fs2Stream: Stream[Task,RowType] = Stream.eval(async.boundedQueue[Task,Option[RowType]]())
.flatMap { q =>
Task(source.to(new FS2Gate[RowType](q)).run).unsafeRunAsyncFuture //enqueue Task(new thread)
pipe.unNoneTerminate(q.dequeue) //dequeue in current thread
} fs2Stream.map{row => toTypedRow(row)}
.map(qmr => {
println(s"州名: ${qmr.state}")
println(s"县名:${qmr.county}")
println(s"年份:${qmr.year}")
println(s"取值:${qmr.value}")
println("-------------")
}).run.unsafeRun scala.io.StdIn.readLine()
actorSys.terminate() }

Akka(27): Stream:Use case-Connecting Slick-dbStream & Scalaz-stream-fs2的更多相关文章

  1. [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]

    [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链] 项目实战 实战4:从零实现BTC区块链 我们今天来开发我们的BTC区块链系统. 简单来说,从数据结构的 ...

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

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

  3. Windows Phone开发(27):隔离存储A

    原文:Windows Phone开发(27):隔离存储A 在很多资料或书籍上都翻译为"独立存储",不过,我想了一下,决定将IsolatedStorage翻译为"隔离存储& ...

  4. Akka(8): 分布式运算:Remoting-远程查找式

    Akka是一种消息驱动运算模式,它实现跨JVM程序运算的方式是通过能跨JVM的消息系统来调动分布在不同JVM上ActorSystem中的Actor进行运算,前题是Akka的地址系统可以支持跨JVM定位 ...

  5. Qt 学习之路 2(27):渐变

    Qt 学习之路 2(27):渐变 豆子 2012年11月20日 Qt 学习之路 2 17条评论 渐变是绘图中很常见的一种功能,简单来说就是可以把几种颜色混合在一起,让它们能够自然地过渡,而不是一下子变 ...

  6. MySQL(27):行锁、表锁、乐观锁、悲观锁

    1. 首先说一下:行锁 和 表锁  主要是针对锁粒度划分的. 一般分为:行锁.表锁.库锁 (1)行锁:访问数据库的时候,锁定整个行数据,防止并发错误. (2)表锁:访问数据库的时候,锁定整个表数据,防 ...

  7. STL笔记(6)标准库:标准库中的排序算法

    STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...

  8. Android Animation学习(二) ApiDemos解析:基本Animatiors使用

    Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.ObjectAnimator.AnimatorSet ApiDemos中Animation部分是单独 ...

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

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

随机推荐

  1. flannel 的连通与隔离 - 每天5分钟玩转 Docker 容器技术(61)

    上一节我们在 flannel 网络中部署了容器,本节讨论 flannel 的连通和隔离特性. flannel 网络连通性 测试 bbox1 和 bbxo2 的连通性: bbox1 能够 ping 到位 ...

  2. Akka(22): Stream:实时操控:动态管道连接-MergeHub,BroadcastHub and PartitionHub

    在现实中我们会经常遇到这样的场景:有一个固定的数据源Source,我们希望按照程序运行状态来接驳任意数量的下游接收方subscriber.又或者我需要在程序运行时(runtime)把多个数据流向某个固 ...

  3. Nodejs进阶:服务端字符编解码&乱码处理

    写在前面 在web服务端开发中,字符的编解码几乎每天都要打交道.编解码一旦处理不当,就会出现令人头疼的乱码问题. 不少从事node服务端开发的同学,由于对字符编码码相关知识了解不足,遇到问题时,经常会 ...

  4. 解决tomcat的安装文件中点击startup.bat闪退的问题

    遇到这样的问题的时候不要慌,可以使用下面的方式去解决! 如果不用Myeclipse部署Tomcat,使用Tomcat中bin文件加下的startup.bat启动服务会出现闪退的情况,其原因是没有为To ...

  5. Junit单元测试实例

    1.非注解 public class Test { @org.junit.Test public void testPrice() { ClassPathXmlApplicationContext b ...

  6. 探索 Java 热部署

    在 JAVA 开发领域,热部署一直是一个难以解决的问题,目前的 JAVA 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的应用来 ...

  7. 规则集Set与线性表List性能分析

    前言 本章节将通过实验,测试规则集与线性表的性能.那么如何进行实验呢?针对不同的集合都进行指定数量元素的添加和删除操作,计算耗费时间进行分析. 那么,前两个章节呢,我们分别讲述了什么时候使用Set以及 ...

  8. VMware bridge 桥接方式连接internet

    经过反复测试,关于VMware内虚拟机(包括ubuntu linux和windows)连接internet 目前的结论是 使用bridge方式时,VMware相当于一个交换机(switch),虚拟机和 ...

  9. C#之实参和形参

    1.值类型 例如:我们定义一个函数 static void Exchange(int x, int y) { int flag = x; flag = y; y = x; x = flag; } 其中 ...

  10. grunt之clean、copy

    心情不太好,正好这部分比较简单,记个流水账. ----------流水很清楚惜花这个责任,真的身份不过送运---------- clean.copy算是很重要也很简单的基本组件了. clean(V0. ...