Spring Boot中异步请求的使用

1. 简介

同步请求时序图:

在这里插入图片描述

异步请求时序图:

在这里插入图片描述

异步请求处理特点

可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。

一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

2. 实现方式

2.1 Servlet

Servlet方式实现异步请求(javax.servlet)

/**
 * 异步实现方式一:Servlet方式实现异步请求
 *
 * @param request HttpServletRequest对象
 * @return void
 * @author Liangyixiang
 * @date 2021/11/14
 **/
@GetMapping(value = "/async/servletReq")
public void servletAsyncReq (HttpServletRequest request) {
    AsyncContext asyncContext = request.startAsync();
    //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
    asyncContext.addListener(new AsyncListener() {
        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
            System.out.println("超时了...");
            //做一些超时后的相关操作...
        }
        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
            System.out.println("线程开始");
        }
        @Override
        public void onError(AsyncEvent event) throws IOException {
            System.out.println("发生错误:"+event.getThrowable());
        }
        @Override
        public void onComplete(AsyncEvent event) throws IOException {
            System.out.println("执行完成");
            //这里可以做一些清理资源的操作...
        }
    });
    //设置超时时间
    asyncContext.setTimeout(20000);
    asyncContext.start(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(10000);
                System.out.println("内部线程:" + Thread.currentThread().getName());
                asyncContext.getResponse().setCharacterEncoding("utf-8");
                asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                asyncContext.getResponse().getWriter().println("这是异步的请求返回");
            } catch (Exception e) {
                System.out.println("异常:"+e);
            }
            //异步请求完成通知
            //此时整个请求才完成
            asyncContext.complete();
        }
    });
    //此时之类 request的线程连接已经释放了
    System.out.println("主线程:" + Thread.currentThread().getName());
}

2.2 callable

使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理(java.util.concurrent)

/**
 * 异步实现方式二:返回的参数包裹一层callable即可,可以继承WebMvcConfigurer类来设置默认线程池和超时处理(配置类:CallableTestAsyncConfig)
 *
 * @return Callable<String>
 * @author Liangyixiang
 * @date 2021/11/14
 **/
@RequestMapping(value = "/async/callableReq", method = GET)
@ResponseBody
public Callable<String> callableReq () {
    System.out.println("外部线程:" + Thread.currentThread().getName());

    return new Callable<String>() {

        @Override
        public String call() throws Exception {
                Thread.sleep(1000);
            System.out.println("内部线程:" + Thread.currentThread().getName());
            return "callable!";
        }
    };
}

配置类示例:

/**
 * @Author LiangYiXiang
 * @Description 对应TestController中 callableReq测试 添加的配置类 (配不配置都行)
 * @Date 2021/11/14
 **/
@Configuration
public class CallableTestAsyncConfig implements WebMvcConfigurer {

    @Resource
    private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        //处理 callable超时
        configurer.setDefaultTimeout(2*1000);
        configurer.setTaskExecutor(myThreadPoolTaskExecutor);
        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }
}

2.3 WebAsyncTask

和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理(org.springframework.web.context.request.async)

/**
 * 异步实现方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理
 * @return WebAsyncTask<String>
 * @author Liangyixiang
 * @date 2021/11/14
 **/
