php秒杀系统架构设计实例

php秒杀系统架构设计实例_第1张图片

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

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

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

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

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

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

抢购静态页面代码

 
  1. 秒杀!
  2. 01天01时01分01秒
  • 使用二级域名,用cdn缓存html页面,css,js,图片等

    nocdn.js生成脚本

     
    1.  
    2. $redis = new \Redis();
    3. if ($redis->connect('127.0.0.1','6379') == false) {
    4. die($redis->getLastError());
    5. }
    6.  
    7.  
    8.  
    9. //设置token
    10. $token=md5(rand(100,10000));
    11. $redis->set("token",$token);
    12.  
    13. $hl=fopen("public/js/nocdn.js","w");
    14.  
    15.  
    16. $js=<<
    17. var button = '
      '+
    18. '秒杀已经开始'+
    19. '抢购按钮
    ';
  • $(".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种解决方案】

    抢购代码

     
    1. $redis = new \Redis();
    2.  
    3. if ($redis->connect('127.0.0.1','6379') == false) {
    4. die($redis->getLastError());
    5. }
    6.  
    7. //判断用户是否已经抢购
    8. if($redis->hexists("mywatchlist","user_id_")==1){
    9. exit("已经抢购");
    10. }
    11. //带参数的url
    12. if($redis->get("token")!=$_GET['token']){
    13. exit("参数错误");
    14. }
    15.  
    16. $redis->watch("mywatchkey");//命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
    17. $mywatchkey=$redis->get("mywatchkey");
    18. $limit=10;
    19. if($mywatchkey>=$limit){
    20. exit("活动结束");
    21. }
    22. $redis->multi();//事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。
    23. $redis->set("mywatchkey",$mywatchkey+1);
    24. //sleep(5);//测试watch
    25. $rob_result = $redis->exec();//按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil,在php中成功返回array(0->1)失败返回空
    26.  
    27. if($rob_result){
    28. //保证库存,原子判断,确保当两个客户同时访问 Redis 服务器得到的是更新后的值
    29. if($redis->incr("stock")>$limit){
    30. echo "抢购失败,请重试";
    31. }
    32. echo "抢购成功";
    33. //抢购成功
    34. //$redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());
    35. $redis->LPUSH("success",rand(1,20));
    36. }else{
    37. echo "抢购失败,请重试";
    38. }
    39. exit;

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

     
    1. 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

    你可能感兴趣的:(PHP)