>>分享流行的Java框架以及开源软件,对孙卫琴的《精通Spring:Java Web开发技术详解》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19210 个阅读者 刷新本主题
 * 贴子主题:  在Spring MVC中配置线程池,进行异步请求处理 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2024-01-20 07:14:17     消息  查看  搜索  好友  邮件  复制  引用

问题:

A项目中遇到一个导出功能,中间有调用到B项目视图,B项目提供的视图在业务高发期极度缓慢,正常运行时间为20-50ms,但是偶尔会慢到七八分钟,最长时会阻塞到两三个小时.故,在多用户同时进行导出时,会占满服务器连接,导致新请求阻塞,项目卡死.跟B系统开发反馈会得知,该视图已经优化多次,继续优化空间不大.同时,数据实时性要求较高,无法添加redis缓存.所以提出解决方案如下:

1.为该请求加上超时时间

2.进行http异步请求

3.限制单个用户同时发起多个请求

4.历史报表进行预下载,保存在服务器

解决方案:

1.首先要添加一个线程池来运行我们的业务代码,从而空出服务器线程以便接受其他请求.

因为我们项目框架是SpringMVC,所以我们要用Spring来创建一个线程池

    <!-- 配置异步http请求的线程池 -->
    <bean id="asyncHttpExecutor"                 class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 最少的线程数 -->
        <property name="corePoolSize" value="${asyncHttpExecutor.core_pool_size}"/>
        <!-- 最大线程数 -->
        <property name="maxPoolSize" value="${asyncHttpExecutor.max_pool_size}"/>
        <!-- 缓存队列 -->
        <property name="queueCapacity" value="${asyncHttpExecutor.queue_capacity}"/>
        <!-- 线程池维护线程所允许的空闲时间,默认为60s -->
        <property name="keepAliveSeconds" value="${asyncHttpExecutor.keep_alive_seconds}"/>
        <!-- 对拒绝task的处理策略 -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$AbortPolicy"/>
        </property>
    </bean>

2.配置spring可以处理异步的http请求

2.1:首先我们要给诶web.xml添加异步支持,这里是servlet3.0之后支持的,所以web-app标签中的version要大于3.0.但是都9012年了,用springmvc的项目都少,更别说太低版本的了.

接着给所有的servlet和filter都加上

<async-supported>true</async-supported>

注意的是,所有的都要加,刚开始只给DispatcherServlet加了,果然有问题.

2.2:Controller中返回特定的结果

2.2.1:Callable接口

返回一个Callable接口,spring会自己帮我们用线程池启动新的线程去执行,这里会使用默认的线程池,有多少请求进来会开多少线程,所以我们需要配置自己需要的线程池来限制.

需要在springmvc的配置里加上

<mvc:annotation-driven>
    <!--  可不设置,使用默认的超时时间 -->
    <mvc:async-support default-timeout="3000" task-executor="asyncHttpExecutor"/>
</mvc:annotation-driven>

这样可以自定义超时时间和执行业务代码的线程池.

2.2.2 DeferredResult<String>

通过返回这个参数,也可以起到异步的作用,这样可以更灵活的处理业务.

DeferredResult<String> deferredResult = new DeferredResult<String>(800_000L);
        deferredResult.onTimeout(() ->
                deferredResult.setResult("fail")
        );
        try {
            asyncHttpExecutor.submit(() -> {

                try {
                        ShardedJedis je = shardedJedisPool.getResource();
                        if (je.exists(userId)) {
                            je.close();
                            deferredResult.setResult("处理中,请稍后重试");
                            return;
                        } else {
                            je.setex(userId, 700, "true");
                            je.close();
                        }
                } catch (Exception e) {
                    deferredResult.setResult("未知错误");
                }finally{
                    ShardedJedis je = shardedJedisPool.getResource();
                    System.out.println("delete" + userId);
                    je.close();
                }
            });
        } catch (RejectedExecutionException e) {
            LogHelper.i(this.getClass(), "并发请求过多,请稍后再试");
            deferredResult.setResult("并发请求过多,请稍后再试");
        }
        return deferredResult;

首先,我们新建一个deferredResult,构造函数里可以设置超时时间,到达这个时间后会自动触发timeout方法,但是需要注意的是这里的超时并不会将业务线程终止.

那么,我们应该如何终止业务线程呢:

错误尝试:

首先想到的是用callable接口去处理,async.submit().get()来获取结果

这里有两个问题,

一是主线程中使用get会将主线程卡死,无法起到异步处理的作用,所以我新建里监控线程,在监控线程中get,防止主线程锁死.

二是get后如何终止线程,可以看到的是有shutdown(),shutdonwnnow(),interrupt()等方法,但是不幸的是,这几个方法都无法保证可以真正的停止线程,不像linux里面kill -9,想想也是有道理的,别的线程怎么能处理业务线程的状态呢,万一数据不一致怎么办.(要么处于sleep(),wait()状态可以抛出异常,要么自己的代码里判断isinterrupt 来确定线程是否继续执行)

所以,我在mybatis中设置了超时时间,当sql查询过慢时终止查询.(socket层,statement层,Transaction事务层)这里我设置在statement层.()

在业务处理结束后把返回结果写入deferredResult中就可以了.注意下如果是下载的功能,如果使用了response的write,就不用再写入deferredResult了,这时返回的流已经关闭了,会报错哦.

3,单个用户发起请求的限制

在redis中用用户id为key去做记录,进行业务操作前先判断redis中该key是否存在,这里要注意报错删除key,超时删除key等
  Java面向对象编程-->输入与输出(上)
  JavaWeb开发-->使用Session(Ⅰ)
  JSP与Hibernate开发-->数据库事务的概念和声明
  Java网络编程-->基于MVC和RMI的分布式应用
  精通Spring-->Vue Router路由管理器
  Vue3开发-->Vue指令
  微服务的拆分方式
  @Service注解的使用
  Spring 自动注入的三种方式:byName、byType、constructor
  【项目实践】使用Vue.js和ElementUI快速实现后台管理系统的界...
  @ModelAttribute注解用法详解
  Spring Cloud Config 客户端的高可用实现
  微服务架构的优点和缺点
  【Web服务开发】基于Java开发代驾定位系统,2天完成脚手架
  Spring+JPA+ehcache开启二级本地缓存
  Spring Cloud 服务发布与调用
  网红框架SpringBoot2.x之定制参数浅析
  kubernetes 中的资源
  Git 安装配置
  Spring入门基础知识
  什么是CXF
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。