收藏 分享(赏)

大数据的Reactive设计范式和Akka实践.pdf

上传人:HR专家 文档编号:6251911 上传时间:2019-04-03 格式:PDF 页数:10 大小:1.19MB
下载 相关 举报
大数据的Reactive设计范式和Akka实践.pdf_第1页
第1页 / 共10页
大数据的Reactive设计范式和Akka实践.pdf_第2页
第2页 / 共10页
大数据的Reactive设计范式和Akka实践.pdf_第3页
第3页 / 共10页
大数据的Reactive设计范式和Akka实践.pdf_第4页
第4页 / 共10页
大数据的Reactive设计范式和Akka实践.pdf_第5页
第5页 / 共10页
点击查看更多>>
资源描述

1、大数据时代的软件架构范式 : Reactive 架构 及 Akka 实践 本文原载于 程序员 杂志 2015 2A 期 钟翔 近一两年来, Reactive 突然成为 分布式 系统 设计 领域的热词 , 在 2013 年 , 业内发布了 Reactive宣言 , 到 2014 年 ,又 第一次出现了 以 Reactive 为主题的 行业 会议 Reactconf。 那 什么是 Reactive,Reactive 又 能解决什么问题 ?本文 希望 能抛砖引玉, 引起 读者 一些思考 。 Reactive 架构 要解决什么问题 Reactive 要解决的问题是 分布式系统设计 中的难点 问题 ,包

2、括: 1. 如何处理 各种各样的错误,比如硬件错误 、 软件错误 、 网络错误 、 硬盘错误 、 内存错误等 。 错误 交给谁处理 、 怎 样 处理 、 怎样交出去 、 要不要恢复、 如何 恢复这些问题都 很 复杂。 2. 软件 耦合过紧 导致 维护复杂。 比如要升级一个公用组件, 牵一发而动全身。 3. 编程复杂。 多线程编程中用锁来保护共享数据,分布式编程中的用事务来实现并发读写,这两个 都非常复杂。 4. 性能调优复杂。 我们通常很习惯过程式的编程, 但调用链上可能有各种阻塞运算比如 IO等, 把它们绑在一个线程 栈 上 是 不合适的。 而要做优化需要修改大量代码 。 这些难点并不直接

3、和业务逻辑 相关,但却占用了开发人员大量的时间。 Reactive 架构 就想解决这 些难点 ,把开发人员从这些业务无关的编程工作中解放出来。 Reactive 架构既是 一个方法论 , 又 是一个 工具 集 ,能够系统性的 化解这些复杂性。 什么是 Reactive 架构 Reactive 架构是以消息驱动为核心的架构 。 实现 Reactive 架构的 应用 是 由许 许 多 多 的微型服务 编织而成。 每个微型服务 粒度很小, 只做一件简单的事情 ; 微型服务 间通过异步的消息发送驱动对方工作 , 一起 组合 实现应用 所需 的 功能 。 Reactive 架构 极 像我们人类社会 的架

4、构 ,每个人 独立承担自己的工作, 相互间 通过 电子 邮件等 消息 通道 来 驱动对方工作 , 实现协同合作 。 而且 这个系统容错性 良 好 , 一方面, 每个人都可以失败, 地球不因某 一 个人就不转 ; 另一方面, 地球上每一个人都在运转,几十亿人分布式工作,忙碌 而 和谐。 人类社会也许是 已知的 最大的分布式系统。 2013 年, Akka 的发明人 Jonas Bonr 等发布了 Reactive 宣言 1,从 定义 上 明确了 Reactive 架构 必备 的 四个 属性 : Message driven: 消息驱动 。 1 Reactive 宣言 http:/www.reac

5、tivemanifesto.org/ Elastic:负载变化 时 弹性增减资源 。 Resilient: 失败时能从中恢复 。 Responsive: 按 QoS 质量要求 响应用户请求。 如果一个应用 满足这四个 属性 , 那 我们就称这个应用 符合 Reactive 架构 。 Reactive 技术上最显著的特点就是消息驱动。 Reactive 架构 的 历史和 现实意义 Reactive 本身而言不是一个新的技术 。 早在 上世纪 70 年代 , 就有科学家对以 消息驱动 为 核心 的 架构 做过细致的理论分析 2。但在大数据时代 的今天 ,这是一个新的命题, 重新 认识Reactiv

