当前位置: 首页 > news >正文

Redis 网络模型

一、用户空间和内核空间

1.1 linux 简介

        服务器大多采用 Linux 系统,这里我们以 Linux 为例来讲解,下面有两个不同的 linux 发行版,分别位 ubuntu centos,其实发行版就是在 Linux 系统上包了一层壳。

        任何 Linux 发行版,其系统内核都是 Linux。我们在 Linux 上安装的应用,比如说 mysqlredis 等,它们没有办法直接访问计算机硬件,它们需要先访问内核,然后再基于内核去操作计算机硬件。如下图:

        再细化的说下,计算机硬件一般包括 CPU、内存、网卡等各种各样的设备,虽然内核可以操作硬件,但它也需要不同设备的驱动,在设备驱动的基础上就可以形成对内存的管理、进程的管理、文件系统的管理和网络管理等等,如下图:

        最后呢,如果我们想让用户应用来访问,就必须在设备驱动的基础上封装一些接口,这样一来,我们的用户应用,包括一些依赖库等就可以调用这些接口,从而间接的实现对硬件设备的访问。

1.2 寻址空间

        但是这里有一个问题,我们的内核本质也是个应用,它在运行的过程中也需要一些 cpu 资源,内存资源等。所以为了避免用户应用导致冲突甚至内核崩溃,用户应用与内核是分离的。

        首先把进程的寻址空间划分为两部分:内核空间、用户空间。寻址空间是什么呢?其实无论是内核还是用户应用,都无法直接访问物理内存,而是给他们分配虚拟的内存空间,映射到不同的物理内存上,这样一来,我们的内核或者用户应用再去访问虚拟内存空间的时候就需要一个虚拟的地址了,这个地址是一个无符号的整数,从 0 开始,它的最大值取决于 cpu 的地址总线和寄存器的带宽,以一个 32 位系统为例,那么它的带宽一般为 32,因此它的地址的最大值就是 2^32 ,也就是说寻址的范围就是从 02^32,这个寻找的范围就是寻址空间。

        我们内存地址的每一个值代表的就是一个存储单元,也就是一个字节。所以 2^32 这么大的寻址空间最多代表的就是 2^32 字节,也即是 4GB,这也就是为什么一个 32 位的系统它的寻址空间最多就是 4GB 的原因。而这 4GB 又会被分为两部分,内核空间和用户空间。如下图:

        光在内存上做一个划分还不够,我们还需要在系统权限上做一个划分,因为在 cpu 运行的各种命令中,有一些命令的风险等级比较低,有一些比较高。

        用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问。

        内核空间可以执行特权命令(Ring0),调用一切系统资源。

        一般来说用户的应用都是运行在用户空间,而内核的应用运行在内核空间。

         但是假设一个进程,执行的业务比较多,可能会执行一些普通的命令,也有可能需要去执行一些特权命令去调用系统资源,因此我们的进程就需要在用户空间和内核空间来回切换,当一个进程运行在用户空间的时候我们就称其为用户态,当期运行在内核空间的时候,就称其为内核态,如下图:

        所以我们的进程很有可能在两个状态之间进行切换,那么这个切换的流程到底是什么样子的呢?接下来举个 IO 读写的例子。

        Linux 系统为了提高 IO 效率,会在用户空间和内核空间都加入缓冲区:

        假设此时用户空间发起请求要去读数据,这个请求到达内核空间之后先去判断有没有数据,假如此时读取的是磁盘的数据,那么此时磁盘就需要去寻址,所以在这个过程中就需要等待;如果读取的不是磁盘的数据,读取的是网卡的数据,网卡的数据是别人传过来的,如果别人还没传过来,你还是需要等待的。所以当你发送一个读的请求时,首先是要等,等数据到达。数据到达之后,先把数据读取到缓冲区里面,然后把数据拷贝回用户空间里面做处理。

        其实影响我们 IO 效率最主要的第一个原因就是需要数据的等待,第二个原因是数据的拷贝,所以要想提高 IO 的效率,就需要从这两个点出发。减少无效的等待和减少用户态和内核态之间缓冲区的拷贝。后面的五种不同的 IO 模型都是针对这两个点进行优化的。

