响应式编程_01基本概念:前世今生

文章目录

  • 引言
  • 响应式编程的技术优势
  • 全栈式响应式编程
  • 从传统开发模式到异步执行技术
    • Web 请求与 I/O 模型
    • 异步调用的实现技术
      • 回调
      • Future机制
  • 响应式编程实现方法
    • 观察者模式
    • 发布-订阅模式
    • 数据流与响应式
  • 响应式宣言和响应式系统

在这里插入图片描述


引言

大流量、高并发的访问请求的项目,在各种请求压力下,系统可能会出现一系列可用性问题,但作为系统的设计者,我们需要保证其拥有即时的响应性,如何时刻确保系统具有应对请求压力的弹性,成为一个非常现实且棘手的问题。

经典的服务隔离、限流、降级以及熔断等机制,能够在一定程度上实现系统的弹性。但通过对比了更多可选的技术体系之后,我发现了构建系统弹性的一种崭新的解决方案,那就是响应式编程


响应式编程的技术优势

响应式编程打破了传统的同步阻塞式编程模型,基于响应式数据流和背压机制实现了异步非阻塞式的网络通信、数据访问和事件驱动架构,能够减轻服务器资源之间的竞争关系,从而提高服务的响应能力。

服务 A 和服务 B 的交互过程图

在这里插入图片描述

可以设想一下,当系统中存在的服务 A 需要访问服务 B 时,在服务 A 发出请求之后,执行线程会等待服务 B 的返回,这段时间该线程就是阻塞的,整个过程的 CPU 利用效率低下,很多时间线程被浪费在了 I/O 阻塞上.

更进一步,当执行数据访问时,数据库的执行操作也面临着同样的阻塞式问题,整个请求链路的各个环节都会导致资源的浪费,从而降低系统弹性。而引入响应式编程技术,可以很好地解决这种问题。


全栈式响应式编程

在响应式编程领域存在一个核心的理念,即全栈式响应式编程,也就是响应式开发方式的有效性取决于在整个请求链路的各个环节是否都采用了响应式编程模型

在这里插入图片描述


从传统开发模式到异步执行技术

现实的开发过程普遍采用的是同步阻塞式的开发模式,以实现业务系统。在这种模式下,开发、调试和维护都很简单。我们先以 Web 系统中最常见的 HTTP 请求为例,来分析其背后的 I/O 模型。

Web 请求与 I/O 模型

RestTemplate restTemplate = new RestTemplate();ResponseEntity<User> restExchange = restTemplate.exchange("http://localhost:8080/users/{userName}", HttpMethod.GET, null, User.class, userName);User result = restExchange.getBody();process(result); 

这里,我们传入用户名 UserName 调用远程服务获取一个 User 对象,技术上使用了 Spring MVC 中的 RestTemplate 模板工具类,通过该类所提供的 exchange 方法对远程 Web 服务所暴露的 HTTP 端点发起了请求,并对所获取的响应结果进行进一步处理。

这是日常开发过程中非常具有代表性的一种场景,整个过程很熟悉也很自然。

那么,这个实现过程背后有没有一些可以改进的地方呢?为了更好地分析整个调用过程,我们假设服务的提供者为服务 A,而服务的消费者为服务 B,那么这两个服务的交互过程应该是下图所示这样的。

在这里插入图片描述

可以看到,当服务 B 向服务 A 发送 HTTP 请求时,线程 B 只有在发起请求和响应结果的一小部分时间内在有效使用 CPU,而更多的时间则只是在阻塞式地等待来自服务 A 中线程的处理结果。显然,整个过程的 CPU 利用效率是很低的,很多时间线程被浪费在了 I/O 阻塞上,无法执行其他的处理过程。


我们继续分析服务 A 中的处理过程。如果我们采用典型的 Web 服务分层架构,那么就可以得到如下图所示的用户信息查询实现时序图,这是日常开发过程中普遍采用的一种实现方式。

一般我们使用 Web 层所提供的 HTTP 端点作为查询的操作入口,然后该操作入口会进一步调用包含业务逻辑处理的服务层,而服务层再调用数据访问层,数据访问层就会连接到数据库获取数据。数据从数据库中获取之后逐层向上传递,最后返回给服务的调用者。
在这里插入图片描述

