Reactive架构才是未来

简介: ## 背景 最近一些同学在使用[CSE Fn](https://yuque.antfin-inc.com/cse/docs/cse-fn_intro)(Faas 平台)过程中, 经常会询问Reactive编程模型的价值, 原理以及如何正确使用它。所以根据前段时间的学习、使用经历,整理了这篇文章。这篇文章不会讲解如果使用相关的API,主要讲解它的概念,规范,价值,原理。如有理解不对的地方,欢迎斧正

背景

最近一些同学在使用CSE Fn(Faas 平台)过程中, 经常会询问Reactive编程模型的价值, 原理以及如何正确使用它。所以根据前段时间的学习、使用经历,整理了这篇文章。这篇文章不会讲解如果使用相关的API,主要讲解它的概念,规范,价值,原理。如有理解不对的地方,欢迎斧正.

Reactive和Reactive programming

Reactive直接翻译的意思式反应式,反应性. 咋一看,似乎不太好懂。
举个例子: 在Excel里,C单元格上设置函数: Sum(A+B). 当你改变单元格A或者单元格B的数值时,单元格C的值同时也会发生变化. 这种行为就是Reactive.

在计算机编程领域, Reactive一般指的是Reactive programming. 指的是一种面向数据流并传播事件的异步编程范式(asynchronous programming paradigm).

先举个例子大家感受一下:

public static void main(String[] args) {
        FluxProcessor<Integer, Integer> publisher = UnicastProcessor.create();
        publisher.doOnNext(event -> System.out.println("receive event: " + event)).subscribe();

        publisher.onNext(1); // print 'receive event: 1'
        publisher.onNext(2); // print 'receive event: 2'
}

(代码1)

以上例代码(使用Reactor类库)为例, publisher产生了数据流(1,2), 并且传播给了OnNext事件, 上例中lambda响应了该事件,输出了相应的信息. 上例代码中生成数据流和注册/执行lambda是在同一线程中,但也可以在不同线程中.

注: 如果上述代码执行逻辑有些疑惑,可以暂时将lambda理解成callback就可以了.

Reactive Manifesto

对于Reactive现在你应该大致有一点感觉了,但是Reactive有什么价值, 有哪些设计原则, 估计你还是有些模糊. 这就是Reactive Manifesto要解决的疑问了.

使用Reactive方式构建的系统具有以下特征:

即时响应性(Responsive): :只要有可能, 系统就会及时地做出响应。 即时响应是可用性和实用性的基石, 而更加重要的是,即时响应意味着可以快速地检测到问题并且有效地对其进行处理。 即时响应的系统专注于提供快速而一致的响应时间, 确立可靠的反馈上限, 以提供一致的服务质量。 这种一致的行为转而将简化错误处理、 建立最终用户的信任并促使用户与系统作进一步的互动。

回弹性(Resilient):系统在出现失败时依然保持即时响应性。 这不仅适用于高可用的、 任务关键型系统——任何不具备回弹性的系统都将会在发生失败之后丢失即时响应性。 回弹性是通过复制、 遏制、 隔离以及委托来实现的。 失败的扩散被遏制在了每个组件内部, 与其他组件相互隔离, 从而确保系统某部分的失败不会危及整个系统,并能独立恢复。 每个组件的恢复都被委托给了另一个(外部的)组件, 此外,在必要时可以通过复制来保证高可用性。 (因此)组件的客户端不再承担组件失败的处理。

弹性(Elastic): 系统在不断变化的工作负载之下依然保持即时响应性。 反应式系统可以对输入(负载)的速率变化做出反应,比如通过增加或者减少被分配用于服务这些输入(负载)的资源。 这意味着设计上并没有争用点和中央瓶颈, 得以进行组件的分片或者复制, 并在它们之间分布输入(负载)。 通过提供相关的实时性能指标, 反应式系统能支持预测式以及反应式的伸缩算法。 这些系统可以在常规的硬件以及软件平台上实现成本高效的弹性。

消息驱动(Message Driven):反应式系统依赖异步的消息传递,从而确保了松耦合、隔离、位置透明的组件之间有着明确边界。 这一边界还提供了将失败作为消息委托出去的手段。 使用显式的消息传递,可以通过在系统中塑造并监视消息流队列, 并在必要时应用回压, 从而实现负载管理、 弹性以及流量控制。 使用位置透明的消息传递作为通信的手段, 使得跨集群或者在单个主机中使用相同的结构成分和语义来管理失败成为了可能。 非阻塞的通信使得接收者可以只在活动时才消耗资源, 从而减少系统开销。

reactive_ manifesto.png

