基于PHP+redis的秒杀实现

来源:互联网 发布:java 电子商城系统 编辑:程序博客网 时间:2024/05/17 22:19

抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:
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
/*########################################################
 ## 极客之家 高端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 "<br/>";
    }
    else
    {
        // 5. 如果当天人数已经达到10个人,则返回秒杀已完成
        echo "秒杀已结束";
    
}
$redis->close();

sp170925_163855.png

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
/*############################################################################
 ## 极客之家 高端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 "<br/>";
 sleep(2);
 
}
// 6. 释放一下reads
$redis->close();

sp170925_163906.png

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

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


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

原创粉丝点击