今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具。之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深入,才逐渐明白它到底是怎么一回事。下面介绍主要摘自akka官网,但会融入我的理解,以及部分源码,以减少大家学习的难度。

  首先近几年流式计算很火,有各种各样的框架,比如spark、storm、flink等,当然前提是我们得有这样的需求。随着数据量越来越大,我们很难一次性处理全部的数据,只能采用流水线或周期性的取一部分数据进行加工。简单来说就是“分而治之”。

  Actors是基于消息通信的异步机制,也可以用来处理流式数据。akka使actor变得稳定、可恢复,但我们还需要仔细的考虑数据过载的问题。比如某个actor处理消息过慢,导致后续消息积压在mailbox中。Actor的消息也可能丢失,必要时就需要重传。当以固定的模式处理流式数据的元素时,actor就显得力不从心了,或者我们需要花很大的代价来确保正确性、准确性。

  所以,akka团队提供了一套Akka Streams API,主要目的是提供一套直观的、安全的方法来规范流式处理过程,这样我们就可以用有限的资源来高效的执行流式计算,当然再也不会有内存溢出的错误了。当然前提是有一套背压的机制,背压是“Reactive Streams”的核心概念,Akka是“Reactive Streams”的创建成员。这也就意味着我们可以在Akka Streams中无缝的与其他Reactive Streams实现进行交互。

  Akka Streams与Reactive Streams完全解耦,前者关注在数据流转换的格式化,后者是用来定义通用的机制来跨异步边界移动数据而且不会丢失数据、缓存数据、耗尽资源。简单来说,Akka Streams是面向开发者的,它内部使用Reactive Streams接口来传递数据。其实,简单来说就是Akka Streams定义了一套开发者友好的API,并在内部把这些API转换成了Reactive Streams接口,并在内部用actor实现了Reactive Streams接口。

  那Reactive Streams接口都有什么呢?

  • Publisher
  • Subscriber
  • Subscription
  • Processor

  Reactive Streams由四个组件构成,分别为消息发布者、订阅者、订阅(或者称为令牌)、处理器。

public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}

  Publisher貌似很简单,就只有subscribe接口,是订阅者调用的,用来订阅发布者的消息。发布者在订阅者调用request之后把消息push给订阅者。

public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}

  Subscriber也很简单,就是四个接口,分别为异步触发。当然了,是由Publisher触发调用的。onSubscribe告诉订阅者订阅成功,并返回了一个Subscription,通过Subscription订阅者可以告诉发布者发送指定数量的消息;onNext是发布者有消息时,调用订阅者这个接口来达到发布消息的目的的;onError通知订阅者,发布者出现了错误;onComplete通知订阅者消息发送完毕。当然这些接口都是异步的。

public interface Subscription {
public void request(long n);
public void cancel();
}

  Subscription只有两个接口,请求n个消息,取消此次订阅。

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

  Processor代表一个处理阶段,同时继承了Subscriber,Publisher。

  其实Reactive Streams只是通过上面的四个组件和相关的函数,对反应式流进行了一个框架性的约定,并没有具体的实现。简单来说,它只提供通用的、合适的解决方案,大家都按照这个规约来实现就好了。Akka Streams就是这样的一个实现,只不过又对其进行了封装,使其更加易用。

  我们来看看Akka Streams的核心概念。Akka Streams是一个库,用有限的缓冲空间来处理、转换一系列数据。翻译成日常术语就是,它能够表达成对一系列数据处理的连,每个加工节点都是独立的(而且尽量并行的),同时只缓存有限数量的元素。当然了,有限的缓存这一点与Actor模型有很大不同,因为Akka Streams并不会去主动丢弃数据。

  Akka Streams中的Stream就是一个active的移动、转换数据的进程。Element是流中南的一个处理单元。所有的转换操作把Elements从上游移动到下游。背压是一种流量控制手段,一种数据消费者通知生产者当前可用性的方法,它可以减慢上游生产者产生数据的速度。在Akka中,背压是非阻塞和异步的。Akka Streams中所有操作都是非阻塞的。Akka Streams的计算逻辑是用Graph来描述的,它定义了元素被处理的路径,但不一定是一个DAG。Operator是编译Graph的通用名称,常见的有map、filter。

  Akka Streams有几个核心的概念,需要我们理解和掌握。

  Source。这是一个只会产生数据的操作,它在下游可以接收的时候发送数据。

  Sink。这是一个只有输入的操作。对数据的请求和接受有可能会减慢上游数据的产生速度。

  Flow。这是只有一个输入和一个输出的操作,它连接上下游,传输数据。

  RunnableGraph。这是一个同时具有Source和Sink的流,也就意味着它可以运行。简单来说就是,它可以被编译成actor拓扑了,数据可以经过actor进行流转并被处理。

    val source = Source(1 to 10)
val sink = Sink.fold[Int, Int](0)(_ + _) // connect the Source to the Sink, obtaining a RunnableGraph
val runnable: RunnableGraph[Future[Int]] = source.toMat(sink)(Keep.right) // materialize the flow and get the value of the FoldSink
val sum: Future[Int] = runnable.run()

  我们简要分析一下这几个核心概念的源码。

