zoukankan      html  css  js  c++  java
  • PHP高并发商城秒杀

     

    1.什么是秒杀

      秒杀活动是一些购物平台推出的集中人气的活动,一般商品数量很少,价格很便宜,限定开始购买的时间,会在以秒为单位的时间内被购买一空。比如原价千元甚至万元的商品以一元的价格出售,但数量只有一件,在某天的某个时间开始出售,这就造成很多人去抢这一件商品。当然想抢到是需要很多因素的,比如你的电脑配置、网速,还有你的运气。

    2.秒杀会带来的问题

      (1)、高并发

        比较火热的秒杀在线人数都是10w起的,如此之高的在线人数对于网站架构从前到后都是一种考验。

      (2)、超卖

        任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难题。

    3.解决的方式

      前台方面:

        A:扩容

          加机器,这是最简单的方法,通过增加前端池的整体承载量来抗峰值。

        B:静态化

          将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。

        C:限流

          一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。或者活动入口的时候增加游戏或者问题环节进行消峰操作。

      后台方面:

        A: 锁机制

          乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。

          悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。

          文件锁,通过锁定文件来判断,确保其他线程无法提交数据

        B: redis队列

          引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。

          优点:解决超卖问题,略微提升性能。

          缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。

     

    4.演示

    原生写法,会出现多卖现象

     

    <?php
    header("content-type:text/html;charset=utf-8");
    
    $dsn='mysql:host=localhost;dbname=test';
    try {
        $conn= new PDO($dsn, 'root', 'root');
        $conn->exec("set names utf8");
    
    } catch (PDOException $e) {
        exit('数据库连接失败,错误信息:'. $e->getMessage());
    }
    
    $price    = 10;
    $user_id  = 1;
    $goods_id = 1;
    $sku_id   = 11;
    $number   = 1;
    
    //生成唯一订单
    function build_order_no(){
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
    
    $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
    $rs  = $conn->query($sql);
    $row = $rs->fetch(); //获取一行数据
    
    if ($row['number']>0) {//库存是否大于0
        $order_sn=build_order_no();
        $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
        values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
        $conn->query($sql);
    
        //库存减少
        $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
        $f = $conn->query($sql);
        if ($f) {
            // 库存减少成功
        } else {
            // 库存减少失败
        }
    } else {
        // 库存不够
    }
    echo "success";
    

     

     通过文件锁来解决

     

    <?php
    header("content-type:text/html;charset=utf-8");
    
    $dsn='mysql:host=localhost;dbname=test';
    try {
        $conn= new PDO($dsn, 'root', 'root');
        $conn->exec("set names utf8");
    
    } catch (PDOException $e) {
        exit('数据库连接失败,错误信息:'. $e->getMessage());
    }
    
    $price    = 10;
    $user_id  = 1;
    $goods_id = 1;
    $sku_id   = 11;
    $number   = 1;
    
    //生成唯一订单
    function build_order_no(){
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
    $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";//解锁 此时ih_store数据中goods_id='$goods_id' and sku_id='$sku_id' 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行
    $rs  = $conn->query($sql);
    $row = $rs->fetch(); //获取一行数据
    
    $fp = fopen("lock.txt", "w+"); // 通过文件锁住操作执行完再执行下一个
    if (!flock($fp, LOCK_EX | LOCK_NB)) {
        echo "系统繁忙,请稍后再试";
        return;
    }
    
    if ($row['number']>0) {//库存是否大于0
        $order_sn=build_order_no();
        $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
        values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
        $conn->query($sql);
    
        //库存减少
        $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
        $f = $conn->query($sql);
        if ($f) {
            // insertLog('库存减少成功');
            flock($fp, LOCK_UN);//释放锁
        } else {
            // insertLog('库存减少失败');
        }
    } else {
        // insertLog('库存不够');
    }
    fclose($fp);
    echo "success";
    

     

      通过redis队列来实现

      客户端

    <?php
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis_name = 'miaosha';
    $sales='goods_store';
    //
    $uid = mt_rand(1000,9999);
    $store = 10;
    usleep(100000);// usleep()函数的功能是把调用该函数的线程挂起一段时间 [1]  , 单位是微秒(microseconds:即百万分之一秒)
    if ($store > $redis->get($sales)) {
        $redis->incr($sales);
        $redis->lpush($redis_name, $uid);//
        // echo $uid."秒杀成功|";
    } else {
        // echo "秒杀已结束";
    }
    
    $redis->close();
    

      服务端

    <?php
    header("content-type:text/html;charset=utf-8");
    
    $dsn='mysql:host=localhost;dbname=test';
    try {
        $conn= new PDO($dsn, 'root', 'root');
        $conn->exec("set names utf8");
    
    } catch (PDOException $e) {
        exit('数据库连接失败,错误信息:'. $e->getMessage());
    }
    
    $price    = 10;
    $user_id  = 1;
    $goods_id = 1;
    $sku_id   = 11;
    $number   = 1;
    
    //生成唯一订单
    function build_order_no(){
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
    
    $redis  = new Redis();
    $result = $redis->connect('127.0.0.1',6379);
    //通过死循环从队列中一直取值
    while (true) {
        //模拟下单操作
        //下单前判断redis队列库存量
        $userid  = $redis->rpop('miaosha');
    
        if(!$userid){
            // insertLog('error:no store redis');
            continue;
        }
        //生成订单
        $order_sn = build_order_no();
        $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
        values('$order_sn','$userid','$goods_id','$sku_id','$price')";
        $conn->query($sql);
    
        //库存减少
        $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
        $f = $conn->query($sql);
        if ($f) {
            // insertLog('库存减少成功');
        }else{
            // insertLog('库存减少失败');
        }
    }
    $redis->close();
    

      

     

     压力测试工具jmeter安装方法 :    https://www.cnblogs.com/houss/p/10619453.html

  • 相关阅读:
    央视好节目整理
    IT项目各阶段管理
    使用mint-ui Loadmore组件时出现报错
    简单了解map,filter,some,every,forEach,for in,for of,find,用法
    h5简单学习总结
    video标签详解(转载)
    浏览器对象
    获取后三天的时间
    Map和Set以及iterable类型集合的循环遍历
    placeholder的样式设置
  • 原文地址:https://www.cnblogs.com/houss/p/10621033.html
Copyright © 2011-2022 走看看