一个简单的接口优化思路

1,178 阅读3分钟

前言:在服务端与客户端接口调用时,我们经常会遇到客户端高频地调用服务端同一个接口,并且每次调用耗时很短,内容传输也很少。例如在一单位时间内(秒),客户端发出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~