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

Spring Webflux - 01 MVC的困境

文章目录

  • Spring MVC的困境
  • Servlet 异步请求缓解线程池压力
    • Servlet 3.0 异步请求处理
    • Code 演示
      • 工程
      • pom
      • 配置文件
      • 启动类
      • 同步servlet
        • 演示
      • 异步servlet
        • 辅助Code
        • 演示
  • Tomcat 请求处理流程以及异步请求工作原理

在这里插入图片描述


Spring MVC的困境

我们先看一段工作中大家常见的代码

@RestController
public class TestAController {
@RequestMapping(value ="resource",method = RequestMethod.GET)
public Object processResult(){
 	RestTemplate restTemplateew RestTemplate();
	 ∥请求外部资源
	String result =  restTemplate. getForObject("http://example.com/api/resource2", String.class)
	return processResultFurther(result);
}

private String processResultFurther(String result){
	return "resource here"
}

Tomcat处理请求,线程状态的变化如下:
在这里插入图片描述

我们发现这里的请求和响应事实上 是 同步阻塞

再深入想一下,如果每个线程的执行时间是不可控的,而Tomcat线程池中的线程数量是有限的…

那该怎么办呢?

在这里插入图片描述


Servlet 异步请求缓解线程池压力

我们来算一下:

  • TPS : 2000/s
  • 请求耗时:250ms

那么在这种情况下:

  • tomcat最大线程数配置: 2000/s * 0.25s=500

因此 server. tomcat. threads. max=500 基本能满足需求

那假设 tps 到了 4000 呢?

虽然我们可以扩大线程数量,但线程是要消耗操作系统资源的,也并非越多越好,当然了还有其他很多影响因素。

那怎么办呢?
在这里插入图片描述


Servlet 3.0 异步请求处理

Filter/Servlet在生成响应之前可能要等待一些资源的响应以完成请求处理,比如一个jdbc查询,或者远程服务rpc调用。

Servlet阻塞等待是一个低效的操作,这将导致受限系统资源急剧紧张,比如线程数、连接数等等

Servlet 3.0引入了异步处理请求的能力,使得线程可以不用阻塞等待,提早返回到容器,从而执行更多的任务请求。把耗时的任务提交给另一个异步线程去执行,以及产生响应


Code 演示

工程

在这里插入图片描述


pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.artisan</groupId>
    <artifactId>servlet-asyn</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

</project>

配置文件

server.tomcat.threads.max=1

启动类

package com.artisan;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 启动类
 * @date 2022/10/6 12:03
 * @mark: show me the code , change the world
 */

@SpringBootApplication
// 使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码
@ServletComponentScan
public class ServletAsyncApplication {


    public static void main(String[] args) {
        SpringApplication.run(ServletAsyncApplication.class,args);
    }
}
    

同步servlet

package com.artisan.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 同步Servlet请求
 * @date 2022/10/6 12:06
 * @mark: show me the code , change the world
 */

@WebServlet(value = "/sync")
@Slf4j
public class SyncServlet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("request: {}, currentThread: {}", req.getQueryString(), Thread.currentThread().getName());

        processFuture(req, resp);
    }

    private void processFuture(HttpServletRequest req, HttpServletResponse resp) {
        try {
            TimeUnit.SECONDS.sleep(10);
            resp.getWriter().println("sync handler");
        } catch (IOException  | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}
    

演示

在这里插入图片描述


异步servlet

package com.artisan.servlet;

import com.artisan.handler.AsyncRequestWrapper;
import com.artisan.handler.AsyncServletRejectedHandler;
import com.artisan.handler.AsyncThreadFactory;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 异步Servlet请求
 * @date 2022/10/6 15:23
 * @mark: show me the code , change the world
 */

@WebServlet(value = "/async", asyncSupported = true)
@Slf4j
public class AsyncServlet extends HttpServlet {


    private ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(1),
            AsyncThreadFactory.builder().threadName("async-thread-pool").build(),
            new AsyncServletRejectedHandler());


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("request: {}, currentThread: {}", req.getQueryString(), Thread.currentThread().getName());

        AsyncContext asyncContext = req.startAsync();

        AsyncRequestWrapper wrapper = AsyncRequestWrapper.builder().asyncContext(asyncContext)
                .servletRequest(req)
                .servletResponse(resp)
                .thread(Thread.currentThread())
                .build();

        executor.execute(wrapper);
    }
}
    

辅助Code

【AsyncThreadFactory 】

package com.artisan.handler;

import lombok.Builder;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 线程池工厂
 * @date 2022/10/6 15:35
 * @mark: show me the code , change the world
 */

@Builder
public class AsyncThreadFactory implements ThreadFactory {

    private final ThreadFactory threadFactory = Executors.defaultThreadFactory();

    private String threadName;

    private final AtomicInteger atomicInteger = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = threadFactory.newThread(r);
        thread.setName(this.threadName + "-" + atomicInteger.getAndIncrement());
        return thread;
    }
}
    

【AsyncRequestWrapper 】

package com.artisan.handler;

import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 线程包装对象
 * @date 2022/10/6 15:42
 * @mark: show me the code , change the world
 */

