一.查询场景
二.什么是请求合并
三.请求合并的设计思路
四.请求合并的代码演示
package com.hong.concurrent;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* Created by John on 2019/1/30.
* 高并发下性能优化的一种方式-请求合并
* 场景:根据movieCode查询对应的movie信息
* 假设对外暴露的api:http://movieapi/queryByCode?code=1001,
* 且存在高并发查询。假设1s内有1000个请求过来,按照常规设计,则会
* 进行1000次http调用,如何优化减少调用次数?
*/
@Service
public class MovieService {
/**
* 使用内部类或者接口是实现Java中多继承的两种方式
*/
class Request {
private String movieCode;
/**
* 接收异步处理的结果信息
*/
private CompletableFuture future;
public String getMovieCode() {
return movieCode;
}
public void setMovieCode(String movieCode) {
this.movieCode = movieCode;
}
public CompletableFuture getFuture() {
return future;
}
public void setFuture(CompletableFuture future) {
this.future = future;
}
}
public static final long REQUEST_BATCH_INTERVAL = 10L;
/**
* 积攒汇聚请求
*/
private LinkedBlockingQueue<Request> requestQueue = new LinkedBlockingQueue<>();
/**
* 定时任务,每隔REQUEST_BATCH_INTERVAL毫秒批量处理一次汇聚的请求
*/
@PostConstruct
public void init() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
//1.取出requestQueue汇聚的请求,生成一次批量查询
int request_size = requestQueue.size();
if (request_size == 0) {
return;
}
ArrayList<Request> requestList = new ArrayList<>(request_size);
for (int i = 0; i < request_size; i++) {
requestList.add(requestQueue.poll());
}
System.out.println(Thread.currentThread() + "批量处理数据量:" + request_size);
//2.组装一个批量查询
List<String> movieCodeList = requestList.stream().map(r -> r.getMovieCode()).collect(Collectors.toList());
List<Map<String, Object>> resultList = batchQueryMovieByCode(movieCodeList);
//3.将结果响应分发给每一个单独的用户请求
Map<Object, Map<String, Object>> responseMap = resultList.stream().collect(Collectors.toMap(m -> m.get("code"), m -> m));
//4.根据请求中能表示唯一参数,去批量查询的结果中找对应的响应,并将结果返回到对应的请求线程
for (Request request:requestList){
// CompletableFuture一旦有结果会立马返回到get()方法调用处
request.getFuture().complete(responseMap.get(request.getMovieCode()));
}
}, 0, REQUEST_BATCH_INTERVAL, TimeUnit.MILLISECONDS);
}
public Map<String,Object> queryMovie(String movieCode) throws ExecutionException, InterruptedException {
// return queryMovieByCode(movieCode);
/**
* 将不同用户的同类请求合并起来
* 当一个用户的请求到来时,不立刻发起接口调用,先收集起来,延迟一定间隔后再进行批量处理
*/
Request request = new Request();
request.setMovieCode(movieCode);
//异步编程,获取异步处理的结果
CompletableFuture<Map<String,Object>> future = new CompletableFuture<>();
request.setFuture(future);
requestQueue.add(request);
/**
* 此处get()方法会阻塞线程运行,直到future有返回
*/
return future.get();
}
/**
* Mock远程调用批量查询接口
*/
public Map<String,Object> queryMovieByCode(String code){
Map<String,Object> result = new HashMap<>();
result.put("movieId",new Random().nextInt(9999));
result.put("code",code);
result.put("star","wanghong");
return result;
}
public List<Map<String,Object>> batchQueryMovieByCode(List<String> codeList){
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Map<String,Object>> list = new ArrayList<>();
for (String code:codeList){
list.add(queryMovieByCode(code));
}
return list;
}
}
五.请求合并的优缺点