SpringCloud解决Feign异步回调问题(SpringBoot+Async+Future实现)

目录
  • 一、背景
  • 二、设计方案
  • 三、示例代码
  • 四、实现效果测试
  • 五、总结

近期,需要对之前的接口进行优化,缩短接口的响应时间,但是springcloud中的feign是不支持传递异步化的回调结果的,因此有了以下的解决方案,记录一下,仅供参考。

一、背景

对于一个页面上的所有实例有一个查询权限的接口,同时有四个操作类型的需要查询且接口仅支持单个实例单个操作类型操作。

简单来说,假设实例查询退订权限需要1秒钟,那么四个操作共需4秒钟,一共20个实例的话,那么实际所需时间为80秒。

这样显然是不符合要求的。经过本文的优化后,可以达到20秒。

二、设计方案

需要指出,由于各种限制原因,无法直接在工程中进行rest接口的调用,否则是直接可以@Async异步调用的。

(一)使用框架

产品工程使用SpringCloud微服务,同时通过Feign调用权限的微服务,服务都是使用SpringBoot实现。

(二)实现方案

由于Feign的特殊性,目前并不支持异步返回Future,直接通过Future是会报错的。

因此可以在权限微服务中异步执行查询操作权限,同时再异步起一个线程去等待所有的异步结果,这一个线程是阻塞的,同时由阻塞得到的最终结果,封装成自己想要的返回体,返回给调用方即可,这样相当于大致只需要等待一个实例的操作时间即可。

三、示例代码

项目代码我就不放上来了,我将写一个Demo作为示例,基本的思想都在里面了。

