网站模版开发,uncode wordpress主题,wordpress优化cookie,做网站用asp还是php好简介#xff1a; 前端开发的本质是什么#xff1f;响应式编程相对于 MVVM 或者 Redux 有什么优点#xff1f;响应式编程的思想是否可以应用到后端开发中#xff1f;本文以一个新闻网站为例#xff0c;阐述在前端开发中如何使用响应式编程思想#xff1b;再以计算电商平台… 简介 前端开发的本质是什么响应式编程相对于 MVVM 或者 Redux 有什么优点响应式编程的思想是否可以应用到后端开发中本文以一个新闻网站为例阐述在前端开发中如何使用响应式编程思想再以计算电商平台双11每小时成交额为例分享同样的思想在实时计算中的相同与不同之处。 一 前端开发在开发什么
大家在前端开发的过程中可能会想过这样一个问题前端开发究竟是在开发什么在我看来前端开发的本质是让网页视图能够正确地响应相关事件。在这句话中有三个关键字网页视图正确地响应和相关事件。
相关事件可能包括页面点击鼠标滑动定时器服务端请求等等正确地响应意味着我们要根据相关的事件来修改一些状态而网页视图就是我们前端开发中最熟悉的部分了。
按照这样的观点我们可以给出这样 视图 响应函数(事件) 的公式 View reactionFn(Event) 在前端开发中需要被处理事件可以归类为以下三种
用户执行页面动作例如 click, mousemove 等事件。远程服务端与本地的数据交互例如 fetch, websocket。本地的异步事件例如 setTimeout, setInterval async_event。这样我们的公式就可以进一步推导为 View reactionFn(UserEvent | Timer | Remote API) 二 应用中的逻辑处理
为了能够更进一步理解这个公式与前端开发的关系我们以新闻网站举例该网站有以下三个要求
单击刷新单击 Button 刷新数据。勾选刷新勾选 Checkbox 时自动刷新否则停止自动刷新。下拉刷新当用户从屏幕顶端下拉时刷新数据。
如果从前端的角度分析这三种需求分别对应着
单击刷新click - fetch勾选刷新change - (setInterval clearInterval) - fetch下拉刷新(touchstart touchmove touchend) - fetch news_app1 MVVM
在 MVVM 的模式下对应上文的响应函数reactionFn会在 Model 与 ViewModel 或者 View 与 ViewModel 之间进行被执行而事件 (Event) 会在 View 与 ViewModel 之间进行处理。 MVVM 可以很好的抽象视图层与数据层但是响应函数reactionFn会散落在不同的转换过程中这会导致数据的赋值与收集过程难以进行精确追踪。另外因为事件 (Event) 的处理在该模型中与视图部分紧密相关导致 View 与 ViewModel 之间对事件处理的逻辑复用困难。
2 Redux
在 Redux 最简单的模型下若干个事件 (Event) 的组合会对应到一个 Action 上而 reducer 函数可以被直接认为与上文提到的响应函数 (reactionFn) 对应。 但是在 Redux 中
State 只能用于描述中间状态而不能描述中间过程。Action 与 Event 的关系并非一一对应导致 State 难以追踪实际变化来源。
3 响应式编程与 RxJS
维基百科中是这样定义响应式编程 在计算中响应式编程或反应式编程英语Reactive programming是一种面向数据流和变化传播的声明式编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流而相关的计算模型会自动将变化的值通过数据流进行传播。 以数据流维度重新考虑用户使用该应用的流程
点击按钮 - 触发刷新事件 - 发送请求 - 更新视图勾选自动刷新手指触摸屏幕自动刷新间隔 - 触发刷新事件 - 发送请求 - 更新视图手指在屏幕上下滑自动刷新间隔 - 触发刷新事件 - 发送请求 - 更新视图手指在屏幕上停止滑动 - 触发下拉刷新事件 - 发送请求 - 更新视图自动刷新间隔 - 触发刷新事件 - 发送请求 - 更新视图关闭自动刷新
以 Marbles 图表示 拆分上图逻辑就会得到使用响应式编程开发当前新闻应用时的三个步骤
定义源数据流组合/转换数据流消费数据流并更新视图
我们分别来进行详细描述。
定义源数据流
使用 RxJS我们可以很方便的定义出各种 Event 数据流。
1单击操作
涉及 click 数据流。
click$ fromEventMouseEvent(document.querySelector(button), click);
2勾选操作
涉及 change 数据流。
change$ fromEvent(document.querySelector(input), change);
3下拉操作
涉及 touchstart, touchmove 与 touchend 三个数据流。
touchstart$ fromEventTouchEvent(document, touchstart);
touchend$ fromEventTouchEvent(document, touchend);
touchmove$ fromEventTouchEvent(document, touchmove);
4定时刷新
interval$ interval(5000);
5服务端请求
fetch$ fromFetch(https://randomapi.azurewebsites.net/api/users);
组合/转换数据流
1点击刷新事件流
在点击刷新时我们希望短时间内多次点击只触发最后一次这通过 RxJS 的 debounceTime operator 就可以实现。 clickRefresh$ this.click$.pipe(debounceTime(300));
2自动刷新流
使用 RxJS 的 switchMap 与之前定义好的 interval$ 数据流配合。 autoRefresh$ change$.pipe(switchMap(enabled (enabled ? interval$ : EMPTY))
);
3下拉刷新流
结合之前定义好的 touchstart$touchmove$ 与 touchend$ 数据流。 pullRefresh$ touchstart$.pipe(switchMap(touchStartEvent touchmove$.pipe(map(touchMoveEvent touchMoveEvent.touches[0].pageY - touchStartEvent.touches[0].pageY),takeUntil(touchend$))),filter(position position 300),take(1),repeat()
);
最后我们通过 merge 函数将定义好的 clickRefresh$autoRefresh$ 与 pullRefresh$ 合并就得到了刷新数据流。 refresh$ merge(clickRefresh$, autoRefresh$, pullRefresh$));
消费数据流并更新视图
将刷新数据流直接通过 switchMap 打平到在第一步到定义好的 fetch$我们就获得了视图数据流。 可以通过在 Angular 框架中可以直接 async pipe 将视图流直接映射为视图
div *ngForlet user of view$ | async
/div
在其他框架中可以通过 subscribe 获得数据流中的真实数据再更新视图。
至此我们就使用响应式编程完整的开发完成了当前新闻应用示例代码[1]由 Angular 开发行数不超过 160 行。
我们总结一下使用响应式编程思想开发前端应用时经历的三个过程与第一节中公式的对应关系
View reactionFn(UserEvent | Timer | Remote API)
1描述源数据流
与事件UserEvent | Timer | Remote API 对应在 RxJS 中对应函数分别是
UserEvent: fromEventTimer: interval, timerRemote API: fromFetch, webSocket
2组合转换数据流
与响应函数(reactionFn)对应在 RxJS 中对应的部分方法是
COMBINING: merge, combineLatest, zipMAPPING: mapFILTERING: filterREDUCING: reduce, max, count, scanTAKING: take, takeWhileSKIPPING: skip, skipWhile, takeLast, lastTIME: delay, debounceTime, throttleTime
3消费数据流更新视图
与 View 对应在 RxJS 及 Angular 中可以使用
subscribeasync pipe
响应式编程相对于 MVVM 或者 Redux 有什么优点呢
描述事件发生的本身而非计算过程或者中间状态。提供了组合和转换数据流的方法这也意味着我们获得了复用持续变化数据的方法。由于所有数据流均由层层组合与转换获得这也就意味着我们可以精确追踪事件及数据变化的来源。
如果我们将 RxJS 的 Marbles 图的时间轴模糊并在每次视图更新时增加纵切面我们就会发现这样两件有趣的事情 Action 是 EventStream 的简化。State 是 Stream 在某个时刻的对应。
难怪我们可以在 Redux 官网中有这样一句话如果你已经使用了 RxJS很可能你不再需要 Redux 了。 The question is: do you really need Redux if you already use Rx? Maybe not. Its not hard to re-implement Redux in Rx. Some say its a two-liner using Rx.scan() method. It may very well be! 写到这里我们对网页视图能够正确地响应相关事件这句话是否可以进行进一步的抽象呢 所有事件 -- 找到 -- 相关事件 -- 做出 -- 响应 而按时间顺序发生的事件本质上就是数据流进一步拓展就可变成 源数据流 -- 转换 -- 中间数据流 -- 订阅 -- 消费数据流 这正是响应式编程在前端能够完美工作的基础思想。但是该思想是否只在前端开发中有所应用呢
答案是否定的该思想不仅可以应用于前端开发在后端开发乃至实时计算中都有着广泛的应用。
三 打破信息之墙
在前后端开发者之间通常由一面叫 REST API 的信息之墙隔开REST API 隔离了前后端开发者的职责提升了开发效率。但它同样让前后端开发者的眼界被这面墙隔开让我们试着来推倒这面信息之墙一窥同样的思想在实时计算中的应用。
1 实时计算 与 Apache Flink
在开始下一部分之前让我们先介绍一下 Flink。Apache Flink 是由 Apache 软件基金会开发的开源流处理框架用于在无边界和有边界数据流上进行有状态的计算。它的数据流编程模型在有限和无限数据集上提供单次事件event-at-a-time处理能力。 在实际的应用中Flink 通常用于开发以下三种应用
事件驱动型应用 事件驱动型应用从一个或多个事件流提取数据并根据到来的事件触发计算、状态更新或其他外部动作。场景包括基于规则的报警异常检测反欺诈等等。数据分析应用 数据分析任务需要从原始数据中提取有价值的信息和指标。例如双十一成交额计算网络质量监测等等。数据管道(ETL)应用 提取-转换-加载ETL是一种在存储系统之间进行数据转换和迁移的常用方法。ETL 作业通常会周期性地触发将数据从事务型数据库拷贝到分析型数据库或数据仓库。
我们这里以计算电商平台双十一每小时成交额为例看下我们在之前章节得到方案是否仍然可以继续使用。
在这个场景中我们首先要获取用户购物下单数据随后计算每小时成交数据然后将每小时的成交数据转存到数据库并被 Redis 缓存最终通过接口获取后展示在页面中。
在这个链路中的数据流处理逻辑为 用户下单数据流 -- 转换 -- 每小时成交数据流 -- 订阅 -- 写入数据库 与之前章节中介绍的 源数据流 -- 转换 -- 中间数据流 -- 订阅 -- 消费数据流 思想完全一致。
如果我们用 Marbles 描述这个过程就会得到这样的结果看起来很简单似乎使用 RxJS 的 window operator 也可以完成同样的功能但是事实真的如此吗 2 被隐藏的复杂度
真实的实时计算比前端中响应式编程的复杂度要高很多我们在这里举几个例子
事件乱序
在前端开发过程中我们也会碰到事件乱序的情况最经典的情况先发起的请求后收到响应可以用如下的 Marbles 图表示。这种情况在前端有很多种办法进行处理我们在这里就略过不讲。 我们今天想介绍的是数据处理时面临的时间乱序情况。在前端开发中我们有一个很重要的前提这个前提大幅度降低了开发前端应用的复杂度那就是前端事件的发生时间和处理时间相同。 想象一下如果用户执行页面动作例如 click, mousemove 等事件都变成了异步事件并且响应时间未知那整个前端的开发复杂度会如何。
但是事件的发生时间与处理时间不同在实时计算领域是一个重要的前提。我们仍以每小时成交额计算为例当原始数据流经过层层传输之后在计算节点的数据的先后顺很可能已经乱序了。 如果我们仍然以数据的到来时间来进行窗口划分最后的计算结果就会产生错误 为了让 window2 的窗口的计算结果正确我们需要等待 late event 到来之后进行计算但是这样我们就面临了一个两难问题
无限等下去late event 可能在传输过程中丢失window2 窗口永远没有数据产出。等待时间太短late event 还没有到来计算结果错误。
Flink 引入了 Watermark 机制来解决这个问题Watermark 定义了什么时候不再等待 late event本质上提供了实时计算的准确性和实时性的折中方案。
关于 Watermark 有个形象的比喻上学的时候老师会将班级的门关上然后说“从这个点之后来的同学都算迟到了统统罚站“。在 Flink 中Watermark 充当了老师关门的这个动作。 数据反压
在浏览器中使用 RxJS 时不知道大家有没有考虑这样一种情况observable 产生的速度快于 operator 或者 observer 消费的速度时会产生大量的未消费的数据被缓存在内存中。这种情况被称为反压幸运的是在前端产生数据反压只会导致浏览器内存被大量占用除此之外不会有更严重的后果。
但是在实时计算中当数据产生的速度高于中间节点处理能力或者超过了下游数据的消费能力时应当如何处理 对于许多流应用程序来说数据丢失是不可接受的为了保证这一点Flink 设计了这样一种机制
在理想情况在一个持久通道中缓冲数据。当数据产生的速度高于中间节点处理能力或者超过了下游数据的消费能力时速度较慢的接收器会在队列的缓冲作用耗尽后立即降低发送器的速度。更形象的比喻是在数据流流速变慢时将整个管道从水槽“回压”到水源并对水源进行节流以便将速度调整到最慢的部分从而达到稳定状态。Checkpoint
实时计算领域每秒钟处理的数据可能有数十亿条这些数据的处理不可能由单台机器独立完成。事实上在 Flink 中operator 运算逻辑会由不同的 subtask 在 不同的 taskmanager 上执行这时我们就面临了另外一个问题当某台机器发生问题时整体的运算逻辑与状态该如何处理才能保证最后运算结果的正确性 Flink 中引入了 checkpoint 机制用于保证可以对作业的状态和计算位置进行恢复checkpoint 使 Flink 的状态具有良好的容错性。Flink 使用了 Chandy-Lamport algorithm 算法的一种变体称为异步 barrier 快照asynchronous barrier snapshotting。
当开始 checkpoint 时它会让所有 sources 记录它们的偏移量并将编号的 checkpoint barriers 插入到它们的流中。这些 barriers 会经过每个 operator 时标注每个 checkpoint 前后的流部分。 当发生错误时Flink 可以根据 checkpoint 存储的 state 进行状态恢复保证最终结果的正确性。
冰山一角
由于篇幅的关系今天介绍的部分只能是冰山一角不过 源数据流 -- 转换 -- 中间数据流 -- 订阅 -- 消费数据流 的模型无论在响应式编程还是实时计算都是通用的希望这篇文章能够让大家对数据流的思想有更多的思考。
作者开发者小助手_LS
原文链接
本文为阿里云原创内容未经允许不得转载