目录
1.写在前面
本案例模拟的是5000个人同时抢红包池中的3000个红包的场景,以此案例来讨论高并发编程会遇到的问题以及如何解决。
本案例在全注解SSM环境中编写,关于如何搭建SSM,可以参考博主其他博客:
《全注解搭建SSM》https://blog.youkuaiyun.com/czr11616/article/details/84325586
2.模拟超发现象
2.1 概述
什么是超发现象?
在本案例下,红包池中一共只有3000个红包,5000个人同时抢,最后会出现有超过3000个人抢到了红包,红包池中的剩余红包数变成了负数,这就是超发现象。
超发现象产生的原因?
先来看看抢红包的逻辑,在抢红包之前,需要判断红包池中是否还有红包,如果有,则红包池中的剩余红包数减1,并新增一条抢到红包的记录,如果没有,直接返回抢红包失败。
在这样的逻辑下,假设最后红包池中只有一个红包了,有5个人在同一时刻去抢,这时他们会同时判断红包池中是否还有红包,发现有,红包池中剩余红包数减一并新增抢红包记录。最后就会出现有3004个人抢到了红包,红包池中的红包数变成了负4,超发现象就这样产生了。
综上,超发现象产生的根本原因就是由于多线程操作同一条数据从而导致数据不一致。。
接下来模拟超发现象
2.2 数据库建表
创建红包池表 t_red_packet ,并插入红包数据,下面是建表和插入数据sql语句
-- ----------------------------
-- Table structure for t_red_packet
-- ----------------------------
DROP TABLE IF EXISTS `t_red_packet`;
CREATE TABLE `t_red_packet` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`user_id` int(12) NOT NULL,
`amount` decimal(16,2) NOT NULL,
`send_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`total` int(12) NOT NULL,
`unit_amount` decimal(12,0) NOT NULL,
`stock` int(12) NOT NULL,
`version` int(12) NOT NULL DEFAULT '0',
`note` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_red_packet
-- ----------------------------
INSERT INTO `t_red_packet` VALUES ('1', '1', '30000.00', '2018-11-22 18:25:29', '3000', '10', '3000', '0', '3万元金额,3千个小红包,每个10元');
建好的红包池表如下图
其中amount为红包池总金额,total为红包池总红包数,unit_amount为每个红包金额,stock为红包池剩余红包数。
创建用户抢红包表 t_user_red_packet
-- ----------------------------
-- Table structure for t_user_red_packet
-- ----------------------------
DROP TABLE IF EXISTS `t_user_red_packet`;
CREATE TABLE `t_user_red_packet` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`red_packet_id` int(12) NOT NULL,
`user_id` int(12) NOT NULL,
`amount` decimal(16,2) NOT NULL,
`grap_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`note` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=240 DEFAULT CHARSET=utf8;
建好的抢红包表如下:
red_packet_id为红包池id,user_id为抢到红包的用户id,amount为抢到的金额,grap_time为抢到红包的时间。
2.3 编写SQL
根据之前描述的抢红包逻辑,在抢红包之前先检查红包池,如果有剩余,则红包池剩余红包数减一,并新增一条抢到红包的记录。所以一共有三条sql:查看红包池、更新红包池库存、新增抢到红包记录。
在你的红包池映射器配置文件中加入以下sql
查看红包池sql
<select id="getRedPacket" parameterType="long" resultType="com.ssm.pojo.RedPacket">
select id,user_id as userId,amount,send_date as sendDate,total,unit_amount as unitAmount,stock,version,note from T_RED_PACKET where id = #{id}
</select>
更新红包池库存sql
<update id="decreaseRedPacket" parameterType="long">
update T_RED_PACKET set stock = stock-1 where id = #{id}
</update>
在你的用户抢红包映射器配置文件