显然上图 所展示的整个过程中,每一步的操作过程都存在着前面描述的线程等待问题。也就是说,整个技术栈中的每一个环节都可能是同步阻塞的。

针对同步阻塞问题,在技术上也可以引入一些实现技术来将同步调用转化为异步调用。我们一起来看一下。


异步调用的实现技术

在 Java 世界中,为了实现异步非阻塞,一般会采用回调和 Future 这两种机制,但这两种机制都存在一定局限性

回调

在这里插入图片描述
回调的含义如图 所示,即服务 B 的 methodB() 方法调用服务 A 的 methodA() 方法,然后服务 A 的 methodA() 方法执行完毕后,再主动调用服务 B 的 callback() 方法。

回调体现的是一种双向的调用方式,实现了服务 A 和服务 B 之间的解耦。在这个 callback 回调方法中,回调的执行是由任务的结果来触发的,所以我们就可以异步来执行某项任务,从而使得调用链路不发生任何的阻塞。

回调的最大问题是复杂性,一旦在执行流程中包含了多层的异步执行和回调,那么就会形成一种嵌套结构,给代码的开发和调试带来很大的挑战。所以回调很难大规模地组合起来使用,因为很快就会导致代码难以理解和维护,从而造成所谓的“回调地狱”问题。


Future机制

说完回调,我们来看 Future。可以把 Future 模式简单理解为这样一种场景:我们有一个需要处理的任务,然后把这个任务提交到 Future,Future 就会在一定时间内完成这个任务,而在这段时间内我们可以去做其他事情。作为 Future 模式的实现,Java 中的 Future 接口只包含如下 5 个方法

public interface Future<V> {//取消任务的执行boolean cancel(boolean mayInterruptIfRunning);//判断任务是否已经取消boolean isCancelled();//判断任务是否已经完成boolean isDone();//等待任务执行结束并获取结果V get() throws InterruptedException, ExecutionException;//在一定时间内等待任务执行结束并获取结果V get(long timeout, TimeUnit unit)?throws InterruptedException, ExecutionException, TimeoutException;
}

