通过请求队列的方式来缓解高并发抢购(初探)

通过请求队列的方式来缓解高并发抢购(初探)

一、背景

在移动互联网高速发展的时代,各种电商平台的抢购业务变得越来越火爆,抢购业务所带来的高并发问题值得我们去探索,主要涉及的方面包括处理和响应速度、数据的一致性等。抢购开放的一瞬间,可能有成千上万的下订单请求发送到服务器去处理,如果只是简单的请求处理响应方式,不做任何处理,导致的结果很可能是很多客户很长时间得不到响应,根本不知道自己是否下订单成功,或者下订单的数量已经超过了商品的数量,这就导致了超发的问题。

二、设计思路

1、用户在下订单之前当然是先查询到这个商品,在这个查询的时候,将数据库中商品的剩余数量存到redis中;

2、服务器在一瞬间接到成千上万的下订单请求,在控制层没有直接处理请求数据,而是先根据redis中商品的剩余数量来判断,如果>0,就将请求放到请求队列中,否则直接响

应客户端“卖完了”;

3、考虑到数据的一致性,队列采用的是线程安全的队列LinkedBlockingQueue,

三、实现步骤

说明:用java语言,springmvc框架+redis实现

1、准备工作,查询商品信息,将剩余数量同步到redis中

Jedis jedis = jedisPool.getResource();
         BuyGood good=buyGoodService.getById(good_id);
        jedis.set("residue"+good_id, good.getResidue()+"");
        jedisPool.returnResource(jedis);

2、下订单的方法,下面直接展示代码,包括请求对象,控制层方法,请求处理线程类的具体实现

2.1 请求封装对象

public class BuyRequest {
    private int good_id;//商品id
    private int user_id;//用户ID
    private int order_id;//订单id
    private BuyOrders buyOrders;//订单信息
    private int response_status;//0:未处理;1:正常;2:异常
    
    public BuyOrders getBuyOrders() {
        return buyOrders;
    }

public void setBuyOrders(BuyOrders buyOrders) {
        this.buyOrders = buyOrders;
    }

public int getGood_id() {
        return good_id;
    }

public void setGood_id(int good_id) {
        this.good_id = good_id;
    }

public int getOrder_id() {
        return order_id;
    }

public void setOrder_id(int order_id) {
        this.order_id = order_id;
    }

public int getResponse_status() {
        return response_status;
    }

public void setResponse_status(int response_status) {
        this.response_status = response_status;
    }

public int getUser_id() {
        return user_id;
    }

public void setUser_id(int user_id) {
        this.user_id = user_id;
    }
}

2.2 处理请求的controller

@Controller
@RequestMapping("/buy")
public class BuyController {
    
    private static BuyQueue<BuyRequest> buyqueue =null;//线程安全的请求队列

@RequestMapping("/addOrders.do")
    @ResponseBody
    public Object addOrders(BuyRequest buyrequest){
        Map<String, Object> results = new HashMap<>();
        Jedis jedis = jedisPool.getResource();
        try {
            //下订单之前,先获取商品的剩余数量
        int residue = Integer.valueOf(jedis.get("residue"+buyrequest.getGood_id()));
        if(residue<1){//如果剩余数量不足,直接响应客户端“卖完了”
            results.put("msg", "卖完了");
            results.put("done", false);
            BaseLog.info("addOrders results="+JSON.toJSONString(results));
            return results;
        }
        //如果还有剩余商品,就准备将请求放到请求队列中
        if(buyqueue==null){//第一次初始化请求队列,队列的容量为当前的商品剩余数量
            buyqueue=new BuyQueue<BuyRequest>(residue);
            }
        if(buyqueue.remainingCapacity()>0){//当队列的可用容量大于0时,将请求放到请求队列中
            buyqueue.put(buyrequest);
        }else{//当请求队列已满,本次请求不能处理,直接响应客户端提示请求队列已满
            results.put("msg", "抢购队列已满,请稍候重试!");
            results.put("done", false);
            return results;
        }
            
        if(!DealQueueThread.excute){//如果线程类的当前执行标志为未执行,即空闲状态,通过线程池启动线程
        DealQueueThread dealQueue = new DealQueueThread(buyqueue);
        ThreadPoolUtil.pool.execute(dealQueue);
        BaseLog.info("Thread.activeCount()="+Thread.activeCount());
        }
        //将请求放到请求队列中之后,等待该请求处理完成
        while(buyrequest.getResponse_status()==0){
            //请求没有处理
        }
        BaseLog.info("wait_out..."+buyrequest.getResponse_status());
        //response_status;//0:未处理;1:正常;2:异常
        if(buyrequest.getResponse_status()==1){//请求正常处理完成,下单成功
            results.put("orders", buyrequest.getBuyOrders());
            results.put("done", true);
            results.put("msg", "下订单成功");
        }else if(buyrequest.getResponse_status()==3){//请求正常处理完成,没抢到
            results.put("msg", "没有了");
            results.put("done", false);
        }
        } catch (Exception e) {
            results.put("done", false);
            results.put("msg", "请求无效");
            BaseLog.info("addOrders results="+JSON.toJSONString(results));
            BaseLog.error("addOrders",e);
        }finally{
            jedisPool.returnResource(jedis);
        }
        return results;
    }

}