@Slf4j
@Data
@Builder
public class AsyncRequestWrapper implements Runnable {

    private AsyncContext asyncContext;
    private ServletRequest servletRequest;
    private ServletResponse servletResponse;
    private Thread thread;

    @Override
    public void run() {
        processFuture(asyncContext, servletRequest, servletResponse);
    }

    private void processFuture(AsyncContext asyncContext, ServletRequest servletRequest, ServletResponse servletResponse) {

        HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;

        try {
            TimeUnit.SECONDS.sleep(10);
            log.info("processFuture 当前处理线程 {}",  Thread.currentThread().getName());
            servletResponse.getWriter().println("async handler -->" + httpServletRequest.getQueryString());
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 完成
        asyncContext.complete();

    }
}
    

【AsyncServletRejectedHandler】

package com.artisan.handler;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 拒绝策略
 * @date 2022/10/6 15:24
 * @mark: show me the code , change the world
 */
@Slf4j
public class AsyncServletRejectedHandler  implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        AsyncRequestWrapper wrapper = (AsyncRequestWrapper) r ;
        HttpServletRequest httpServletRequest = (HttpServletRequest) wrapper.getServletRequest();

        try {
            String queryString = httpServletRequest.getQueryString();
            String threadName = wrapper.getThread().getName();
            log.info("当前线程:{} , 当前线程请求参数:{}", threadName, queryString);
            wrapper.getServletResponse().getWriter().println("too many request , current thread:" + threadName + " , current param:" + queryString );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 别忘了 complete
        wrapper.getAsyncContext().complete();
    }
}
    

演示

在这里插入图片描述

2022-10-06 21:30:09.700  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=1, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:11.277  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=2, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:12.813  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=3, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:12.813  INFO 7860 --- [nio-8080-exec-1] c.a.handler.AsyncServletRejectedHandler  : 当前线程:http-nio-8080-exec-1 , 当前线程请求参数:i=3
2022-10-06 21:30:15.355  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=4, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:15.355  INFO 7860 --- [nio-8080-exec-1] c.a.handler.AsyncServletRejectedHandler  : 当前线程:http-nio-8080-exec-1 , 当前线程请求参数:i=4
2022-10-06 21:30:19.712  INFO 7860 --- [c-thread-pool-1] com.artisan.handler.AsyncRequestWrapper  : processFuture 当前处理线程 async-thread-pool-1
2022-10-06 21:30:29.719  INFO 7860 --- [c-thread-pool-1] com.artisan.handler.AsyncRequestWrapper  : processFuture 当前处理线程 async-thread-pool-1


Tomcat 请求处理流程以及异步请求工作原理

在这里插入图片描述

在这里插入图片描述

相关文章:

  • 自动登录禅道和自动开Bug(JMeter中HTTP Cookie管理器和HTTP请求默认值的用法)
  • 【Day23】力扣:LeetCode算法刷题 [927. 三等分 ] [415. 字符串相加]
  • 芯动科技面试——数字IC/FPGA面试案例总结1
  • [SpringMVC] SSM整合-前后台协议联调
  • DOM操作.操作元素内容
  • 【Java SE】String类
  • 数据可视化看板:基于 Echarts + Python Flask 动态实时大屏
  • 5道真题训练|学会了二叉树的前世今生
  • python专区--时间模块
  • 36、Java 中的 String、StringBuilder、StringBuffer、字符串常量池和 intern 方法
  • 基于正交设计的折射反向学习樽海鞘群算法
  • 国庆假期浏览了几十篇YOLO改进英文期刊,总结改进创新的一些相同点(期刊创新点持续更新)
  • 《计算机视觉基础知识蓝皮书》第5篇 目标检测基础
  • 提升能力和认知边界,最有效的方法是赚钱
  • Window下使用RegisterWindowMessage来实现消息通讯
  • 【347天】每日项目总结系列085(2018.01.18)
  • css系列之关于字体的事
  • docker python 配置
  • input的行数自动增减
  • JAVA 学习IO流
  • Node + FFmpeg 实现Canvas动画导出视频
  • Redis 中的布隆过滤器
  • SQL 难点解决:记录的引用
  • TypeScript迭代器
  • Vue实战(四)登录/注册页的实现
  • 高程读书笔记 第六章 面向对象程序设计
  • 机器学习中为什么要做归一化normalization
  • 聊聊flink的BlobWriter
  • 那些被忽略的 JavaScript 数组方法细节
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 实现菜单下拉伸展折叠效果demo
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 跳前端坑前,先看看这个!!
  • 做一名精致的JavaScripter 01:JavaScript简介
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • #includecmath
  • #pragam once 和 #ifndef 预编译头
  • #vue3 实现前端下载excel文件模板功能
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (06)Hive——正则表达式
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (一)基于IDEA的JAVA基础10
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .gitignore
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .net MVC中使用angularJs刷新页面数据列表
  • .net 微服务 服务保护 自动重试 Polly
  • .net6使用Sejil可视化日志
  • .so文件(linux系统)
  • :“Failed to access IIS metabase”解决方法
  • @requestBody写与不写的情况