6、e 具有时代 价值 。 一方面,大数据的 应用 特点 决定它 正是 Reactive 架构善于解决的问题 领域 ; 第二 方面, 在 现今 大数据 领域 按照 Reactive 思想建构的软件 还太少 ; 第三方面, 建构 Reactive 应用的工具链正在快速成熟,现在使用正当其时。 历史上, Reactive 架构有两次大发展。第一次是 在上世纪 80 年代 的 电 信领域, 当时 Ericsson的 Joe Armstrong 发明了 Erlang 语言 , 实现了 消息驱动的 Actor 模型, 用 Actor 实现的 ATM交换机 AXD3013,达到了 99.9999999%4的在

7、线率, Actor 模型 成为高性能高容错的代表 。 第二次 大 发展 是在上世纪末的 互联网 领域 。 从 1993 年开始的 10 年期间,互联网用户数 爆发增长了 54 倍 5,在 巨大 并发访问情况下,许多 网站 都 遇到了 性能的瓶颈, 即 著名的 C10K6问题 (同时并发请求数无法超过 10K)。 为解决这个问题, 网站架构逐渐切换 到以事件驱动为核心。这里面最负盛名的是 Nginx,它 采用了 异步 IO 和 事件驱动 的模型 , 成功 解决 了 C10K 问题 。 Nginx 因此也快速流行起来 , 2014 年 在 Top 1000 流量 的网站中, 市场 占有率达 42%

8、7。 Reactive 架构 在电信和 互联网 行业 率先落地, 原因在于 这两个行业对高可用性和高并发性都有非常高的要求, 于是相对其他行业 更早 触碰到了 传统架构的瓶颈。 现在 已经 进入大数据和物联网 的新 时代, 据报道, 全球 每年的 新生成 数据将以 140%的速度指数 增长 8;物联网方面,到 2020 年 , 将有 260 亿设备 9。 大规模和分布式 计算 将成为一种新 常态 。 Reactive 架构 的应用将 超越 电信和网站系统 的范围 ,影响更多的垂直行业 , 深入 人们 生活每一处 。 正如 Scala 语言 发明者 Martin Odersky 所说,“ Rea

9、ctive 编程无所不在 10” 。 这正在 成为 Reactive 架构发展的第三波浪潮。 现在业界已经看到了这股趋势, 2014 年, 来自Typesafe、 Twitter、 Oracle 等 公司 的 有识之士 提出了 Reactive Stream11接口规范 ,定义了不同厂商的服务间 如 何以 Reactive 的方式互联,这样整个行业能够相互 兼容的 发展。 2 Carl Hewitt http:/ 3 AXD301 http:/ll2.ai.mit.edu/talks/armstrong.pdf 4 AXD301 NINE nines reliability https:/ 5

10、 Internet user trend http:/ 6 C10K http:/en.wikipedia.org/wiki/C10k_problem 7 W3Techs 2014 http:/ 8 IDC http:/ 9 Gartner report on IOT http:/ 10 Reactive programming is ubiquitous https:/class.coursera.org/reactive-001/lecture/3 11 Reactive Stream http:/www.reactive-streams.org/ Reactive 架构的 技术 要求和

11、设计模式 Reactive 定义的 四个属性中, Responsive 是最终目的 , Message-Driven, Elastic, Resilient 是手段 。 本节将 按四个属性分组, 介绍 Reactive 架构的 设计 模式和实践经验 : 属性一: Message-Driven Message-Driven 是整个 Reactive 架构的技术基础。 Message-Driven 是 指系统由消息推动,实现并发和隔离。 具体 有以下要求: 1. 消息是 唯一 的通信手段。 2. 使用 不可变 的消息。 3. 异步非阻塞 。异步非阻塞是指 发送 方 不用等待接收方处理完 消息 。

