• 【高并发】秒杀系统高并发请求排队处理


    今天无意中看见了这位兄弟的文章 通过请求队列的方式来缓解高并发抢购(初探)  但文章最后说并发超过500 就会出现超发,看了下代码,的确有这个问题

    抽空简单完善了下,经压力测试后发现暂无超发现象, 下面为我的代码,有不足之处请指正交流:

    1.请求参数封装,有个随机的用户ID 用来区分不同用户的请求:

    import java.util.Random;
    
    public class OrderRequest {
        private int goodId = new Random().nextInt(100000);// 商品id
        private int userId = new Random().nextInt(100000);// 用户ID
        private int status;// 0:未处理;1:正常;2:异常
    
        public int getGoodId() {
            return goodId;
        }
    
        public void setGoodId(int goodId) {
            this.goodId = goodId;
        }
    
        public int getUserId() {
            return userId;
        }
    
        public void setUserId(int userId) {
            this.userId = userId;
        }
    
        public int getStatus() {
            return status;
        }
    
        public void setStatus(int status) {
            this.status = status;
        }
    
    }

    2.controller 入口:

    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.locks.ReentrantLock;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class QueueController {
    
        private static ConcurrentLinkedQueue<OrderRequest> orderQueue = new ConcurrentLinkedQueue<OrderRequest>();
    
        private static AtomicInteger totalprodocut;
        private static AtomicInteger totalQueSize;
        private ExecutorService excutorService = Executors.newCachedThreadPool();
        public static ReentrantLock queueLock = new ReentrantLock(true);
    
        static {
            totalprodocut = new AtomicInteger(15);
            totalQueSize = new AtomicInteger(15);
        }
    
        @RequestMapping("/queque")
        public String queque(OrderRequest orderRequest) {
            try {
                // queueLock.lock();
                if (totalprodocut.get() < 1) {
                    return "error";
                } else {
                    if (orderQueue.size() < totalQueSize.get()) {
                        // System.out.println(orderRequest.getGood_id()+" 增加到待处理队列成功:" + orderQueue.size());
                        orderQueue.add(orderRequest);
                    } else {
                        return "error";
                    }
                }
                if (!OrderDealThread.dealLock.isLocked()) {
                    OrderDealThread dealQueue = new OrderDealThread(orderQueue);
                    excutorService.execute(dealQueue);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return "not done";
            } finally {
                // queueLock.unlock();
            }
            return "ok";
        }
    }

    说明: 如果这里放开lock,可以保证只有允许的请求进入到请求队列中去,但是效率会降低很多,毕竟每个请求都要去上锁开锁

        如果这里不要锁,进入请求队列的请求会超过我们设定的个数,但不会多太多;

        其实这里应该不用锁,应该快速的响应大多数不能进入请求队列用户的请求,已经进入请求队列的请求在后续处理的时候还会进行业务判断的

    3.业务处理线程

    import java.util.Iterator;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class OrderDealThread implements Runnable {
    
        ConcurrentLinkedQueue<OrderRequest> orderQueue;
    
        private static AtomicInteger totalprodocut;
    
        public static ReentrantLock dealLock = new ReentrantLock(true);
    
        static {
            totalprodocut = new AtomicInteger(15);
        }
    
        public OrderDealThread() {
        }
    
        public OrderDealThread(ConcurrentLinkedQueue<OrderRequest> queque) {
            this.orderQueue = queque;
        }
    
        @Override
        public void run() {
            while (!orderQueue.isEmpty()) {
                try {
                    dealLock.lock();
                    Iterator<OrderRequest> it = orderQueue.iterator();
                    while (it.hasNext()) {
                        dealQueque(it.next());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    dealLock.unlock();
                }
            }
        }
    
        void dealQueque(OrderRequest orderRequest) {
            if (orderRequest.getStatus() == 0) {
                int status = 2;
                if (totalprodocut.get() > 0) {//需再次判断是否还有商品,加锁范围内
                    //TODO 下单处理其他逻辑
                    totalprodocut.decrementAndGet();// 减库存
                    status =1;
                }
    
                if (status == 2) {
    //                System.out.println(orderRequest.getUserId() + " deal er:" + Thread.currentThread().getName());
                    orderRequest.setStatus(2);
                } else {
                    System.out.println(orderRequest.getUserId() + " deal ok:" + Thread.currentThread().getName());
                    orderRequest.setStatus(1);
                }
            }
        }
    }

    说明:在真正处理业务的时候还要判断是否有库存等逻辑

    上述代码中,部分内容,比如产品数目等应该在活动开始前同步到redis等能快速获取的中间件中去

    下面是我的测试结果:有个疑问? 我设置的线程组是1秒内启动6000个,为啥这里显示6万个?  疑问解除,原来我线程组里面循环了10次 没注意到那个参数

    经过多轮测试,暂未发现多发现象, OK项目始终只有15个

    欢迎指正

    由于是在windows下测试,并发高了就报错  java.net.BindException: Address already in use  这个初看上去很像端口被占用,其实是因为已经完成请求的 socket 端口 还处于 TIME_WAIT状态,把端口耗尽了

    处理办法 参考 https://blog.csdn.net/ywb201314/article/details/51258777

    =============增加=====================

    有同学给我发邮件提了一个问题:

    问:你代码里面请求进入了请求队列就返回了成功,但是你写了请求队列中请求数会超过预期值,那我怎么做下一步的操作呢?比如进行付款?

    答:很好的问题!  感谢你的提问

    说下处理逻辑:1.进入了请求队列,就有可能被请求到,而且这里是异步,就是说请求收到ok了,但后台逻辑完全可能还没处理

            所以,在接收到OK后,前端应该发起一个类似倒计时页面,如下:

            

            提示系统正常处理中,同时隔一定时间去后台确认是否处理完成以及状态

            当获取到的状态为完成且成功时,跳转到下一步如付款操作界面,现在很多秒杀系统都是这么处理的

    我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=bunl3uhohufn

  • 相关阅读:
    react-router刷新页面Cannot GET 问题
    react学习之弹出层
    react学习之js-xlsx导入和导出excel表格
    c#串口通信并处理接收的多个参数
    react与微信小程序
    promise知识点小结
    汇编命令小记
    Firebase-config 在android中的使用
    python:html元素解析
    Toast实现源码解析
  • 原文地址:https://www.cnblogs.com/xiaochangwei/p/miaosha.html
Copyright © 2020-2023  润新知