中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

SpringBoot | 第二十章:異步開發(fā)之異步請(qǐng)求

2018-09-17    來源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬Linux鏡像隨意使用

前言

關(guān)于web開發(fā)的相關(guān)知識(shí)點(diǎn),后續(xù)有補(bǔ)充時(shí)再開續(xù)寫了。比如webService服務(wù)、發(fā)郵件等,這些一般上覺得不完全屬于web開發(fā)方面的,而且目前webService作為一個(gè)接口來提供服務(wù)的機(jī)會(huì)應(yīng)該比較小了吧。所以本章節(jié)開始,開始講解關(guān)于異步開發(fā)過程中會(huì)使用到的一些知識(shí)點(diǎn)。本章節(jié)就來講解下異步請(qǐng)求相關(guān)知識(shí)點(diǎn)。

一點(diǎn)知識(shí)

何為異步請(qǐng)求

Servlet 3.0之前,Servlet采用Thread-Per-Request的方式處理請(qǐng)求,即每一次Http請(qǐng)求都由某一個(gè)線程從頭到尾負(fù)責(zé)處理。如果一個(gè)請(qǐng)求需要進(jìn)行IO操作,比如訪問數(shù)據(jù)庫、調(diào)用第三方服務(wù)接口等,那么其所對(duì)應(yīng)的線程將同步地等待**IO操作完成, 而IO操作是非常慢的,所以此時(shí)的線程并不能及時(shí)地釋放回線程池以供后續(xù)使用,在并發(fā)量越來越大的情況下,這將帶來嚴(yán)重的性能問題。其請(qǐng)求流程大致為:

正常請(qǐng)求

而在Servlet3.0發(fā)布后,提供了一個(gè)新特性:異步處理請(qǐng)求?梢先釋放容器分配給請(qǐng)求的線程與相關(guān)資源,減輕系統(tǒng)負(fù)擔(dān),釋放了容器所分配線程的請(qǐng)求,其響應(yīng)將被延后,可以在耗時(shí)處理完成(例如長(zhǎng)時(shí)間的運(yùn)算)時(shí)再對(duì)客戶端進(jìn)行響應(yīng)。其請(qǐng)求流程為:

異步請(qǐng)求

Servlet 3.0后,我們可以從HttpServletRequest對(duì)象中獲得一個(gè)AsyncContext對(duì)象,該對(duì)象構(gòu)成了異步處理的上下文,RequestResponse對(duì)象都可從中獲取。AsyncContext可以從當(dāng)前線程傳給另外的線程,并在新的線程中完成對(duì)請(qǐng)求的處理并返回結(jié)果給客戶端,初始線程便可以還回給容器線程池以處理更多的請(qǐng)求。如此,通過將請(qǐng)求從一個(gè)線程傳給另一個(gè)線程處理的過程便構(gòu)成了Servlet 3.0中的異步處理。


多說幾句:

隨著Spring5發(fā)布,提供了一個(gè)響應(yīng)式Web框架:Spring WebFlux。之后可能就不需要Servlet容器的支持了。以下是其先后對(duì)比圖:

左側(cè)是傳統(tǒng)的基于ServletSpring Web MVC框架,右側(cè)是5.0版本新引入的基于Reactive StreamsSpring WebFlux框架,從上到下依次是Router FunctionsWebFlux,Reactive Streams三個(gè)新組件。

對(duì)于其發(fā)展前景還是拭目以待吧。有時(shí)間也該去了解下Spring5了。


原生異步請(qǐng)求API說明

在編寫實(shí)際代碼之前,我們來了解下一些關(guān)于異步請(qǐng)求的api的調(diào)用說明。

  • 獲取AsyncContext:根據(jù)HttpServletRequest對(duì)象獲取。
AsyncContext asyncContext = request.startAsync();
  • 設(shè)置監(jiān)聽器:可設(shè)置其開始、完成、異常、超時(shí)等事件的回調(diào)處理

其監(jiān)聽器的接口代碼:

public interface AsyncListener extends EventListener {
    void onComplete(AsyncEvent event) throws IOException;
    void onTimeout(AsyncEvent event) throws IOException;
    void onError(AsyncEvent event) throws IOException;
    void onStartAsync(AsyncEvent event) throws IOException;
}

說明:

  1. onStartAsync:異步線程開始時(shí)調(diào)用
  2. onError:異步線程出錯(cuò)時(shí)調(diào)用
  3. onTimeout:異步線程執(zhí)行超時(shí)調(diào)用
  4. onComplete:異步執(zhí)行完畢時(shí)調(diào)用

