思维导图
文章已收录Github精选,欢迎Star:https://github.com/yehongzhi/learningSummary
前言
经过《ZooKeeper入门》后,我们学会了ZooKeeper的基本用法。
实际上ZooKeeper的应用是非常广泛的,实现分布式锁只是其中一种。接下来我们就ZooKeeper实现分布式锁解决秒杀超卖问题进行展开。
一、什么是秒杀超卖问题
秒杀活动应该都不陌生,不用过多解释。
不难想象,在这种"秒杀"的场景中,实际上会出现多个用户争抢"资源"的情况,也就是多个线程同时并发,这种情况是很容易出现数据不准确,也就是超卖问题。
1.1 项目演示
下面使用程序演示,我使用了SpringBoot2.0、Mybatis、Mybatis-Plus、SpringMVC搭建了一个简单的项目,github地址:
https://github.com/yehongzhi/mall
创建一个商品信息表:
CREATE TABLE `tb_commodity_info` (
`id` varchar(32) NOT NULL,
`commodity_name` varchar(512) DEFAULT NULL COMMENT '商品名称',
`commodity_price` varchar(36) DEFAULT '0' COMMENT '商品价格',
`number` int(10) DEFAULT '0' COMMENT '商品数量',
`description` varchar(2048) DEFAULT '' COMMENT '商品描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';
添加一个商品[叉烧包]进去:
核心的代码逻辑是这样的:
@Override
public boolean purchaseCommodityInfo(String commodityId, Integer number) throws Exception {
//1.先查询数据库中商品的数量
TbCommodityInfo commodityInfo = commodityInfoMapper.selectById(commodityId);
//2.判断商品数量是否大于0,或者购买的数量大于库存
Integer count = commodityInfo.getNumber();
if (count <= 0 || number > count) {
//商品数量小于或者等于0,或者购买的数量大于库存,则返回false
return false;
}
//3.如果库存数量大于0,并且购买的数量小于或者等于库存。则更新商品数量
count -= number;
commodityInfo.setNumber(count);
boolean bool = commodityInfoMapper.updateById(commodityInfo) == 1;
if (bool) {
//如果更新成功,则打印购买商品成功
System.out.println("购买商品[ " + commodityInfo.getCommodityName() + " ]成功,数量为:" + number);
}
return bool;
}
逻辑示意图如下:
上面这个逻辑,如果单线程请求的话是没有问题的。
但是多线程的话就出现问题了。现在我就创建多个线程,通过HttpClient进行请求,看会发生什么:
public static void main(String[] args) throws Exception {
//请求地址
String url = "http://localhost:8080/mall/commodity/purchase";
//请求参数,商品ID,数量
Map<String, String> map = new HashMap<>();
map.put("commodityId", "4f863bb5266b9508e0c1f28c61ea8de1");
map.put("number", "1");
//创建10个线程通过HttpClient进行发送请求,测试
for (int i = 0; i < 10; i++) {
//这个线程的逻辑仅仅是发送请求
CommodityThread commodityThread = new CommodityTh