从这些基础方法中可以看到,我们可以通过对任务进行灵活的控制和判断,来达到一定的异步执行效果。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;public class NamedThreadPoolDemo {public static void main(String[] args) {// 1. 创建带名称的线程池ExecutorService customExecutor = Executors.newFixedThreadPool(3,new NamedThreadFactory("custom-pool"));// 2. 执行异步任务CompletableFuture.supplyAsync(() -> {log("Task 1 starts");return "Hello";}, customExecutor).thenApplyAsync(result -> {log("Task 2 processes: " + result);return result + " World";}, customExecutor).thenAcceptAsync(finalResult -> {log("Final Result: " + finalResult);}, customExecutor);// 3. 关闭线程池(实际应在程序结束时调用)customExecutor.shutdown();}private static void log(String message) {System.out.println(Thread.currentThread().getName() + " | " + message);}static class NamedThreadFactory implements ThreadFactory {private final AtomicInteger counter = new AtomicInteger(1);private final String namePrefix;public NamedThreadFactory(String namePrefix) {this.namePrefix = namePrefix;}@Overridepublic Thread newThread(Runnable r) {return new Thread(r, namePrefix + "-thread-" + counter.getAndIncrement());}}
}

但从本质上讲,Future 以及由 Future 所衍生出来的 CompletableFuture 等各种优化方案就是一种多线程技术。多线程假设一些线程可以共享一个 CPU,而 CPU 时间能在多个线程之间共享,这一点就引入了“上下文切换”的概念。

如果想要恢复线程,就需要涉及加载和保存寄存器等一系列计算密集型的操作。因此,大量线程之间的相互协作同样会导致资源利用效率低下。


响应式编程实现方法

观察者模式

在引入响应式编程技术之前,我们同样先来回顾一个大家可能都知道的设计模式,即观察者模式。观察者模式拥有一个主题(Subject),其中包含其依赖者列表,这些依赖者被称为观察者(Observer)。主题可以通过一定的机制将任何状态变化通知到观察者。

针对前面介绍的用户信息查询操作,我们同样可以应用观察者模式,如下图所示。

观察者模式下的用户信息获取过程

在这里插入图片描述


发布-订阅模式

如果系统中存在一批类似上图中的用户信息获取场景,针对每个场景都实现一套观察者模式显然是不合适的。更好的方法是使用发布-订阅模式,该模式可以认为是对观察者模式的一种改进。

在这一模式中,发布者和订阅者之间可以没有直接的交互,而是通过发送事件到事件处理平台的方式来完成整合,如下图所
示。

发布-订阅模式下的用户信息获取过程

在这里插入图片描述

由此可见,通过发布-订阅模式,我们可以基于同一套事件发布机制和事件处理平台来应对多种业务场景,不同的场景只需要发送不同的事件即可。

同样,如果我们聚焦于服务 A 的内部,那么从 Web 服务层到数据访问层,再到数据库的整个调用链路,同样可以采用发布-订阅模式进行重构。这时候,我们希望当数据库中的数据一有变化就通知上游组件,而不是上游组件通过主动拉取数据的方式来获取数据

基于响应式实现方法的用户信息查询场景时序图

在这里插入图片描述
显然,现在我们的处理方式发生了本质性的变化。图中,我们没有通过同步执行的方式来获取数据,而是订阅了一个 UserChangedEvent 事件。UserChangedEvent 事件会根据用户信息是否发生变化而进行触发,并在 Web 应用程序的各个层之间进行传播。如果我们在这些层上都对这个事件进行了订阅,那么就可以对其分别进行处理,并最终将处理结果从服务 A 传播到服务 B 中。


数据流与响应式

接下来,我们扩大讨论范围,来想象系统中可能会存在着很多类似 UserChangedEvent 这样的事件。每一种事件会基于用户的操作或者系统自身的行为而被触发,并形成了一个事件的集合。针对事件的集合,我们可以把它们看成是一串串联起来的数据流,而系统的响应能力就体现在对这些数据流的即时响应过程上。

数据流对于技术栈而言是一个全流程的概念。也就是说,无论是从底层数据库,向上到达服务层,最后到 Web 服务层,抑或是在这个流程中所包含的任意中间层组件,整个数据传递链路都应该是采用事件驱动的方式来进行运作的。

这样,我们就可以不采用传统的同步调用方式来处理数据,而是由处于数据库上游的各层组件自动来执行事件。这就是响应式编程的核心特点

相较传统开发所普遍采用的“拉”模式,在响应式编程下,基于事件的触发和订阅机制,这就形成了一种类似“推”的工作方式。这种工作方式的优势就在于,生成事件和消费事件的过程是异步执行的,所以线程的生命周期都很短,也就意味着资源之间的竞争关系较少,服务器的响应能力也就越高。


响应式宣言和响应式系统

所谓的“响应式”并不是一件颠覆式的事情,而只是一种新型的编程模式。它不局限于某种开发框架,也并非解决分布式环境下所有问题的银弹,而是随着技术的发展自然而然诞生的一种技术体系。

关于响应式,业界也存在一个著名的响应式宣言,下图就是响应式宣言的官方网站给出的,对于这一宣言的图形化描述

在这里插入图片描述
可以看到,即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)构成了响应式宣言的主体内容。响应式宣言认为,具备上图中各个特性的系统,就可以称为响应式系统。

而这些特性又可以分为三个层次,其中

  • 即时响应性、可维护性(Maintainable)和扩展性(Extensible)体现的是价值,
  • 回弹性和弹性是表现形式,
  • 而消息驱动则是实现手段。

从设计理念上讲,即时响应性指的就是无论在任何时候,系统都会及时地做出响应,并对那些出现的问题进行快速的检测和处理,这是可用性的基石。

要注意,这里的回弹性和弹性比较容易混用。所谓回弹性指的是系统在出现失败时,依然能够保持即时响应性;而弹性则是指的系统在各种请求压力之下,都能保持即时响应性