12、异步非阻塞能提高应用的并行度。 要实现这些要求,有以下几种典型 设计模式 : 函数式编程 语言 函数式编程 语言 非常适合实现事件驱动的编程。 函数式编程之前,我们只能用事件回调函数,而 回调函数 之间不容易组合 , 比如第二个回调想依赖第一个回调的计算结果, 代码会有多层嵌套,写起来很复杂。而 函数式编程 支持高阶函数,比如 foo1.map(foo2).map(foo3)线性的串联 三 个 回调 ,编程可以大大简化 , 其中 的 map 就是高阶函数 。很多函数式编程语言还提供了 Future Monad, Future Monad 能够串联多个异步非阻塞的调用 。 比如 我们 需 要 先

13、 做磁盘IO, 再做 数据库访问, 使用 Monad 可以这么写: future do disk IO flatmap disk = future do database query 消息队列模式 我们可以用 消息队列 来异步非阻塞的 传送 消息 。发送方和接受方通过消息队列完全解耦,互相之间没有依赖。 Apache Storm 就是一个 很 好 的 应用 例子, 它 用 到了两 类 消息队列,一 类 是ZeroMQ(Storm 0.8 以前 )做跨进程的 消息通道 , 另一个是 Disruptor queue 做进程内的消息通道。 属性二: Responsive Responsive 是指

14、应用 在 大 负载和软硬件错误的情况下仍然能保持实时的 、 交互式的响应 ,它有以下 需求 : 1. 服务 必须在 确定的 时间 边界 内响应 ,无论负载 状况如何,无论发生何种错误。 2. 服务间能组合 。 从前 端 到后端, 一个 用户请求 可能涉及多个服务协同工作。服务 间 要能组合,确保从全 局 来看, 系统 整体仍然 是 Responsive 的。 要做到这一点, 需要 做流速控制, 小心设计服务边界。 3. 在线不停机, 系统升级必须平滑,不影响服务的可用性。 要实现这些要求, 有以下几种 典型 设计模式 : 一问一答模式 一问一答模式的目的是为了确保系统能在确定的时间边界内响应用

15、户请求。 一问一答模式是指对每一个“问”的消息都必须“答”一个消息,无论这个“答”的消息代表成功还是失败。比如用户 U 问 服务 A 一个问题 ,而服务 A 在回答前,需要先咨询服务 B, 数据流 向 是 U - A -B。 该 模式要求 : 无论 A 是否 已经 收 到 B 的回应 , A 都必须在给定的时间边界内回应用户U。如果 B 没有响应 ,则 A 必须 自己 创造 一个超时 消息发送给用户 U。 路由器模式 路由器模式是为了解决 数据流不同节点 处理能力不匹配 的问题 ,提高响应速度 。 假设 数据流 A-B-C 中 A 和 C 比较快,而 B 比较慢,整个系统的响应性能受 B 的拖

16、累。 路由器模式 可以根据负载 变化 动态调整 组件的 并发 度 来解决这个问题 。 假设 B 是瓶颈, 我们可以把 B 转成一个路由器 图 1, 把 B 的工作量分发到多个 B 的 子任务上,提高 B 的并发度 ,同时 对 A 和C 保持 透明 , 即不中断 A 和 C 的 计算 , 这样整个系统 的响应时间可以更快 。 图 1 Router Pattern 开关 模式 (Circuit Breaker) 路由器模式 仍然 假定 整个系统 有空余资源可用 , 如果系统已经到了极限 , 无资源可用 , 这个时候 需要 使用 断 路器 模式 来使服务平滑降级, 保持 对用户响应。 如图 2 所示

17、 ,如果负载过大,断路器会切到 Fail Fast 模式,如果负载恢复正常,则会切回到正常 Service 模式 。 Fail-Fast 模式能 及时返回 给用户一些 信息,缓解用户的焦虑。 图 2 Circuit Breaker Pattern 问答 流控 模式 如果 两个 服务间 通信 量 极大 , 一方可能耗尽另一方的所有资源 怎么办? 这个时候必须做流速控制 。 流控的理论基础是 Little 法则 12, Little 法则是指可以根据 系统 中 存量消息 量和每个消息的 处理 时间来推算系统 的 服务 容量。 流控可以用滑动窗 来实现 图 3, A 发送给 B 已12 http:/