二、阻塞 IO

        顾名思义,阻塞 IO 就是两个阶段都必须阻塞等待。这个过程需要经历两个阶段。

阶段一:

        1、用户进程尝试读取数据(比如网卡数据)

        2、此时数据尚未到达,内核需要等待数据

        3、此时用户进程也处于阻塞状态

阶段二:

        1、数据到达并拷贝到内核缓冲区,代表已就绪

        2、将内核数据拷贝到用户缓冲区

        3、拷贝过程中,用户进程依然阻塞等待

        4、拷贝完成,用户进程解除阻塞,处理数据

        可以看到,阻塞 IO 模型中,用户进程在两个阶段都是阻塞状态。

三、非阻塞 IO

        顾名思义,非阻塞 IOrecvfrom 操作会立即返回结果而不是阻塞用户进程。这个过程需要经历两个阶段。

阶段一:

        1、用户进程尝试读取数据(比如网卡数据)

        2、此时数据尚未到达,内核需要等待数据

        3、返回异常给用户进程

        4、用户进程拿到 error 后,再次尝试读取

        5、循环往复,直到数据就绪

阶段二:

        1、将内核数据拷贝到用户缓冲区

        2、拷贝过程中,用户进程依然阻塞等待

        3、拷贝完成,用户进程解除阻塞,处理数据

        可以看到,非阻塞 IO 模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致 CPU 空转,CPU 使用率暴增。 

四、IO 多路复用

4.1 简介

        无论是阻塞 IO 还是非阻塞 IO,用户应用在一阶段都需要调用 recvfrom 来获取数据,差别在于无数据时的处理方案:

        1、如果调用 recvfrom 时,恰好没有数据,阻塞 IO 会使 CPU 阻塞,非阻塞 IO 使 CPU 空转,都不能充分发挥 CPU 的作用。

        2、如果调用 recvfrom 时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据。

        而在单线程情况下,只能依次处理 IO 事件,如果正在处理的 IO 事件恰好未就绪(数据不可读或不可写),线程就会被阻塞,所有 IO 事件都必须等待,性能自然会很差。

上面的情况就比如服务员给顾客点餐,分两步:

        1、顾客思考要吃什么(等待数据就绪)

        2、顾客想好了,开始点餐(读取数据)

        要提高效率有两种办法,第一种增加更多服务员(多线程)。第二种是不排队,谁想好了吃什么(数据就绪了),服务员就给谁点餐(用户应用就去读取数据)。那么用户进程如何知道内核中数据是否就绪呢?此时就需要用到 FD 了。

4.2 文件描述符 FD

        文件描述符(File Descriptor):简称 FD,是一个从 开始的无符号整数,用来关联 Linux 中的一个文件。在 Linux 中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。

        IO 多路复用:是利用单个线程来同时监听多个 FD,并在某个 FD 可读、可写时得到通知,从而避免无效的等待,充分利用 CPU 资源。也是分为两个阶段:

阶段一:

        1、用户进程调用 select,指定要监听的 FD 集合。

        2、内核监听 FD 对应的多个 socket

        3、任意一个或多个 socke t数据就绪则返回 readabl

        4、此过程中用户进程阻塞

阶段二:

        1、用户进程找到就绪的 socket

        2、依次调用 recvfrom 读取数据

        3、内核将数据拷贝到用户空间

        4、用户进程处理数据

        IO 多路复用是利用单个线程来同时监听多个 FD,并在某个 FD 可读、可写时得到通知,从而避免无效的等待,充分利用 CPU 资源。不过监听 FD 的方式、通知的方式又有多种实现,常见的有:selectpollepoll。它们之间的差异如下:

        1、select poll 只会通知用户进程有 FD 就绪,但不确定具体是哪个 FD,需要用户进程逐个遍历 FD 来确认。

        2、epoll 则会在通知用户进程 FD 就绪的同时,把已就绪的 FD 写入用户空间。

五、信号驱动 IO

        信号驱动 IO 是与内核建立 SIGIO 的信号关联并设置回调,当内核有 FD 就绪时,会发出 SIGIO 信号通知用户,期间用户应用可以执行其它业务,无需阻塞等待。这个过程可以分为两个阶段。