最后的消息驱动指的是响应式系统需要构建异步的消息通信机制。可以把这里的消息等同于前面提到的事件,通过使用消息通信,可以通过在系统中实现连续的数据流,从而达到对流量进行控制的管理目标。我们知道消息通信是非阻塞的,非阻塞的通信使得只有在有消息到来时才需要资源的投入,而避免了很多同步等待导致的资源浪费。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/69053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文速览DeepSeek-R1的本地部署——可联网、可实现本地知识库问答:包括671B满血版和各个蒸馏版的部署

前言 自从deepseek R1发布之后「详见《一文速览DeepSeek R1&#xff1a;如何通过纯RL训练大模型的推理能力以比肩甚至超越OpenAI o1(含Kimi K1.5的解读)》」&#xff0c;deepseek便爆火 爆火以后便应了“人红是非多”那句话&#xff0c;不但遭受各种大规模攻击&#xff0c;即便…

队列 + 宽搜(4题)

目录 1.n叉树的层序遍历 2.二叉树的锯齿形层序遍历 3.二叉树的最大宽度 4.在每个树行中找最大值 1.n叉树的层序遍历 429. N 叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 我们只需要把某个节点出队的时候把它的孩子节点添加进来即可。 出队的次数就是最开始队列…

惰性函数【Ⅱ】《事件绑定的自我修养:从青铜到王者的进化之路》

【Ⅱ】《事件绑定的自我修养&#xff1a;从青铜到王者的进化之路》 1. 代码功能大白话&#xff08;给室友讲明白版&#xff09; // 青铜写法&#xff1a;每次都要问浏览器"你行不行&#xff1f;" function addEvent青铜版(element, type, handler) {if (window.add…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.28 NumPy+Matplotlib:科学可视化的核心引擎

2.28 NumPyMatplotlib&#xff1a;科学可视化的核心引擎 目录 #mermaid-svg-KTB8Uqiv5DLVJx7r {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KTB8Uqiv5DLVJx7r .error-icon{fill:#552222;}#mermaid-svg-KTB8Uqiv5…

upload-labs安装与配置

前言 作者进行upload-labs靶场练习时&#xff0c;在环境上出了很多问题&#xff0c;吃了很多苦头&#xff0c;甚至改了很多配置也没有成功。 upload-labs很多操作都是旧时代的产物了&#xff0c;配置普遍都比较老&#xff0c;比如PHP版本用5.2.17&#xff08;还有中间件等&am…

【OS】AUTOSAR架构下的Interrupt详解(下篇)

目录 3.代码分析 3.1中断配置代码 3.2 OS如何找到中断处理函数 3.3 Os_InitialEnableInterruptSources实现 3.4 Os_EnableInterruptSource 3.5 DisableAllInterrupts 3.5.1Os_IntSuspendCat1 3.5.2 Os_InterruptDisableAllEnter 3.5.3 Disable二类中断 3.5.4 Disable一…

RabbitMQ快速上手及入门

概念 概念&#xff1a; publisher&#xff1a;生产者&#xff0c;也就是发送消息的一方 consumer&#xff1a;消费者&#xff0c;也就是消费消息的一方 queue&#xff1a;队列&#xff0c;存储消息。生产者投递的消息会暂存在消息队列中&#xff0c;等待消费者处理 exchang…

糖化之前,为什么要进行麦芽粉碎?

糖化的目的是将麦芽中的淀粉转化为可发酵性的糖分&#xff0c;而糖化之前&#xff0c;进行麦芽粉碎是确保糖化效果的关键步骤。本文天泰将阐述麦芽粉碎的重要性及其对酿造过程的影响。 一、麦芽粉碎的目的 增加酶的作用面积&#xff1a;麦芽中的淀粉和蛋白质等物质需要通过酶…

【办公类-99-01】20250201学具PDF打印会缩小一圈——解决办法:换一个PDF阅读器