一般上,我們?cè)诔瑫r(shí)或者異常時(shí),會(huì)返回給前端相應(yīng)的提示,比如說超時(shí)了,請(qǐng)?jiān)俅握?qǐng)求等等,根據(jù)各業(yè)務(wù)進(jìn)行自定義返回。同時(shí),在異步調(diào)用完成時(shí),一般需要執(zhí)行一些清理工作或者其他相關(guān)操作。

需要注意的是只有在調(diào)用request.startAsync前將監(jiān)聽器添加到AsyncContext,監(jiān)聽器的onStartAsync方法才會(huì)起作用,而調(diào)用startAsyncAsyncContext還不存在,所以第一次調(diào)用startAsync是不會(huì)被監(jiān)聽器中的onStartAsync方法捕獲的,只有在超時(shí)后又重新開始的情況下onStartAsync方法才會(huì)起作用。

  • 設(shè)置超時(shí):通過setTimeout方法設(shè)置,單位:毫秒。

一定要設(shè)置超時(shí)時(shí)間,不能無限等待下去,不然和正常的請(qǐng)求就一樣了。。

Servlet方式實(shí)現(xiàn)異步請(qǐng)求

前面已經(jīng)提到,可通過HttpServletRequest對(duì)象中獲得一個(gè)AsyncContext對(duì)象,該對(duì)象構(gòu)成了異步處理的上下文。所以,我們來實(shí)際操作下。

0.編寫一個(gè)簡(jiǎn)單控制層

/**
 * 使用servlet方式進(jìn)行異步請(qǐng)求
 * @author oKong
 *
 */
@Slf4j
@RestController
public class ServletController {
    
    @RequestMapping("/servlet/orig")
    public void todo(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        //這里來個(gè)休眠
        Thread.sleep(100);
        response.getWriter().println("這是【正常】的請(qǐng)求返回");
    }
    
    @RequestMapping("/servlet/async")
    public void todoAsync(HttpServletRequest request,
            HttpServletResponse response) {
        AsyncContext asyncContext = request.startAsync();
        asyncContext.addListener(new AsyncListener() {
            
            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
                log.info("超時(shí)了:");
                //做一些超時(shí)后的相關(guān)操作
            }
            
            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {
                // TODO Auto-generated method stub
                log.info("線程開始");
            }
            
            @Override
            public void onError(AsyncEvent event) throws IOException {
                log.info("發(fā)生錯(cuò)誤:",event.getThrowable());
            }
            
            @Override
            public void onComplete(AsyncEvent event) throws IOException {
                log.info("執(zhí)行完成");
                //這里可以做一些清理資源的操作
                
            }
        });
        //設(shè)置超時(shí)時(shí)間
        asyncContext.setTimeout(200);
        //也可以不使用start 進(jìn)行異步調(diào)用
//        new Thread(new Runnable() {
//            
//            @Override
//            public void run() {
//                編寫業(yè)務(wù)邏輯
//                
//            }
//        }).start();
        
        asyncContext.start(new Runnable() {            
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                    log.info("內(nèi)部線程:" + Thread.currentThread().getName());
                    asyncContext.getResponse().setCharacterEncoding("utf-8");
                    asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                    asyncContext.getResponse().getWriter().println("這是【異步】的請(qǐng)求返回");
                } catch (Exception e) {
                    log.error("異常:",e);
                }
                //異步請(qǐng)求完成通知
                //此時(shí)整個(gè)請(qǐng)求才完成
                //其實(shí)可以利用此特性 進(jìn)行多條消息的推送 把連接掛起。。
                asyncContext.complete();
            }
        });
        //此時(shí)之類 request的線程連接已經(jīng)釋放了
        log.info("線程:" + Thread.currentThread().getName());
    }

}

注意:異步請(qǐng)求時(shí),可以利用ThreadPoolExecutor自定義個(gè)線程池。

1.啟動(dòng)下應(yīng)用,查看控制臺(tái)輸出就可以獲悉是否在同一個(gè)線程里面了。同時(shí),可設(shè)置下等待時(shí)間,之后就會(huì)調(diào)用超時(shí)回調(diào)方法了。大家可自己試試。

2018-08-15 23:03:04.082  INFO 6732 --- [nio-8080-exec-1] c.l.l.s.controller.ServletController     : 線程:http-nio-8080-exec-1
2018-08-15 23:03:04.183  INFO 6732 --- [nio-8080-exec-2] c.l.l.s.controller.ServletController     : 內(nèi)部線程:http-nio-8080-exec-2
2018-08-15 23:03:04.190  INFO 6732 --- [nio-8080-exec-3] c.l.l.s.controller.ServletController     : 執(zhí)行完成

