PHP

首页 -  PHP  -  秒杀有哪些特点,秒杀解决方案

秒杀有哪些特点,秒杀解决方案

1 秒杀有哪些特点

常见的场景比如100000人在同一秒抢一个手机。比如12:00:00抢购, 12:00:01活动就结束了

1.1 突然多了很多访问,可能导致原有商城瘫痪 秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一 起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。

解决方案:将秒杀系统独立部署,独立域名。

前端如何优化:

首先要有一个展示秒杀商品的页面,在这个页面上做一个秒杀活动开始的倒计时,在准备阶段内用户会陆续打 开这个秒杀的页面, 并且可能不停的刷新页面。这里需要考虑两个问题:

    秒杀前按钮是灰的,不能发送请求, 当然后端肯定也是要有判断。 

    产品层面,用户点击查询或者购票后,按钮置灰,禁止用户重复提交请求;

    JS层面,限制用户在x秒之内只能提交一次请求;前端缓存,当用户一直刷新页面的时候, 前端可以到浏览器里面获取缓存数据。

1.2 带宽问题

假设商品页面大小1M(主要是商品图片大小),那么10000用户并发,需要的网络带宽是:10G1M×10000),这 些网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽。

解决方案:因为秒杀新增的网络带宽,必须和运营商重新购买或者租借。为了减轻网站服务器的压力,需要将 秒杀商品页面缓存在CDN,同样需要和CDN服务商临时租借新增的出口带宽。

1.3 有大部分请求不会生成订单

接入层(nginx)漏桶限流。真正进入phpmysql等应用层的流量极少,大多被过滤。

1.4 超卖问题 

秒杀商品的数量是有限的

2 秒杀难点行业主流解决方案

2.1 请求负载大的行业主流解决方法

1 队列 

2 负载均衡:更多的机器和PHP应用来接收下单请求

2.3 超卖问题的行业主流解决方法:

1 mysql悲观锁 

2 mysql乐观锁 

3 PHP+队列

4 PHP+redis分布式锁,以及分布式锁主流优化方案 

5 PHP+redis乐观锁 redis watch

6.nginx+lua+redis(【watch+事务】实现乐观锁)

2.3.1 mysql悲观锁

悲观锁,正如其名,它指的是对数据被外界(包括当前系统的其它事务,以及来自外部系统的事务处理)修改 持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁 机制(也只有数据库层提供的锁机制才能真正保证数据访问的排它性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。

SELECT * FROM employee WHERE id = 1 FOR UPDATE;    //FOR UPDAT开启悲观锁

2.3.1 mysql乐观锁 

乐观锁认为一般情况下数据不会造成冲突,所以在数据进行提交更新时才会对数据的冲突与否进行检测。如果没有冲 突那就OK;如果出现冲突了,则返回错误信息并让用户决定如何去做。 乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现

<?php $version = mysqlquery(SELECT VERSION FROM employee) 
#这里写业务逻辑 
#省略
 mysqlquery("UPDATE employee SET money = 1, VERSION=VERSION+1 WHERE VERSION=$version")

于是,php层面的话: 成功的那个,mysql会返回更新成功, php就返回前端: 恭喜你,抢购成功 失败的那个,mysql会返回更新失败, php就返回前端: 不好意思,抢购失败 总结: 乐观锁不锁数据,而是通过版本号控制,会有不同结果返回给php,把决策权交给php对比: 乐观锁:不需要锁数据,性能高于悲观锁

3 PHP+队列

序列化,不会产生多个线程之间的冲突

4 PHP+redis分布式锁,以及分布式锁主流优化方案

相当于是php线程锁,100000个抢购请求并发过来,有100000个线程,但同一时刻只会有一个线程在执行业务代 码,其它线程都在死循环中等待。redis 分布式锁与原理:

EXISTS job # job 不存在 (integer) 0
SETNX job "programmer" # job 设置成功 (integer) 1
SETNX job "code-farmer" # 尝试覆盖 job ,失败 (integer) 0 
GET job # 没有被覆盖 "prog

可见 SETNXset是有区别的,SETNX只能1次,set可以无数次的。redis分布式锁就是利用了这点来做文章的。

分布式锁示例代码:

$expire = 10;//有效期10秒 
$key = 'lock';//key 
$value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期 
$status = true; 
while($status) { 
$lock = $redis->setnx($key, $value);
 if(empty($lock)) { 
  $value = $redis->get($key); 
      if($value < time()) { 
       $redis->del($key); 
      } 
  }else{
 $status = false; //下步操作....
  } 
}

设置更对的锁,比如抢购20个商品,就可以设置20个锁, 100000个人进来, 就有20个线程是在执行业 务逻辑的,其它的就在等待。

5 PHP+redis乐观锁

header("content-type:text/html;charset=utf-8");
$redis = new redis();
$result = $redis->connect('127.0.0.1', 6379);
$mywatchkey = $redis->get("");
$rob_total = 10; //抢购数量
if($mywatchkey<$rob_total){
    $redis->watch("mywatchkey"); $redis->multi();
    //设置延迟,方便测试效果。
    sleep(5); 
    //插入抢购数据
    $redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time()); 
    $redis->set("mywatchkey",$mywatchkey+1); $rob_result = $redis->exec(); 
    if($rob_result){ 
        $mywatchlist = $redis->hGetAll("mywatchlist"); 
        echo "抢购成功!"; 
        echo "剩余数量:".($rob_total-$mywatchkey-1).""; 
        echo "用户列表:"; var_dump($mywatchlist); 
    }else{ 
        echo "手气不好,再抢购!";exit; 
    }
}

提炼上面的核心代码:

$redis->watch("mywatchkey"); 
//声明一个乐观锁 $redis->multi(); 
//redis事务开始 
$redis->set("mywatchkey",$mywatchkey+1); 
//乐观锁的版本号+1 
$rob_result = $redis->exec();
//redis事务提交

优点如下:

1. 首先选用内存数据库来抢购速度极快。 

2. 速度快并发自然没不是问题。 

3. 使用悲观锁,会迅速增加系统资源。 

4. 比队列强的多,队列会使你的内存数据库资源瞬间爆棚。 

5. 使用乐观锁,达到综合需求。

6 nginx+lua+redis乐观锁代码:

(0)
分享:

本文由:xiaoshu168.com 作者:xiaoshu发表,转载请注明来源!

标签:

相关阅读