基于PHP+redis的秒杀实现

抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少("超卖"问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。
重点在于第二个问题

常规写法:

查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数


优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

优化方案2:使用mysql的事务,锁住操作的行

优化方案3:使用非阻塞的文件排他锁

优化方案4:使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)

以上四种方案如果想详细了解请查看原文链接:http://blog.csdn.net/nuli888/article/details/51865401


这里我主要说的是redis队列实现思路

采用Redis + List类型实现秒杀 

由于测试限制,我在这里写了一个循环来测试,效果不是很好,但是也可以模仿测试

规定一个的队列长度,比如长度为10,超过这个长度不参与活动没直接返回活动结束

写一个死循环来查询队列进行下订单操作。

好了,不多说了,直接上代码,代码里面有注释

user.php  执行加入队列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*########################################################
  ## 极客之家 高端PHP - 秒杀实现思路            ##
  ## 流量削峰案例 采用Redis + List类型实现秒杀        ##
  ## LPUSH/LPUSHX : 将值插入到(/存在的)列表头部  ##
  ## RPUSH/RPUSHX : 将值插入到(/存在的)列表尾部  ##
  ## LPOP : 移出并获取列表的第一个元素            ##
  ## RPOP : 移出并获取列表的最后一个元素           ##
  ## LLEN :获取列表长度                 ##
  ## 秒杀业务程序 ->  Redis  ->  入库程序、数据库   ##
  ## 秒杀程序吧请求写入Redis。(Uid,time_stamp)   ##
  ## 检查Redis已存放数据长度,超出上限直接丢弃       ##
  ## 死循环处理存入Redis的数据并入库            ##
  ## 对于reads服务以及扩展自己安装,这里就不再讲解了    ##
  #########################################################*/
 
// 1. 首先呢,我要加载一下redis组件
$redis  new  Redis();
//连接redis
$redis ->connect( '127.0.0.1' ,6379);
$redis_name  "miaosha" ;
 
for  ( $i =0;  $i  < 100;  $i ++) { 
     $uid  = rand(10000,999999);
     // 2. 接受用户uid
     // $uid = $_GET['uid'];
     // 3. 获取一下redis里面已有的数据
     $num  = 10;
     // 4. 如果当天人数少于10的话,则加入这个对列
     if ( $redis ->lLen( $redis_name ) < 10)
     {
         $redis ->rPush( $redis_name , $uid . '%' .microtime());
         echo  $uid . "秒杀成功" ;
         echo  "
"
;
     }
     else
     {
         // 5. 如果当天人数已经达到10个人,则返回秒杀已完成
         echo  "秒杀已结束" ;
    
}
$redis ->close();

savetodb.php  循环取出队列中数据进行操作数据库进行下订单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*############################################################################
  ## 极客之家 高端PHP - 秒杀实现思路                        ##
  ## 流量削峰案例 采用Redis + List类型实现秒杀                    ##
  ## 写一个死循环来将用户加入队列的数据去除进行入库操作             ##
  ## 让程序不断检测队列里面是否有值加入,一旦加入队列就会取出开始下订单操作  ##
  ## 数据库插入的失败时候执行数据回滚机制                    ##
  #############################################################################*/
header( 'content-type:text/html;charset=utf8' );
// 1. 连接数据库
$link =mysqli_connect( '127.0.0.1' , 'root' , 'root' , 'test' );
//设置字符集
$db  = mysqli_query( $link , 'set names utf8' );
 
//首先呢,我要加载一下redis组件
$redis  new  Redis();
 
// 2. 连接redis
$redis ->connect( '127.0.0.1' ,6379);
$redis_name  "miaosha" ;
 
//死循环
while  ( 1 ) {
// 3. 从对列最左侧取出一个值,
$user  $redis ->lPop( $redis_name );
// 4. 然后判断这个值知否存在
if (! $user  ||  $user  ==  'null' ){
     sleep(2);
     echo  "下单完成" ;
     // continue;
     break ;
}
//切割出时间,uid
$user_arr  explode ( '%' $user );
 
$uid  $user_arr [0];
$time_stamp  $user_arr [1];
 
// 5. 保存数据到数据库中
$sql = "insert into readis_queue(id,uid,time_stamp) values(null,'$uid','$time_stamp')" ;
$res =mysqli_query( $link , $sql );
//数据库插入的失败时候的回滚机制
  if (! $res )
  {
    $redis ->rPush( $redis_name , $user );
  }
  echo  "用户" . $uid . "下单成功" ;
  echo  "
"
;
  sleep(2);
 
}
// 6. 释放一下reads
$redis ->close();

上面只是简单模拟高并发下的抢购思路,真实场景要比这复杂很多,比如双11活动远远比这更复杂多啦,很多注意的地方如抢购活动页面做成静态的,通过ajax调用接口

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


原文地址:http://www.qinlinhui.cn/index.php/Home/Index/read?id=181(我的朋友)

你可能感兴趣的:(基于PHP+redis的秒杀实现)