redis+PHP实现高并发下秒杀数据入库的问题

在高并发下实现抢购秒杀功能中,我有一个疑问,就是数据入库的问题,什么时候入库。
设想思路:
1.判断他抢购成功了,立马把生成的订单数据写入mysql订单表,同时库存表字段减少1;
2.判断抢购成功后,把用户的user_id存入到redis的list列表里(比如:order,以user_id为值的列表)。然后再用crontab定时去一个一个插入到mysql订单表里,同时库存表字段减少1。

设想结果:
第一种思路,很好理解。简单的代码实现如下:

$num=10; //假设库存量
 for($i=0;$i<$num;$i++)
   \Redis::lpush('goods_store',1);//往goods_store列表中,
   未抢购之前这里应该是默认push 10个1进去,当然里面的1没有实际意义

在抢购之前,上面的代码可以先执行,把商品入队。

抢购时间到了:(大量用户请求下面代码执行操作)

/* 模拟抢购操作,抢购前判断redis队列库存量 */
 $count=\Redis::lpop('goods_store');//lpop是原子性的,可以保证不会出现超卖现象。
 if(!$count)
    return '已经抢光了';
    
 /* 下面处理抢购成功后与mysql数据库的交互 */
  1. //根据规则生成订单号(order_num),然后把相关的字段数据插入到订单列表里
    $data['order_num'] = *****************;
    $data['user_id']   = ***;
    $data['goods_id']  = **;
    .......
    $res = DB::table('order')->insert($data);
  
 2. //减少num库存字段
  if($res)
    DB::table('goods')->decrement('num', 1);,

上面的代码中,当用户抢购成功后,立马把相关的订单数据插入mysql订单表中,同时库存减少。现在我的疑问来了,要是用这种思路的话,大并发下,要是多个用户都同时进入到插入数据到订单列表和减少商品库存量这个过程中,是不是也会造成并发操作导致服务器压力瞬间过大导致数据入库不正确呢,比如说存库少减了一个(还是说根据mysql增删改查的原子性,并不会造成这样的错误)?。

针对上面抢购成功后,立马把相关的订单数据插入mysql订单表中,同时库存减少,造成的数据库服务器压力过大的问题;

于是有了第二种思路,把用户的user_id存入到redis列表里(比如:order,以user_id为值的列表),在通过定时器crontab定时去从列表里一个一个取出user_id,生成相关的数据插入到mysql订单表里,同时库存表字段减少1。

代码实现跟上面差不多,

 /* 模拟抢购操作,抢购前判断redis队列库存量 */
 $count=\Redis::lpop('goods_store');//lpop是原子性的,可以保证不会出现超卖现象。
 if(!$count)
    return '已经抢光了';
    
   /* 下面处理抢购成功后把user_id存入列表 */ 
    \Redis::lpush('order',user_id);


通过定时器crontab定时去下面的代码

   $user_id = \Redis::rpop('order',user_id);
  
  1. //根据规则生成订单号(order_num),然后把相关的字段数据插入到订单列表里
    $data['order_num'] = *****************;
    $data['user_id']   = $user_id;
    $data['goods_id']  = **;
    .......
    $res = DB::table('order')->insert($data);
  
 2. //减少num库存字段
  if($res)
    DB::table('goods')->decrement('num', 1);,

第二种思路,我的疑问是,要是抢购成功后,先把user_id存入队列,再用定时器每隔一段时间去队列里取数据,然后生成相关的数据插入的mysql订单表里,同时减少库存。这样是可以减轻数据库服务器的压力了。但是我的抢购流程是这么设计的,用户抢购成功后,弹出//去支付按钮//进入订单列表页面(订单列表页数据是从mysql读取出来的),由于使用定时器去执行生成订单数据然后再插入到mysql数据库,这个过程肯定会有延迟,要是用户此时通过//去支付按钮//进入订单列表页面,发现订单列表还没有生成订单数据,那不是很悲催吗?
【这样设计抢购流程是否合理,是不是不用进入到订单列表,直接点击去支付,支付成功后,才生成订单数据插入到数据库呢】

以上就是我对两种思路存在的疑惑,希望可以得到专业人士的讲解,或者一起探讨。

阅读 4.8k
2 个回答
  1. 把这个抢购的数量等信息也存入 redis json, 同时直接减去库存量,以后操作只操作 redis json. 想看全部剩余量的时候读取数据库的 + redis 剩余量
  2. 所谓秒杀,如果里面有一百万个商品,那还叫作秒杀吗.所有秒杀数据不是很多的过了所有验证之后,直接入库.
  3. 如果真有所谓这么多的.可以考虑在你原来的基础上做点小改动.

你把你在 crontab 生成订单的逻辑写成一个 services,定期通过 crontab 触发,如果用户点了付款之后,首先先去 reids 查询有没有这个 key,有的话,通过 services 生成订单,并且移除掉这个 key,如果没有,有可能就是通过 crontab 已经入库.不然就是非法订单.

你的问题有个大前提,那就是大并发,但是,大并发是个非常笼统的概念,多少并发数量才算大并发呢,实际上在这种极限情况中往往要考虑很多问题,服务器配置是否能承载大并发,你内存不行是垃圾颗粒,怎么想都承载不了大并发。
对于你的问题,有以下几个看法:
第一,除了少数几个大电商平台,基本没有什么平台有大并发的抢购情况(包括百度,百度前段时间的百度云11块钱买6个月的主机的时候都没有发生抢购一空的情况),你研究这个几乎是屠龙术,毫无用武之地,就算你去了这几个大公司,甚至担任了架构师,也不会使用redis+php去解决这个问题,php本来就不是解决并发问题的语言,而redis天生的不稳定性也不会随意使用在一个亿级以上的产品里。
第二,高并发现在都会先考虑go,就像写网站会先考虑php而不是asp一样,我说的是先考虑。
第三,你的思路本身没有大问题,简单的说就是硬盘速度不够,入库太慢,只好先存到内存里然后再从内存里搬运到硬盘中,中间如果内存完蛋(redis崩溃)一切玩完了。确实是这样的,而且无解。除非你自己去改redis代码或者mysql二次开发,但是人家kafka本身就能解决很多问题,你自己造轮子得不偿失,而且二开也不一定能解决问题啊。
第四,你这个问题是很多刚开始敲代码的新手,包括我自己在大学刚开始学的时候。尤其是培训班里学了一大半准备找工作的准就业的phper最喜欢问的问题,就是依赖自己的想法去构建使用环境,但是眼光太窄只局限了一两个环节,大并发从网络到最后的入库是一个整体情况,单单是nginx这一块就有很多人在思考这个问题,封装了很多层几乎没有什么效率的php也去解决这个问题,那只能说明你这根本不是并发,php去应付个几万个连接数上千个并发量还不至于崩溃,上千个并发量你可能没什么概念,可以查询下ddos是怎么攻击的,你就知道你通常认为的并发数根本不值一提

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题