18、en.wikipedia.org/wiki/Littles_law 发送 消息 的 序列号 , B 回 复 A 已 接收 消息 的 序列号 , A 根据 B 的回复来确定是否继续发送消息。 通过这一问一答,我们就可以控制 传输中 消息 的滑动窗的大小。 图 3 Sliding Window 属性三 : Elastic Elastic 指 应用能 弹性使用 计算资源 , 它有以下要求: 1. 增加机器能提高性能 。 2. 升级 CPU 能提升性能 。 3. 能 响应 负载变化 , 弹性 伸缩 。 要实现 Elastic, 最重要的是数据的 切分 和计算的 水平 扩展 。 除此 之 外, 还 有以

19、下几种设计模式 可供参考 : 负载状态即 消息 系统需要监控负载 的 变化 , 并把它定义成显式的消息, 让它 像 正常消息一样在系统中传递 。这样的好处是我们 能够灵活的选择 应对 策略以响应负载变化。 无锁 设计 每个组件状态 都 应该 是私有的 ,不允许别的组件访问 。 这样大部分时候 我们可以 避免使用锁,因为 锁对性能的影响是破坏性的。 根据 Amdahl 法则 13,并行 (指 多线程运行 )化后程序性能加速的上限只取决于代码并发度 (指不同的任务逻辑上不相互依赖 ), 如果代码并发度是 50%,则 即使 有 100 个 CPU,性能上限仍然是 2 倍 。 那有需要共享的数据时如何

20、避免使用锁呢? 锁的目的是控制并发修改, 有的时候我们 目的只是 共享 数据 , 需要 修改 的是引用而不是数据本身 , 这时 可以用 Immutable 的数据结构解决。 比如线程 A 往线程 B 传一个 Immutable Map,B 可以修改,但修改的是一个新的数据副本,和原始数据无关。 Scala 语言的 Immutable 数据结构可以 在 内存上很高效的保存副本。 最终一致性 模式 如果 一定要 对同一份数据 做 并发修改怎么办 ?这时 还 可以用最终一致性模型 来 解决 ,同样可13 http:/en.wikipedia.org/wiki/Amdahl%27s_law 以避免 使

21、用 锁 。 这里的典型代表是 CRDT14数据结构, CRDT 数据结构允许 不同系统 对同一份数据写 , 而且 能 保证 写的 结果是收敛一致的。 图 4 是用 CRDT 的 GCounter 做 分布式 计数 的例子 , a 和 b 代表两个系统,它们各自 维护一个计数 器 ,经过两次异步消息交换,计数收敛到了相同的值 (a:1, b:3), 而最终的计数是两者相加 的结果 4。 图 4 CRDT GCounter CRDT 数据类型使用非常广泛,比如 Akka Cluster 集群管理功能 用 到了 CRDT 数据类型来 保存整个集群的状态。 除了 GCounter 外, CRDT 还

22、有多种数据类型, 比如做分布式加减计数PNCounter,标 记 全局状态的 Flag, 全局收敛的 Set 和 Map 等 。更多数据类型可以参考 NoSQL数据库 Riak 的 实现 15。 位置透明 模式 位置透明是指 一个 应用的不同组件 既 可以在一个 JVM 内运行,也可以在一个分布式环境中运行, 组件间的交互方式是一样的,和 部署 环境无关。 有了 位置透明 模式 , 应用逻辑 就和 部署环境 完全隔离 开来 ,降低了应用的复杂性。 属性四: Resilient Resilient 是指 服务 能够容忍错误发生, 它 有 以下 要求 : 1. 软件异常、硬件错误 、 网络 错误发

23、生时,服务仍然保持可用。 2. 必须响应错误,对错误做恰当的处理和恢复。 3. 一个错误不 应 链式的传播到整个系统,引起锁链式的失败。 要实现一个 Resilient 的系统, 从设计 之初 就 要 考虑容错,系统开发好后 再 增加容错 逻辑 会非常困难。 实现容错的核心是隔离和控制 , 有以下几种设计模式可供参考。 错误 即消息 每一个错误都要 显式 地 定义成消息。 比如 软件异常、硬件错误 , 网络 连接错误 等 ,这些 错误传统上 都是 隐式 定义 的消息, 在函数的返回值上是不体现的,需要专门的 try catch 语句 才能捕捉到 。 把它们 显式定义 成消息 后,错误就可以像正

