今天无意中看见了这位兄弟的文章 通过请求队列的方式来缓解高并发抢购(初探) 但文章最后说并发超过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