注:

  1. 上面描述有很多专有名词, 可能有些疑惑,可以看下相关名词解释
  2. 为什么使用Reactive方式构建的系统会具有以上价值, 我稍后在Reactor章节中介绍

Reactive Stream

知道了Reactive的概念,特征和价值后,是否有相关的产品或者框架来帮助我们构建Reactive式系统呢?在早些时候有一些类库(Rxjava 1.x, Rx.Net)可以使用,但是规范并不统一,所以后来Netfilx, Pivotal等公司就制定了一套规范指导大家便于实现它(该规范也是受到早期产品的启发),这就是Reactive Stream的作用。

Reactive Stream是一个使用非阻塞back pressure(回压)实现异步流式数据处理的标准. 目前已经在JVMJavaScript语言中实现同一套语意的规范; 以及尝试在各种涉及到序列化和反序列化的传输协议(TCP, UDP, HTTP and WebSockets)基础上,定义传输reactive数据流的网络协议.

The purpose of Reactive Streams is to provide a standard for asynchronous stream processing with non-blocking backpressure.

  • Reactive Streams解决的问题场景: 当遇到未预料数据流时,依然可以在可控资源消耗下保持系统的可用性.
  • Reactive Streams的目标: 控制在一个异步边界的流式数据交换。例如传递一个数据到另外一个线程或者线程池, 确保接收方没有buffer(缓存)任意数量的数据.而back pressure(回压)是解决这种场景的不可或缺的特性
  • Reactive Streams规范适用范围: 此标准只描述通过回压来实现异步流式数据交换的必要的行为和实体,最小接口, 例如下方的Publisher, Subscriber。Reactive Streams只关注在这些组件之间的流式数据中转, 并不关注流式数据本身的组装,分割,转换等行为, 例如 map, zip等operator.
  • Reactive Streams规范包括:

    • Publisher: 产生一个数据流(可能包含无限数据), Subscriber们可以根据它们的需要消费这些数据.
  1. interface Publisher {

    public void subscribe(Subscriber<? super T> s);

    }

    * **Subscriber**: Publisher创建的元素的接收者. 监听指定的事件,例如OnNext,OnComplete,OnError等
    
  2. interface Subscriber {

    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();

    }

    
    * **Subscription**: 是Publisher和Subscriber一对一的协调对象. Subscriber可以通过它来向Publisher取消数据发送或者request更多数据.
    
  3. interface Subscription {

    public void request(long n);
    public void cancel();

    }

* **Processor**: 同时具备Publisher和Subscriber特征. 代码1中FluxProcessor既可以发送数据(OnNext),也可以接收数据(doOnNext).
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

为什么规范强调使用非阻塞异步方式而不是阻塞同步方式?

  • 同步方式一般通过多线程来提高性能, 但系统可创建的线程数是有限的, 且线程多以后造成线程切换开销
  • 同步方式很难进一步提升资源利用率
  • 同步调用依赖的系统出现问题时,自身稳定性也会受到影响

实现非阻塞的方式有很多种,为什么规范会选择上述的实现方式呢?

  • Thread

    • thread不是非常轻量(相比下面几种实现方案)
    • thread数量是有限的, 最终可能会成为主要瓶颈
    • 有一些平台可能不支持多线程. 例如: JavaScript
    • 调试,实现上有一定复杂性
  • Callback

    • 多层嵌套callback比较复杂,容易形成"圣诞树"(callback hell)
    • 错误处理比较复杂
    • 多用于event loop架构的语言中, 例如JavaScript
  • Future

    • 无法逻辑组合各种行为, 支持业务场景有限.
    • 错误处理依然复杂
  • Reactive Extensions(Rx)

    • 和Future很相似。Future可以认为返回一个独立的元素, 而Rx返回一个可以被订阅的Stream.
    • 多平台支持同一套规范
    • 同一套API同时支持异步、同步
    • 错误处理方便
  • Coroutines

    • kotlin coroutinegoroutine在语法层面上提供异步支持, 而且比Rx更简洁,但无法跨多个语言平台形成统一的规范。

Reactive的实现原理个人认为还是回调,kotlin协程实现原理同样也是回调。但实现回掉的方式不一样。一个是通过事件传播, 一个是通过状态机.但cooutine编程的易用性明显强于Rx, 后面有空我会专门写篇文章介绍kotlin coroutine的实现原理.

Reactor

有了Reactive Stream这个规范,就会有相应实现该规范的类库. Reactor就是其中之一。

Reactor是遵守Reactive Stream规范构建非阻塞应用的Java语言Reactive类库, 已经在spring 5中集成, 与他相似的类库有RxJava2, RxJs, JDK9 Flow等。