阶段一:

        1、用户进程调用 sigaction,注册信号处理函数

        2、内核返回成功,开始监听 FD

        3、用户进程不阻塞等待,可以执行其它业务

        4、当内核数据就绪后,回调用户进程的 SIGIO 处理函数

阶段二:

        1、收到 SIGIO 回调信号

        2、调用 recvfrom,读取数据。

        3、内核将数据拷贝到用户空间

        4、用户进程处理数据

        当有大量 IO 操作时,信号较多,SIGIO 处理函数不能及时处理可能导致信号队列溢出,而且内核空间与用户空间的频繁信号交互性能也较低。

六、异步 IO

        异步 IO 的整个过程都是非阻塞的,用户进程调用完异步 API 后就可以去做其它事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。整个过程分为两个阶段。

阶段一:

        1、用户进程调用 aio_read,创建信号回调函数

        2、内核等待数据就绪

        3、用户进程无需阻塞,可以做任何事情

阶段二:

        1、内核数据就绪

        2、内核数据拷贝到用户缓冲区

        3、拷贝完成,内核递交信号触发 aio_read 中的回调函数

        4、用户进程处理数据

        可以看到,异步 IO 模型中,用户进程在两个阶段都是非阻塞状态。

七、同步还是异步

        IO 操作是同步还是异步,关键看数据在内核空间与用户空间的拷贝过程(数据读写的 IO 操作),也就是阶段二是同步还是异步:

八、Redis 网络模型

8.1 Redis 是否为单线程

Redis 到底是单线程还是多线程?

        1、如果仅仅聊 Redis 的核心业务部分(命令处理),答案是单线程。

        2、如果是聊整个 Redis,那么答案就是多线程。

Redis 版本迭代过程中,在两个重要的时间节点上引入了多线程的支持

        1、Redis v4.0:引入多线程异步处理一些耗时较旧的任务,例如异步删除命令 unlink

        2、Redis v6.0:在核心网络模型中引入多线程,进一步提高对于多核 CPU 的利用率。

        因此,对于 Redis 的核心网络模型,在 Redis 6.0 之前确实都是单线程。是利用 epollLinux 系统)这样的 IO 多路复用技术在事件循环中不断处理客户端情况。

为什么 Redis 要选择单线程?

        1、抛开持久化不谈,Redis 是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升。

        2、多线程会导致过多的上下文切换,带来不必要的开销

        3、引入多线程会面临线程安全问题,必然要引入线程锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣。

8.2 Redis 网络模型

        Redis 6.0 版本中引入了多线程,目的是为了提高 IO 读写效率。因此在解析客户端命令、写响应结果时采用了多线程。核心的命令执行、IO 多路复用模块依然是由主线程执行。如下图:

相关文章:

Redis 网络模型

一、用户空间和内核空间 1.1 linux 简介 服务器大多采用 Linux 系统,这里我们以 Linux 为例来讲解,下面有两个不同的 linux 发行版,分别位 ubuntu 和 centos,其实发行版就是在 Linux 系统上包了一层壳。 任何 Linux 发行版&#…...

【设计模式之组合模式 -- C++】

组合模式 – 树状结构,递归遍历 组合模式(Composite Pattern)是一种结构型设计模式,它可以让你将对象组合成树形结构,并且能像使用独立对象一样使用它们。这种模式定义了包含人和组的类,每个类都有可以在树形结构中显示的方法。这…...

C# 通过Win32API设置客户端系统时间

在日常工作中,有时可能会需要获取或修改客户端电脑的系统时间,比如软件设置了Licence有效期,预计2024-06-01 00:00:00到期,如果客户手动修改了客户端电脑时间,往前调整了一年,则软件就可以继续使用一年&…...

VirtualHere 允许通过网络远程使用 USB 设备,就像本地连接一样!

传统上,USB 设备需要直接插入计算机才能使用。有了 VirtualHere,就不再需要这样做,网络本身就变成了传输 USB 信号的电缆(也称为 USB over IP、USB/IP、USB over WiFi、USB over Ethernet、USB 设备服务器)。 此 USB …...

【Kubernetes】k8s 自动伸缩机制—— HPA 部署

