简体   繁体   English

对同一Web服务进行多个异步调用的最佳方法

[英]Best way to make multiple asynchronous calls to same web service

Application on which I am working is going to consume 2 REST web service in below sequence: 我正在处理的应用程序将按以下顺序使用2个REST Web服务:

1) Count Records - To know the numbers of records within a particular time frame. 1)计数记录-了解特定时间范围内的记录数。

2) Fetch Records - Once we have number of records then we need to call this service. 2)提取记录-一旦有了记录数量,就需要调用此服务。 But this service has a threshold to fetch 10K records in a go. 但是此服务具有一次获取1万条记录的阈值。 Lets say if first service tell me within particular time interval, it has 100K of records, then I need to call second web service 10 times in paginated way considering it's threshold is 10K in one go. 可以说,如果第一个服务在特定时间间隔内告诉我,它有100K记录,那么考虑到它的阈值为一万次,我需要以分页方式调用第二个Web服务10次。

So if I will make 10 synchronous calls, my application would be too slow to respond back. 因此,如果我要进行10次同步调用,我的应用程序将太慢而无法响应。 So I need to a mechanism to make asynchronous calls. 因此,我需要一种进行异步调用的机制。

I am using spring framework in the back end code and using rest template for web service call. 我在后端代码中使用spring框架,并在Web服务调用中使用rest模板。 I am looking to find the best way to make asynchronous call to the above mentioned POST web service 我正在寻找找到对上述POST Web服务进行异步调用的最佳方法

I have done some research and found Asynchronous method useful as below: https://spring.io/guides/gs/async-method/ 我进行了一些研究,发现异步方法非常有用,如下所示: https : //spring.io/guides/gs/async-method/

Can you please guide me if this is a right approach what I am looking at or is their a better way to make asynchronous call? 如果这是我正在寻找的正确方法,或者它们是进行异步调用的更好方法,您能否指导我? Looking for your suggestions, Thanks! 寻找您的建议,谢谢!

What @Journycorner linked is a good start but it doesn't really show the whole picture as it only makes a single request. @Journycorner链接的内容是一个不错的开始,但是由于它仅发出一个请求,因此并不能真正显示整个图片。 Working with Future is definitely on the right path. Future绝对是正确的道路。 The fact that Spring 4 offers an AsyncRestTemplate that returns a Future is exactly what you want to use. Spring 4提供了一个返回FutureAsyncRestTemplate的事实正是您要使用的。

On my phone so can't write up the full code but this is roughly what you want to do. 因此无法在我的手机上写下完整的代码,但这大致就是您想要做的。

@Component
public class SampleAsyncService {
    private RestTemplate restTemplate;
    private AsyncRestTemplate asyncRestTemplate;

    @Value("${myapp.batchSize:1000}")
    private int batchSize;

    public SampleAsyncService(AsyncRestTemplate asyncRestTemplate, RestTemplate restTemplate) {
        this.asyncRestTemplate = asyncRestTemplate;
        this.restTemplate = restTemplate;
    }

