1. 概念html
Reactive 很是适合低延迟、高吞吐量的工做负载。vue
Reactive Processing 是一种范式(规范),它使开发人员可以构建非阻塞的、异步的应用程序,这些应用程序可以处理背压(流控制)react
Reactive Streams 为无阻塞背压的异步流处理提供标准。angularjs
Reactor 是基于Reactive Streams规范的第四代响应库,用于在JVM上构建非阻塞的应用程序。spring
Project Reactor 是一个彻底无阻塞的基础,其中包括背压支持。它是Spring生态系统中的响应式堆栈的基础,而且在诸如Spring WebFlux,Spring Data和Spring Cloud Gateway等项目中都有它的身影。利用Project Reactor能够高效的响应式系统。刚才说Reactive Streams是规范,那么Project Reactor就是实现。编程
2. 响应式编程数组
响应式编程是一种异步编程风格,它关注数据流和变化的传播。服务器
响应式编程是一种与数据流和变化传播相关的声明式编程范式。使用此范例,能够轻松地表示静态(例如,数组)或动态(例如,事件发射器)数据流,而且还能够表示关联执行模型中的推断出的依赖关系,这有助于更改数据流的自动传播。 网络
reactive programming (响应式编程)多线程
imperative programming(命令式编程)
在命令式编程中,a:=b+c意味着将b+c的结果赋值给a,而且此后b或c的值发生变化不会影响到a的值。而在响应式编程中,a的值会随着b或c的改变而自动更新,而且不须要从新执行a:=b+c来肯定当前分配给a的值。(PS:是否是很像angularjs、vuejs这种MVVM框架,视图绑定模型,模型变了,视图自动就跟着变了)
例如,在 model–view–controller (MVC) 架构中,响应式编程能够促进基础模型中的更改,这些更改会自动反映在关联的视图中。
响应式编程与面向对象编程中一般使用的观察者模式具备不少类似之处。
若是从推拉的角度来看的话,响应式编程是“推”,它主动将变化推送给它的订阅者。Publisher-Subscriber是两个很是重要的概念。
想象一下,数据流从源出发,通过一个一个节点的处理,最终达到目的地。节点就至关于操做符,处理完了之后就将流发射出去,到下一个节点再执行再发射。
我总以为这个流程很眼熟,很像 Apache Storm 的处理方式。在一个拓扑结构中,数据流从Spout发出,通过若干bolt的处理,最终聚集到某个地方。
还有一种理解,我以为也很不错,说响应式编程是一种经过异步和数据流来构建事务关系的编程模型。事物能够理解程一次处理过程,一次执行过程。响应式编程就是要构建关系,事务和事务之间的关系。而数据流就像是一个桥梁同样,数据流从一个事务流向下一个事务。
想象一下,长江流经宜宾、泸州、重庆、涪陵、万州、宜昌、荆州、武汉、黄石、鄂州、九江、安庆、铜陵、芜湖、南京、上海,最终汇入东海。
就像CompleteFuture把Future进行编排同样。
本质来说,响应式编程上是对数据流或某种变化所做出的反应,可是这个变化何时发生是未知的,因此他是一种基于异步、回调的方式在处理问题
3. NIO
NIO(Non-Blocking I/O)
BIO(Blocking I/O)
在经典的线程模型中,socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个链接在处理I/O的时候,系统是阻塞的,若是使用单线程的话就阻塞在那里了,但CPU是并无阻塞,若是用多线程的话,就可让CPU去处理更多的事情。其实这也是全部使用多线程的本质: 当I/O阻塞系统,但CPU空闲的时候,能够利用多线程使用CPU资源。然而,线程的建立、销毁、切换成本都是很高的。
事实上,全部的系统I/O都分为两个阶段:等待就绪和操做。举例来讲,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡能够写和真正的写。
须要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操做的阻塞是使用CPU的,真正在”干活”。
以socket.read()为例子:
传统的BIO里面socket.read(),若是TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
对于NIO,若是TCP RecvBuffer有数据,就把数据从网卡读到内存,而且返回给用户;反之则直接返回0,永远不会阻塞。
在BIO模型中,没有办法知道到底能不能写、能不能读,只能”傻等”。而在NIO模型中,若是一个链接不能读写(socket.read()返回0或者socket.write()返回0),咱们能够把这件事记下来,记录的方式一般是在Selector上注册标记位,而后切换到其它就绪的链接(channel)继续进行读写。
NIO的主要事件有几个:读就绪、写就绪、有新链接到来。那么,首先须要注册当这几个事件到来的时候所对应的处理器,而后在合适的时机告诉事件选择器:我对这个事件感兴趣,最后用一个死循环选择就绪的事件。select是阻塞的,因此你能够放心大胆地在一个while(true)里面调用这个函数而不用担忧CPU空转。
总结起来就是:注册全部感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
咱们大概能够总结出NIO是怎么解决掉线程的瓶颈并处理海量链接的:
NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到能够进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必需要阻塞),剩余的I/O操做都是纯CPU操做,没有必要开启多线程。
NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到能够进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必需要阻塞),剩余的I/O操做都是纯CPU操做,没有必要开启多线程。而且因为线程的节约,链接数大的时候由于线程切换带来的问题也随之解决,进而为处理海量链接提供了可能。单线程处理I/O的效率确实很是高,没有线程切换,只是拼命的读、写、选择事件。但如今的服务器,通常都是多核处理器,若是可以利用多核心进行I/O,无疑对效率会有更大的提升。
Buffer(缓冲区)
在NIO中,全部数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,它也是写入到缓冲区中的。
Channel(通道)
通道是一个对象,经过它能够读取和写入数据,固然了全部数据都经过Buffer对象来处理。咱们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。一样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
Selector(选择器)
Selector类是NIO的核心类,Selector(选择器)选择器提供了选择已经就绪的任务的能力。Selector会不断的轮询注册在上面的全部channel,若是某个channel为读写等事件作好准备,那么就处于就绪状态,经过Selector能够不断轮询发现出就绪的channel,进行后续的IO操做。一个Selector可以同时轮询多个channel。这样,一个单独的线程就能够管理多个channel,从而管理多个网络链接。这样就不用为每个链接都建立一个线程,同时也避免了多线程之间上下文切换致使的开销。
参考:
https://www.jianshu.com/p/d47835316016