一、在K8s中扩缩容分为两种: ●Node层面:对K8s物理节点扩容和缩容,根据业务规模实现物理节点自动扩缩容 ●Pod层面:我们一般会使用Deployment中的Replicas参数,设置多个副本集来保证服务的高可用,但是这是…...

MT1415 大小相同

题目 给定一个由N(<10)个正整数组成的数组A&#xff0c;生成一些最小元素和最大元素相同的子数组数&#xff08;可以仅包含1个元素&#xff09;&#xff0c;统计这些子数组的数量并输出。 注&#xff1a;最大元素和最小元素相同就是数组中的元素全部为同一个值。如数组&am…...

使用python库moviepy完成视频剪辑

1.关于moviepy和原理 moviepy事github上面的一个开源项目&#xff0c;地址是&#xff1a;GitHub - Zulko/moviepy: Video editing with Python 官方文档地址&#xff1a; User Guide — MoviePy 1.0.2 documentation 中文版文档可参考&#xff1a; MoviePy中文手册 — mov…...

Java高手的30k之路|面试宝典|精通泛型

泛型 知识点 在Java高级开发中&#xff0c;掌握泛型&#xff08;Generics&#xff09;是非常重要的&#xff0c;它是Java语言中的一项重要特性&#xff0c;提供了编译时类型安全检查机制&#xff0c;使得代码更加灵活和可重用。以下是Java高级开发需要掌握的泛型知识点&#…...

清理Linux操作系统buff/cache缓存

清理Linux操作系统buff/cache缓存 清理页缓存 echo 1 > /proc/sys/vm/drop_caches 或者 sysctl -w vm.drop_caches1 清理目录项和inode缓存 echo 2 > /proc/sys/vm/drop_caches 或者 sysctl -w vm.drop_caches2 同时清理页缓存、目录项和inode缓存 echo 3 > /pr…...

接口测试的几种方法

其实无论用那种测试方法&#xff0c;接口测试的原理是通过测试程序模拟客户端向服务器发送请求报文&#xff0c;服务器接收请求报文后对相应的报文做出处理然后再把应答报文发送给客户端&#xff0c;客户端接收应答报文这一个过程。 方法一、用LoadRunner实现接口测试 大家都…...

OpenGL3.3_C++_Windows(3)

GLSL Shader基础 Shader&#xff08;把输入转化为输出,运行在GPU上&#xff09;&#xff1a;首先要声明版本&#xff0c;有各自的入口点main&#xff08;&#xff09;顶点数据上限:16个包含4分量&#xff1a;16 * 4 64个分量向量&#xff1a;容器vec。使用.x、.y、.z和.w&am…...

24执业药师报名时间汇总及报名流程!

24执业药师报名时间汇总&#xff01;报名流程&#xff01; &#x1f55b;️各省市报名时间汇总&#xff08;共9地&#xff09; 西藏&#xff1a;6月29日-7月8日 新疆&#xff1a;6月25日10:30-7月9日19:00 内蒙古&#xff1a;6月20日9:00-7月3日24:00 新疆兵团&#xff1a;6月2…...

成都跃享未来教育咨询解锁新篇章

在快节奏的现代社会中&#xff0c;每个人都在追求着属于自己的非凡人生。而成都跃享未来教育咨询&#xff0c;正是那个能够智慧引领你走向成功、成就非凡人生的灯塔。 跃享未来教育咨询&#xff0c;位于历史悠久的文化名城成都&#xff0c;这里不仅有丰富的文化底蕴&#xff0c…...

怎么把网页上的接口信息导入postman

第一步 打开f12&#xff0c;右键选中需要的接口。选择copy-copy as cURL 第二步 打开postman&#xff0c;选择"Raw Text"&#xff0c; 把刚才复制的curl粘贴到空白位置&#xff0c;点击Continue - 最后的效果。导入的接口自带cookie&#xff0c;不用再输入cookie&a…...

10KM无人机高清图传通信模组,低延迟、抗干扰,飞睿智能无线MESH组网模块

随着科技的飞速发展&#xff0c;无人机技术在各个领域的应用越来越广泛。尤其在海上监测、搜索救援、货物运输等场景中&#xff0c;无人机的应用显得尤为重要。然而&#xff0c;要实现无人机在复杂海域环境中的高效通信&#xff0c;高清图传通信模组的作用不可忽视。本文将深入…...

