这是我参与「第四届青训营 」笔记创作活动的第5天
StreamProcessing
我为自己没有课前预习的习惯忏悔,这第二节课听得就跟听天书似的。
流处理的一些疑惑
虽然是天书,抱着啃完之后,如果不去思考具体业务和代码编写,其实理解起来也挺容易的,但是最让我感到困惑的,还是流处理的特性。
这里总述一下我对 flink,或者说 所有的流处理 都存在的疑惑:老师说流处理就像水龙头一样,是源源不绝的,那么就是说即便流正在别的地方运行,你依旧可以向流中塞东西;flink处理中有task的概念,并衍生出上游和下游的概念,但是不管是在上游或者下游,至少这是同一个流,也就是说同一个流中的状态是可以不同的。
整理一下得到以下两点:
- 为什么流在运行时可以继续向其中添加数据
- 为什么同一个流中的数据状态可以是不同的
我自然是学习过stream的,但是并没有深入,我知道stream将数据进行统一的管理,对数据整体做出归并、排序、过滤、分组等操作,我都有一一尝试过,但是都浅尝辄止,没有继续深入。
疑惑解决
为什么一个流中的数据状态可以不同
其实这个问题很好解决,只要一些简单的代码就能验证:
public class test {
List<String> strings = new ArrayList<>(Arrays.asList("樱庭怜依","桃沢萌樱子","酒井樱子","小林诗帆乃","芙兰达","陈玉君"));
@Test
public void Test1() {
strings.stream()
.filter(item -> {
System.out.println("1-过滤: " + item);
return item.length() >= 5;
})
.map(item -> {
System.out.println("2-逐个: " + item);
return item + "123";
}).forEach(System.out::println);
}
}
很容易验证,数据的处理是一条数据跑完之后,再去运行下一条数据,这就可以清晰的认识到为什么一个流中数据的状态可以不同。其实我一直以为是基于行为的,所有数据将操作一完成,再一起去跑操作二。这与 深度遍历与广度遍历 的差别还挺相似的。
当然这里指的是处理都是无状态的中间操作,像sort之类的有状态操作就需要等到所有数据到齐之后才会统一排序。
为什么正在运行的流可以添加数据
产生这个问题的原因,是我没有正确认识到流的本质。*流不是一种数据结构,而是一种数据处理的解决方案。*流不储存数据,仅仅只是处理数据。如果将其比喻成河流,stream其实指的并不是其中的河水,而是指河床。数据源一直是你调用stream的那个集合。并且流的处理是依照类似于"深度遍历"一样的操作,因此新增数据的状态不同也不会影响流的运行
public class test {
List<String> strings = new ArrayList<>(Arrays.asList("樱庭怜依","桃沢萌樱子","酒井樱子","小林诗帆乃","芙兰达","陈玉君"));
@Test
public void Test3() {
// 启一个线程去执行流操作
new Thread(() -> {
strings.stream().filter(item -> {
if (item.length() < 5) {
System.out.println(item + "字数小于5,通过");
return true;
} else {
System.out.println(item + "字数大于5,过滤");
return false;
}
}).sorted().forEach(System.out::println);
}).start();
// 主线程中去新增数据
int i = 0;
do {
if (++i < 10)
strings.add(new Random().toString());
} while (true);
}
}
这样就能看到确实即便流正在运行,然而依旧可以从数据源处增加数据。但是实验过程中着实遇到很多难以理解的bug,比如将主线程中增加的字符串变为拼接字符串,那就必然报错,原因未知。
新的疑惑
虽然解决了以上两个问题,但是仔细思考之后,又出现了两个疑惑,这俩疑惑确实我没能解决,希望有看到这篇笔记的大佬知道的话Q我以下:
如何使得流持久?
即便部分时间中流中没有数据,数据流也不会关闭
同一个数据流可以长时间反复利用,而不是每次来数据后重新开辟内存并初始化流
如何使得流并发?
线程与线程的并发?进程与进程的并发?分布式跨机器的并发?
如何做到数据条目唯一和数据处理唯一?
java Stream
这里面东西挺多的,就不细讲了,罗列一下关键词:Predicate,referencePipeline双向链表,Stage,sink,spliterator。
如果你能切实掌握这几个关键词的含义,那么java stream应该就算是道行较深了
Flink
Why Flink
spark是一款优秀的批处理计算系统,它适合做周期较长(24小时以上)的大批量数据的处理,但是却失去了实时性。假设存在需要实时监控业务系统,或者金融系统检测异常交易等等及时性的工作,那么spark就难以胜任。
flink可以做到实时计算,快速,低延迟,可以处理无边界的数据,同样的,它也支持批处理的计算,做到了"流批一体"
What Flink
分层架构
flink的架构由上到下可以大体分为4层:
- SDK:留给开发人员的API
- Runtime:执行引擎层,将底层所有的数据格式处理为DAG图,再将DAG转化为分布式Task,Task之间通过Shuffle传输数据
- State Backend:状态存储层,负责存储算子的状态信息
- Resource Manager:资源调度层,多环境部署支持
Flink 运作机制
客户端(请求)->Dispatcher(拉起)->JobMaster(申请插槽)->ResourceManager(拉起)->TaskManager(注册)->
ResourceManager(响应申请)->JobMaster(调度task)->TaskManager
Flink 作业流程
source operator(资源算子)->transformation operators(多个变换算子)->sink operator(下沉算子)
下沉可以理解为结束,将处理后数据写到磁盘上。
Flink会在流程中优化算法,这里不同的 operator 会尽可能被链接(chain)到一起,这样每一个 task 可以在同一个线程中执行,减少cpu和内存开销,其内部被称为 OperatorChain。
参考上面的 flink运作机制 ,这个 task 最终会被调度到具体的 TaskManager 上的 slot (插槽) 上,然后slot去运行task上的subtask。
总结
很遗憾很多地方因为缺乏基础知识难以听懂,我能做到的就是将一些简单的,以及抽象的与代码无关的东西看懂,剩下的就交给时间沉淀了
总的来说我收获了 flink 流处理方面的运行机制,知道他的分层架构,以及实际作业中flink的运转机制,以及一些私下来查资料了解的stream流知识
不过请注意不要把 flink 的流和 java stream流 混为一谈,这两不是一个东西,flink 的流处理底层的确依赖 java stream,但是并不全是,而且早期 flink 中几乎见不着 java stream 的影子。不过他们所想要表达的思想都是类似的