CSE Fn目前使用Reactor来构建整个系统,包括函数应用和各种核心应用(逻辑架构). 根据我们压测结果显示, 使用Reactive方式构建的系统确实会有这些特点:

  • 回弹性(Resilient): 当函数出现严重超时时(rt>=10s),函数上游的broker,gateway应用几乎无任何影响.
  • 及时响应性: 不管是高并发场景(资源足够),还是正常场景,RT表现一致.

另外从原理上,我认为资源利用率吞吐量也会高于非反应式的应用.

为什么Reactive的架构系统有这些特点?

CSE Fn主要做了两件事情:

  1. 涉及到IO的地方几乎全异步化。例如中间件(HSF, MetaQ等提供异步API)调用
  2. IO线程模型变化. 使用较少(一般CPU核数)线程处理所有的请求

传统Java应用IO线程模型: 参考Netty中Reactor IO(worker thread pool)模型, 下方伪代码(kotlin)进行了简化。

Screen Shot 2019-08-30 at 12.49.32 PM.png

// 非阻塞读取客户端请求数据(in), 读取成功后执行lambda.
inChannel.read(in) {
    workerThreadPool.execute{
      // 阻塞处理业务逻辑(process), 业务逻辑在worker线程池中执行,同步执行完后,再向客户端返回输出(out)
      val out = process(in)
      outChannel.write(out)
    }  
}

Reactive应用IO线程模型: IO线程也可以执行业务逻辑(process), 可以不需要worker线程池
Screen Shot 2019-08-30 at 12.50.36 PM.png

// 非阻塞读取客户端请求数据(in), 读取成功后执行lambda
inChannel.read(in) {
    // IO线程执行业务逻辑(process),  然后向客户端返回输出(out). 这要求业务处理流程必须是非阻塞的.
    process(in){ out->
        outChannel.write(out) {
            // this lambda is executed when the writing completes
        ...
        }
    }
}

如何开始Reactive Programing

以Reactive方式构建的系统有很多值得学习和发挥价值的地方,但坦白讲Reactive programing方式目前接受程度并不高.特别是使用Java语言开发同学,我个人也感同身受,因为这和Java面向命令控制流程的编程思维方式有较大差异。所以这里以Reactor(Java)学习为例:

总结

反应式的系统有很多优点,但是完整构建反应式的系统却并不容易。不仅仅是语言上的差异,还有一些组件就不支持非阻塞式的调用方式,例如: JDBC. 但是有一些开源组织正在推动这些技术进行革新, 例如: R2DBC。 另外,为了方便构建反应式系统,一些组织/个人适配了一些主流技术组件reactor-core, reactor-netty, reactor-rabbimq, reactor-kafka等,来方便完整构建反应式系统。

当你的系统从底层到上层,从系统内部到依赖外部都变成了反应式, 这就形成了Reactive架构.
这种架构价值有多大?未来可期.

参考

https://www.reactivemanifesto.org/
https://www.reactive-streams.org/
https://kotlinlang.org/docs/tutorials/coroutines/async-programming.html
https://projectreactor.io/docs/core/release/reference/index.html

目录
相关文章
|
JavaScript 前端开发 网络协议
Reactive 架构才是未来
Reactive 编程模型有哪些价值?它的原理是什么?如何正确使用?本文作者将根据他学习和使用的经历,分享 Reactive 的概念、规范、价值和原理。欢迎同学们共同探讨、斧正。(文末福利:Java 系列直播,畅谈架构、Reactive Spring、DDD 和高可用。)
8798 0
Reactive 架构才是未来
|
Cloud Native Java
重磅来袭!Reactive 架构专场四城巡回演讲
作者 | 铃儿响叮当导读:涉及开发的技术人员,永远绕不开的就是将应用部署到相应服务器上,本文将给大家讲解:对于容器服务 ACK,怎么实现真正“一键部署”,提高开发部署效率,在 K8s 的运用上做到快人一步。
|
11天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
9天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
10天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
24 1
服务架构的演进:从单体到微服务的探索之旅
|
8天前
|
消息中间件 监控 安全
后端架构演进:从单体到微服务####
在数字化转型的浪潮中,企业应用的后端架构经历了从传统单体架构到现代微服务架构的深刻变革。本文探讨了这一演进过程的背景、驱动力、关键技术及面临的挑战,揭示了如何通过微服务化实现系统的高可用性、扩展性和敏捷开发,同时指出了转型过程中需克服的服务拆分、数据管理、通信机制等难题,为读者提供了一个全面理解后端架构演变路径的视角。 ####
24 8
|
9天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
33 5
|
11天前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
30 7