分布式文件存储 - - - MinIO从入门到飞翔

MinIO从入门到飞翔 文章目录 MinIO从入门到飞翔0、前言1、分布式文件系统2、MinIO 介绍3、 MinIO安装&#xff08;docker&#xff09;4、基本概念5、通过代码上传文件到MinIO6、封装MinIO为starter7、在其他项目中集成封装好的模块 0、前言 对象存储是一种数据存储架构&#x…...

Python界面编辑器Tkinter布局助手 使用体验

一、发现 我今天在网上搜关于Python Tkinter方面的信息时&#xff0c;发现了Python界面编辑器 Tkinter布局助手 的使用说明。 https://blog.csdn.net/weixin_52777652/article/details/135291731?spm1001.2014.3001.5506 这个编辑器是个开源的项目&#xff0c;个人用户可以…...

嵌入式操作系统_2.嵌入式操作系统的一般架构

1.嵌入式操作系统的概念 嵌入式操作系统通常由硬件驱动程序、调式代理、操作系统内核、文件系统和可配置组件等功能组成&#xff0c;并为应用软件提供标准的API&#xff08;Application Programming Interface&#xff09;接口服务。 2.一般嵌入式操作系统的体系结构 从嵌入…...

docker 容器 network host 模式启动

docker 默认启动容器 network 是 bridge 模式&#xff0c;需使用 -p 映射端口实现容器与宿主机网络通信&#xff0c;较安全&#xff1b; 当使用 network host 模式&#xff0c;直接走宿主机网络通信&#xff0c;较不安全。 下面来一个 docker 容器 network host 模式启动 的 实…...

群晖NAS安装配置Joplin Server用来存储同步Joplin笔记内容

一、Joplin Server简介 1.1、Joplin Server介绍 Joplin支持多种方式进行同步用户的笔记数据&#xff08;如&#xff1a;Joplin自己提供的收费的云服务Joplin Cloud&#xff0c;还有第三方的云盘如Dropbox、OneDrive&#xff0c;还有自建的云盘Nextcloud、或者通过WebDAV协议来…...

leetcode-08-[151]翻转字符串里的单词[卡码网55]右旋转字符串

一、[151]翻转字符串里的单词 重点&#xff1a;见注释 class Solution {public String reverseWords(String s) {//1、移除多余的空格StringBuilder stringBuilder removeSpace(s);//2、反转整个字符串reverse(stringBuilder,0,stringBuilder.length()-1);//3、反转每个单词…...

Json-server 的使用教程

目录 前言一、简介二、安装与配置1. 安装 node-js2. npm 镜像设置3. 安装 json-server 三、使用1. 创建本地数据源2. 启动 Json Server3. 操作数据&#xff08;1&#xff09;查询数据&#xff08;2&#xff09;新增数据&#xff08;3&#xff09;修改数据&#xff08;4&#xf…...

LLM中表格处理与多模态表格理解

文档处理中不可避免的遇到表格&#xff0c;关于表格的处理问题&#xff0c;整理如下&#xff0c;供各位参考。 问题描述 RAG中&#xff0c;对上传文档完成版式处理后进行切片&#xff0c;切片前如果识别文档元素是表格&#xff0c;那么则需要对表格进行处理。一般而言&#x…...

短剧系统搭建全攻略:功能齐全,一步到位

前言 近年来&#xff0c;短剧系统以其独特魅力&#xff0c;成为大众消遣娱乐的热门选择。简单来说&#xff0c;短剧系统就是用来看短剧的小程序&#xff0c;它汇集了丰富多彩的短剧资源&#xff0c;让观众随时随地享受观影乐趣。本文将为你详细解析短剧系统的搭建全攻略&#…...

【Linux】进程_7

文章目录 五、进程8. 进程地址空间9. 进程终止10. 进程等待 未完待续 五、进程 8. 进程地址空间 我们上节知道了进程地址空间是根据页表来使虚拟地址转换成内存中的物理地址&#xff0c;那这种 地址空间 页表 的机制有什么好处呢&#xff1f;①这种机制可以将物理内存从无序…...

从中概回购潮,看互联网的未来

