Web异步处理请求

1.场景描述

在做项目的时候,有这样一个场景,以下是简化版描述:
浏览器A发起http请求到服务器,但响应数据却需要从浏览器B发起的http请求中获取,相当于请求/响应在两个方法,我在B中拿到的数据怎么响应给A的请求呢?


请求响应在两个方法中

2.同步处理方法

既然问题的核心在于A发送的http请求,在用websocket通知B之后就结束了,但客户端却什么都没收到;等B的请求到达b方法的时候,虽然拿到了数据,但却没法再找到A请求了,无法将真正的数据返给客户端。那我让a方法阻塞直到b方法中拿到数据,不就可以解决这个问题了吗。A、B两个请求是两个线程,现在两个线程需要通信了,线程通信有哪些方式呢?wait/notify不就是用来线程通信的嘛,多线程的知识终于排上用场了。
看代码:

//存放wait/notify使用的对象
public Map<String,MsgData> map1 = new HashMap<>();

@ResponseBody
@RequestMapping("/aToServer1")
public MsgData aToServer1() throws InterruptedException {
    MsgData msg = new MsgData();
    //将要返回的对象放入Map
    map1.put("1", msg);
    synchronized (msg) {
        //利用msg来阻塞A线程,在b中需要利用同一个msg来通知A线程
        msg.wait();
    }
    System.out.println("A请求结束:");
    return msg;
}

@RequestMapping("/bToServer1")
public void bToServer1() throws IOException {
    MsgData msg = map1.get("1");
    msg.setDesc("B响应");
    synchronized (msg) {
        msg.notify();
    }
    System.out.println("B响应结束:");
}

class MsgData implements Serializable{
    private String title;
    private Integer status;
    private String desc;
    //get set方法...
}

通过wait/notify机制,将A请求和B响应建立了关系,让两者可以相互通信。问题是解决了,但这样做却有一个很严重的问题。

3.同步处理方法的问题

使用wait将A请求的线程阻塞,直到超时或者B的响应到来,将会导致在此期间,A请求所占用的线程无法释放,如果访问量很大,将会导致大量线程处于阻塞状态,对于新的请求必须不断从线程池取新的线程来处理,最终导致无法再响应请求。那有没有更好的方式呢?

4.异步处理方法

异步处理方法和上面的同步方法最大的区别是,在A请求等待B响应的这段时间内,处理A请求的线程会被释放,但请求不会结束,待B响应到来后,给A响应数据后,A请求才会真正结束。

1.Servlet的异步处理支持

Servlet3.0之后,支持异步处理,通过request.startAsync(request, response)来将同步请求转为异步,该方法返回一个AsyncContext 对象,在A中将该对象保存起来,等B的响应到来的时候,就可以利用该对象来拿到response,向A反馈响应了。
注意需要在servlet中开启异步支持,加入这一行:

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

如果是SpringMVC,直接加到DispatcherServlet中。同时注意需要在所有的Filter中也要加入异步支持,否则会报错。
Servlet异步处理的代码:

//存放AsyncContext对象
public Map<String,AsyncContext> map2 = new HashMap<>();
@RequestMapping("/aToServer2")
public void aToServer2(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //将请求转为异步处理
    AsyncContext asyncContext = request.startAsync(request, response);
    //设置超时
    asyncContext.setTimeout(10000);
    //将异步状态保存下来
    map2.put("1", asyncContext);
    //可以监听一些事件
    asyncContext.addListener(new AsyncListener() {
        
        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
            response.getWriter().write("捕获超时事件");
        }
        
        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
        }
        
        @Override
        public void onError(AsyncEvent event) throws IOException {
        }
        
        @Override
        public void onComplete(AsyncEvent event) throws IOException {
            System.out.println("捕获完成事件");
        }
    });
    System.out.println("A请求结束");
}

@RequestMapping("/bToServer2")
public void bToServer2(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //拿到之前的异步状态
    AsyncContext asyncContext = map2.get("1");
    ServletResponse mobileResponse = asyncContext.getResponse();
    mobileResponse.getWriter().write("B响应");
    //记得要手动调用complete,A请求才会结束
    asyncContext.complete();
    System.out.println("B响应结束");
}
2.SpringMVC异步处理的支持

SpringMVC提供了DeferredResult,和上面的AsyncContext作用类似,但是使用更为简单方便。最终采用了这种方式。
直接看代码:

//存放DeferredResult对象 泛型指定返回的数据类型
public Map<String,DeferredResult<String>> map3 = new HashMap<>();

@ResponseBody
@RequestMapping("/aToServer3")
public DeferredResult<String> aToServer3() {
    // 超时时间为10s,超时返回"timeout"
    DeferredResult<String> deferredResult = new DeferredResult<String>(10000L, "timeout");
    map3.put("1", deferredResult);
    System.out.println("A请求结束");
    return deferredResult;
}

@RequestMapping("/bToServer3")
public void bToServer3() throws IOException {
    DeferredResult<String> deferredResult = map3.get("1");
    deferredResult.setResult("B响应");
    System.out.println("B响应结束");
}

5.参考资料

异步处理主要参考了如下资料:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容