使用单体锁和分布式锁解决超卖问题
使用单体锁和分布式锁解决超卖问题
超卖现象演示
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistributeLockApplicationTests {
@Autowired
private OrderService orderService;
/**
* 模拟超卖
*/
@Test
public void concurrentOrder() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
try {
//等待5个线程执行到await时,再去执行后面得到步骤; 让所有的线程在等待等到某一刻再去执行,实现并发的效果
cyclicBarrier.await();
Integer orderId = orderService.createOrder();
System.out.println("订单id: " + orderId + " " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
//等到5个线程执行完毕后,再去结束主线程,不这样做数据库连接会被关闭
countDownLatch.await();
//关闭线程池
executorService.shutdown();
}
}
}
创建订单相关数据的方法如下:
@Transactional(rollbackFor = Exception.class)
public Integer createOrder() throws Exception {
Product product = productMapper.selectByPrimaryKey(purchaseProductId);
if (product == null) {
throw new Exception("购买商品:" + purchaseProductId + "不存在");
}
//商品当前库存
Integer currentCount = product.getCount();
//校验库存
if (purchaseProductNum > currentCount) {
throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
}
//计算剩余库存
int leftCount = currentCount - purchaseProductNum;
//更新库存
product.setCount(leftCount);
// 将提交的数据添加到数据库中.
product.setUpdateTime(new Date());
product.setUpdateUser("xxx");
productMapper.updateByPrimaryKeySelective(product);
//创建订单
Order order = new Order();
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
order.setOrderStatus(1);
order.setReceiverName("xxx");
order.setReceiverMobile("13311112222");
order.setCreateTime(new Date());
order.setCreateUser("xxx");
order.setUpdateTime(new Date());
order.setUpdateUser("xxx");
orderMapper.insertSelective(order);
//创建订单中的
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(purchaseProductNum);
orderItem.setCreateUser("xxx");
orderItem.setCreateTime(new Date());
orderItem.setUpdateTime(new Date());
orderItem.setUpdateUser("xxx");
orderItemMapper.insertSelective(orderItem);
return order.getId();
}
单体锁
数据库update行锁
数据库在更新一行数据时,会自动对这一行数据加锁,借助这个特性,我们可以解决并发问题。
将原来在程序中更新库存改成直接在数据库中更新库存。传入
/**
* 使用数据库update行锁处理
* 数据库在更新某一行记录的时候会这一行加锁,一个线程处理完了,下一个线程才能继续处理
*/
@Transactional(rollbackFor = Exception.class)
public Integer createOrderByUpdateLock() throws Exception {
Product product = productMapper.selectByPrimaryKey(purchaseProductId);
if (product == null) {
throw new Exception("购买商品:" + purchaseProductId + "不存在");
}
//商品当前库存
Integer currentCount = product.getCount();
//校验库存
if (purchaseProductNum > currentCount) {
throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
}
// //计算剩余库存
// int leftCount = currentCount - purchaseProductNum;
// //更新库存
// product.setCount(leftCount);
//
// // 将提交的数据添加到数据库中.
// product.setUpdateTime(new Date());
// product.setUpdateUser("xxx");
// productMapper.updateByPrimaryKeySelective(product);
//直接写入剩余库存,而不是在内存中计算出剩余库存后再写入数据库
productMapper.updateProductCount(purchaseProductNum, "xxx", new Date(), product.getId());
//创建订单
Order order = new Order();
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
order.setOrderStatus(1);//待处理
order.setReceiverName("xxx");
order.setReceiverMobile("13311112222");
order.setCreateTime(new Date());
order.setCreateUser("xxx");
order.setUpdateTime(new Date());
order.setUpdateUser("xxx");
orderMapper.insertSelective(order);
//创建订单中的
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(purchaseProductNum);
orderItem.setCreateUser("xxx");
orderItem.setCreateTime(new Date());
orderItem.setUpdateTime(new Date());
orderItem.setUpdateUser("xxx");
orderItemMapper.insertSelective(orderItem);
return order.getId();
}
通过synchronzied关键字
将校验库存以及扣减库存的代码放在synchroinzed代码块中;但是需要自己手动管理事务。如果使用事务注解,第一个线程进入执行完后,事务未提交,第二个线程执行时获取到的库存还是1,这样还是会有并发问题。
/**
* 通过synchronized关键字
*/
public Integer createOrderBySynchronized1() throws Exception {
Product product = null;
synchronized (OrderService.class) {
product = productMapper.selectByPrimaryKey(purchaseProductId);
if (product == null) {
throw new Exception("购买商品:" + purchaseProductId + "不存在 " + Thread.currentThread().getName());
}
//商品当前库存
Integer currentCount = product.getCount();
//校验库存
if (purchaseProductNum > currentCount) {
throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买 " + Thread.currentThread().getName());
}
//计算剩余库存
int leftCount = currentCount - purchaseProductNum;
//更新库存
product.setCount(leftCount);
// 将提交的数据添加到数据库中.
product.setUpdateTime(new Date());
product.setUpdateUser("xxx");
productMapper.updateByPrimaryKeySelective(product);
}
//创建订单
Order order = new Order();
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
order.setOrderStatus(1);//待处理
order.setReceiverName("xxx");
order.setReceiverMobile("13311112222");
order.setCreateTime(new Date());
order.setCreateUser("xxx");
order.setUpdateTime(new Date());
order.setUpdateUser("xxx");
orderMapper.insertSelective(order);
//创建订单中的
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(purchaseProductNum);
orderItem.setCreateUser("xxx");
orderItem.setCreateTime(new Date());
orderItem.setUpdateTime(new Date());
orderItem.setUpdateUser("xxx");
orderItemMapper.insertSelective(orderItem);
return order.getId();
}
ReentrantLock
/**
* 通过ReentrantLock锁解决并发问题
*/
public Integer createOrderByReentrantLock() throws Exception {
Product product = null;
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
reentrantLock.lock();
try {
product = productMapper.selectByPrimaryKey(purchaseProductId);
if (product == null) {
throw new Exception("购买商品:" + purchaseProductId + "不存在");
}
//商品当前库存
Integer currentCount = product.getCount();
//校验库存
if (purchaseProductNum > currentCount) {
throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
}
//计算剩余库存
int leftCount = currentCount - purchaseProductNum;
//更新库存
product.setCount(leftCount);
// 将提交的数据添加到数据库中.
product.setUpdateTime(new Date());
product.setUpdateUser("xxx");
productMapper.updateByPrimaryKeySelective(product);
platformTransactionManager.commit(transaction);
} catch (Exception e) {
platformTransactionManager.rollback(transaction);
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);
//创建订单
Order order = new Order();
assert product != null;
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
order.setOrderStatus(1);
order.setReceiverName("xxx");
order.setReceiverMobile("13311112222");
order.setCreateTime(new Date());
order.setCreateUser("xxx");
order.setUpdateTime(new Date());
order.setUpdateUser("xxx");
orderMapper.insertSelective(order);
//创建订单中的
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(purchaseProductNum);
orderItem.setCreateUser("xxx");
orderItem.setCreateTime(new Date());
orderItem.setUpdateTime(new Date());
orderItem.setUpdateUser("xxx");
orderItemMapper.insertSelective(orderItem);
platformTransactionManager.commit(transaction1);
return order.getId();
}
分布式锁
在分布式集群的环境中,上面的单体锁会失效,此时需要通过分布式锁去处理并发问题。
数据库update锁
通过下面的sql给数据加锁,其它请求去更改数据时会被阻塞。
select... for update
Controller中的代码
@Autowired
private DistributeLockMapper distributeLockMapper;
@RequestMapping("/test1")
public String test1() throws Exception {
log.info("进入方法..");
DistributeLock lock = distributeLockMapper.selectLockByCode("demo");
if (lock == null) {
throw new Exception("找不到锁");
}
log.info("获取到了锁");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "执行完成";
}
mapper文件中的代码
<select id="selectLockByCode" resultType="com.lock.distribute_lock.model.DistributeLock">
select * from distribute_lock where business_code = #{code} for update;
</select>
redis实现分布式锁
Redis实现分布式锁的核心命令:
SETNX key value
SETNX 命令的作用是:如果指定的 key 不存在,则创建并为其设置值,然后返回状态码 1;如果指定的 key 存在,则直接返回 0。如果返回值为 1,代表获得该锁;此时其他进程再次尝试创建时,由于 key 已经存在,则都会返回 0 ,代表锁已经被占用。
当获得锁的进程处理完成业务后,再通过 del 命令将该 key 删除,其他进程就可以再次竞争性地进行创建,获得该锁。
封装的RedisLock工具类
@Slf4j
public class RedisLock implements AutoCloseable{
private RedisTemplate redisTemplate;
private String key;
private String value;
private Long expireTime;
public RedisLock(RedisTemplate redisTemplate, String key, Long expireTime) {
this.redisTemplate = redisTemplate;
this.key = key;
this.value = UUID.randomUUID().toString();
this.expireTime = expireTime;
}
/**
* 获取分布式锁
* @return
*/
public boolean getLock() {
RedisCallback<Boolean> redisCallback = redisConnection -> {
//设置NX
RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
//设置过期时间
Expiration expiration = Expiration.seconds(expireTime);
//序列化key
byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
//序列化value
byte[] redisValu = redisTemplate.getValueSerializer().serialize(value);
//执行setNx操作
Boolean result = redisConnection.set(redisKey, redisValu, expiration, setOption);
return result;
};
//获取分布式锁
Boolean lock = (Boolean) redisTemplate.execute(redisCallback);
return lock;
}
public boolean unLock() {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
List<String> keys = Arrays.asList(key);
Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);
log.info("释放锁的结果: " + result);
return result;
}
@Override
public void close() throws Exception {
unLock();
}
}
测试代码
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/redis-lock")
public String redisLock() {
log.info("redislock begin....");
try (RedisLock redisLock = new RedisLock(redisTemplate, "redisKey", 30L)) {
if (redisLock.getLock()) {
log.info("获取了锁!!");
//模拟业务操作
Thread.sleep(15000);
}
} catch (Exception e) {
e.printStackTrace();
}
log.info("redis lock end");
return "方法执行完成";
}
参考
- 深入解析 Redis 分布式锁原理