@RequestMapping(value = "/async/webAsyncReq", method = GET)
@ResponseBody
public WebAsyncTask<String> webAsyncReq () {
    System.out.println("外部线程:" + Thread.currentThread().getName());
    Callable<String> result = () -> {
        System.out.println("内部线程开始:" + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (Exception e) {
            // TODO: handle exception
        }
        log.info("副线程返回");
        System.out.println("内部线程返回:" + Thread.currentThread().getName());
        return "success";
    };

    // WebAsyncTask设置等待3秒就执行超时回调
    WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
    wat.onTimeout(new Callable<String>() {

        @Override
        public String call() throws Exception {
            // TODO Auto-generated method stub
            return "超时";
        }
    });
    return wat;
}

2.4 DeferredResult

DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信(org.springframework.web.context.request.async)

@Resource
private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

ExecutorService exec = Executors.newCachedThreadPool();

/**
 * 异步实现方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,
 * 最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。
 *
 * @return DeferredResult<String>
 * @author Liangyixiang
 * @date 2021/11/14
 **/
@RequestMapping(value = "/async/deferredResultReq", method = GET)
@ResponseBody
public DeferredResult<String> deferredResultReq () {
    System.out.println("外部线程:" + Thread.currentThread().getName());
    //设置超时时间
    DeferredResult<String> result = new DeferredResult<String>(60*1000L);
    //处理超时事件 采用委托机制
    result.onTimeout(new Runnable() {

        @Override
        public void run() {
            System.out.println("DeferredResult超时");
            result.setResult("超时了!");
        }
    });
    result.onCompletion(new Runnable() {

        @Override
        public void run() {
            //完成后
            System.out.println("调用完成");
        }
    });
    exec.execute(new Runnable() {

        @Override
        public void run() {
            //处理业务逻辑
            System.out.println("内部线程:" + Thread.currentThread().getName());
            //返回结果
            result.setResult("DeferredResult!!");
        }
    });
    return result;
}

部分参考:cnblogs.com/baixianlong/p/10661591.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spring-boot-seckill分布式秒杀系统是一个用SpringBoot开发的从0到1构建的分布式秒杀系统,项目案例基本成型,逐步完善。 开发环境: JDK1.8、Maven、Mysql、IntelliJ IDEA、SpringBoot1.5.10、zookeeper3.4.6、kafka_2.11、redis-2.8.4、curator-2.10.0 启动说明: 1、启动前 请配置application.properties相关redis、zk以及kafka相关地址,建议在Linux下安装使用。 2、数据库脚本位于 src/main/resource/sql 下面,启动前请自行导入。 3、配置完成,运行Application的main方法,访问 http://localhost:8080/seckill/swagger-ui.html 进行API测试。 4、秒杀商品页:http://localhost:8080/seckill/index.shtml ,部分功能待完成。 5、本测试案例单纯为了学习,某些案例并不适用于生产环境,大家根据所需自行调整。 秒杀架构: 架构层级 1、一般商家在做活动的时候,经常会遇到各种不怀好意的DDOS攻击(利用无辜的吃瓜群众夺取资源),导致真正的我们无法获得服务!所以说高防IP还是很有必要的。 2、搞活动就意味着人多,接入SLB,对多台云服务器进行流量分发,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 3、基于SLB价格以及灵活性考虑后面我们接入Nginx做限流分发,来保障后端服务的正常运行。 4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库间件实现数据的读写分离。 优化思路 1、分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的并发。 2、限流、限流、限流,毕竟秒杀商品有限,防刷的前提下没有绝对的公平,根据每个服务的负载能力,设定流量极限。 3、缓存、缓存、缓存、尽量不要让大量请求穿透到DB层,活动开始前商品信息可以推送至分布式缓存。 4、异步、异步、异步,分析并识别出可以异步处理的逻辑,比如日志,缩短系统响应时间。 5、主备、主备、主备,如果有条件做好主备容灾方案也是非常有必要的(参考某年锤子的活动被攻击)。 6、最后,为了支撑更高的并发,追求更好的性能,可以对服务器的部署模型进行优化,部分请求走正常的秒杀流程,部分请求直接返回秒杀失败,缺点是开发部署时需要维护两套逻辑。 分层优化 1、前端优化:活动开始前生成静态商品页面推送缓存和CDN,静态文件(JS/CSS)请求推送至文件服务器和CDN。 2、网络优化:如果是全国用户,最好是BGP多线机房,减少网络延迟。 3、应用服务优化:Nginx最佳配置、Tomcat连接池优化、数据库配置优化、数据库连接池优化。
- chapter1:[基本项目构建(可作为工程脚手架),引入web模块,完成一个简单的RESTful API](http://blog.didispace.com/spring-boot-learning-1/) - [使用IntellijSpring Initializr来快速构建Spring Boot/Cloud工程](http://blog.didispace.com/spring-initializr-in-intellij/) ### 工程配置 - chapter2-1-1:[配置文件详解:自定义属性、随机数、多环境配置等](http://blog.didispace.com/springbootproperties/) ### Web开发 - chapter3-1-1:[构建一个较为复杂的RESTful API以及单元测试](http://blog.didispace.com/springbootrestfulapi/) - chapter3-1-2:[使用Thymeleaf模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-3:[使用Freemarker模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-4:[使用Velocity模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-5:[使用Swagger2构建RESTful API](http://blog.didispace.com/springbootswagger2/) - chapter3-1-6:[统一异常处理](http://blog.didispace.com/springbootexception/) ### 数据访问 - chapter3-2-1:[使用JdbcTemplate](http://blog.didispace.com/springbootdata1/) - chapter3-2-2:[使用Spring-data-jpa简化数据访问层(推荐)](http://blog.didispace.com/springbootdata2/) - chapter3-2-3:[多数据源配置(一):JdbcTemplate](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-4:[多数据源配置(二):Spring-data-jpa](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-5:[使用NoSQL数据库(一):Redis](http://blog.didispace.com/springbootredis/) - chapter3-2-6:[使用NoSQL数据库(二):MongoDB](http://blog.didispace.com/springbootmongodb/) - chapter3-2-7:[整合MyBatis](http://blog.didispace.com/springbootmybatis/) - chapter3-2-8:[MyBatis注解配置详解](http://blog.didispace.com/mybatisinfo/) ### 事务管理 - chapter3-3-1:[使用事务管理](http://blog.didispace.com/springboottransactional/) - chapter3-3-2:[分布式事务(未完成)] ### 其他内容 - chapter4-1-1:[使用@Scheduled创建定时任务](http://blog.didispace.com/springbootscheduled/) - chapter4-1-2:[使用@Async实现异步调用](http://blog.didispace.com/springbootasync/) #### 日志管理 - chapter4-2-1:[默认日志的配置](http://blog.didispace.com/springbootlog/) - chapter4-2-2:[使用log4j记录日志](http://blog.didispace.com/springbootlog4j/) - chapter4-2-3:[对log4j进行多环境不同日志级别的控制](http://blog

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值