王兴的饭否语录里有这样一句话&#xff1a;“对未来越有信心&#xff0c;对现在越有耐心。” 而如今的美团&#xff0c;已经不再掩饰对未来的坚定信心。6月11日&#xff0c;美团在港交所公告&#xff0c;计划回购不超过20亿美元的B类普通股股份。 而自从港股一季度财报季结束…...

【OceanBase DBA早下班系列】—— 性能问题如何 “拍CT“ (一键获取火焰图和扁鹊图)

1. 前言 最近接连遇到几个客户的环境在排查集群性能问题&#xff0c;总结了一下&#xff0c;直接教大家如何去获取火焰图、扁鹊图&#xff08;调用关系图&#xff09;&#xff0c;直击要害&#xff0c;就像是内脏的疾病去医院看病&#xff0c;上来先照一个CT&#xff0c;通过分…...

4.类,方法,对象

1.1.2. 面向对象程序设计的三大特征 1.1.2.1. 封装 面向对象编程核心思想之一就是将数据和对数据的操作封装在一起&#xff0c;形成一般的概念&#xff0c;比如类的概念。 1.1.2.2. 继承 继承体现了一种先进的编程模式。子类可以继承父类的属性和方法。 1.1.2.3. 多态 多…...

重学java 71.网络编程

人生不是坐等暴风雨过去&#xff0c;而是学会在雨中起舞 —— 24.6.14 一、网络编程的基础概念 1.概述&#xff1a; 在网络通信协议下,不同计算机上运行的程序,进行数据传输 比如&#xff1a;通信、视频通话、网络、邮件 只要是计算机之间通过网络进行数据传输&#xff0c;就有…...

Linux驱动面试题

1.导出符号表的原理&#xff1f; 2.字符设备驱动的框架流程 open read wirte close 是系统调用&#xff08;从用户空间进入内核空间的唯一的方法&#xff09;会产生swi软中断《也会存在软中断号》&#xff08;从User模式切换到SVC&#xff08;管理模式&#xff09;下因为在…...

西安注册公司在哪个网站系统/整合营销包括哪些内容

前言在MySQL 3.23.44版本后&#xff0c;InnoDB引擎类型的表支持了外键约束。外键的使用条件&#xff1a;1.两个表必须是InnoDB表&#xff0c;MyISAM表暂时不支持外键(据说以后的版本有可能支持&#xff0c;但至少目前不支持)&#xff1b;2.外键列必须建立了索引&#xff0c;MyS…...

南通政府网站建设/网上网络推广

在 webpack-dev-server 的配置中添加disableHostCheck: true转载于:https://www.cnblogs.com/xiaoroad/p/8967353.html...

那些网站布局好看/深圳谷歌优化seo

构造函数 function Promise(fn) {this.status "pending"; // promise的状态 pending/fullfiled/rejected&#xff0c;不可逆转this.fn fn; // 保存promise传入的回调&#xff0c;在then里面执行 } 复制代码then函数 Promise.prototype.then function(onFullfilled…...

网站开发的核心技术/新网站如何推广

小X 正困在一个密室里&#xff0c;他希望尽快逃出密室。 密室中有N 个房间&#xff0c;初始时&#xff0c;小X 在1 号房间&#xff0c;而出口在N 号房间。 密室的每一个房间中可能有着一些钥匙和一些传送门&#xff0c;一个传送门会单向地创造一条从房间X 到房间Y 的通道。另外…...

荔湾网站建设公司/天津搜索引擎优化

在刚刚过去的MWC2019上&#xff0c;如果要为无数终端展台总结一个特点&#xff0c;那么可能是这样的&#xff1a;千变万化&#xff0c;各显神通。早先几年&#xff0c;手机行业似乎比拼的是对趋势的跟随能力&#xff0c;行业有清晰的聚焦点&#xff0c;谁能更快跟随主流&#x…...

国内谷歌网站SEO优化/seo收索引擎优化

RxJava正在Android开发者中变的越来越流行。唯一的问题就是上手不容易&#xff0c;尤其是大部分人之前都是使用命令式编程语言。但是一旦你弄明白了&#xff0c;你就会发现RxJava真是太棒了。 这里仅仅是帮助你了解RxJava&#xff0c;整个系列共有四篇文章&#xff0c;希望你看…...