1 秒杀有哪些特点
常见的场景比如100000人在同一秒抢一个手机。比如12:00:00抢购, 12:00:01活动就结束了
1.1 突然多了很多访问,可能导致原有商城瘫痪 秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一 起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。
解决方案:将秒杀系统独立部署,独立域名。
前端如何优化:
首先要有一个展示秒杀商品的页面,在这个页面上做一个秒杀活动开始的倒计时,在准备阶段内用户会陆续打 开这个秒杀的页面, 并且可能不停的刷新页面。这里需要考虑两个问题:
秒杀前按钮是灰的,不能发送请求, 当然后端肯定也是要有判断。
产品层面,用户点击“查询”或者购票后,按钮置灰,禁止用户重复提交请求;
JS层面,限制用户在x秒之内只能提交一次请求;前端缓存,当用户一直刷新页面的时候, 前端可以到浏览器里面获取缓存数据。
1.2 带宽问题
假设商品页面大小1M(主要是商品图片大小),那么10000用户并发,需要的网络带宽是:10G(1M×10000),这 些网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽。
解决方案:因为秒杀新增的网络带宽,必须和运营商重新购买或者租借。为了减轻网站服务器的压力,需要将 秒杀商品页面缓存在CDN,同样需要和服务商临时租借新增的出口带宽。
1.3 有大部分请求不会生成订单
接入层(Nginx)漏桶限流。真正进入PHP和MysqL等应用层的流量极少,大多被过滤。
1.4 超卖问题
秒杀商品的数量是有限的
2 秒杀难点行业主流解决方案
1 队列
2.3 超卖问题的行业主流解决方法:
1 MysqL悲观锁
2 MysqL乐观锁
3 PHP+队列
6.Nginx+lua+redis(【watch+事务】实现乐观锁)
2.3.1 MysqL悲观锁
悲观锁,正如其名,它指的是对数据被外界(包括当前系统的其它事务,以及来自外部系统的事务处理)修改 持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁 机制(也只有数据库层提供的锁机制才能真正保证数据访问的排它性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。
SELECT * FROM employee WHERE id = 1 FOR UPDATE; //FOR UPDAT开启悲观锁
乐观锁
乐观锁认为一般情况下数据不会造成冲突,所以在数据进行提交更新时才会对数据的冲突与否进行检测。如果没有冲 突那就OK;如果出现冲突了,则返回错误信息并让用户决定如何去做。 乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现。
<?PHP $version = MysqLquery(SELECT VERSION FROM employee) #这里写业务逻辑 #省略 MysqLquery("UPDATE employee SET money = 1, VERSION=VERSION+1 WHERE VERSION=$version")
于是,PHP层面的话: 成功的那个,MysqL会返回更新成功, PHP就返回前端: 恭喜你,抢购成功 失败的那个,会返回更新失败,就返回前端: 不好意思,抢购失败 总结: 乐观锁不锁数据,而是通过版本号控制,会有不同结果返回给,把决策权交给。 对比: 乐观锁:不需要锁数据,性能高于悲观锁
3 PHP+队列
序列化,不会产生多个线程之间的冲突
相当于是线程锁,100000个抢购请求并发过来,有个线程,但同一时刻只会有一个线程在执行业务代 码,其它线程都在死循环中等待。redis 分布式锁与原理:
EXISTS job # job 不存在 (integer) 0 SETNX job "programmer" # job 设置成功 (integer) 1 SETNX job "code-farmer" # 尝试覆盖 job ,失败 (integer) 0 GET job # 没有被覆盖 "prog
可见 SETNX和set是有区别的,SETNX只能1次,可以无数次的。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个商品,就可以设置个锁, 100000个人进来, 就有个线程是在执行业 务逻辑的,其它的就在等待。
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. 使用乐观锁,达到综合需求。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。