欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Springboot使用異步請(qǐng)求提高系統(tǒng)的吞吐量詳解

 更新時(shí)間:2023年08月12日 09:41:34   作者:牛奮lch  
這篇文章主要介紹了Springboot使用異步請(qǐng)求提高系統(tǒng)的吞吐量詳解,和同步請(qǐng)求相對(duì),異步不需要等待響應(yīng),隨時(shí)可以發(fā)送下一次請(qǐng)求,如果是同步請(qǐng)求,需要將信息填寫完整,再發(fā)送請(qǐng)求,服務(wù)器響應(yīng)填寫是否正確,再做修改,需要的朋友可以參考下

前言:

在我們的實(shí)際生產(chǎn)中,常常會(huì)遇到下面的這種情況,某個(gè)請(qǐng)求非常耗時(shí)(大約5s返回),當(dāng)大量的訪問該請(qǐng)求的時(shí)候,再請(qǐng)求其他服務(wù)時(shí),會(huì)造成沒有連接使用的情況,造成這種現(xiàn)象的主要原因是,我們的容器(tomcat)中線程的數(shù)量是一定的,例如500個(gè),當(dāng)這500個(gè)線程都用來請(qǐng)求服務(wù)的時(shí)候,再有請(qǐng)求進(jìn)來,就沒有多余的連接可用了,只能拒絕連接。

要是我們?cè)谡?qǐng)求耗時(shí)服務(wù)的時(shí)候,能夠異步請(qǐng)求(請(qǐng)求到controller中時(shí),則容器線程直接返回,然后使用系統(tǒng)內(nèi)部的線程來執(zhí)行耗時(shí)的服務(wù),等到服務(wù)有返回的時(shí)候,再將請(qǐng)求返回給客戶端),那么系統(tǒng)的吞吐量就會(huì)得到很大程度的提升了。

當(dāng)然,大家可以直接使用Hystrix的資源隔離來實(shí)現(xiàn),今天我們的重點(diǎn)是spring mvc是怎么來實(shí)現(xiàn)這種異步請(qǐng)求的。

一、使用Callable來實(shí)現(xiàn)

controller如下:

@RestController
public class HelloController {
	private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
	@Autowired
	private HelloService hello;
	@GetMapping("/helloworld")
	public String helloWorldController() {
		return hello.sayHello();
	}
	/**
	 * 異步調(diào)用restful
	 * 當(dāng)controller返回值是Callable的時(shí)候,springmvc就會(huì)啟動(dòng)一個(gè)線程將Callable交給TaskExecutor去處理
	 * 然后DispatcherServlet還有所有的spring攔截器都退出主線程,然后把response保持打開的狀態(tài)
	 * 當(dāng)Callable執(zhí)行結(jié)束之后,springmvc就會(huì)重新啟動(dòng)分配一個(gè)request請(qǐng)求,然后DispatcherServlet就重新
	 * 調(diào)用和處理Callable異步執(zhí)行的返回結(jié)果, 然后返回視圖
	 * 
	 * @return
	 */
	@GetMapping("/hello")
	public Callable<String> helloController() {
		logger.info(Thread.currentThread().getName() + " 進(jìn)入helloController方法");
		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				logger.info(Thread.currentThread().getName() + " 進(jìn)入call方法");
				String say = hello.sayHello();
				logger.info(Thread.currentThread().getName() + " 從helloService方法返回");
				return say;
			}
		};
		logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
		return callable;
	}
}

我們首先來看下上面這兩個(gè)請(qǐng)求的區(qū)別

下面這個(gè)是沒有使用異步請(qǐng)求的結(jié)果

2017-12-07 18:05:42.351  INFO 3020 --- [nio-8060-exec-5] c.travelsky.controller.HelloController   : http-nio-8060-exec-5 進(jìn)入helloWorldController方法
2017-12-07 18:05:42.351  INFO 3020 --- [nio-8060-exec-5] com.travelsky.service.HelloService       : http-nio-8060-exec-5 進(jìn)入sayHello方法!
2017-12-07 18:05:44.351  INFO 3020 --- [nio-8060-exec-5] c.travelsky.controller.HelloController   : http-nio-8060-exec-5 從helloWorldController方法返回

我們可以看到,請(qǐng)求從頭到尾都只有一個(gè)線程,并且整個(gè)請(qǐng)求耗費(fèi)了2s鐘的時(shí)間。

下面,我們?cè)賮砜聪率褂肅allable異步請(qǐng)求的結(jié)果:

