高并发场景中,调用批量接口相比调用非批量接口有更大的性能优势。但有时候,请求更多的是单个接口,不能够直接调用批量接口,如果这个接口是高频接口,对其做请求合并就很有必要了。比如电影网站的获取电影详情接口,APP的一次请求是单个接口调用,用户量少的时候请求也不多,完全没问题;但同一时刻往往有大量用户访问电影详情,是个高并发的高频接口,如果都是单次查询,后台就不一定能hold住了。为了优化这个接口,后台可以将相同的请求进行合并,然后调用批量的查询接口。如下图所示

或者下图

设计决绝方案

代码实现
@Test
public void benchmark() throws IOException {
// 创建 并不是马上发起请求
for (int i = 0; i < THREAD_NUM; i++) {
final String code = "code-" + (i + 1); // 番号
// 多线程模拟用户查询请求
Thread thread = new Thread(() -> {
try {
// 代码在这里等待,等待countDownLatch为0,代表所有线程都start,再运行后续的代码
countDownLatch.await();
// http请求,实际上就是多线程调用这个方法
Map<String, Object> result = commodityService.queryCommodity(code);
System.out.println(Thread.currentThread().getName() + " 查询结束,结果是:" + result);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " 线程执行出现异常:" + e.getMessage());
}
});
thread.setName("price-thread-" + code);
thread.start();
// 田径。启动后,倒计时器倒计数 减一,代表又有一个线程就绪了
countDownLatch.countDown();
}
// 输入任意内容退出
System.in.read();
}
@Service
public class CommodityService {
class Request {
String commdityCode;
CompletableFuture<Map<String, Object>> future; // 接受结果
}
// 积攒 请求。(每隔N毫秒批量处理一次)
LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue<>();
// 定时任务的实现,N秒钟处理一次数据
@PostConstruct
public void init() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
// 1、 取出queue的请求,生成一次批量查询
int size = queue.size();
if (size == 0) {
return;
}
ArrayList<Request> requests = new ArrayList<>();
for (int i = 0; i < size; i++) {
Request request = queue.poll();
requests.add(request);
}
System.out.println("批量处理数据量:" + size);
// 2、 组装一个批量查询(一定需要 目的资源能够支持批量查询。 http)
ArrayList<String> commodityCodes = new ArrayList<>();
for (Request request : requests) {
commodityCodes.add(request.commdityCode);
}
List<Map<String, Object>> responses = queryServiceRemoteCall.queryCommodityByCodeBatch(commodityCodes);
// 3、将结果响应 分发给每一个单独的用户请求。 由定时任务处理线程 --> 1000个用户的请求线程
// [
// {"code":"500",star: tony}
// {"code":"600",star: tony}
// ]
HashMap<String, Map<String, Object>> responseMap = new HashMap<>();
for (Map<String, Object> response : responses) {
String code = response.get("code").toString();
responseMap.put(code, response);
}
for (Request request : requests) {
// 根据请求中携带的能表示唯一参数,去批量查询的结果中找响应
Map<String, Object> result = responseMap.get(request.commdityCode);
// 将结果返回到对应的请求线程
request.future.complete(result);
}
}, 0, 10, TimeUnit.MILLISECONDS);
}
@Autowired
QueryServiceRemoteCall queryServiceRemoteCall;
// 1000 用户请求,1000个线程
public Map<String, Object> queryCommodity(String movieCode) throws ExecutionException, InterruptedException {
// 1000次 怎么样才能变成 更少的接口
// 思路: 将不同用户的同类请求合并起来
// 并非立刻发起接口调用,请求 收集起来,再进行
Request request = new Request();
request.commdityCode = movieCode;
// 异步编程: 获取异步处理的结果
CompletableFuture<Map<String, Object>> future = new CompletableFuture<>();
request.future = future;
queue.add(request);
return future.get(); // 此处get方法,会阻塞线程运行,直到future有返回
// 什么时候返回结果? 批量查询之后。 怎么进行等待
// return queryServiceRemoteCall.queryMovieInfoByCode(movieCode);
}
}
@Service
public class QueryServiceRemoteCall {
/**
* 调用远程的商品信息查询接口
*
* @param code 商品编码
* @return 返回商品信息,map格式
*/
public HashMap<String, Object> queryCommodityByCode(String code) {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("commodityId", new Random().nextInt(999999999));
hashMap.put("code", code);
hashMap.put("phone", "huawei");
hashMap.put("isOk", "true");
hashMap.put("price","4000");
return hashMap;
}
/**
* 批量查询 - 调用远程的商品信息查询接口
*
* @param codes 多个商品编码
* @return 返回多个商品信息
*/
public List<Map<String, Object>> queryCommodityByCodeBatch(List<String> codes) {
// 不支持批量查询 http://moviewapi.com/query.do?id=10001 --> {code:10001, star:xxxx.....}
// http://moviewapi.com/query.do?ids=10001,10002,10003,10004 --> [{code:10001, star///}, {...},{....}]
List<Map<String, Object>> result = new ArrayList<>();
for (String code : codes) {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("commodityId", new Random().nextInt(999999999));
hashMap.put("code", code);
hashMap.put("phone", "huawei");
hashMap.put("isOk", "true");
hashMap.put("price","4000");
result.add(hashMap);
}
return result;
}
}
弊端:
实行合并请求实际上是执行实际路基之前增加了延迟,如果平均需要5毫秒的执行时间,放在10毫秒做一次批处理的场景下,则最坏的情况可能变成15秒(不适合低延迟的RPC场景、低并发场景)

1073

被折叠的 条评论
为什么被折叠?



