请求合并

高并发场景中,调用批量接口相比调用非批量接口有更大的性能优势。但有时候,请求更多的是单个接口,不能够直接调用批量接口,如果这个接口是高频接口,对其做请求合并就很有必要了。比如电影网站的获取电影详情接口,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场景、低并发场景)

 

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值