2.3 处理请求的线程类,线程类中涉及到的service代码就不必写出来了,你懂的

@Component
public class DealQueueThread implements Runnable {

private static DealQueueThread dealQueueThread;
    @Autowired
    BuyGoodService buyGoodService;
    @Autowired
    BuyOrdersService buyOrdersService;
    @Autowired
    JedisPool jedisPool;

private Jedis jedis;

private BuyQueue<BuyRequest> buyqueue;

public static boolean excute = false;//线程的默认执行标志为未执行,即空闲状态

public DealQueueThread() {

}

public DealQueueThread(BuyQueue<BuyRequest> buyqueue) {
        this.buyqueue = buyqueue;
        jedis = dealQueueThread.jedisPool.getResource();
    }

@PostConstruct
    public void init() {
        dealQueueThread = this;
        dealQueueThread.buyGoodService = this.buyGoodService;
        dealQueueThread.buyOrdersService = this.buyOrdersService;
        dealQueueThread.jedisPool = this.jedisPool;
    }

@Override
    public void run() {
        try {
            excute = true;//修改线程的默认执行标志为执行状态
            //开始处理请求队列中的请求,按照队列的FIFO的规则,先处理先放入到队列中的请求
            while (buyqueue != null && buyqueue.size() > 0) {
                BuyRequest buyreq = buyqueue.take();//取出队列中的请求
                dealWithQueue(buyreq);//处理请求
            }
        } catch (InterruptedException e) {
            BaseLog.error("DealQueueThread:", e);
        } finally {
            excute = false;
        }
    }

public synchronized void dealWithQueue(BuyRequest buyreq) {
        try {
            //为了尽量确保数据的一致性,处理之前先从redis中获取当前抢购商品的剩余数量
            int residue = Integer.valueOf(jedis.get("residue" + buyreq.getGood_id()));
            if (residue < 1) {//如果没有剩余商品,就直接返回
                buyreq.setResponse_status(3);
                return;
            }
            //如果有剩余商品,先在redis中将剩余数量减一,再开始下订单
            jedis.decr("residue" + buyreq.getGood_id());
            //将数据库中将剩余数量减一,这一步处理可以在队列处理完成之后一次性更新剩余数量
            dealQueueThread.buyGoodService.minusResidue(buyreq.getGood_id());

//处理请求,下订单
            BuyOrders bo = new BuyOrders();
            bo.setGood_id(buyreq.getGood_id());
            bo.setUser_id(buyreq.getUser_id());
            int order_id = dealQueueThread.buyOrdersService.insert(bo);
            BuyOrders orders = dealQueueThread.buyOrdersService.getById(order_id);
            buyreq.setOrder_id(order_id);//订单id
            buyreq.setBuyOrders(orders);//订单信息
            buyreq.setResponse_status(1);//处理完成状态
        } catch (Exception e) {
            buyreq.setResponse_status(2);//异常状态
            BaseLog.error("DealQueueThread dealWithQueue:", e);
        }
    }

}

经过测试在并发量超过五百的时候会出现超发现象,程序还有待完善,欢迎大家给出自己的见解,谢谢!

时间: 2024-11-08 12:58:44

通过请求队列的方式来缓解高并发抢购(初探)的相关文章

nginx、swoole高并发原理初探

原文:https://segmentfault.com/a/1190000007614502 一.阅前热身 为了更加形象的说明同步异步.阻塞非阻塞,我们以小明去买奶茶为例. 1.同步与异步 ①同步与异步的理解 同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式. 同步当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行 异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回. 异步调用,要想获得结果,一般有两种方式:1.主动轮询异步调用的结果;2.被调用

关于PHP高并发抢购系统设计

