最新消息:欢迎访问小松个人博客,小松最近开始学习java,感觉spring boot比较简单,下载文档开始学习一下

php秒杀系统架构设计实例

PHP 1052浏览 0评论

秒杀架构

① 对现有网站业务的冲击,如果秒杀程序部署到现有的服务器上,可能导致整个网站瘫痪

解决方法
把秒杀活动部署到单独的机子上,并且用单独的域名

② 高并发,用户在秒杀活动开始之前会不停的刷新页面,如果用php脚本连接数据库的方式,会对服务器的压力较大
解决方法
使用静态页面,并且使用cdn缓存,解决带宽压力大等问题

③ 避免用户直接通过下单连接下单
解决方法
带个随机参数,在秒杀开始之前才能得到

④ 控制抢购按钮,页面设计为静态页面并且使用了cdn缓存,如何点亮抢购按钮
解决方法
js文件后面带个随机版本号,这样不会被cdn缓存,直接到达服务器,来控制按钮点亮,这个js文件要小,不然会对服务器带来带宽的压力

⑤ 抢购程序设计,如果直接使用数据库事务,数据库压力太大
解决方法
使用redis或memcache等内存缓存,速度快还能解决超卖等问题

抢购静态页面代码

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>秒杀!</title>
    <link rel="stylesheet" href="/public/css/level1_index.css"/>
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" ></script>
    <script type="text/javascript" src="/public/js/level1_config.js"></script>
    <script type="text/javascript" src="/public/js/level1_index.js"></script>
</head>
<body>
    <div class="jingshan">
        <span id="timebox">01天01时01分01秒</span>
    </div>
</body>
<script type="text/javascript" src="/public/js/level1_countdown.js" ></script>
<script type="text/javascript">
//动态加载js文件
document.write("<s" + "cript type='text/javascript' src='/public/js/nocdn.js?ver=" + Math.random() + "'></s" + "cript>");
</script>
</html>

使用二级域名,用cdn缓存html页面,css,js,图片等

nocdn.js生成脚本

<?php

$redis = new \Redis();
if ($redis->connect('127.0.0.1','6379') == false) {
    die($redis->getLastError());
}



//设置token
$token=md5(rand(100,10000));
$redis->set("token",$token);

$hl=fopen("public/js/nocdn.js","w");


$js=<<<EOF
var button = '<div class="jingshan">'+
    '<span id="timebox" class="start">秒杀已经开始</span>'+
    '<span id="qianggou"><a href="javascript:;" ><img src="/public/images/level1_button.jpg" alt="抢购按钮"/></a></span></div>';
$(".jingshan").html(button);

$(function(){
    var flag=1;
    $("#qianggou").click(function(){
        if(flag!=1){
          return ;
        }
        flag=2;
        var token='{$token}';
        var url="http://192.168.128.128/redis.php"
        $.ajax({
           type: "POST",
           url: url,
           data: "token="+token,
           success: function(msg){
             alert( "Data Saved: " + msg );
           }
        });
    })

})
EOF;
fwrite($hl, $js);
fclose($hl);

主要生成nocdn.js文件的内容,用linux crontab设置定时脚本
内容主要是显示秒杀的按钮,生成随机的参数,生成ajax的提交脚本,如果要跨域使用jsonp【推荐阅读: js跨域4种解决方案

抢购代码

$redis = new \Redis();

if ($redis->connect('127.0.0.1','6379') == false) {
    die($redis->getLastError());
}

//判断用户是否已经抢购
if($redis->hexists("mywatchlist","user_id_")==1){
     exit("已经抢购");
}
//带参数的url
if($redis->get("token")!=$_GET['token']){
    exit("参数错误");
}

$redis->watch("mywatchkey");//命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
$mywatchkey=$redis->get("mywatchkey");
$limit=10;
if($mywatchkey>=$limit){
    exit("活动结束");
}
$redis->multi();//事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。
$redis->set("mywatchkey",$mywatchkey+1); 
//sleep(5);//测试watch
$rob_result = $redis->exec();//按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil,在php中成功返回array(0->1)失败返回空

if($rob_result){     
    //保证库存,原子判断,确保当两个客户同时访问 Redis 服务器得到的是更新后的值
    if($redis->incr("stock")>$limit){
        echo "抢购失败,请重试";
    }
    echo "抢购成功";
    //抢购成功
    //$redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());
    $redis->LPUSH("success",rand(1,20));
}else{
    echo "抢购失败,请重试";
}
exit;

主要是redis的watch,如果执行事务发现mywatchkey变动过就执行事务失败,redis事务失败不会回滚,代码测试过
用ab测试没有超卖的问题

ab -n 1000 -c 1000 http://127.0.0.1/redis.php

上面的代码有个问题,就是会出现少卖的问题
下面来解释一下原因,

时间 客户端 A 客户端 B
T1 WATCH name
T2 MULTI
T3 SET name peter
T4 SET name john
T5 EXEC

在时间 T4 ,客户端 B 修改了 name 键的值, 当客户端 A 在 T5 执行 EXEC 时,Redis 会发现 name 这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。
所以结果是name等于john

上面的代码sleep(5)可以测试,就会发现出现少买的问题,我测试去除sleep之后不会出现这个问题

上面就是我的秒杀设计

Demo:  http://pan.baidu.com/s/1bWa1cE

QQ交流群:136351212(满) 455721967

如无特别说明,本站文章皆为原创,若要转载,务必请注明以下原文信息:
转载保留版权:小松博客» php秒杀系统架构设计实例
本文链接地址:https://www.phpsong.com/2906.html

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
木有头像就木有JJ!点这里按步骤申请Gravatar头像吧!

网友最新评论 (4)

加载中,请稍候...
  1. demo 的提取密码是多少啊
    鲁初雪2017-05-12 16:38:46回复
    • 页面上不是有代码吗
      小松2017-05-15 08:32:09回复
  2. $hl=fopen("public/js/nocdn.js","w");这个哪里来?
    lulu2017-07-16 11:47:42回复
    • 这个文件是代码生成的
      小松2017-07-17 09:08:09回复