堵塞与非堵塞原理
传统硬件的堵塞(IO)如下,从内存中读取数据,然后写到磁盘,而CPU一直等到磁盘写完成,磁盘的写操作是慢的,这段时间CPU被堵塞不能发挥效率。
使用非堵塞(NIO)的DMA如下图:CPU只是发出写操作这样的指令,做一些初始化工作,DMA具体执行,从内存中读取数据,然后写到磁盘,当完成写后发出一个中断事件给CPU。这段时间CPU是空闲的,可以做别的事情。这个原理称为Zero.copy零拷贝。
非阻塞IO模型
几种 IO 模型比较
Linux 的 IO 多路复用模型
IO 多路复用模型又称为事件驱动模型。IO 多路复用通过把多个 IO 阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下,可以同时处理多个 client 请求。IO 多路复用实际上就是通过一种机制,一个进程可以监视多个描 fd,一旦某个 fd 就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,目前支持 IO 多路复用的系统有 select、pselect、poll、epoll,但它们本质上都是同步 IO。
多路复用三种模型的区别
类别 | select | poll | epoll |
---|---|---|---|
支持的最大连接数 | 由 FD_SETSIZE 限制 1024(x86) or 2048(x64) |
基于链表存储,没有限制 | 受系统最大句柄数限制 |
IO效率 | 每次调用进行线性变量,时间复杂度O(n) | 同 select | 使用“事件”通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdlist里面,这样epoll_wait返回的时候应用就拿到了就绪的fd。时间复杂度O(1) |
fd 剧增的影响 | 线性扫描 fd 导致性能很低 | 同 select | 基于 fd 上 callback 实现,没有性能下降的问题 |
消息传递机制 | 内核需要将消息传递到用户空间,需要内核拷贝 | 同 select | epoll 通过内核与用户空间共享内存来实现 |
Java的NIO
Netty是一个高性能事件驱动的异步的非堵塞的IO(NIO)框架,用于建立TCP等底层的连接,基于Netty可以建立高性能的Http服务器。Netty的一个线程服务于很多请求,如下图,当从Java NIO获得一个Selector事件,将激活通道Channel。
WebClient
WebClient是spring的一个响应式客户端,它提供了RestTemplate的替代方法。它公开了一个功能齐全、流畅的API,非阻塞的、被动的客户端执行HTTP请求。WebClient使用与WebFlux服务器应用程序相同的编解码器,并与服务器功能Web框架共享公共基本包。默认情况下,它使用Reactor Netty作为HTTP客户端库,但其他人可以通过自定义ClientHttpConnector插入。
WebClient运行时核心类库
WebClient请求就是这么简单
WebClient.builder()
.baseUrl("http://www.baidu.com")
.build()
.get()
.exchange()
.flatMap(response->response.bodyToMono(String.class))
.subscribe(s->{
System.out.println(s);
});
运行时核心代码分析
- 订阅http请求响应
//reactor.core.publisher.Mono
public final Disposable subscribe(
@Nullable Consumer<? super T> consumer,
@Nullable Consumer<? super Throwable> errorConsumer,
@Nullable Runnable completeConsumer,
@Nullable Consumer<? super Subscription> subscriptionConsumer) {
return subscribeWith(new LambdaMonoSubscriber<>(consumer, errorConsumer,
completeConsumer, subscriptionConsumer));
}
//reactor.ipc.netty.http.client.MonoHttpClientResponse
public void subscribe(final CoreSubscriber<? super HttpClientResponse> subscriber) {
ReconnectableBridge bridge = new ReconnectableBridge();
bridge.activeURI = startURI;
Mono.defer(() -> parent.client.newHandler(new HttpClientHandler(this, bridge),
parent.options.getRemoteAddress(bridge.activeURI),
HttpClientOptions.isSecure(bridge.activeURI),
bridge))
.retry(bridge)
.cast(HttpClientResponse.class)
.subscribe(subscriber);
}
- 发起http请求
//reactor.ipc.netty.http.client.MonoHttpClientResponse
public Publisher<Void> apply(NettyInbound in, NettyOutbound out) {
try {
URI uri = bridge.activeURI;
HttpClientOperations ch = (HttpClientOperations) in;
String host = uri.getHost();
int port = uri.getPort();
if (port != -1 && port != 80 && port != 443) {
host = host + ':' + port;
}
ch.getNettyRequest()
.setUri(uri.getRawPath() + (uri.getQuery() == null ? "" :
"?" + uri.getRawQuery()))
.setMethod(parent.method)
.setProtocolVersion(HttpVersion.HTTP_1_1)
.headers()
.add(HttpHeaderNames.HOST, host)
.add(HttpHeaderNames.ACCEPT, ALL);
if (Objects.equals(parent.method, HttpMethod.GET)
|| Objects.equals(parent.method, HttpMethod.HEAD)
|| Objects.equals(parent.method, HttpMethod.DELETE)) {
ch.chunkedTransfer(false);
}
if (parent.handler != null) {
return parent.handler.apply(ch);
}
else {
return ch.send();
}
}
catch (Throwable t) {
return Mono.error(t);
}
}
- 接收http数据
exchange()
//org.springframework.web.reactive.function.client.ExchangeFunctions
public Mono<ClientResponse> exchange(ClientRequest request) {
return this.connector
.connect(request.method(), request.url(),
clientHttpRequest -> request.writeTo(clientHttpRequest, this.strategies))
.doOnSubscribe(subscription -> logger.debug("Subscriber present"))
.doOnRequest(n -> logger.debug("Demand signaled"))
.doOnCancel(() -> logger.debug("Cancelling request"))
.map(response -> {
if (logger.isDebugEnabled()) {
int status = response.getRawStatusCode();
HttpStatus resolvedStatus = HttpStatus.resolve(status);
logger.debug("Response received, status: " + status +
(resolvedStatus != null ? " " + resolvedStatus.getReasonPhrase() : ""));
}
return new DefaultClientResponse(response, this.strategies);
});
}
- 合并数据
//org.springframework.core.io.buffer.DataBufferUtils
public static Mono<DataBuffer> join(Publisher<DataBuffer> dataBuffers) {
return Flux.from(dataBuffers)
.collectList()
.filter(list -> !list.isEmpty())
.map(list -> {
DataBufferFactory bufferFactory = list.get(0).factory();
return bufferFactory.join(list);
});
}
- 转换数据
flatMap(response->response.bodyToMono(String.class));
public Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
return DataBufferUtils.join(inputStream)
.map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
}
protected String decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
Charset charset = getCharset(mimeType);
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
return charBuffer.toString();
}
reactor core核心接口分析
构建
接口 | 说明 | 举例 |
---|---|---|
from | 从其他响应式流中创建reactor core的流,适用于所有不同reactor stream的实现间的适配 | Mono.from(Publisher<? extends T> source) |
defer | 延迟建立新的流,defer一开始不会马上创建Publisher,直到有订阅者订阅时才会创建,且每次都创建全新的 Publisher。 | Mono.defer(Supplier<? extends Mono<? extends T>> supplier) |
just | 基于对象创建流,参数是流将要发送的元素 | Mono.just(T t) |
转换
接口 | 说明 | 举例 |
---|---|---|
map | 将流的元素按指定的方法变换 | map(o-> o.toString()) |
flatMap | 异步转换当前流发出的项,返回另一个流发出的值(可能更改值类型) | flatMap(s->Mono.just(s)) |
flatMap适用两种场景
a. 将当前流的元素转换为多个元素,并在新的流中逐一发出转换的多个元素。这里的flat平铺就是这个含义。
b. 由当前流的元素事件触发一个新的流。在http请求场景下,Mono<ClientResponse> clientResponse = request.exchange(); clientResponse这个流在http请求response header返回时得到了流元素ClientResponse,但此时response body还在管道中传输,ClientResponse实际没有拿到完整的body内容,所以ClientResponse将body封装到了一个新的流中,如果下游需要取到这个流,就必须通过flatMap获取。
参考文档
Unix 网络 IO 模型及 Linux 的 IO 多路复用模型: https://matt33.com/2017/08/06/unix-io/
Netty 之 Java 的 I/O 演进之路: https://www.jianshu.com/p/22f8586bb6f3
I/O多路复用和异步I/O:https://www.cnblogs.com/bigberg/p/8034629.html
I/O模型与多路复用: http://www.rainybowe.com/blog/2016/09/12/IO%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/index.html
使用 Reactor 进行反应式编程
https://www.ibm.com/developerworks/cn/java/j-cn-with-reactor-response-encode/index.html?lnk=hmhm
Reactive响应式编程
https://www.jdon.com/reactive.html
那些年我们错过的响应式编程
http://www.open-open.com/lib/view/open1429151543096.html
Reactor Reactive编程介绍
https://luyiisme.github.io/2017/02/11/spring-reactor-programing/
Reactor 入门与实践
https://studygolang.com/articles/11341?fr=sidebar
Reactor 实例解析
https://www.infoq.cn/article/reactor-by-example
Netty原理和使用
https://www.jdon.com/concurrent/netty.html
使用 UT 玩转 defer 和 retryWhen
https://www.jianshu.com/p/d42793eba591
照虎画猫深入理解响应式流规范
http://blog.51cto.com/liukang/2090922
Spring 5 WebClient
https://www.baeldung.com/spring-5-webclient