内容并发抢购系统注意事项高并发架构设计描述程序端核心代码实现订单流程mysql 端并发解决方案 注意事项(1)高并发环境下,对于服务器cup.内存.网络宽带使用率会瞬间暴涨,需要注意对同服务器上其他应用的影响.(项目解耦,高并发应用独立部署)(2)服务器高负载运行,容易出现死机,重启服务器场景,要提前考虑内存(redis)数据备份与恢复,防止用户抢购数据丢失.(3)高并发应用首先要注重稳定性,其次是性能上优化. (4) 一台服务器能够支持多少并发量nginx服务为例:worker_process

[日常] 高并发抢购方案的思考

经常在面试中被问到如何设计一个高并发环境下的抢购方案,虽然网上的资料已经很多了,但是都是很简单的说了一些用队列之类的套话,没有更详细的细节考虑.被问的实在是太多了,不得已我也仔细想想这些该怎么设计.抛开运维阶段的多层负载均衡,直接只说PHP的业务层面的逻辑. 整个流程如下:web界面点击抢购==>弹出答题弹窗==>答对判定当前队列长度==>队列未满就进入队列,显示排队中(状态),使用wbsocker实时关注用户状态 ==>答错再答基本就没戏了返回失败 ==>队列满了,返回失败

java高并发,如何解决,什么方式解决 (转)

之前我将高并发的解决方法误认为是线程或者是队列可以解决,因为高并发的时候是有很多用户在访问,导致出现系统数据不正确.丢失数据现象,所以想到的是用队列解决,其实队列解决的方式也可以处理,比如我们在竞拍商品.转发评论微博或者是秒杀商品等,同一时间访问量特别大,队列在此起到特别的作用,将所有请求放入队列,以毫秒计时单位,有序的进行,从而不会出现数据丢失系统数据不正确的情况. 今天我经过查资料,高并发的解决方法有俩种,一种是使用缓存.另一种是使用生成静态页面:还有就是从最基础的地方优化我们写代码减少不必

Java高并发,如何解决,什么方式解决

对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧. 为了更好的理解并发和同步,我们需要先明白两个重要的概念:同步和异步    1.同步和异步的区别和联系 所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到 返回的值或消息后才往下执行其它的命令. 异步,执行完函数或方法

高并发电子商务平台技术架构

原文出自:http://blog.csdn.net/yangbutao/article/details/12242441 http://stamen.iteye.com/blog/1525924 我自己的大型B2B和B2C站点原来也是用Hibernate,可是后来不得不换成mybatis, 第一是用Hibernate 因为它封装得太高了.非常多东西是隐式进行的.常常引起问题,非常难定位.毕竟凡事有利必有弊: 第二大型站点肯定不是一个数据库.这点Hibernate是非常麻烦的,用Jdbc或Myba

转---高并发Web服务的演变——节约系统内存和CPU

[问底]徐汉彬:高并发Web服务的演变——节约系统内存和CPU 发表于22小时前| 4223次阅读| 来源CSDN| 22 条评论| 作者徐汉彬 问底Web服务内存CPU并发徐汉彬 摘要:现在的Web系统面对的并发连接数在近几年呈现指数增长,高并发成为了一种常态,给Web系统带来不小的挑战.一味地通过增加机器来解决并发量的增长,成本是非常高昂的.结合技术优化方案,才是更有效的解决方法. [导读] 徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小

JAVA中怎么处理高并发的情况

一.背景综述 并发就是可以使用多个线程或进程,同时处理(就是并发)不同的操作. 高并发的时候就是有很多用户在访问,导致系统数据不正确.糗事数据的现象.对于一些大型网站,比如门户网站,在面对大量用户访问.高并发请求方面,基本的解决方案集中在这样几个环节:使用高性能的服务器.高性能的数据库.高效率的编程语言.还有高性能的Web容器.这几个解决思路在一定程度上意味着更大的投入. 使用一般的synchronized或者是lock或者是队列都是无法满足高并发的问题. 二.解决方法有三: 1.使用缓存 2.

高并发Web服务的演变:节约系统内存和CPU

一.越来越多的并发连接数 现在的Web系统面对的并发连接数在近几年呈现指数增长,高并发成为了一种常态,给Web系统带来不小的挑战.以最简单粗暴的方式解决,就是增加Web系统的机器和升级硬件配置.虽然现在的硬件越来越便宜,但是一味地通过增加机器来解决并发量的增长,成本是非常高昂的.结合技术优化方案,才是更有效的解决方法. 并发连接数为什么呈指数增长?实际上,从这几年的用户基数上看,这个数量并没有出现指数增长,因此它并非主要原因.主要原因,还是web变得更复杂,交互更丰富所导致的. 1. 页面元素增