一 初识Web Flux
- 概念
Web Flux是Spring5.0提出的新的开发web的技术栈,它是一种非阻塞的开发模式,它运行在Netty或者Servlet 3.1的容器上边,支持非常高的并发量,也就是说我们现在开发Web应用又有了一种新的开发模式,可以使用之前的mvc开发模式也可以使用web Flux开发模式;
- Web Flux 与 MVC的区别
2.1 开发方式的不同:Web Flux是异步非阻塞的开发模式,MVC同步的阻塞的I/O开发模式;
2.2 运行环境的不同:MVC的运行环境是基于Servlet API的,所以它必须运行在Servlet容器上边,而Web Flux的开发模式是基于Reactive Stream流的方式,它可以运行在Netty或者Servlet 3.1及以后版本的容器上边;其实Spring默认的容器就是Netty;
2.3 数据库类型的不同:关系型数据库(mysql等)暂时不支持响应式的开发模式;
- Web Flux的优势
支持非常高的并发量;
二 异步Servlet
- 为什么要使用异步Servlet?
因为使用异步Servlet不会阻塞Tomcat的Servlet线程,所以异步Servlet可以达到非常高的吞吐量,可以处理非常高的并发;
- 同步Servlet阻塞了什么?
2.1 其实阻塞的是Tomcat容器的Servlet线程,当有网络请求发送到Tomcat以后,Tomcat会为每一个请求分配一个线程去处理,线程会调用对应的Servlet容器去处理,在这个处理的过程中,相关的业务代码所花费的时间就是Servlet线程等待的时间,这就是同步Servlet的阻塞;
代码演示:
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SyncServlet
*/
@WebServlet("/SyncServlet")
public class SyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SyncServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();
// 执行业务代码
doSomeThing(request, response);
System.out.println("sync use:" + (System.currentTimeMillis() - t1));
}
private void doSomeThing(HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
response.getWriter().append("done");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
- 异步Servlet是怎么工作的?
因为使用异步Servlet不会阻塞Tomcat的Servlet线程,它会将请求放在独立的线程池中(特别是对于比较耗时的操作),结果会立马被返回,结果返回后就接着处理下一个请求,所以异步Servlet可以达到非常高的吞吐量;
代码演示:
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class AsyncServlet
*/
@WebServlet(asyncSupported = true, urlPatterns = { "/AsyncServlet" })
public class AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public AsyncServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();
// 开启异步
AsyncContext asyncContext = request.startAsync();
// ִ执行业务代码
CompletableFuture.runAsync(() -> doSomeThing(asyncContext,
asyncContext.getRequest(), asyncContext.getResponse()));
System.out.println("async use:" + (System.currentTimeMillis() - t1));
}
private void doSomeThing(AsyncContext asyncContext,
ServletRequest servletRequest, ServletResponse servletResponse) {
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
//
try {
servletResponse.getWriter().append("done");
} catch (IOException e) {
e.printStackTrace();
}
// 业务代码处理完毕,通知结束
asyncContext.complete();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
三 Web Fulx开发效率对比
package com.imooc;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@Slf4j
public class TestController {
@GetMapping("/1")
private String get1() {
log.info("get1 start");
String result = createStr();
log.info("get1 end.");
return result;
}
@GetMapping("/2")
private Mono get2() {
log.info("get2 start");
Mono result = Mono.fromSupplier(() -> createStr());
log.info("get2 end.");
return result;
}
/**
* Flux : 返回0-n个元素
*
* @return
*/
@GetMapping(value = "/3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
private Flux flux() {
Flux result = Flux
.fromStream(IntStream.range(1, 5).mapToObj(i -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
return "flux data--" + I;
}));
return result;
}
private String createStr() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
return "some string";
}
}
从上边可以看出,get1(正常模式)执行时间为5秒,get2(Web Flux模式)执行基本没有时间损耗,性能更高;关于原因已经在上边进行了阐述这里就不在进行说明了;
四 SSE(server-sent events)
- SSE的本质
服务器向客户端声明,接下来要发送的是流信息(streaming),也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。
- SSE的服务端实现
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SSE
*/
@WebServlet("/SSE")
public class SSE extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SSE() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 实现SSE必须要设置的内容
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
for (int i = 0; i < 5; i++) {
// 指定事件标识
response.getWriter().write("event:me\n");
// 格式: data: + 数据 + 两个回车
response.getWriter().write("data:" + i + "\n\n");
response.getWriter().flush();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
- SSE的前端实现(基于H5)
Insert title here
- 效果展示 我们可以自定义我们的消息返回,请看图示 但是此时我们发现一个问题,就是如果不主动关闭的话,浏览器会一直的接受这个流,所以我们需要手动关闭,当执行到4的时候又从头开始接受了,所以我们限制一下让他接受到3的时候就关闭这个动作
- 更多关于SSE的问题请参考阮一峰的网络日志