今天我们来讲解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. wps填充1到1000

    A1单元格1 ,选中,填充,序列,确定

  2. 慕课笔记利用css进行布局【两列布局】

    <html> <head> <title>两列布局</title> <style type="text/css"> bo ...

  3. 详解js变量声明提升

    之前一直觉会认为javascript代码执行是由上到下一行行执行的.自从看了<你不知道的JS>后发现这个观点并不完全正确.先来给大家举一个书本上的的例子: var a='hello wor ...

  4. Codeforces Round #372 (Div. 2) A .Crazy Computer/B. Complete the Word

    Codeforces Round #372 (Div. 2) 不知不觉自己怎么变的这么水了,几百年前做A.B的水平,现在依旧停留在A.B水平.甚至B题还不会做.难道是带着一种功利性的态度患得患失?总共 ...

  5. [luoguP1489] 猫狗大战(DP)

    传送门 类似背包的做法. f[i][j]表示是否能放i个物品,价格为j #include <cstdio> #include <iostream> #define N 8001 ...

  6. SOJ 2800_三角形

    真的是O不是0[看了discuss才发现.....一个大写的蠢 [题意]多个黑白三角形组成的倒三角,求白三角形组成的最大倒三角的面积 [分析]由于问的是倒三角个数,所以只需看与行数奇偶性相同的白色倒三 ...

  7. Redis Cluster集群搭建后,客户端的连接研究(Spring/Jedis)(待实践)

    说明:无论是否已经搭建好集群,还是使用什么样的客户端去连接,都是必须把全部IP列表集成进去,然后随机往其中一个IP写. 这样做的好处: 1.随机IP写入之后,Redis Cluster代理层会自动根据 ...

  8. DELL T110II Server如何通过RAID 级别迁移的方式在OMSA下实现磁盘阵列扩容?

    目录: RAID 转移规则说明 操作步骤 本文介绍了 通过RAID 级别转换来实现扩容的方法注意:本文相关RAID的操作,仅供在测试环境里学习和理解戴尔PowerEdge服务器RAID控制卡的功能和使 ...

  9. vmstat输出项解释

    输出项的解释例如以下: procs * r列表示执行和等待cpu时间片段的进程数,这个值假设长期大约系统cpu个数.说明cpu不足 * b列表示在等待资源的进程数.比方正在等待IO或者内存交换等等 m ...

  10. hbase shell经常使用命令

    hbase经常使用命令 /usr/local/cloud/hbase/bin/hbase shell 用shell来连接hbase exit 退出hbase shell version 查看hbase ...