    public List<Record> callForRecords() {
        ResponseEntity<Integer> response = restTemplate.getForEntity("http://localhost:8081/countService",
                Integer.class);
        int totalRecords = response.getBody().intValue();
        List<Future<ResponseEntity<List<Record>>>> futures = new ArrayList<Future<ResponseEntity<List<Record>>>>();
        for (int offset = 0; offset < totalRecords;) {
            ListenableFuture<ResponseEntity<List<Record>>> future = asyncRestTemplate.exchange(
                    "http://localhost:8081/records?startRow={}&endRow={}", HttpMethod.GET, null,
                    new ParameterizedTypeReference<List<Record>>() {
                    }, offset, batchSize);
            futures.add(future);
            offset = offset + batchSize;
        }

        int responses = 0;
        List<Record> fullListOfRecords = new ArrayList<Record>();
        while (responses < futures.size()) {
            for (Future<ResponseEntity<List<Record>>> future : futures) {
                if (future.isDone()) {
                    responses++;
                    try {
                        ResponseEntity<List<Record>> responseEntity = future.get();
                        fullListOfRecords.addAll(responseEntity.getBody());
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return fullListOfRecords;
    }

    public class Record {
    }
}

* Update * Created complete code sample. *更新*创建了完整的代码示例。

This is just an improvement over @shawn answer. 这只是对@shawn答案的改进。 With the implementation provided earlier, sometimes I was facing issue due to below block: 有了前面提供的实现,有时由于以下原因而遇到问题:

while (responses < futures.size()) {        
   for (Future<ResponseEntity<List<Record>>> future : futures) {
       if (future.isDone()) {
            responses++;
            try {
                ResponseEntity<List<Record>> responseEntity = future.get();
                fullListOfRecords.addAll(responseEntity.getBody());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
        }
    }
}

Here if 6 threads are processed, sometimes same thread is allowed to enter multiple times in above block so it ends up having duplicate records in response. 在这里,如果处理了6个线程,则有时同一线程在上面的块中允许多次输入,因此最终会有重复的记录作为响应。 So to avoid such situation, I have added callback block to create final response which make sure no duplicate response, though we still need while(responses < futures.size()) loop block with future.get() to block method to return final combined response until all the asynchronous calls are processed. 因此,为避免这种情况,我添加了回调块以创建最终响应,以确保没有重复的响应,尽管我们仍然需要while(responses <futures.size())循环带有future.get()的块来阻塞方法以返回final组合响应,直到处理完所有异步调用。

@Component
public class SampleAsyncService {
private RestTemplate restTemplate;
private AsyncRestTemplate asyncRestTemplate;

@Value("${myapp.batchSize:1000}")
private int batchSize;

public SampleAsyncService(AsyncRestTemplate asyncRestTemplate, RestTemplate restTemplate) {
    this.asyncRestTemplate = asyncRestTemplate;
    this.restTemplate = restTemplate;
}

public List<Record> callForRecords() {
    ResponseEntity<Integer> response = restTemplate.getForEntity("http://localhost:8081/countService",
            Integer.class);
    int totalRecords = response.getBody().intValue();
    List<Future<ResponseEntity<List<Record>>>> futures = new ArrayList<Future<ResponseEntity<List<Record>>>>();
    for (int offset = 0; offset < totalRecords;) {
        ListenableFuture<ResponseEntity<List<Record>>> future = asyncRestTemplate.exchange(
                "http://localhost:8081/records?startRow={}&endRow={}", HttpMethod.GET, null,
                new ParameterizedTypeReference<List<Record>>() {
                }, offset, batchSize);
        future.addCallback(
                new ListenableFutureCallback<ResponseEntity<ChatTranscript>>() {  
                    @Override  
                    public void onSuccess(ResponseEntity<ChatTranscript> response) {  
                        fullListOfRecords.addAll(responseEntity.getBody());
                        log.debug("Success: " + Thread.currentThread());  
                    }  

                    @Override  
                    public void onFailure(Throwable t) {
                        log.debug("Error: " + Thread.currentThread());
                    }  
                }
            );
        futures.add(future);
        offset = offset + batchSize;
    }

    int responses = 0;
    List<Record> fullListOfRecords = new ArrayList<Record>();
    while (responses < futures.size()) {
        for (Future<ResponseEntity<List<Record>>> future : futures) {
            if (future.isDone()) {
                responses++;
                try {
                    future.get();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    return fullListOfRecords;
}

public class Record {
}

} }

如果真的必须异步,那么使用Spring Implementation似乎是一个聪明的主意。

I am assuming you need all the 100k records in a single execution so as to package all the data maybe into a file or perform some business logic in one go . 我假设您需要在一次执行中存储所有10万条记录,以便将所有数据打包到一个文件中或一次性执行一些业务逻辑。 If thats not case it would be wise to reconsider the need to load all data into single execution straining the memory usage of jvm or running into Outofmemory errors. 如果不是这种情况,明智的做法是重新考虑将所有数据加载到单个执行中的需求,从而使jvm的内存使用紧张或出现内存不足错误。

Assuming the former, Async could be an option to execute parallel threads and capture and collate responses from each. 假设是前者,异步可能是执行并行线程并捕获和整理每个线程的响应的一种选择。 However you need to keep an upper limit of number of threads to be executed in parallel using a "thread pool size" of a task executor. 但是,您需要使用任务执行程序的“线程池大小”来保持要并行执行的线程数的上限。 One more reason to limit the thread size is avoid loading your partner rest webservice with too many parallel calls. 限制线程大小的另一个原因是,避免通过过多的并行调用加载合作伙伴的其余Web服务。 Eventually the partner webservice is loading data from database, which will give optimum performance for certain limit of parallel query executions. 最终,合作伙伴Web服务正在从数据库加载数据,这将为并行查询执行的某些限制提供最佳性能。 Hope this helps! 希望这可以帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM