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.参考资料

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容