2017-12-07 18:11:55.671  INFO 6196 --- [nio-8060-exec-1] c.travelsky.controller.HelloController   : http-nio-8060-exec-1 進(jìn)入helloController方法
2017-12-07 18:11:55.672  INFO 6196 --- [nio-8060-exec-1] c.travelsky.controller.HelloController   : http-nio-8060-exec-1 從helloController方法返回
2017-12-07 18:11:55.676  INFO 6196 --- [nio-8060-exec-1] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-1 進(jìn)入afterConcurrentHandlingStarted方法
2017-12-07 18:11:55.676  INFO 6196 --- [      MvcAsync1] c.travelsky.controller.HelloController   : MvcAsync1 進(jìn)入call方法
2017-12-07 18:11:55.676  INFO 6196 --- [      MvcAsync1] com.travelsky.service.HelloService       : MvcAsync1 進(jìn)入sayHello方法!
2017-12-07 18:11:57.677  INFO 6196 --- [      MvcAsync1] c.travelsky.controller.HelloController   : MvcAsync1 從helloService方法返回
2017-12-07 18:11:57.721  INFO 6196 --- [nio-8060-exec-2] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-2服務(wù)調(diào)用完成,返回結(jié)果給客戶端

從上面的結(jié)果中,我們可以看出,容器的線程http-nio-8060-exec-1這個(gè)線程進(jìn)入controller之后,就立即返回了,具體的服務(wù)調(diào)用是通過MvcAsync2這個(gè)線程來做的,當(dāng)服務(wù)執(zhí)行完要返回后,容器會(huì)再啟一個(gè)新的線程http-nio-8060-exec-2來將結(jié)果返回給客戶端或?yàn)g覽器,整個(gè)過程response都是打開的,當(dāng)有返回的時(shí)候,再從server端推到response中去。

1、異步調(diào)用的另一種方式

上面的示例是通過callable來實(shí)現(xiàn)的異步調(diào)用,其實(shí)還可以通過WebAsyncTask,也能實(shí)現(xiàn)異步調(diào)用,下面看示例:

@RestController
public class HelloController {
	private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
	@Autowired
	private HelloService hello;
		/**
	 * 帶超時(shí)時(shí)間的異步請(qǐng)求 通過WebAsyncTask自定義客戶端超時(shí)間
	 * 
	 * @return
	 */
	@GetMapping("/world")
	public WebAsyncTask<String> worldController() {
		logger.info(Thread.currentThread().getName() + " 進(jìn)入helloController方法");
		// 3s鐘沒返回,則認(rèn)為超時(shí)
		WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {
			@Override
			public String call() throws Exception {
				logger.info(Thread.currentThread().getName() + " 進(jìn)入call方法");
				String say = hello.sayHello();
				logger.info(Thread.currentThread().getName() + " 從helloService方法返回");
				return say;
			}
		});
		logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
		webAsyncTask.onCompletion(new Runnable() {
			@Override
			public void run() {
				logger.info(Thread.currentThread().getName() + " 執(zhí)行完畢");
			}
		});
		webAsyncTask.onTimeout(new Callable<String>() {
			@Override
			public String call() throws Exception {
				logger.info(Thread.currentThread().getName() + " onTimeout");
				// 超時(shí)的時(shí)候,直接拋異常,讓外層統(tǒng)一處理超時(shí)異常
				throw new TimeoutException("調(diào)用超時(shí)");
			}
		});
		return webAsyncTask;
	}
	/**
	 * 異步調(diào)用,異常處理,詳細(xì)的處理流程見MyExceptionHandler類
	 * 
	 * @return
	 */
	@GetMapping("/exception")
	public WebAsyncTask<String> exceptionController() {
		logger.info(Thread.currentThread().getName() + " 進(jìn)入helloController方法");
		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				logger.info(Thread.currentThread().getName() + " 進(jìn)入call方法");
				throw new TimeoutException("調(diào)用超時(shí)!");
			}
		};
		logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
		return new WebAsyncTask<>(20000, callable);
	}
}

運(yùn)行結(jié)果如下:

2017-12-07 19:10:26.582  INFO 6196 --- [nio-8060-exec-4] c.travelsky.controller.HelloController   : http-nio-8060-exec-4 進(jìn)入helloController方法
2017-12-07 19:10:26.585  INFO 6196 --- [nio-8060-exec-4] c.travelsky.controller.HelloController   : http-nio-8060-exec-4 從helloController方法返回
2017-12-07 19:10:26.589  INFO 6196 --- [nio-8060-exec-4] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-4 進(jìn)入afterConcurrentHandlingStarted方法
2017-12-07 19:10:26.591  INFO 6196 --- [      MvcAsync2] c.travelsky.controller.HelloController   : MvcAsync2 進(jìn)入call方法
2017-12-07 19:10:26.591  INFO 6196 --- [      MvcAsync2] com.travelsky.service.HelloService       : MvcAsync2 進(jìn)入sayHello方法!
2017-12-07 19:10:28.591  INFO 6196 --- [      MvcAsync2] c.travelsky.controller.HelloController   : MvcAsync2 從helloService方法返回
2017-12-07 19:10:28.600  INFO 6196 --- [nio-8060-exec-5] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-5服務(wù)調(diào)用完成,返回結(jié)果給客戶端
2017-12-07 19:10:28.601  INFO 6196 --- [nio-8060-exec-5] c.travelsky.controller.HelloController   : http-nio-8060-exec-5 執(zhí)行完畢