24、常消息一样被 标记、 传输和 处理 了 。 异步 错误链模式 异步 错误链模式 可 以 系统的解决错误 传输 和 错误 处理 问题 。 传统 编程实践中 ,错误发生时,我们都是抛给调用 方 处理,但调用 方 未必知道该如何处理。异步错误链模式则不同, 它的 核14 CRDT http:/pagesperso-systeme.lip6.fr/Marc.Shapiro/papers/RR-6956.pdf 15 Riak http:/ 心 是 分离 业务调用 链和错误处理链 , 调用链传递正常业务数据, 而错误 链 负责 传递和处理 错误 消息 。 错误发生时, 并 不 把错误 抛 给 调用者 ,

25、而是封装成 显式 消息 发给 它专属的 Supervisor,如图 5 所示, 如果第一级 Supervisor 处理不了,则发给上一级 Supervisor 做处理,级联下去形成一个 错误处理链条,不同的链条 还可以合并成为一颗树 ,称为 Supervision 树。 有了 异步 错误链,错误 就可以按照受控的方式传播 并 被 集中处理 ,而不需要在程序中处处插入错误处理代码。 图 5 Async Error Supervision Chain 超时闹钟模式 超时闹钟模式是为了解决请求超时的问题。 比如 A 通过 UDP 往 B 发消息,但 B 也许永远 没收到 这条消息,也无法回复 。 使

26、用 超时闹钟模式 图 6后 , A 在给 B 发 正常请求 消息的 同时 ,也 给自己设个闹钟,如果没收到 B 的回应,则 闹钟会显式 给自己发一条超时消息。 这样, 这样, B 没回应这个异常本身也被显式的定义成了消息。 图 6 Timeout Timer 替身模式 如果一个服务很复杂,但这个服务同时又很重要,不能失败怎么办? 这个时候可以用替身模式, 危险的事情要交给替身去做 。对每一件危险 任务 都可以创建一个替身, 如果替身殉职了,可以 原地复活 , 本尊 则去监督替身有没有完成工作。危险的 任务 包括 但不限于 1)状态多逻辑复杂的代码 2) 频繁修改 未经充分测试的代码 3)用户上

27、传的自定义的代码 4) IO 网络 操作等可能失败的代码 5)其他可能抛出未预期异常的代码。通过使用替身模式,我们可以把错误封禁在 临时 替身这个“错误内核”里, 而 本尊 则 专 司 简单的,稳定的,不易出错的 工作 ,以及 管理“ 替身 ”们 。 通过这种 隔离 设计,可以 提高整个系统的健壮性 。 有这些设计模式,我们就可以确保错误 可以 得到隔离和控制,每条错误都被显式定义、显式传递、和显式处理。 Reactive 架构 的 开发 工具 Reactive 应用最显著的技术特点就是 Message-Driven。 过去 5 年, Reactive 开发 工具 中 要属Typesafe 的

28、 Akka 最具光彩 。 Akka 在 Scala 语言上实现了 1973 年 Carl Hewitt 提出的 Actor 模型 。 Actor 是一种细粒度的并发 编程 单位,用起来和线程类似,但比线程的粒度要小得多,一个 JVM 里面可以启动数百万个 Actor。然后 Actor 之间是完全隔离的,不共享任何状态数据 , 如果要交换信息,只能像发短信一样发 条 消息 给对方。 Actor 还是 位置透明 的 ,即不论Actor 是 部署 在单机 上 还是在分布式环境中, Actor 之间 都能以一致性的方式交互,和 Actor所在的 物理 位置无关。 有了位置透明, 一个应用不论运行在单个

29、 JVM 里面或在分布式集群中, 代码逻辑都是一样的 , 这让 基于 Actor 实现 的程序 天生 能 scale up 和 Scale out。 图 7 Akka Actor Actor 的行为类似 一个状态机 , 如图 7 所示, 首先 接收输入消息 , 处理消息 然后 改变自己的状态, 最后 发送消息给其他的 Actor。 结构上 Actor由三部分构成 , 状态( State),收件箱 (Inbox),和消息 处理函数 (Message Handler)。 1. 状态: Actor 的状态是私有的,只 对自己可见,对外完全隔离。一个 Actor 看不到另一个 Actor 的任何状态。

