分布式锁解决方案_数据库乐观锁实现的分布式锁
什么是乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有 去更新这个数据,可以使用版本号机制和CAS算法实现。
编写乐观锁更新语句
<update id="decreaseStockForVersion" parameterType="int" > UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version} </update>
编写创建订单业务层
/** * 创建订单 乐观锁 * * @return */ @Transactional(rollbackFor = Exception.class) @Override public String createOrder(Integer productId, Integer count) throws Exception { int retryCount = 0; int update = 0; // 1、根据商品id查询商品信息 Product product = productMapper.selectById(productId); // 2、判断商品是否存在 if (product == null) { throw new RuntimeException("购买商品不存在:" + productId + "不存在"); } // 3、校验库存 if (count > product.getCount()) { throw new Exception("库存不够"); } // 乐观锁更新库存 // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回 // 最多重试3次 while(retryCount < 3 && update == 0){ update = this.reduceStock(product.getId(),count); retryCount++; } if (update == 0){ throw new Exception("库存不够"); } // 6、 创建订单 TOrder order = new TOrder(); order.setOrderStatus(1);//待处理 order.setReceiverName("张三"); order.setReceiverMobile("18587781068"); order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格 baseMapper.insert(order); // 7、 创建订单和商品关系数据 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProduceId(product.getId()); orderItem.setPurchasePrice(product.getPrice()); orderItem.setPurchaseNum(count); orderItemMapper.insert(orderItem); return order.getId(); } /** * 减库存 * <p> * 由于默认的事务隔离级别是可重复读,produce.findById() * 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。 */ @Transactional(rollbackFor = Exception.class) public int reduceStock(int gid,int count) { int result = 0; //1、查询商品库存 Product product = productMapper.selectById(gid); //2、判断库存是否充足 if (product.getCount() >= count) { //3、减库存 // 乐观锁更新库存 result = productMapper.decreaseStockForVersion(gid,count, product.getVersion()); } return result; }
分布式锁解决方案_Redis实现的分布式锁原理
获取锁
互斥:确保只有一个线程获得锁
# 添加锁 利用setnx的互斥性 127.0.0.1:6379> setnx lock thread1
释放锁
1、手动释放锁
2、超时释放:获取锁时设置一个超时时间
#释放锁 删除即可 127.0.0.1:6379> del lock
超时释放
127.0.0.1:6379> setnx lock tread1 127.0.0.1:6379> expire lock 5 127.0.0.1:6379> ttl lock
两步合成一步
help set SET key value [EX seconds] [PX milliseconds] [NX|XX] summary: Set the string value of a key since: 1.0.0 group: string 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379> set lock k1 ex 5 nx OK 127.0.0.1:6379> set lock k1 ex 5 nx nil
分布式锁解决方案_Redis实现的分布式锁
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
添加Redis配置
spring: redis: host: localhost port: 6379
编写创建订单实现类
@Override public String createOrderRedis(Integer productId, Integer count) throws Exception { log.info("*************** 进入方法 **********"); String key = "lock:"; String value = UUID.randomUUID().toString(); // 获取分布式锁 Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS); // 判断是否获取锁成功 if (!result){ log.info("我进入了锁"); return "不允许重复下单"; } try { // 1、根据商品id查询商品信息 Product product = productMapper.selectById(productId); // 2、判断商品是否存在 if (product == null) { throw new RuntimeException("购买商品不存在:" + productId + "不存在"); } // 3、校验库存 if (count > product.getCount()) { throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买"); } // 4、计算库存 Integer leftCount = product.getCount() - count; // 5、更新库存 product.setCount(leftCount); productMapper.updateById(product); // 6、 创建订单 TOrder order = new TOrder(); order.setOrderStatus(1);//待处理 order.setReceiverName("张三"); order.setReceiverMobile("18587781068"); order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格 baseMapper.insert(order); // 7、 创建订单和商品关系数据 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProduceId(product.getId()); orderItem.setPurchasePrice(product.getPrice()); orderItem.setPurchaseNum(count); orderItemMapper.insert(orderItem); return order.getId(); }catch (Exception e){ e.printStackTrace(); }finally { // 释放锁 stringRedisTemplate.delete(key+productId); } return "创建失败"; }
分布式锁解决方案_Redis分布式锁误删除问题
配置锁标识
private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");
获取锁
//1、获取线程标识 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 2、获得锁 setnx key value time type Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);
释放锁
// 获取锁标识 String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId); // 判断标识是否一致 if (s.equals(threadId)){ // 释放锁 stringRedisTemplate.delete(KEY_PREFIX + produceId); }
分布式锁解决方案_Redis分布式锁不可重入问题
不可重入问题
如何解决
分布式锁解决方案_基于Redisson实现的分布式锁实现
Redisson介绍
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。
引入Redisson依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.17.2</version> </dependency>
添加Reids的配置
spring: redis: host: localhost port: 6379
编写Redis分布式锁工具类
package com.itbaizhan.lock.utils; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component @Slf4j public class DistributedRedisLock { @Autowired private RedissonClient redissonClient; // 加锁 public Boolean lock(String lockName) { if (redissonClient == null) { log.info("DistributedRedisLock redissonClient is null"); return false; } try { RLock lock = redissonClient.getLock(lockName); // 锁15秒后自动释放,防止死锁 lock.lock(15, TimeUnit.SECONDS); log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName); // 加锁成功 return true; } catch (Exception e) { log.error("DistributedRedisLocklock [{}] Exception:", lockName, e); return false; } } // 释放锁 public Boolean unlock(String lockName) { if (redissonClient == null) { log.info("DistributedRedisLock redissonClient is null"); return false; } try { RLock lock = redissonClient.getLock(lockName); lock.unlock(); log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName); // 释放锁成功 return true; } catch (Exception e) { log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e); return false; } } }
编写创建订单接口实现
/** * Redis锁实现 * * @param productId * @param count * @return * @throws Exception */ @Override public String createOrderRedis(Integer productId, Integer count) throws Exception { //获取锁对象 if (distributedRedisLock.lock(String.valueOf(productId))) { try { // 1、根据商品id查询商品信息 Product product = productMapper.selectById(productId); // 2、判断商品是否存在 if (product == null) { throw new RuntimeException("购买商品不存在:" + productId + "不存在"); } // 3、校验库存 if (count > product.getCount()) { throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买"); } // 4、计算库存 Integer leftCount = product.getCount() - count; // 5、更新库存 product.setCount(leftCount); productMapper.updateById(product); // 6、 创建订单 TOrder order = new TOrder(); order.setOrderStatus(1);//待处理 order.setReceiverName("张三"); order.setReceiverMobile("18587781068"); order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格 baseMapper.insert(order); // 7、 创建订单和商品关系数据 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProduceId(product.getId()); orderItem.setPurchasePrice(product.getPrice()); orderItem.setPurchaseNum(count); orderItemMapper.insert(orderItem); return order.getId(); } catch (Exception e) { e.printStackTrace(); } finally { distributedRedisLock.unlock(String.valueOf(productId)); } } return "创建失败"; }
分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)(下):https://developer.aliyun.com/article/1420041