抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少("超卖"问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。
重点在于第二个问题
常规写法:
查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数
优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
优化方案2:使用mysql的事务,锁住操作的行
优化方案3:使用非阻塞的文件排他锁
优化方案4:使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)
以上四种方案如果想详细了解请查看原文链接:http://blog.csdn.net/nuli888/article/details/51865401
这里我主要说的是redis队列实现思路
采用Redis + List类型实现秒杀
由于测试限制,我在这里写了一个循环来测试,效果不是很好,但是也可以模仿测试
规定一个的队列长度,比如长度为10,超过这个长度不参与活动没直接返回活动结束
写一个死循环来查询队列进行下订单操作。
好了,不多说了,直接上代码,代码里面有注释
user.php 执行加入队列操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
/*########################################################
## 极客之家 高端PHP - 秒杀实现思路 ##
## 流量削峰案例 采用Redis + List类型实现秒杀 ##
## LPUSH/LPUSHX : 将值插入到(/存在的)列表头部 ##
## RPUSH/RPUSHX : 将值插入到(/存在的)列表尾部 ##
## LPOP : 移出并获取列表的第一个元素 ##
## RPOP : 移出并获取列表的最后一个元素 ##
## LLEN :获取列表长度 ##
## 秒杀业务程序 -> Redis -> 入库程序、数据库 ##
## 秒杀程序吧请求写入Redis。(Uid,time_stamp) ##
## 检查Redis已存放数据长度,超出上限直接丢弃 ##
## 死循环处理存入Redis的数据并入库 ##
## 对于reads服务以及扩展自己安装,这里就不再讲解了 ##
#########################################################*/
// 1. 首先呢,我要加载一下redis组件
$redis
=
new
Redis();
//连接redis
$redis
->connect(
'127.0.0.1'
,6379);
$redis_name
=
"miaosha"
;
for
(
$i
=0;
$i
< 100;
$i
++) {
$uid
= rand(10000,999999);
// 2. 接受用户uid
// $uid = $_GET['uid'];
// 3. 获取一下redis里面已有的数据
$num
= 10;
// 4. 如果当天人数少于10的话,则加入这个对列
if
(
$redis
->lLen(
$redis_name
) < 10)
{
$redis
->rPush(
$redis_name
,
$uid
.
'%'
.microtime());
echo
$uid
.
"秒杀成功"
;
echo
"
;
}
else
{
// 5. 如果当天人数已经达到10个人,则返回秒杀已完成
echo
"秒杀已结束"
;
}
}
$redis
->close();
|
savetodb.php 循环取出队列中数据进行操作数据库进行下订单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
/*############################################################################
## 极客之家 高端PHP - 秒杀实现思路 ##
## 流量削峰案例 采用Redis + List类型实现秒杀 ##
## 写一个死循环来将用户加入队列的数据去除进行入库操作 ##
## 让程序不断检测队列里面是否有值加入,一旦加入队列就会取出开始下订单操作 ##
## 数据库插入的失败时候执行数据回滚机制 ##
#############################################################################*/
header(
'content-type:text/html;charset=utf8'
);
// 1. 连接数据库
$link
=mysqli_connect(
'127.0.0.1'
,
'root'
,
'root'
,
'test'
);
//设置字符集
$db
= mysqli_query(
$link
,
'set names utf8'
);
//首先呢,我要加载一下redis组件
$redis
=
new
Redis();
// 2. 连接redis
$redis
->connect(
'127.0.0.1'
,6379);
$redis_name
=
"miaosha"
;
//死循环
while
( 1 ) {
// 3. 从对列最左侧取出一个值,
$user
=
$redis
->lPop(
$redis_name
);
// 4. 然后判断这个值知否存在
if
(!
$user
||
$user
==
'null'
){
sleep(2);
echo
"下单完成"
;
// continue;
break
;
}
//切割出时间,uid
$user_arr
=
explode
(
'%'
,
$user
);
$uid
=
$user_arr
[0];
$time_stamp
=
$user_arr
[1];
// 5. 保存数据到数据库中
$sql
=
"insert into readis_queue(id,uid,time_stamp) values(null,'$uid','$time_stamp')"
;
$res
=mysqli_query(
$link
,
$sql
);
//数据库插入的失败时候的回滚机制
if
(!
$res
)
{
$redis
->rPush(
$redis_name
,
$user
);
}
echo
"用户"
.
$uid
.
"下单成功"
;
echo
"
;
sleep(2);
}
// 6. 释放一下reads
$redis
->close();
|
上面只是简单模拟高并发下的抢购思路,真实场景要比这复杂很多,比如双11活动远远比这更复杂多啦,很多注意的地方如抢购活动页面做成静态的,通过ajax调用接口
高性能系统的优化原则: 写入内存而不是写入硬盘、异步处理而不是同步处理、分布式处理。
原文地址:http://www.qinlinhui.cn/index.php/Home/Index/read?id=181(我的朋友)