這種方式和上面的callable方式最大的區(qū)別就是,WebAsyncTask支持超時(shí),并且還提供了兩個(gè)回調(diào)函數(shù),分別是onCompletion和onTimeout,顧名思義,這兩個(gè)回調(diào)函數(shù)分別在執(zhí)行完成和超時(shí)的時(shí)候回調(diào)。

2、Deferred方式實(shí)現(xiàn)異步調(diào)用

在我們是生產(chǎn)中,往往會(huì)遇到這樣的情景,controller中調(diào)用的方法很多都是和第三方有關(guān)的,例如JMS,定時(shí)任務(wù),隊(duì)列等,拿JMS來說,比如controller里面的服務(wù)需要從JMS中拿到返回值,才能給客戶端返回,而從JMS拿值這個(gè)過程也是異步的,這個(gè)時(shí)候,我們就可以通過Deferred來實(shí)現(xiàn)整個(gè)的異步調(diào)用。

首先,我們來模擬一個(gè)長時(shí)間調(diào)用的任務(wù),代碼如下:

@Component
public class LongTimeTask {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	@Async
	public void execute(DeferredResult<String> deferred){
		logger.info(Thread.currentThread().getName() + "進(jìn)入 taskService 的 execute方法");
		try {
			// 模擬長時(shí)間任務(wù)調(diào)用,睡眠2s
			TimeUnit.SECONDS.sleep(2);
			// 2s后給Deferred發(fā)送成功消息,告訴Deferred,我這邊已經(jīng)處理完了,可以返回給客戶端了
			deferred.setResult("world");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

接著,我們就來實(shí)現(xiàn)異步調(diào)用,controller如下:

@RestController
public class AsyncDeferredController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final LongTimeTask taskService;
    @Autowired
    public AsyncDeferredController(LongTimeTask taskService) {
        this.taskService = taskService;
    }
    @GetMapping("/deferred")
    public DeferredResult<String> executeSlowTask() {
        logger.info(Thread.currentThread().getName() + "進(jìn)入executeSlowTask方法");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        // 調(diào)用長時(shí)間執(zhí)行任務(wù)
        taskService.execute(deferredResult);
        // 當(dāng)長時(shí)間任務(wù)中使用deferred.setResult("world");這個(gè)方法時(shí),會(huì)從長時(shí)間任務(wù)中返回,繼續(xù)controller里面的流程
        logger.info(Thread.currentThread().getName() + "從executeSlowTask方法返回");
        // 超時(shí)的回調(diào)方法
        deferredResult.onTimeout(new Runnable(){
			@Override
			public void run() {
				logger.info(Thread.currentThread().getName() + " onTimeout");
				// 返回超時(shí)信息
				deferredResult.setErrorResult("time out!");
			}
		});
        // 處理完成的回調(diào)方法,無論是超時(shí)還是處理成功,都會(huì)進(jìn)入這個(gè)回調(diào)方法
        deferredResult.onCompletion(new Runnable(){
			@Override
			public void run() {
				logger.info(Thread.currentThread().getName() + " onCompletion");
			}
		});
        return deferredResult;
    }
}

執(zhí)行結(jié)果如下:

2017-12-07 19:25:40.192  INFO 6196 --- [nio-8060-exec-7] c.t.controller.AsyncDeferredController   : http-nio-8060-exec-7進(jìn)入executeSlowTask方法
2017-12-07 19:25:40.193  INFO 6196 --- [nio-8060-exec-7] .s.a.AnnotationAsyncExecutionInterceptor : No TaskExecutor bean found for async processing
2017-12-07 19:25:40.194  INFO 6196 --- [nio-8060-exec-7] c.t.controller.AsyncDeferredController   : http-nio-8060-exec-7從executeSlowTask方法返回
2017-12-07 19:25:40.198  INFO 6196 --- [nio-8060-exec-7] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-7 進(jìn)入afterConcurrentHandlingStarted方法
2017-12-07 19:25:40.202  INFO 6196 --- [cTaskExecutor-1] com.travelsky.controller.LongTimeTask    : SimpleAsyncTaskExecutor-1進(jìn)入 taskService 的 execute方法
2017-12-07 19:25:42.212  INFO 6196 --- [nio-8060-exec-8] c.t.i.MyAsyncHandlerInterceptor          : http-nio-8060-exec-8服務(wù)調(diào)用完成,返回結(jié)果給客戶端
2017-12-07 19:25:42.213  INFO 6196 --- [nio-8060-exec-8] c.t.controller.AsyncDeferredController   : http-nio-8060-exec-8 onCompletion

從上面的執(zhí)行結(jié)果不難看出,容器線程會(huì)立刻返回,應(yīng)用程序使用線程池里面的cTaskExecutor-1線程來完成長時(shí)間任務(wù)的調(diào)用,當(dāng)調(diào)用完成后,容器又啟了一個(gè)連接線程,來返回最終的執(zhí)行結(jié)果。

這種異步調(diào)用,在容器線程資源非常寶貴的時(shí)候,能夠大大的提高整個(gè)系統(tǒng)的吞吐量。

ps:異步調(diào)用可以使用AsyncHandlerInterceptor進(jìn)行攔截,使用示例如下:

@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
	private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
//		HandlerMethod handlerMethod = (HandlerMethod) handler;
		logger.info(Thread.currentThread().getName()+ "服務(wù)調(diào)用完成,返回結(jié)果給客戶端");
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		if(null != ex){
			System.out.println("發(fā)生異常:"+ex.getMessage());
		}
	}
	@Override
	public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 攔截之后,重新寫回?cái)?shù)據(jù),將原來的hello world換成如下字符串
		String resp = "my name is chhliu!";
		response.setContentLength(resp.length());
		response.getOutputStream().write(resp.getBytes());
		logger.info(Thread.currentThread().getName() + " 進(jìn)入afterConcurrentHandlingStarted方法");
	}
}