使用過濾器時(shí),需要加入asyncSupportedtrue配置,開啟異步請(qǐng)求支持。

@WebServlet(urlPatterns = "/okong", asyncSupported = true )   
public  class AsyncServlet extends HttpServlet ...

題外話:其實(shí)我們可以利用在未執(zhí)行asyncContext.complete()方法時(shí)請(qǐng)求未結(jié)束這特性,可以做個(gè)簡(jiǎn)單的文件上傳進(jìn)度條之類的功能。但注意請(qǐng)求是會(huì)超時(shí)的,需要設(shè)置超時(shí)的時(shí)間下。

Spring方式實(shí)現(xiàn)異步請(qǐng)求

Spring中,有多種方式實(shí)現(xiàn)異步請(qǐng)求,比如callable、DeferredResult或者WebAsyncTask。每個(gè)的用法略有不同,可根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇不同的方式。以下主要介紹一些常用的用法

Callable

使用很簡(jiǎn)單,直接返回的參數(shù)包裹一層callable即可。

用法

@RequestMapping("/callable")
public Callable<String> callable() {
    log.info("外部線程:" + Thread.currentThread().getName());
    return new Callable<String>() {

        @Override
        public String call() throws Exception {
            log.info("內(nèi)部線程:" + Thread.currentThread().getName());
            return "callable!";
        }
    };
}

控制臺(tái)輸出:

2018-08-15 23:32:22.317  INFO 15740 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController      : 外部線程:http-nio-8080-exec-2
2018-08-15 23:32:22.323  INFO 15740 --- [      MvcAsync1] c.l.l.s.controller.SpringController      : 內(nèi)部線程:MvcAsync1

超時(shí)、自定義線程設(shè)置

從控制臺(tái)可以看見,異步響應(yīng)的線程使用的是名為:MvcAsync1的線程。第一次再訪問時(shí),就是MvcAsync2了。若采用默認(rèn)設(shè)置,會(huì)無限的創(chuàng)建新線程去處理異步請(qǐng)求,所以正常都需要配置一個(gè)線程池及超時(shí)時(shí)間。

編寫一個(gè)配置類:CustomAsyncPool.java

@Configuration
public class CustomAsyncPool extends WebMvcConfigurerAdapter{

    /**
     * 配置線程池
     * @return
     */
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("callable-");
        // 線程池對(duì)拒絕任務(wù)(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認(rèn)為后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
    
    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        //處理 callable超時(shí)
        configurer.setDefaultTimeout(60*1000);
        configurer.registerCallableInterceptors(timeoutInterceptor());
        configurer.setTaskExecutor(getAsyncThreadPoolTaskExecutor());
    }
    
    @Bean
    public TimeoutCallableProcessor timeoutInterceptor() {
        return new TimeoutCallableProcessor();
    }
    
}

自定義一個(gè)超時(shí)異常處理類:CustomAsyncRequestTimeoutException.java

/**
 * 自定義超時(shí)異常類
 * @author oKong
 *
 */
public class CustomAsyncRequestTimeoutException extends RuntimeException {

    /**
     * 
     */
    private static final long serialVersionUID = 8754629185999484614L;

    public CustomAsyncRequestTimeoutException(String uri){
        super(uri);
    }
}

同時(shí),在統(tǒng)一異常處理加入對(duì)CustomAsyncRequestTimeoutException類的處理即可,這樣就有個(gè)統(tǒng)一的配置了。

之后,再運(yùn)行就可以看見使用了自定義的線程池了,超時(shí)的可以自行模擬下:

2018-08-15 23:48:29.022  INFO 16060 --- [nio-8080-exec-1] c.l.l.s.controller.SpringController      : 外部線程:http-nio-8080-exec-1
2018-08-15 23:48:29.032  INFO 16060 --- [     oKong-1] c.l.l.s.controller.SpringController      : 內(nèi)部線程:oKong-1

DeferredResult

相比于callable,DeferredResult可以處理一些相對(duì)復(fù)雜一些的業(yè)務(wù)邏輯,最主要還是可以在另一個(gè)線程里面進(jìn)行業(yè)務(wù)處理及返回,即可在兩個(gè)完全不相干的線程間的通信。

/**
     * 線程池
     */
    public static ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(30);
    
