性能优化-请求合并

通常我们的系统查询过程如下图
应用
当我们面临百万级甚至更过查询请求时,这种简单应用结构明显无法满足业务及性能要求。此时我们需要对上述结构进行优化。通常的做法有一下两种方式

  1. 负载均衡
    将应用后台集群部署,降低单个后台服务器压力。
  2. 缓存、缓冲
    服务层查询数据库时做缓存(Cache/Redis)、缓冲(MQ)

2但是上述两种优化方式中百万级别的请求依然会进行百万级别的数据库或者缓存访问。并没有降低服务层服务器、缓存服务器、数据库服务器压力。服务器又无法无限扩张,此时可以通过请求合并的方式进行优化。请求合并是将前端大量的相同内容的请求做一次中转合并,降低服务层以及数据库查询的压力。


优化前
每次前端请求都查询一次数据库

try{
    countDownLatch.await();
    ReqMerger reqMerger =reqMergerService.queryById(id);
    System.out.println("第"+id+"次请求,结果:"+reqMerger);
}catch (Exception ex){
    ex.printStackTrace();
}

测试
模拟并发1000个请求

static int THREAD_NUM=1000;
static CountDownLatch countDownLatch=new CountDownLatch(THREAD_NUM);

@ResponseBody
@RequestMapping(value="request/single",method ={RequestMethod.GET})
@ApiOperation(value = "测试:单次请求", notes = "测试单次请求", response = Result.class)
public void singleRequest(
        HttpServletRequest req,
        HttpServletResponse resp){
    for(int i=0;i<THREAD_NUM;i++){
        final int id=i;
        Thread thread=new Thread(()->{
            try{
                countDownLatch.await();
                //每次请求都直接通过service获取数据
                ReqMerger reqMerger =reqMergerService.queryById(id);
                System.out.println("第"+id+"次请求,结果:"+reqMerger);
            }catch (Exception ex){
                 ex.printStackTrace();
            }
        });
        thread.setName("线程-"+i);
        thread.start();
        countDownLatch.countDown();
    }
}

测试结果
1000次请求则查询1000次数据并返回结果

216次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@49714d61519次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@1ddf25d518次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@537f1e85521次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@7bfc1127515次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@661bc49d514次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@7ce7a566511次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@26bdd1d2517次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@75744aeb516次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@9b9abf7513次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@2d6dcc5f512次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@1852941e509次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@1dc83922507次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@5348df6f506次请求,结果:com.ym.reqmerger.entity.ReqMergerEntity@42f1262

优化后
添加一层服务,先将请求缓存至队列中,同时设置定时任务每隔10ms从队列中获取缓存的请求并合并后调用接口方式批量查询数据再返回结果

@Resource
ReqMergerService reqMergerService;
//自定义类用于包装请求
class Request{
    int id;
    CompletableFuture<ReqMerger> future;
}
//将前端请求先缓存至队列中
LinkedBlockingQueue<Request> queue=new LinkedBlockingQueue<>();

/**
 * 添加定时任务
 * 设置每10ms从缓存队列中将缓存的请求合并后调用接口进行查询
 */
@PostConstruct
public void init(){
    ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(1);
    scheduledExecutorService.scheduleAtFixedRate(()->{
        synchronized (queue){
            int size=queue.size();
            if(size==0){
                return;
            }
            ArrayList<Request> requests=new ArrayList<>();
            int[] ids=new int[size];
            for (int i = 0; i < size; i++) {
                Request req=queue.poll();
                requests.add(req);
                ids[i]=req.id;
            }
            System.out.println("合并了"+size+"个请求");
            List<ReqMerger> reqMergers=reqMergerService.queryByIds(ids);
            Map<Integer,ReqMerger> responseMap=new HashMap<>();
            for (ReqMerger response : reqMergers) {
                responseMap.put(response.getId(),response);
            }
            for (Request request : requests) {
                ReqMerger result=responseMap.get(request.id);
                request.future.complete(result);
            }
        }
    },0,10, TimeUnit.MILLISECONDS);
}

/**
 * 请求过来时先将请求缓存至队列中
 * 通过Future来阻塞线程,等待合并的请求执行完后返回结果
 * @param id
 * @return
 * @throws Exception
 */
public ReqMerger queryById(int id) throws Exception{
    Request req=new Request();
    req.id=id;
    CompletableFuture<ReqMerger> future=new CompletableFuture<>();
    req.future=future;
    queue.add(req);
    return future.get();
}

测试
模拟并发1000个请求

@ResponseBody
@RequestMapping(value="request/merger",method ={RequestMethod.GET})
@ApiOperation(value = "测试:合并请求", notes = "测试合并请求", response = Result.class)
public void mergerRequest(
        HttpServletRequest req,
        HttpServletResponse resp){
    for(int i=0;i<THREAD_NUM;i++){
        final int id=i;
        Thread thread=new Thread(()->{
            try{
                countDownLatch.await();
                //每次请求都直接通过service获取数据
                ReqMerger reqMerger =reqMergerCall.queryById(id);
                System.out.println("第"+id+"次请求,结果:"+reqMerger);
            }catch (Exception ex){
                ex.printStackTrace();
            }
        });
        thread.setName("线程-"+i);
        thread.start();
        countDownLatch.countDown();
    }
}

测试结果

合并了23个请求
合并了176个请求
合并了434个请求
合并了9个请求
合并了301个请求
合并了57个请求

结论
通过对比发现同样并发1000个请求,未优化时会调用1000次ReqMergerService中的查询方法,优化后仅仅调用了6次。在高并发情况下大大降低了服务层、数据库层压力。相应的确定也很明显,每一个前端请求都会进行0-50ms的请求等待,降低了相应速度。

发布了22 篇原创文章 · 获赞 27 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u014395955/article/details/103628633