/**
* A `Source` is a set of stream processing steps that has one open output. It can comprise
* any number of internal sources and transformations that are wired together, or it can be
* an “atomic” source, e.g. from a collection or a file. Materialization turns a Source into
* a Reactive Streams `Publisher` (at least conceptually).
*/
final class Source[+Out, +Mat](
override val traversalBuilder: LinearTraversalBuilder,
override val shape: SourceShape[Out])
extends FlowOpsMat[Out, Mat] with Graph[SourceShape[Out], Mat]

  官网注释的最后一句话非常重要,它说Materialization把一个Source转换成了Reactive Streams规范中的Publisher,至少是概念上的。

/**
* A `Sink` is a set of stream processing steps that has one open input.
* Can be used as a `Subscriber`
*/
final class Sink[-In, +Mat](
override val traversalBuilder: LinearTraversalBuilder,
override val shape: SinkShape[In])
extends Graph[SinkShape[In], Mat]

  Sink可以被用作一个Subscriber。

/**
* A `Flow` is a set of stream processing steps that has one open input and one open output.
*/
final class Flow[-In, +Out, +Mat](
override val traversalBuilder: LinearTraversalBuilder,
override val shape: FlowShape[In, Out])
extends FlowOpsMat[Out, Mat] with Graph[FlowShape[In, Out], Mat]
/**
* Flow with attached input and output, can be executed.
*/
final case class RunnableGraph[+Mat](override val traversalBuilder: TraversalBuilder) extends Graph[ClosedShape, Mat] {

  官网对Flow和RunnableGraph的注释很简单,这其实非常不利于我们深层次的研究AkkaStreams的实现原理。但我们可以不负责任的猜一下。AkkaStreams的API首先被翻译成RecativeStreams相关的组件及其接口的调用,然后通过ActorSystem和actors实现这些核心组件,比如Publisher、Subscriber。当然了,考虑到这个编译过程的复杂性,这部分的源码估计要后面很久才能深入分析了。  

akka-streams

Akka源码分析-Akka-Streams-概念入门的更多相关文章

  1. Akka源码分析-Akka Typed

    对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...

  2. Redux源码分析之基本概念

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  3. Akka源码分析-Cluster-DistributedData

    上一篇博客我们研究了集群的分片源码,虽然akka的集群分片的初衷是用来解决actor分布的,但如果我们稍加改造就可以很轻松的开发出一个简单的分布式缓存系统,怎么做?哈哈很简单啊,实体actor的id就 ...

  4. Akka源码分析-Persistence-AtLeastOnceDelivery

    使用过akka的应该都知道,默认情况下,消息是按照最多一次发送的,也就是tell函数会尽量把消息发送出去,如果发送失败,不会重发.但有些业务场景,消息的发送需要满足最少一次,也就是至少要成功发送一次. ...

  5. Akka源码分析-Persistence

    在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...

  6. Akka源码分析-Cluster-Metrics

    一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...

  7. Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster

    在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...

  8. Akka源码分析-Cluster-Singleton

    akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...

  9. Akka源码分析-local-DeathWatch

    生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...

随机推荐

  1. 使用回溯法解批处理作业调度问题<算法分析>

    一.实验内容及要求 1.要求用回溯法原理求解问题: 2.要求手工输入t1[10]及t2[10],t1[i]是任务i在机器1上的执行时间,t2[i]是任务i在机器2上的执行时间: 3.求出最优批处理作业 ...

  2. [luoguP3390]【模板】矩阵快速幂

    传送门 模板不解释. ——代码 #include <cstdio> #include <cstring> #define LL long long int n; LL k; ; ...

  3. noip模拟赛 写代码

    分析:这其实就是括号匹配题,一眼贪心题,不过一开始贪错了,以为([)]是合法的......其实括号之间不能嵌套. 一开始的想法是尽量往左边填左括号,因为每种括号的数量都确定了,那么左括号和右括号的数量 ...

  4. 51nod1020 逆序排列

    t<=10000个问,每次问n<=1000的全排列中逆序数对为k<=10000个的有多少,mod 1e9+7. 直接dp,$f(i,j)$--i的全排列中逆序数对为j的有多少,$f( ...

  5. Linux下汇编语言学习笔记6 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  6. [bzoj4827][Hnoi2017]礼物_FFT

    礼物 bzoj-4827 Hnoi-2017 题目大意:给定两个长度为$n$的手环,第一个手环上的$n$个权值为$x_i$,第二个为$y_i$.现在我可以同时将所有的$x_i$同时加上自然数$c$.我 ...

  7. 原则 principles

    1.找到对的人来讨论问题. 2.把工作分配给对的人才能把事情做对. 3.

  8. linux shell学习一点点

    问题,从shell command 交互式地由用户输入密码,但是输入的过程中不会显示用户输入的密码,起到类似于于html中input type=password的作用. #!/bin/bash rea ...

  9. HDU 4923 Room and Moor(瞎搞题)

    瞎搞题啊.找出1 1 0 0这样的序列,然后存起来,这样的情况下最好的选择是1的个数除以这段的总和. 然后从前向后扫一遍.变扫边进行合并.每次合并.合并的是他的前驱.这样到最后从t-1找出的那条链就是 ...

  10. 使用带粒子效果的 CAEmitterLayer

    1.用CAEmitterLayer产生粒子效果 2.封装CAEmitterLayer 3.封装下雪.下雨的粒子效果控件 一.用CAEmitterLayer产生粒子效果 - (void)emitterL ...