背景需求&#xff1a; 2024年1月13日&#xff0c;快要放寒假了&#xff0c;组长拿着我们班的打印好的一叠教案来调整。 “前面周计划下面的家园共育有调整&#xff0c;你自己看批注。” “还有你这个教案部分的模版有问题&#xff0c;太小&#xff08;窄&#xff09;了。考虑…

利用腾讯云cloud studio云端免费部署deepseek-R1

1. cloud studio 1.1 cloud studio介绍 Cloud Studio&#xff08;云端 IDE&#xff09;是基于浏览器的集成式开发环境&#xff0c;为开发者提供了一个稳定的云端工作站。支持CPU与GPU的访问。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器即可使用。Clo…

《黑马点评》实战笔记

目录 P1 Redis企业实战课程介绍 P2 短信登录 导入黑马点评项目 P3 短信登录 基于session实现短信登录的流程 P4 短信登录 实现发送短信验证码功能 P5 短信登录 实现短信验证码登录和注册功能 P6 短信登录 实现登录校验拦截器 P7 短信登录 隐藏用户敏感信息 P8 短信登录 …

python的pre-commit库的使用

在软件开发过程中&#xff0c;保持代码的一致性和高质量是非常重要的。pre-commit 是一个强大的工具&#xff0c;它可以帮助我们在提交代码到版本控制系统&#xff08;如 Git&#xff09;之前自动运行一系列的代码检查和格式化操作。通过这种方式&#xff0c;我们可以确保每次提…

第一性原理:游戏开发成本的思考

利润 营收-成本 营收定价x销量x分成比例 销量 曝光量x 点击率x &#xff08;购买率- 退款率&#xff09; 分成比例 100%- 平台抽成- 税- 引擎费- 发行抽成 成本开发成本运营成本 开发成本 人工外包办公地点租金水电设备折旧 人工成本设计成本开发成本迭代修改成本后续内容…

Hot100之矩阵

73矩阵置零 题目 思路解析 收集0位置所在的行和列 然后该行全部初始化为0 该列全部初始化为0 代码 class Solution {public void setZeroes(int[][] matrix) {int m matrix.length;int n matrix[0].length;List<Integer> list1 new ArrayList<>();List<…

详解分布式锁

目录 基于MYSQL实现 通过 insert 实现 通过 select 实现 基于Redis实现 基于set实现 redlock 基于zookeeper实现 在分布式系统中对于共享资源我们需要确保在任何时刻只有一个节点或进程能够访问&#xff0c;也就需要加锁&#xff0c;而我们的本地锁无法满足这个需求&…

五. Redis 配置内容(详细配置说明)

五. Redis 配置内容(详细配置说明) 文章目录 五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置…

物业管理系统源码提升社区智能化管理效率与用户体验

内容概要 物业管理系统源码是一种针对社区管理需求而设计的软件解决方案&#xff0c;通过先进的智能化技术&#xff0c;使物业管理变得更加高效和人性化。随着城市化进程的加快&#xff0c;社区的管理复杂性不断增加&#xff0c;而这一系统的推出恰好为物业公司提供了极大的便…

springboot集成钉钉,发送钉钉日报

目录 1.说明 2.示例 3.总结 1.说明 学习地图 - 钉钉开放平台 在钉钉开放文档中可以查看有关日志相关的api&#xff0c;主要用到以下几个api&#xff1a; ①获取模板详情 ②获取用户发送日志的概要信息 ③获取日志接收人员列表 ④创建日志 发送日志时需要根据模板规定日志…

python算法和数据结构刷题[1]:数组、矩阵、字符串

一画图二伪代码三写代码 LeetCode必刷100题&#xff1a;一份来自面试官的算法地图&#xff08;题解持续更新中&#xff09;-CSDN博客 算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 面试经典 150 题 - 学习计…

院校联合以项目驱动联合培养医工计算机AI人才路径探析

一、引言 1.1 研究背景与意义 在科技飞速发展的当下&#xff0c;医疗人工智能作为一个极具潜力的新兴领域&#xff0c;正深刻地改变着传统医疗模式。从疾病的早期诊断、个性化治疗方案的制定&#xff0c;到药物研发的加速&#xff0c;人工智能技术的应用极大地提升了医疗服务…