有興趣的可以了解下,本篇博客的主題是異步調(diào)用,其他的相關(guān)知識(shí)點(diǎn),會(huì)在下一篇博客中進(jìn)行講解。

到此這篇關(guān)于Springboot使用異步請(qǐng)求提高系統(tǒng)的吞吐量詳解的文章就介紹到這了,更多相關(guān)Springboot異步請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解

    Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解

    這篇文章主要介紹了Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 關(guān)于@SpringBootApplication與@SpringBootTest的區(qū)別及用法

    關(guān)于@SpringBootApplication與@SpringBootTest的區(qū)別及用法

    這篇文章主要介紹了關(guān)于@SpringBootApplication與@SpringBootTest的區(qū)別及用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • JVM類加載機(jī)制原理及用法解析

    JVM類加載機(jī)制原理及用法解析

    這篇文章主要介紹了JVM類加載機(jī)制原理及用法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • 使用spring aop統(tǒng)一處理異常和打印日志方式

    使用spring aop統(tǒng)一處理異常和打印日志方式

    這篇文章主要介紹了使用spring aop統(tǒng)一處理異常和打印日志方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • JAVASE系統(tǒng)實(shí)現(xiàn)抽卡功能

    JAVASE系統(tǒng)實(shí)現(xiàn)抽卡功能

    這篇文章主要為大家詳細(xì)介紹了JAVASE系統(tǒng)實(shí)現(xiàn)抽卡功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • java進(jìn)行error捕獲和處理示例(java異常捕獲)

    java進(jìn)行error捕獲和處理示例(java異常捕獲)

    通常來說,大家都是對(duì)Java中的Exception進(jìn)行捕獲和進(jìn)行相應(yīng)的處理,有些人說,error就無法捕獲了。其實(shí),error也是可以捕獲的。Error和Exception都是Throwable的子類。既然可以catch Throwable,那么error也是可以catch的
    2014-01-01
  • Java代碼中與Lua相互調(diào)用實(shí)現(xiàn)詳解

    Java代碼中與Lua相互調(diào)用實(shí)現(xiàn)詳解

    這篇文章主要為大家介紹了Java代碼中與Lua相互調(diào)用實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 在Spring boot的項(xiàng)目中使用Junit進(jìn)行單體測(cè)試

    在Spring boot的項(xiàng)目中使用Junit進(jìn)行單體測(cè)試

    今天小編就為大家分享一篇關(guān)于spring boot使用Junit進(jìn)行測(cè)試,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • IDEA性能優(yōu)化設(shè)置(解決卡頓問題)

    IDEA性能優(yōu)化設(shè)置(解決卡頓問題)

    在我們?nèi)粘J褂肐DEA進(jìn)行開發(fā)時(shí),可能會(huì)遇到許多卡頓的瞬間,本文主要介紹了IDEA性能優(yōu)化設(shè)置,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2023-05-05
  • Java簡(jiǎn)明解讀代碼塊的應(yīng)用

    Java簡(jiǎn)明解讀代碼塊的應(yīng)用

    所謂代碼塊是指用"{}"括起來的一段代碼,根據(jù)其位置和聲明的不同,可以分為普通代碼塊、構(gòu)造塊、靜態(tài)塊、和同步代碼塊。如果在代碼塊前加上 synchronized關(guān)鍵字,則此代碼塊就成為同步代碼塊
    2022-07-07

最新評(píng)論