zoukankan      html  css  js  c++  java
  • JUC高并发编程(一)之请求合并案例

    1.背景

    在做活动或者抢购场景,系统查询的请求并发量非常高

    如果并发的访问数据库,会给数据库带来很大的压力,

    这时候我们可以考虑将多个查询请求合并成一个查询请求返回给客户端,

    比如:根据id查询爆款产品

    并发10000次

    3000次查询 id=1的产品

    4000次查询id=2的产品

    2000次查询id=3的产品

    1000次查询id=4的产品

    如果请求不合并,将到数据库查询10000次,

    如果采用请求合并,只需到数据库查询1次,共查询出4个产品,然后按照id把结果给每一个请求,

    这样大大降低了数据库的压力

    使用到的关于juc相关技术:

    1.

    2.

    3.

    2.代码

    控制层代码:

     /**
         * 查询订单
         *
         * @return
         */
        @RequestMapping("/api/product")
        public Object product(String id) throws ExecutionException, InterruptedException {
            // 为了便于分析,设置一个线程号
            Thread.currentThread().setName("thread-" + id);
            Map<String, Object> map = productServiceImpl.queryList(id);
            // 模拟随机耗时
            ThreadUtil.sleepRandom();
            return map;
        }

    业务实现层代码:

    package com.ldp.jucproject.service.impl;
    
    import org.springframework.stereotype.Service;
    
    import javax.annotation.PostConstruct;
    import java.util.*;
    import java.util.concurrent.*;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 11/06 12:51
     * @description
     */
    @Service
    public class ProductServiceImpl {
        /**
         * 请求类,code为查询的共同特征,例如查询商品,通过不同id的来区分
         * CompletableFuture将处理结果返回
         */
        class Request {
            String code;
            CompletableFuture completableFuture;
        }
    
        /**
         * 存放请求的队列
         */
        LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue(200);
    
        @PostConstruct
        public void init() {
            // 建立定时执行的线程
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                int size = queue.size();
                //如果队列没数据,表示这段时间没有请求,直接返回
                if (size == 0) {
                    return;
                }
                List<Request> list = new ArrayList<>();
                System.out.println("合并请求数:" + size);
                //将队列的请求消费到一个集合保存
                for (int i = 0; i < size; i++) {
                    Request poll = queue.poll();
                    if (poll == null) {
                        System.out.println("无请求对象!");
                        break;
                    }
                    list.add(poll);
                }
                //拿到我们需要去数据库查询的特征,保存为集合
                List<String> listParam = new ArrayList<>();
                for (Request request : list) {
                    listParam.add(request.code);
                }
                //将参数传入service处理
                Map<String, HashMap<String, Object>> response = queryByCodeBatch(listParam);
                //将处理结果返回各自的请求
                for (Request request : list) {
                    Map<String, Object> result = response.get(request.code);
                    //completableFuture.complete方法完成赋值,这一步执行完毕,阻塞的请求可以继续执行了
                    request.completableFuture.complete(result);
                }
            }, 0, 30, TimeUnit.MILLISECONDS);
        }
    
        /**
         * 模拟从数据库查询
         *
         * @param codes
         * @return
         */
        public Map<String, HashMap<String, Object>> queryByCodeBatch(List<String> codes) {
            Map<String, HashMap<String, Object>> result = new HashMap();
            for (String code : codes) {
                HashMap<String, Object> hashMap = new HashMap<>();
                hashMap.put("productCode", new Random().nextInt(999999999));
                hashMap.put("code", code);
                hashMap.put("productNome", "苹果-" + new Random().nextInt(5));
                hashMap.put("price", new Random().nextInt(100));
                result.put(code, hashMap);
            }
            return result;
        }
    
        public Map<String, Object> queryList(String code) throws ExecutionException, InterruptedException {
            Request request = new Request();
            request.code = code;
            CompletableFuture<Map<String, Object>> future = new CompletableFuture<>();
            request.completableFuture = future;
            //将对象传入队列
            boolean offer = queue.offer(request);
            if (!offer) {
                // 放入队列失败,说明队列满了,返回系统忙
                Map<String, Object> result = new HashMap<>();
                result.put("code", "9999");
                result.put("message", "系统忙!");
                return result;
            }
            //如果这时候没完成赋值,那么就会阻塞,知道能够拿到值
            return future.get();
        }
    }

    3.测试

    模拟请求:

     @Test
        void product() throws InterruptedException {
            // 并发请求数
            int num = 1000;
            CountDownLatch countDownLatch = new CountDownLatch(num);
            for (int i = 1; i <= num; i++) {
                // 计数器减一
                countDownLatch.countDown();
                Integer id = i;
                new Thread(() -> {
                    try {
                        String url = "http://localhost:8001/api/product?id=" + id;
                        // 等待计数器归零,归零前都是处于阻塞状态
                        System.out.println("待查询订单号=" + id);
                        countDownLatch.await();
                        HttpRequest request = HttpUtil.createGet(url);
                        String response = request.execute().body();
                        System.out.println("response=" + response);
                    } catch (Exception e) {
                        log.error("模拟并发出错:{}", e);
                    }
                }).start();
            }
            // 避免线程终止
            Thread.sleep(90 * 1000);
        }

    4.完美!

  • 相关阅读:
    如何将程序添加到系统服务实现开机自启动
    ASP.NET速度优化
    layer官方演示与讲解(jQuery弹出层插件)
    SQLSERVER排查CPU占用高的情况
    解决FlexPaper分页分段加载问题(转)
    SQL 2008升级SQL 2008 R2完全教程或者10.00.1600升级10.50.1600
    Virtualbox中win7虚拟机中U盘不可用问题的解决
    解决:对COM 组件的调用返回了错误 HRESULT E_FAIL
    google chrome插件开发,自己动手,丰衣足食
    WebKit.net最简单使用方法
  • 原文地址:https://www.cnblogs.com/newAndHui/p/15614574.html
Copyright © 2011-2022 走看看