前言:在服务端与客户端接口调用时,我们经常会遇到客户端高频地调用服务端同一个接口,并且每次调用耗时很短,内容传输也很少。例如在一单位时间内(秒),客户端发出1000条根据ID查询的请求到服务端,完成该功能的话可能需要产生1000次调用,这时候批量接口算是一个很好的解决方法,对于真实场景,很可能这是1000个用户向这里的客户端发出的请求,但是怎么将这1000个请求进行数据合并然后批量调用,最后正确返回到每个请求呢?
上图为原始模型,这里给出一个优化思路,首先要求服务端能够提供一个批量接口,将客户端接收的请求封装成一个event加入一个阻塞队列中,每100ms,进行一次拿出队列中元素,进行批量请求服务端,然后根据不同的线程请求将接口返回分发给对应的线程;再次说明,这里只是一个思路,不深入考虑例如队列堵塞、内存占用等等情况,安全部分根据实际情况进行扩展和设计
对于上述qps理论上来讲,吞吐量会提升约10倍。如何实现呢,如果看到此已然心领神会了,接下来的文章也没必要看了,不太了解如何实现的同学不妨多花几分钟继续阅读下去;
这里分享一个自己随手写的demo,采用CompletableFuture做异步接口返回,BlockingDeque作为存储请求的队列。
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*;
public class Batch {
static class Resp {
final private String id;
final private String queryId;
CompletableFuture<String> future;
public Resp(String id, String queryId, CompletableFuture<String> future) {
this.id = id;
this.queryId = queryId;
this.future = future;
}
}
public String doService(String queryId) throws ExecutionException, InterruptedException {
CompletableFuture<String> future = new CompletableFuture<>();
deque.add(new Resp(UUID.randomUUID().toString(), queryId, future));
return future.get();
}
BlockingDeque<Resp> deque = new LinkedBlockingDeque<>();
@PostConstruct
void init() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() ->{
int size = deque.size();
if (size <= 0) {
return;
}
List<Resp> list = new ArrayList<>();
List<Map> mapList = new ArrayList<>();
for (int i = 0; i < size; i++) {
Resp resp = deque.poll();
Map<String, String> map = new HashMap<>();
map.put("id", resp.id);
map.put("queryId", resp.queryId);
mapList.add(map);
//map.put("service", resp.service);
list.add(resp);
}
List<Map<String, String>> s = doBatch(mapList);
for (Resp resp : list) {
for (Map<String, String> m : s) {
if (resp.id.equals(m.get("id"))) {
resp.future.complete(m.get("result"));
}
}
}
},0, 100, TimeUnit.MILLISECONDS);
}
public List<Map<String, String>> doBatch(List<Map> mapList) {
System.out.println("执行了一次"+ mapList.size());
List<Map<String, String>> list = new ArrayList<>();
// 为了简单说明问题,并非真实的远程调用
mapList.forEach(x -> list.add(new HashMap<String, String>(){
{
put("id",(String) x.get("id"));
put("result", x.get("queryId")+ "__"+ "resp");
}
}));
return list;
}
public void test() {
for (int i = 0; i < 1000; i++) {
int kj = i;
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
String s = doService(kj + "");
Map<String, String> map = new HashMap<>();
map.put(kj + "", s);
map.forEach((k, v) -> System.out.println(k + " ==== " + v));
}
}).start();
}
}
}
最在最后:该思路是从腾讯公开课学习到的,在这里简单的分享出来。这也是头次写点文章,以前吃过太多复制粘贴等等误导人的文章的亏,担心自己由于水平限制也会误导他人,所以干脆不写也不发表。最近收到朋友的鼓励,改变了想法,就当学习笔记来写吧,如果有人有所收获,那么最好不过,如果产生误导,万分抱歉,同时也希望您回复,一方面我能及时修改,另一方面,避免其他人踩坑。最后感谢,阅读到这里的每一位,加油。 bye~