@RequestMapping("/deferredresult")
    public DeferredResult<String> deferredResult(){
        log.info("外部線程:" + Thread.currentThread().getName());
        //設(shè)置超時(shí)時(shí)間
        DeferredResult<String> result = new DeferredResult<String>(60*1000L);
        //處理超時(shí)事件 采用委托機(jī)制
        result.onTimeout(new Runnable() {
            
            @Override
            public void run() {
                log.error("DeferredResult超時(shí)");
                result.setResult("超時(shí)了!");
            }
        });
        result.onCompletion(new Runnable() {
            
            @Override
            public void run() {
                //完成后
                log.info("調(diào)用完成");
            }
        });
        FIXED_THREAD_POOL.execute(new Runnable() {
            
            @Override
            public void run() {
                //處理業(yè)務(wù)邏輯
                log.info("內(nèi)部線程:" + Thread.currentThread().getName());
                //返回結(jié)果
                result.setResult("DeferredResult!!");
            }
        });
        return result;
    }

控制臺(tái)輸出:

2018-08-15 23:52:27.841  INFO 12984 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController      : 外部線程:http-nio-8080-exec-2
2018-08-15 23:52:27.843  INFO 12984 --- [pool-1-thread-1] c.l.l.s.controller.SpringController      : 內(nèi)部線程:pool-1-thread-1
2018-08-15 23:52:27.872  INFO 12984 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController      : 調(diào)用完成

注意:返回結(jié)果時(shí)記得調(diào)用下setResult方法。

題外話:利用DeferredResult可實(shí)現(xiàn)一些長(zhǎng)連接的功能,比如當(dāng)某個(gè)操作是異步時(shí),我們可以保存這個(gè)DeferredResult對(duì)象,當(dāng)異步通知回來時(shí),我們?cè)谡一剡@個(gè)DeferredResult對(duì)象,之后在setResult會(huì)結(jié)果即可。提高性能。

WebAsyncTask

使用方法都類似,只是WebAsyncTask是直接返回了。覺得就是寫法不同而已,更多細(xì)節(jié)希望大神解答!

@RequestMapping("/webAsyncTask")
   public WebAsyncTask<String> webAsyncTask() {
       log.info("外部線程:" + Thread.currentThread().getName());
       WebAsyncTask<String> result = new WebAsyncTask<String>(60*1000L, new Callable<String>() {

           @Override
           public String call() throws Exception {
               log.info("內(nèi)部線程:" + Thread.currentThread().getName());
               return "WebAsyncTask!!!";
           }
       });
       result.onTimeout(new Callable<String>() {
           
           @Override
           public String call() throws Exception {
               // TODO Auto-generated method stub
               return "WebAsyncTask超時(shí)!!!";
           }
       });
       result.onCompletion(new Runnable() {
           
           @Override
           public void run() {
               //超時(shí)后 也會(huì)執(zhí)行此方法
               log.info("WebAsyncTask執(zhí)行結(jié)束");
           }
       });
       return result;
   }

控制臺(tái)輸出:

2018-08-15 23:55:02.568  INFO 2864 --- [nio-8080-exec-1] c.l.l.s.controller.SpringController      : 外部線程:http-nio-8080-exec-1
2018-08-15 23:55:02.587  INFO 2864 --- [          oKong-1] c.l.l.s.controller.SpringController      : 內(nèi)部線程:oKong-1
2018-08-15 23:55:02.615  INFO 2864 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController      : WebAsyncTask執(zhí)行結(jié)束

參考資料

  1. https://blog.csdn.net/paincupid/article/details/52266905
  2. https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-async

總結(jié)

本章節(jié)主要是講解了異步請(qǐng)求的使用及相關(guān)配置,如超時(shí),異常等處理。設(shè)置異步請(qǐng)求時(shí),記得不要忘記設(shè)置超時(shí)時(shí)間。異步請(qǐng)求只是提高了服務(wù)的吞吐量,提高單位時(shí)間內(nèi)處理的請(qǐng)求數(shù),并不會(huì)加快處理效率的,這點(diǎn)需要注意。。下一章節(jié),講講使用@Async進(jìn)行異步調(diào)用相關(guān)知識(shí)。

最后

目前互聯(lián)網(wǎng)上很多大佬都有SpringBoot系列教程,如有雷同,請(qǐng)多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實(shí)踐的。若文中有所錯(cuò)誤之處,還望提出,謝謝。

標(biāo)簽: 代碼 互聯(lián)網(wǎng) 數(shù)據(jù)庫 通信

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:SpringBoot | 第二十一章:異步開發(fā)之異步調(diào)用

下一篇:SpringBoot | 第十九章:web 應(yīng)用開發(fā)之 WebSocket