最近在学习响应式编程,由此翻看了很多资料,在此把思考过程记录下
目录
来由
规范
具体实现
历史
1. Servlet的尝试
1.1 Async实现demo
2. web应用层
2.1 web-flux
2.2 web-flux/web-mvc 与 spring-web架构关系
3. DBConnection层
相关可能混淆的概念
附:关于Servlet历史的描述来源于:讨论
来由
The Reactive Manifesto:https://www.reactivemanifesto.org/
规范
Jvm实现规范:https://github.com/reactive-streams/reactive-streams-jvm
具体实现
- web层:web-flux
- DB层:r2dbc
历史
1. Servlet的尝试
看以上资料过程中,得知Servlet也对此做过尝试,痛点来源于以下两点:
- client端发送量大且发送速度(比如带宽较小)无法于server端处理速度匹配时(server能够处理过来)
- server端在处理过程中有io(network或disk)延迟较高的处理
以上两种情况都会导致在传统servlet及其web container(经典的比如tomcat)下造成线程阻塞,比如tomcat采用线程池处理request请求,一旦client端数量较多且以上两种情况发生时,会造成server端无法处理request请求(等线程资源)。
历史如下:
- 3.0之前:servlet接收到请求后,释放来自于web container的thread,开启新的线程,处理request和response,但这些由具体的web container实现,Tomcat’s Comet, WebLogic’s FutureResponseServlet and WebSphere’s Asynchronous Request Dispatcher
- 3.0:支持Async,在Servlet spec层面支持一个单独的线程池处理此类请求
- 3.1:NIO,本质就是在对3.0的一个补充,在I/O层面使用了非阻塞IO模型,将原来会阻塞在IO等待上的线程进一步释放,request可读的listener和response可write的listener,也是在spec层面支持,并由spring-web进行了具体实现(未明白和IO多路复用的关系,待深入学习,看样子是拷贝到user-space的监听?)
1.1 Async实现demo
package org.example;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.AsyncContext;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// todo 该注解在springboot下无效,或者是没发现别的机制
// @WebServlet(urlPatterns = "/asyncservlet", asyncSupported = true)
/**
* 主要作用就是把server端servlet的thread释放出来,弄一个新线程,去从input读或response写,
* 进而将container中的thread释放出来,能够再处理更多的这样的连接,不至于卡死web container(如典型的tomcat)
* 好像servlet 3.0已经支持(todo 在头里加ASYNC,而开发者不用管? 待验证)
* 深入想一下,
* 1. tomcat是什么?实现了http协议解析并基于servlet规范实现
* 2. webmvc是什么?实现了具体的servlet,并且基于spring-web规范实现
* 初步总结,他俩和一起能够使用spring mvc,抽象层次不同
* 3. webflux是什么?实现了具体的reactive,并且基于spring-web规范实现
* 所以,
* 第一点,webflux不排斥servlet,只要做好adapter就行
* 第二点,没想通的是,controller和flux提供者该如何写?
* flux提供者需要stream式/async式提供,不能在main中实现,否则就是sync
* 第三点,基于reactive的webflux在:
* netty下,已经是基于recator实现的
* servlet-container下,在具体的servlet实现基础上,尽早的释放了原先会block的thread(web container的http-nio命名的线程池中线程),
* 将result处理过程改为pub-sub的方式,并在处理完后对response做commit处理(与servlet的async机制类似,只不过抽象层级为spring-web)
* httpserver层次的IO多路复用+应用层次的reactive(附带IO部分的NIO)+后端处理层面的r2dbc/r2...整体才是最佳方案。
*/
@Slf4j
public class AsyncServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response) {
response.setContentType("text/html;charset=UTF-8");
final AsyncContext acontext = request.startAsync();
log.info("main, thread:{}", Thread.currentThread());
acontext.start(new Runnable() {
@SneakyThrows
public void run() {
String param = acontext.getRequest().getParameter("param");
log.info("async,param:{}, thread:{}", param, Thread.currentThread());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ServletResponse response = acontext.getResponse();
/* ... print to the response ... */
response.getOutputStream().write("success1".getBytes());
// 执行此,会立即将数据发送给client
response.getOutputStream().flush();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
response.getOutputStream().write("success2".getBytes());
// 未执行flush,故会与3一起返回
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
response.getOutputStream().write("success3".getBytes());
// 以上print信息:说明是异步
// 2023-01-07 11:06:15.398 INFO 32164 --- [nio-8080-exec-1] org.example.AsyncServlet : main, thread:Thread[http-nio-8080-exec-1,5,main]
// 2023-01-07 11:06:15.404 INFO 32164 --- [nio-8080-exec-2] org.example.AsyncServlet : async,param:gxx, thread:Thread[http-nio-8080-exec-2,5,main]
// 中间是10s的等待
// 2023-01-07 11:06:45.894 INFO 32164 --- [nio-8080-exec-3] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
// 2023-01-07 11:06:45.894 INFO 32164 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
// 2023-01-07 11:06:45.897 INFO 32164 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
// 2023-01-07 11:06:45.989 ERROR 32164 --- [nio-8080-exec-3] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/asyncservlet] as the response has already been committed. As a result, the response may have the wrong status code.
// 此处无关
response.getOutputStream().flush();
// 主要是此处complete能及时告知完成了(todo timeout=20s后,也会由client端发起complete??)
acontext.complete();
}
});
}
}
2. web应用层
2.1 web-flux
见官网
2.2 web-flux/web-mvc 与 spring-web架构关系
3. DBConnection层
r2dbc
相关可能混淆的概念
- reactor&&proactor(此为IO多路复用技术,可查看链接学习)
附:关于Servlet历史的描述来源于:讨论
Prior to Servlet 3.0:
The problem with synchronous processing of requests is that it resulted in threads (doing heavy-lifting) running for a long time before the response goes out. If this happens at scale, the servlet container eventually runs out of threads - long running threads lead to thread starvation.Prior to Servlet 3.0, there were container specific solutions for these long running threads where we can spawn a separate worker thread to do the heavy task and then return the response to client. The servlet thread returns to the servlet pool after starting the worker thread. Tomcat’s Comet, WebLogic’s FutureResponseServlet and WebSphere’s Asynchronous Request Dispatcher are some of the example of implementation of asynchronous processing.
(See link 1 for more info.)Servlet 3.0 Async:
The actual work could be delegated to a thread pool implementation (independent of the container specific solutions). TheRunnable
implementation will perform the actual processing and will use theAsyncContext
to either dispatch the request to another resource or write the response. We can also add AsyncListener implementation to the AsyncContext object to implement callback methods.
(See link 1 for more info.)Servlet 3.1 NIO:
As described above, Servlet 3.0 allowed asynchronous request processing but only traditional I/O (as opposed to NIO) was permitted. Why is traditional I/O a problem?In traditional I/O, there are two scenarios to consider:
- If the data coming into the server (I/O) is blocking or streamed slower than the server can read, then the server thread that is trying to read this data has to wait for that data.
- On the other hand, if the response data from the server written to
ServletOutputStream
is slow, the client thread has to wait. In both cases, the server thread doing the traditional I/O (for requests/responses) blocks.In other words, with Servlet 3.0, only the request processing part became async, but not the I/O for serving the requests and responses. If enough threads block, this results in thread starvation and affects performance.
With Servlet 3.1 NIO, this problem is solved by
ReadListener
andWriteListener
interfaces. These are registered inServletInputStream
andServletOutputStream
. The listeners have callback methods that are invoked when the content is available to be read or can be written without the servlet container blocking on the I/O threads. So these I/O threads are freed up and can now serve other request increasing performance. (See link 2 for more info.)
相关资料:
https://blog.csdn.net/GAOXINXINGgaoxinxing/article/details/128591628