30、 2. 收件箱: Actor 之间的通信的唯一通道是消息,消息被保存到收件箱里。消息 发送 是异步 非阻塞的。当某个 Actor 的收件箱有消息时,系统会调度该 Actor 的消息处理函数 到线程池中运行,处理新消息。 如果收件箱没有消息,则 Actor 不占用任何 CPU 资源。 3. 消息 处理函数 : 消息 处理函数 由用户 提供 ,用来处理 收件箱的 消息。 Akka 的消息 处理函数 是纯函数式的, 可以通过 or、 and 等方式组合使用 。 Actor 有良好的容错 设计 。 如果一个 Actor 崩溃了,“崩溃”这个错误 本身 会 被封装成 显式的消息 ,然后 发送给 该 Ac

31、tor 的 supervisor,由 Supervisor Actor 决定如何处理错误。 Supervisor可以选择重启 Actor, 恢复 Actor, 或上报给上游的 Supervisor,直到该错误得到正确处理。 除了隔离和容错, Akka Actor 在实践中 还 有非常优异的性能, 一个 JVM 里面可以启动几百万个 Actor;分布式环境中, 2013 年 的 一组测试 16显示 , Akka Actor 可以 scale out 到 2400 个CPU core 上 。 Reactive 架构的 应用实例 GearPump 流处理 系统 GearPump 是 Intel 开源

32、的 基于 Akka 开发的 流处理平台,是 一个完全 按照 Reactive 思想 设计的大数据产品 。 16 2400 Akka Nodes http:/ 图 8 Gearpump Task Parallelism Gearpump 是完完全全以消息驱动为核心 的流处理引擎 ,设计时 大量采用了前文提到的各种设计模式。 如图 8 所示, 每一个 Task 都有一个 Inbox, Inbox 是一个 消息队列 。 整个系统完全由 消息驱动 ,当 Inbox 有消息时, Task 开始运算,生成的消息送 给 输出 Network Actor, 再经网络送给下级 Task 的 Inbox, 推动下

33、级 Task 运算 ,相互间的数据传输按照 滑动窗做流速控制 。 Task之间是完全隔离的, 不共享任何状态 ,完全 无锁设计 。 应用的 多个 Task 可以在 一个进程内运行 ,也可以 在 不同机器上 运行 , 是 位置透明 的,很容易实现 Scale out 和 Scale up。 为了实现高容错性和可管理性, GearPump实现了一套清晰的 Supervision 架构 ,如图 9所示。 图 9 Actor Supervision Hierarchy 图中的每一个框都是一个 Akka Actor, 它们通过 Supervision 和 Watch 关系 编织在一起。 Actor间的相

34、互通信都遵循 一问一答模式 和 超时闹钟 模式 , 确保整个系统是 Responsive 的。 Master和 Worker 是集群相关的 Actor,用于管理集群资源, Master 管理整个集群的资源, worker管理单个机器的资源。 AppMaster, Executor, Task 是应用相关的 Actor。 集群部分, Master中 的 危险工作通过 替身模式 交给了 AppManager, Scheduler 等 子 Actor, 这样 Master 本身可以更加健壮。应用部分, Task, Executor 和 AppMaster 连成 一条 异步错误链 , 通过增加中间层E

35、xecutor,我们可以做更细粒度的错误处理。 我们还支持 Master HA。 多个 Master 节点形成一个 Akka Cluster 集群, Master 间通过 Gossip 协议交换数据 ,用 最终一致性模式 的 CRDT 数据类型来保存 共享 的状态,一个 Master 挂掉, 其他 Master 能从 CRDT 中恢复数据 。 令人惊喜的是,通过 采用 Reactive 架构的设计方法, GearPump 有非常好的性能。在 四个节点, 用 SOL17 Benchmark, 我们 可以达到 1100 万消息 /秒 (100 字节每消息 ), 每条消息处理的 平均延时 只有 17 毫秒 图 10。 图 10 Gearpump performance 总结 Reactive 技术虽然不是新出现的,但在大数据和物联网 的 时代具有 深远 意义 。 Reactive 架构的Message-Driven、 Elastic、 Resilient、 Responsive 特性非常适合开发分布式应用。 现在已经有一些非常成功的语言和工具,比如 Scala、 Akka 等,设计上转向 Reactive 架构正当其时。 参考资料: http:/ http:/ https:/ 17 https:/

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报