(一)被调用方

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])); //异步方法 异步执行四个用户的查询操作
        futures.add(asyncService.testFuture(ids[1]));
        futures.add(asyncService.testFuture(ids[2]));
        futures.add(asyncService.testFuture(ids[3]));
        return asyncService.getReturnValueMange(futures); 异步方法 异步获取所有的异步回调值
    }

    /**
     * 同步测试方法
     * @param i
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/syncTest")
    public User testsyncTest( Integer i) throws InterruptedException {
        User user = asyncService.testSync(i);
        return user;
    }
}

这里需要解释说明下,使用一个list作为future结果的集合,同时采用getReturnValueMange(future)方法来获取所有的异步执行结果,具体可以看下面Service层的代码逻辑。

3. AsyncService层

@Slf4j
@Service
public class AsyncService {

    @Autowired
    private AsyncUnit asyncUnit; //异步实现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;
    }

    //获取异步方法结果
    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("异步执行start...{}",Thread.currentThread().getName());
        long currentTime = System.currentTimeMillis();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("异步执行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("异步执行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("异步执行done..getReturnValueMange.{}, {}",Thread.currentThread().getName(), String.valueOf(System.currentTimeMillis()- currentTime));
        return new AsyncResult<>(users);
    }
}

(二)调用方

1. Config 层

@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new FeignRequestInterceptor();
    }
}

2.  Controller层

@RestController
@Slf4j
public class AsyncController {

    @Autowired
    private RemoteClient remoteClient;

    //异步测试方法,查询五次 模拟所需时间
    @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;
    }

    //同步测试方法,通过执行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);

}

被调用方的服务名为ASYNC,通过Feign调用,Feign的详细说明就不细说,具体可以自行查询。

(三)公共类 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;
    }

}

四、实现效果测试

调用方端口8305 被调用方8036 eureka端口 8761,将服务启动如图所示。

1. 使用Postman进行接口测试,首先是同步方法的执行。

控制台日志

2. 使用Postman进行异步方法调用

控制台日志

可以看到异步执行之后,查询结果由10秒缩短到2秒,这个优化的时间为实例的个数为倍数作为缩短。

五、总结

本文的优化方法基于Feign无法异步返回调用值得情况下采取的折中方法,如果遇到不在意返回值的异步返回可以直接进行异步执行,这样的话可以在毫秒级就执行结束。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot整合OpenFeign的坑

    目录 项目集成OpenFegin 集成OpenFegin依赖 实现远程调用 解决问题 问题描述 问题分析 问题解决 最近,在使用SpringBoot+K8S开发微服务系统,既然使用了K8S,我就不想使用SpringCloud了.为啥,因为K8S本身的就提供了非常6的服务注册与发现.限流.熔断.负载均衡等等微服务需要使用的技术,那我为啥还要接入SpringCloud呢?额,说了这么多,在真正使用SpringBoot+K8S这一套技术栈的时候,也会遇到一些问题,比如我不需要使用SpringCloud

  • SpringBoot Feign使用教程超全面讲解

    目录 开篇 一.使用 Feign 的示例 1.1 添加依赖 1.2 启用 Feign 1.3 编写 FeignClient 接口 1.4 编写对应的服务端 1.5 调用 FeignClient 二.如何切换 Client 2.1 使用 Apache 的 HTTP Client 2.1.1 添加依赖 2.1.2 配置启用 2.2 使用 OkHttp 2.2.1 添加依赖 2.2.2 配置启用 三.如何修改日志级别 3.1 通过配置文件修改日志级别 3.2 通过配置类修改日志级别 四.如何实现数据压

  • SpringBoot 关于Feign的超时时间配置操作

    目录 Feign的超时时间配置 feign 时间设置 Feign调用问题 \ 超时 1.项目结构 2.在其他微服务中 引入clientXX.jar 3.feign调用超时 Feign的超时时间配置 feign 时间设置 contextId: 可以指定为某个接口进行单独的超时设置 @FeignClient(value = "user",contextId ="device") public interface DeviceFeignService { @Request

  • SpringBoot动态Feign服务调用详解

    目录 1.Feign传统方式的不足 2.动态Feign 2.1.服务生产者 2.2.动态Feign 2.3.服务消费者 3.总结 1.Feign传统方式的不足 ①.在微服务架构中,当我们使用Feign传统方式进行服务调用的时候,需要在每个服务消费者中添加FeignClient接口,编写对应的方法,而且当服务生产者Handler新增方法之后,服务消费者也要在FeignClient接口中添加方法,这样的话,会有些累赘. 那么能不能在调用服务提供者方法的时候,传入生产者服务名称的动态生成FeignCl

  • SpringBoot使用Feign调用其他服务接口

    使用SpringCloud的Feign组件能够为服务间的调用节省编码时间并提高开发效率,当服务本身不复杂时可以单独将该组件拿出使用. 引入依赖 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign --> <dependency> <groupId>org.springframework.cloud</groupId>

  • springboot单独使用feign简化接口调用方式

    目录 单独使用feign简化接口调用 1.引入maven 2.启动类添加@EnableFeignClients注解 3.像平常一样写一个service接口 4.调用接口 springbootfeign调用方式比较 1.事发原因 2.方式1介绍 3.方式2介绍 4.调用结果测试 5.两种方式对比 6.小结一下 单独使用feign简化接口调用 与HttpClient和RestTemplate相比,使用springcloud的feign调用远程接口更为简便,可以通过配置的方式实现远程接口调用.但是有时

  • SpringCloud解决Feign异步回调问题(SpringBoot+Async+Future实现)

    目录 一.背景 二.设计方案 三.示例代码 四.实现效果测试 五.总结 近期,需要对之前的接口进行优化,缩短接口的响应时间,但是springcloud中的feign是不支持传递异步化的回调结果的,因此有了以下的解决方案,记录一下,仅供参考. 一.背景 对于一个页面上的所有实例有一个查询权限的接口,同时有四个操作类型的需要查询且接口仅支持单个实例单个操作类型操作. 简单来说,假设实例查询退订权限需要1秒钟,那么四个操作共需4秒钟,一共20个实例的话,那么实际所需时间为80秒. 这样显然是不符合要求

  • SpringBoot使用Async注解失效原因分析及解决(spring异步回调)

    目录 Async注解失效原因分析及解决(spring异步回调) Spring中@Async 有时候在使用的过程中@Async注解会失效 解决方式一 解决方式二 springboot @Async 失效可能原因 Async注解失效原因分析及解决(spring异步回调) Spring中@Async 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已

  • 解决SpringCloud Feign异步调用传参问题

    背景 各个子系统之间通过feign调用,每个服务提供方需要验证每个请求header里的token. public void invokeFeign() throws Exception { feignService1.method(); feignService2.method(); feignService3.method(); .... } 定义拦截每次发送feign调用拦截器RequestInterceptor的子类,每次发送feign请求前将token带入请求头 @Configurati

  • springcloud之Feign超时问题的解决

    问题背景 最近公司项目有个功能需进行三层Feign调用,且还要调外部接口,延迟挺大,造成Feign一直提示Read timed out executing POST. feign.RetryableException: Read timed out executing POST http://****** at feign.FeignException.errorExecuting(FeignException.java:67) at feign.SynchronousMethodHandler

  • jquery Deferred 快速解决异步回调的问题

    jquery Deferred 快速解决异步回调的问题 function ok(name){ var dfd = new $.Deferred(); callback:func(){ return dfd.resolve( response ); } return dfd.promise(); } $.when(ok(1),ok(2)).then(function(resp1,resp2){}) //相关API 分成3类 1类:$.when(pro1,pro1) 将多个 promise 对象以a

  • springboot @Async 注解如何实现方法异步

    目录 @Async注解如何实现方法异步 一.springboot的App类需要的注解 二.service层的注解 三.调用层 异步注解@Async的使用以及注意事项 第一步开启异步 下面显示配置线程的代码实现 使用@Async导致异步不成功的情况 @Async注解如何实现方法异步 处理大批量数据的时候,效率很慢.所以考虑一下使用多线程. 刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批.但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法. 这里没有详细介绍,只有简单的d

  • 解决springcloud中Feign导入依赖为unknow的情况

    目录 Feign导入依赖为unknow的情况 Feign注解导入失败的处理 Feign导入依赖为unknow的情况 网上很多人在使用的feign时在pom.xml中的依赖为: <!-- SpringCloud 整合 Feign --> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-feign<

  • Java SpringBoot @Async实现异步任务的流程分析

    目录 1.同步任务 2.@Async 异步任务-无返回值 3.@Async 异步任务-有返回值 4.@Async + 自定义线程池 5.CompletableFuture 实现异步任务 依赖pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://

  • Nodejs异步回调的优雅处理方法

    前言 Nodejs最大的亮点就在于事件驱动, 非阻塞I/O 模型,这使得Nodejs具有很强的并发处理能力,非常适合编写网络应用.在Nodejs中大部分的I/O操作几乎都是异步的,也就是我们处理I/O的操作结果基本上都需要在回调函数中处理,比如下面的这个读取文件内容的函数: 复制代码 代码如下: fs.readFile('/etc/passwd', function (err, data) {   if (err) throw err;   console.log(data); }); 那,我们

  • JavaScript详解使用Promise处理回调地狱与async await修饰符

    目录 Promise 回调地狱 Promise简介 Promise简单使用 async和await 修饰符 小结 Promise Promise能够处理异步程序. 回调地狱 JS中或node中,都大量的使用了回调函数进行异步操作,而异步操作什么时候返回结果是不可控的,如果我们希望几个异步请求按照顺序来执行,那么就需要将这些异步操作嵌套起来,嵌套的层数特别多,就会形成回调地狱 或者叫做 横向金字塔. 案例:有a.txt.b.txt.c.txt三个文件,使用fs模板按照顺序来读取里面的内容,代码:

随机推荐