php + redis 实现商城高并发秒杀
秒杀系统,库存只有一份,所有人会在集中的时间读和写这些数据,多个人读一个数据。例如:小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万,瞬时流量非常多,都读相同的库存。读写冲突,MySQL数据库锁非常严重。并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小。
优化方向重点:
- 查询页面使用 redis 缓存 。以12306为例,这是一个典型的读多些少的应用场景,大部分请求是商品查询,下单和支付才是最终的请求。手机只有1万部,100万个人来买,最多1万个人下单成功,其他人都是查询库存,数据库写比例只有1%,读比例占99%。
- 秒杀抢购使用 redis列表数据类型(List) 。因为pop操作是原子的,即使有很多用户同时到达,也是依次执行。将请求尽量拦截在MySQL数据库操作上游。只有购买成功的再写入数据库中,减少了数据库频繁的操作。以购买火车票为例,一趟火车其实只有2000张票,200w个人来买,最后购买成功的只有2千人的数据。
秒杀商品其实就是一个将集合中的 商品id取出和用户id绑定 的过程。只是这个过程进行的非常的快。那么我们将秒杀分为两步,如果秒杀成功,则记录下用户id和商品id 也就是所谓的秒杀订单。如果秒杀失败,我们则简单的记录一个秒杀失败的人数。来确定这次秒杀有多少有效用户参与。
示例
我们先假设我们有10000个人来抢10件商品。那么我们就在我们的商品库里面装载10件的商品id。
<?php class Seckill { const $listKey='goods_list'; const $buyKey='buy_list'; static protected $goodsNum; static protected $userId; static protected $redis; static protected $redisConnect; public function __construct() { if(empty(self::$redisConnect)) { self::redis = new Redis(); self::redisConnect = self::redis->connect('127.0.0.1', 6379); } } static public function addGoods() { if(empty(self::goodsNum)) { self::goodsNum=10; for ($i=1;$i<=self::goodsNum;$i++) { $redis->rPush(self::listKey,$i); } } } static public function kill($userId=0) { if(!empty($userId)) { self::userId=$userId; if($goodsId=self::redis->lPop(self::listKey)) { self::redis->hSet(self::buyKey,$goodsId,self::userId); self::goodsNum--; } else { self::goodsNum=0; //最后再把全部用户ID存入mysql数据库 } } } }
第一步:装载商品
require(Seckill.php); Seckill:: addGoods;
第一步:开始秒杀
require(Seckill.php); Seckill:: kill($userId);