SpringCloud解決Feign異步回調(diào)問題(SpringBoot+Async+Future實(shí)現(xiàn))
近期,需要對之前的接口進(jìn)行優(yōu)化,縮短接口的響應(yīng)時(shí)間,但是springcloud中的feign是不支持傳遞異步化的回調(diào)結(jié)果的,因此有了以下的解決方案,記錄一下,僅供參考。
一、背景
對于一個(gè)頁面上的所有實(shí)例有一個(gè)查詢權(quán)限的接口,同時(shí)有四個(gè)操作類型的需要查詢且接口僅支持單個(gè)實(shí)例單個(gè)操作類型操作。
簡單來說,假設(shè)實(shí)例查詢退訂權(quán)限需要1秒鐘,那么四個(gè)操作共需4秒鐘,一共20個(gè)實(shí)例的話,那么實(shí)際所需時(shí)間為80秒。
這樣顯然是不符合要求的。經(jīng)過本文的優(yōu)化后,可以達(dá)到20秒。
二、設(shè)計(jì)方案
需要指出,由于各種限制原因,無法直接在工程中進(jìn)行rest接口的調(diào)用,否則是直接可以@Async異步調(diào)用的。
(一)使用框架
產(chǎn)品工程使用SpringCloud微服務(wù),同時(shí)通過Feign調(diào)用權(quán)限的微服務(wù),服務(wù)都是使用SpringBoot實(shí)現(xiàn)。
(二)實(shí)現(xiàn)方案
由于Feign的特殊性,目前并不支持異步返回Future,直接通過Future是會(huì)報(bào)錯(cuò)的。
因此可以在權(quán)限微服務(wù)中異步執(zhí)行查詢操作權(quán)限,同時(shí)再異步起一個(gè)線程去等待所有的異步結(jié)果,這一個(gè)線程是阻塞的,同時(shí)由阻塞得到的最終結(jié)果,封裝成自己想要的返回體,返回給調(diào)用方即可,這樣相當(dāng)于大致只需要等待一個(gè)實(shí)例的操作時(shí)間即可。
三、示例代碼
項(xiàng)目代碼我就不放上來了,我將寫一個(gè)Demo作為示例,基本的思想都在里面了。
(一)被調(diào)用方
1. 配置異步化及異步線程池 - AsyncConfig類
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Bean("taskPoolExecutor") public Executor taskPoolExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(20); executor.setMaxPoolSize(30); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskPoolExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } }
2. Controller層
@RestController @Slf4j public class AsyncController { @Autowired private AsyncService asyncService; /** * 異步測試方法 * @param ids * @return * @throws Exception */ @GetMapping("/future") public List<User> testFuture(int[] ids) throws Exception { List<Future<User>> futures = new ArrayList<>(); futures.add(asyncService.testFuture(ids[0])); //異步方法 異步執(zhí)行四個(gè)用戶的查詢操作 futures.add(asyncService.testFuture(ids[1])); futures.add(asyncService.testFuture(ids[2])); futures.add(asyncService.testFuture(ids[3])); return asyncService.getReturnValueMange(futures); 異步方法 異步獲取所有的異步回調(diào)值 } /** * 同步測試方法 * @param i * @return * @throws InterruptedException */ @GetMapping("/syncTest") public User testsyncTest( Integer i) throws InterruptedException { User user = asyncService.testSync(i); return user; } }
這里需要解釋說明下,使用一個(gè)list作為future結(jié)果的集合,同時(shí)采用getReturnValueMange(future)方法來獲取所有的異步執(zhí)行結(jié)果,具體可以看下面Service層的代碼邏輯。
3. AsyncService層
@Slf4j @Service public class AsyncService { @Autowired private AsyncUnit asyncUnit; //異步實(shí)現(xiàn)util public Future<User> testFuture(int i) throws InterruptedException { log.info("start..."); long currentTime = System.currentTimeMillis(); Future<User> user = asyncUnit.testUser(i); log.info("done...{}", String.valueOf(System.currentTimeMillis()- currentTime)); return user; } //獲取異步方法結(jié)果 public List<User> getReturnValueMange(List<Future<User>> futures) throws Exception { Future<List<User>> userFuture = asyncUnit.getReturnValueMange(futures); return userFuture.get(); //get()方法為阻塞方法,等待異步返回值 } //同步方法-作為比較方法 public User testSync(int i) throws InterruptedException { log.info("start..."); Thread.sleep(2000); return new User(i); }
4. AsyncUtil 層
@Service @Slf4j public class AsyncUnit { //異步方法,通過sleep 2秒鐘模擬 @Async("taskPoolExecutor") public Future<User> testUser(int age){ log.info("異步執(zhí)行start...{}",Thread.currentThread().getName()); long currentTime = System.currentTimeMillis(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("異步執(zhí)行done...{}, {}",Thread.currentThread().getName(), String.valueOf(System.currentTimeMillis()- currentTime)); return new AsyncResult<>(new User(age)); } @Async("taskPoolExecutor") public Future<List<User>> getReturnValueMange(List<Future<User>> futures) throws Exception{ log.info("異步執(zhí)行start..getReturnValueMange.{}",Thread.currentThread().getName()); long currentTime = System.currentTimeMillis(); List<User> users = new ArrayList<>(); for (Future<User> future : futures) { users.add(future.get()); } log.info(""+users.size()); log.info("異步執(zhí)行done..getReturnValueMange.{}, {}",Thread.currentThread().getName(), String.valueOf(System.currentTimeMillis()- currentTime)); return new AsyncResult<>(users); } }
(二)調(diào)用方
1. Config 層
@Configuration public class FeignConfig { @Bean public RequestInterceptor requestInterceptor() { return new FeignRequestInterceptor(); } }
2. Controller層
@RestController @Slf4j public class AsyncController { @Autowired private RemoteClient remoteClient; //異步測試方法,查詢五次 模擬所需時(shí)間 @GetMapping("/future") public List<Integer> testFuture() throws InterruptedException, ExecutionException { String res = ""; log.info(" asynccontroller...start..."); long currentTime = System.currentTimeMillis(); int[] ids = new int[]{1,2,3,4,5}; List<User> users = remoteClient.testFuture(ids); List<Integer> resList = new ArrayList<>(); resList.add(users.get(1).getAge()); log.info(" asynccontroller...done...{}", String.valueOf(System.currentTimeMillis()- currentTime)); return resList; } //同步測試方法,通過執(zhí)行5次同步方法模擬查詢 @GetMapping("/syncTest") public String testsyncTest() { long currentTime = System.currentTimeMillis(); List<Integer> ages = new ArrayList<>(); for(int i=0; i<5; i++){ User user = remoteClient.testsyncTest(i); ages.add(user.getAge()); } log.info("done...{}", String.valueOf(System.currentTimeMillis()- currentTime)); return ages.get(0)+ages.get(1)+ages.get(2)+ages.get(3)+ages.get(4)+""; } }
3. FeignClient (RemoteClient)層
@FeignClient(name = "ASYNC") public interface RemoteClient { @GetMapping("/future") List<User> testFuture(@RequestParam("ids") int[] ids); @GetMapping("/futureValue") List<String> getReturnValueMange(@RequestBody List<Future<User>> futures); @GetMapping("/syncTest") User testsyncTest(@RequestParam("i") Integer i); }
被調(diào)用方的服務(wù)名為ASYNC,通過Feign調(diào)用,F(xiàn)eign的詳細(xì)說明就不細(xì)說,具體可以自行查詢。
(三)公共類 User
@Data @NoArgsConstructor @AllArgsConstructor public class User { private String name; private String id; private int age; private String school; public User (int age){ this.age = age; } }
四、實(shí)現(xiàn)效果測試
調(diào)用方端口8305 被調(diào)用方8036 eureka端口 8761,將服務(wù)啟動(dòng)如圖所示。
1. 使用Postman進(jìn)行接口測試,首先是同步方法的執(zhí)行。
控制臺(tái)日志
2. 使用Postman進(jìn)行異步方法調(diào)用
控制臺(tái)日志
可以看到異步執(zhí)行之后,查詢結(jié)果由10秒縮短到2秒,這個(gè)優(yōu)化的時(shí)間為實(shí)例的個(gè)數(shù)為倍數(shù)作為縮短。
五、總結(jié)
本文的優(yōu)化方法基于Feign無法異步返回調(diào)用值得情況下采取的折中方法,如果遇到不在意返回值的異步返回可以直接進(jìn)行異步執(zhí)行,這樣的話可以在毫秒級就執(zhí)行結(jié)束。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Spring Cloud 配置中心多環(huán)境配置bootstrap.yml的實(shí)現(xiàn)方法
- 解決springboot與springcloud版本兼容問題(附版本兼容表)
- 將Springboot項(xiàng)目升級成Springcloud項(xiàng)目的圖文教程
- SpringBoot項(xiàng)目改為SpringCloud項(xiàng)目使用nacos作為注冊中心的方法
- SpringBoot(cloud)自動(dòng)裝配bean找不到類型的問題
- springboot集成springCloud中g(shù)ateway時(shí)啟動(dòng)報(bào)錯(cuò)的解決
- 解決nacos升級spring cloud 2020.0無法使用bootstrap.yml的問題
- 從零開始搭建springboot+springcloud+mybatis本地項(xiàng)目全過程(圖解)
- 詳解SpringBoot與SpringCloud的版本對應(yīng)詳細(xì)版
- Spring、Spring?Boot、Spring?Cloud?的區(qū)別與聯(lián)系分析
相關(guān)文章
單點(diǎn)登錄的概念及SpringBoot實(shí)現(xiàn)單點(diǎn)登錄的操作方法
在本文中,我們將使用Spring Boot構(gòu)建一個(gè)基本的單點(diǎn)登錄系統(tǒng),我們將介紹如何使用Spring Security和JSON Web Tokens(JWTs)來實(shí)現(xiàn)單點(diǎn)登錄功能,本文假設(shè)您已經(jīng)熟悉Spring Boot和Spring Security,感興趣的朋友一起看看吧2024-10-10分布式調(diào)度XXL-Job整合Springboot2.X實(shí)戰(zhàn)操作過程(推薦)
這篇文章主要介紹了分布式調(diào)度XXL-Job整合Springboot2.X實(shí)戰(zhàn)操作,包括定時(shí)任務(wù)的使用場景和常見的定時(shí)任務(wù),通過本文學(xué)習(xí)幫助大家該選擇哪個(gè)分布式任務(wù)調(diào)度平臺(tái),對此文感興趣的朋友一起看看吧2022-04-04JDBC連接MySQL數(shù)據(jù)庫批量插入數(shù)據(jù)過程詳解
這篇文章主要介紹了JDBC連接MySQL數(shù)據(jù)庫批量插入數(shù)據(jù)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11MyBatis插入Insert、InsertSelective的區(qū)別及使用心得
這篇文章主要介紹了MyBatis插入Insert、InsertSelective的區(qū)別及使用心得,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12解決springboot URL帶有斜杠的轉(zhuǎn)義字符百分之2F導(dǎo)致的400錯(cuò)誤
這篇文章主要介紹了解決springboot URL帶有斜杠的轉(zhuǎn)義字符百分之2F導(dǎo)致的400錯(cuò)誤問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08