00-流式编程思想1
背景
- 事件数据的产生随着时间的推移逐渐下降
- 人们对某件事的理解往往来自基于有效论据的结论。要获得这样的结论,最有效的方法就是沿着事件发生的轨迹进行分析
- 传统开发建立在有限的数据集基础上;那么随着数据量的增量,开发成本将会飙升
- 通信行业的发展,促生了很多新生领域,如电商中推荐、物联网、机器学习等,新生的领域对数据的要求天然就是数据低延迟传输
目标
-
低延迟
-
高吞吐
-
可容错
系统崩溃之后,重新启动,并产出准确结果
-
可基于事件发生时间处理
按照正确的顺序跟踪事件,流处理的结果与事件实际发生的顺序一致
流式开发
思考
谈到流,大家很定会想到IO流;那么我们就先看下IO流;
IO流:一连串流动的字符,以先进先出方式发送信息的通道
特点:具有方向(输入流/输出流);具有顺序(字符先进先出);
随着java8的出现,引入了lambda带来的函数式编程;对多个元素进行操作,可以预先拼接一个“模型”步骤方案,然后再按照方案去执行它。
如:list.stream.filter.map.collect();
函数式编程中“Stream流”其实是一个集合元素的函数模型(处理的步骤方法),它并不是集合,也不是数据结构,其本身并不存储元素。
特点:构建执行步骤,各个算法与算法之间是无状态的
在生活中我们也会经常遇到流,网页上的点击流、汽车发送的GPS信号、移动通信基站、可穿戴设备的信号等;再具体些,如水流、公路上车流、滚动电梯、直行电梯、生产车间的工作流等等;
流数据源头广泛,但是流数据更真实的反映了我们的生活方式。
生活场景流式
工厂流水线.png工厂的流水线是一个比较典型的流式场景;如图传送带就是数据流,传送带旁的每个工位,就相当于流式处理中的filter、map等函数
Stream流式编程
Stram流式编程又叫函数式编程,是基于数据的一种声明式编程范式;
函数式编程的核心:
- 输入:不可变的值
- 函数:一种映射关系,将一个值映射成另外一个值
- 输出:映射后的数据
函数式编程的特征:
- 不可变:输入数据是不能改变的,返回的是全新的数据
- 无状态:函数不维护任何状态
函数式编程的优势:
- 线程安全
- 重构代码无伤害
- 函数执行没有顺序上的问题
函数式编程思维:
- 函数式编程是声明式编程,因此关注的是做什么而不是怎么做
函数式编程小例(java8实现):
背景:要筛选出学生中,性别为男、年龄在10岁以上、身高大于1.3m的学生名字
public class Student {
//姓名
private String name;
//年龄
private int age;
//性别
private String sex;
//身高
private double height;
public Student(String name, int age, String sex, double height) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
}
//忽略get/set...
}
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("小明", 11, "男", 15.4));
studentList.add(new Student("小红", 10, "女", 13.0));
studentList.add(new Student("小张", 9, "男", 13.5));
studentList.add(new Student("小王", 11, "男", 12.9));
studentList.add(new Student("小赵", 12, "男", 15.0));
List<String> names = studentList.stream()
.filter(student -> student.getSex().equals("男"))
.filter(student -> student.getAge() > 10)
.filter(student -> student.getHeight() > 13.0)
.sorted(Comparator.comparing(student -> student.getHeight()))
.map(student -> student.getName())
.collect(Collectors.toList());
for (String name : names) {
System.out.println(name);
}
输出:
小赵
小明
例子比较简单,我们主要关注stream编程方式
概述图:
Lambda1.png详细图:
Lambda2.png撇开java8函数式流实现,其实我们用集合for循环也是能实现的,那么流式操作和集合操作有什么区别呢
流和集合的区别
- 流是基于数据的,关注的是一条条数据是什么样的处理;集合是基于存储,关注的是整体
- 流遍历一次后,返回的是一个新流,之前遍历的流已经不存在了,也就是说这个流已经被消费了,和迭代器一样,不能回头消费;集合循环完之后,集合还是存在的,可以继续重头循环
- 从哲学的角度来看,流可以看做时间中分布的一组值。集合则是空间中(计算机内存)分布的一组值
函数式编程用到技术
-
头等函数
-
尾递归优化
-
pipeline(管道)
将函数实例为一个个action,然后action放到一个数组中,再把数据传给这个action list,数据就像一个pipeline一样顺序被这些函数操作,最终得到我们希望的结果
-
递归
-
柯里化
-
高阶函数
编程范式
编程范式一般可以分为三类:命令式、声明式、元编程
编程范式.png-
过程式编程:关注的是怎么做;核心在于模块化,实现过程使用了状态,依赖了外部变量,可读性差,维护成本高
-
面向对象编程:关注的是抽象,提供了清晰的对象边界。封装、继承、多态的特性,降低了代码的耦合度,提升了系统的可维护性
程序是一些列相互作用的对象
抽象:怎么为一个模糊不清的问题找到一个恰当的描述就是抽象,抽象也是我们简化复杂问题的一种方式
在面向对象编程里,计算程序会被设计成彼此相关的对象。对象则是类的实例,它是程序的基本单元。
-
函数式编程:关注的是做什么;传入数据不可变性,当前函数也不依赖函数外的数据,使得函数具有自描述性,可读性高
程序是一系列无状态的函数组合序列
声明式编程不用告诉计算机问题领域,从而避免随之而来的副作用,而命令式编程则需要告诉计算机每一步该怎么做
对比:
面向对象 | 关注抽象和状态;描述对象基本特征 | 易于抽象和理解<br />代码容易重用<br />代码容易维护<br />具有可扩展性,便于阅读 | 并发不安全<br />代码易膨胀<br />性能较低 |
---|---|---|---|
范式 | 特征 | 优点 | 缺点 |
过程式 | 关注怎么做;易于理解 | 性能高 | 依赖外部变量;<br />可读性低 |
函数式 | 关注做什么;不依赖当前函数外的数据 | 描述问题,易于理解;<br />无副作用(并发安全) | 占用资源,性能相对较差 |
传统开发与流失开发区别
传统的开发会将所有数据存放在数据中心中(如一张表),然后进行加工、分析得出结果(如sql);一句话就是统一收集数据,然后进行分析。如果需要对数据中心中每月/每天数据进行分析,还是没有问题;如果需要对数据中心中每小时/每分钟/每秒进行分析,那么就有可能无法满足。
传统方式.png既然对数据中心中的每秒数据不好处理,是否可以转变一下思想,在入数据中心中的时候,就把数据分析好放入到数据中心中呢?
流式方式.png所以流式计算的上游,是数据流,下游是计算中心或者直接对接业务;
传统开发就是批式处理思想,那么批处理和流处理的区别呢
批处理 | 流处理 |
---|---|
数据有限 | 数据无限 |
数据静态 | 数据动态 |
具有周期性 | 无周期性 |
需要时间响应 | 实时响应 |
那么流处理是不是一定优于批量处理呢,答案是否定的;如家庭用水为例,批处理相当于桶装水,流处理相当于接入水管;饮用水一般使用桶装水,生活用水一般使用自来水(接入水管);该例子主要是以使用量来选择使用场景
数据流
时间分布和数量上无解的一些列动态数据集合
流式开发特点
- 时效性:实时(数据的价值随着时间的流逝而降低,因此必须实时计算给出实时响应)
- 数据特征:动态、没有边界
- 场景:实时场景;如推荐、监控等
概念相关
- 数据流:连续数据组成的流
- 流数据:数据流中的数据
有向无环图(DAG)
概念
有向无环图,同一个方向,但不构成闭环
DAG特点
-
严密的拓扑性质
具有很强的流程表达能力;按照拓扑的顺序对零散的组建进行组织执行,就可以得到正确的结果。
计算领域,基于DAG的计算模型应用还是比较广泛的,如spark/storm/tensorflow
-
去中心化
每个组件拥有各自的状态;组件之间状态互不依赖
-
具有方向
整体具有方向;一个组件的输出可以是下个组件的输入
DAG优势
- 任务模块化
- 易于调整
- 结构清晰
网友评论