秒杀场景一般需要需要解决的问题就是超卖和数据库压力。

一般解决数据库商品超卖最直接的方法就是将字段设置为unsigned(无符号),这样库存就不会为负了。
还有就是可以使用事务锁住操作的行,保证库存。当然也使用锁机制(悲观锁、乐观锁),但是这几种都是直接操作数据库的,并且用锁的话在其他场景(比如不参与抢购原价购买的用户),在高并发场景下可能会造成阻塞,直接影响数据库的性能。

还有种方案就是使用文件排他锁的机制,这里不进行介绍了,有兴趣的可以网上看一下。

redis的list数据结构底层实现就是双端链表,链表中的每个节点都保存了一个整数值。redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。链表支持前后插入以及前后取出,所以如果往尾部插入元素,往头部取出元素,实现先进先出,这就是一种消息队列。下来就开始介绍redis中使用list链表实现秒杀的简单消息队列。

以下用到的redis语法简单介绍

  1. LPUSH : 将值插入到列表的头部
  2. LPOP : 移除并返回列表 key 的头元素
  3. LLEN 返回列表 key 的长度
  4. LRANGE:返回列表 key 中指定区间内的元素
  5. EXPIRE:为key设置生存时间
  6. EXPIREAT:为key设置到期时间

第一步:将参加抢购的商品加入队列

  1. <?php
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', '6379');
  4. // 参加抢购的商品库存
  5. $num = 100;
  6. // 将商品写入reids链表
  7. for ($i=0; $i < $num; $i++) {
  8. $redis->lpush('goods', 1);
  9. }
  10. // 返回链表长度
  11. var_dump($redis->llen('goods'));
  12. // 查看链表中的全部元素
  13. var_dump($redis->lrange('goods', 0, -1));

我们可以使用定时任务为redis设置结束时间,或者在抢购方法判断结束时间

  1. // 设置抢购时间为60s
  2. $redis->expire('goods', '60');
  3. //设置抢购过期时间为12点 注意expireat方法只能接受时间戳参数
  4. $redis->expireat('goods', strtotime('2019-03-01 12:00:00'));

第二部:下单执行出队列操作

  1. <?php
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', '6379');
  4. //移除链表的头元素 减一次库存
  5. $res = $redis->lPop('goods_store');
  6. if($res){
  7. echo "秒杀成功";
  8. // 执行入库操作
  9. }else{
  10. echo "秒杀结束";
  11. }

使用ab工具模拟高并发下的测试

  1. ab -n 1000 -c 1000 http://192.168.73.129/pop.php

高性能系统的优化原则: 写入内存而不是写入硬盘、异步处理而不是同步处理、分布式处理。