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);