业务:
抽奖活动 限制每天抽奖次数 每个用户每天只能抽奖3次
同时若有中奖的话更新奖品中奖数和奖品库存数
方案一:
使用mysql(ssd硬盘) 如下所示
#更新用户抽奖次数
select * from draw_count;
+----+---------+------------+-------+
| id | user_id | draw_date | count |
+----+---------+------------+-------+
| 1 | 1 | 2016-03-12 | 3 |
+----+---------+------------+-------+
#显式指定count<3 如果更新count失败 表示已抽完3次 使用这种方式也可以应对同一用户并发超抽的情况。
update draw_count set count=count+1 where user_id = 1 and draw_date=current_date and count<3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
#中奖后更新奖品中奖数和库存
select * from award_count;
+----+----------+-----------+-------+
| id | award_id | win_count | stock |
+----+----------+-----------+-------+
| 1 | 1 | 0 | 100 |
+----+----------+-----------+-------+
#显式指定stock>0 避免并发超抽的情况 只有更新成功了 才表示成功中奖
update award_count set win_count = win_count+1 , stock = stock-1 where award_id = 1 and stock>0;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0
方案二:
使用redis 如下所示
#更新用户抽奖次数
127.0.0.1:6379> incr 1_20160312
(integer) 1
127.0.0.1:6379> incr 1_20160312
(integer) 2
127.0.0.1:6379> incr 1_20160312
(integer) 3
127.0.0.1:6379> get 1_20160312
"3"
#更新奖品中奖数和库存
127.0.0.1:6379> incr 1_win_count
(integer) 1
127.0.0.1:6379> decr 1_stock
(integer) 99
使用redis来限制并发超抽的情况
好像面对上述场景大家想到的都是使用redis, 但使用mysql的劣势在哪呢?尤其当使用的是ssd硬盘的mysql
看具体情况,对并发比较大的情况下redis会比较有优势,而且incr系列api又是保证原子性操作。mysql速度会稍微差些,ssd也不会有内存快。对于这种有key/value操作显然nosql产品更有优势。
使用redis会有一个问题,那就是到最后你的商品数量剩余量可能是小于0的,即因为redis只能保证每个操作的原子性,如果你需要判断商品数量是否大于0,然后再决定是否减少余量,这样redis是没法保证原子性的,并发就会导致余量小于0,但是并不影响业务的正确性,因为你可以通过decr的返回值是否>=0来判断发奖成功与否。
MySQL毕竟是文件存储,肯定没法跟内存比,这种情况下一般方案是redis+mysql,这种数据不落DB谁放心呢。