获取锁
互斥:确保只有一个线程获得锁
添加锁 利用setnx的互斥性
启动Redis的我们进行测试看看怎么能某个数据来获得锁
setnx命令表示上锁只要是aa被创建出来我们不删除aa或者设定时间自动删除,那么这把锁就不能被释放开
释放锁
手动释放锁
如果我们的某台服务抢到锁了但是该服务挂机了,这个时候其他服务将不能在抢到锁,形成了死锁,这个时候我们可以通过设置超级时间进行
超时释放:获取锁时设置一个超时时间
但是还是有个问题我们虽然设置了过期时间让他自动过期,但是如果设置的过期时间还没有到突然又挂机了这个时候我们的锁又称死锁了,那么我们可以使用超时释放操作
超时释放
两步合成一步 ex 秒 px毫秒 ttl 看过期事件
分布式锁解决方案_Redis实现的分布式锁
在项目的pom.xml中引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
修改service 接口ITOrderService
package com.ss.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ss.demo.domain.TOrder; /** * <p> * 服务类 * </p> */ public interface ITOrderService extends IService<TOrder> { /** * 创建订单方法 * @param productId * @param count * @return */ String createOrder(Integer productId, Integer count);
/** * 使用悲观锁进行实现 * @param productId * @param count * @return */ String createOrderPessimisticlock(Integer productId, Integer count);
/** * 乐观锁 * @param productId * @param count * @return */ String createOrderOptmisticlock(Integer productId, Integer count);
/** * Redis操作 * @param productId * @param count * @return */ String createOrderRedis(Integer productId, Integer count); } |
实现类:TOrderServiceImpl
@Autowired private StringRedisTemplate redisTemplate; /** * Redis操作 * @param productId * @param count * @return */ @Transactional @Override public String createOrderRedis(Integer productId, Integer count) {
String key = "lock:"; Boolean result = redisTemplate.opsForValue().setIfAbsent(key + productId, Thread.currentThread().getId() + "", 5, TimeUnit.SECONDS); //如果说我能获得所,也就是为true if(!result) { return "不准许重复下单"; } try { //根据商品id获取商品信息 Product product = productMapper.selectById(productId); if(product == null) { throw new RuntimeException("购买商品不存在"); } log.info(Thread.currentThread().getName() +"库存数量" + product.getCount()); //校验库存 if(count > product.getCount()) { throw new RuntimeException("库存不足"); } //更新库存 Integer iCount = product.getCount() - count; product.setCount(iCount); //更新操作 productMapper.updateById(product); //创建订单操作 TOrder order = new TOrder(); order.setOrderStatus(1); order.setReceiverName("张三"); order.setReceiverMobile("12345678765"); //设置订单价格【商品单价*商品数量】 order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count))); orderMapper.insert(order); //插入订单操作 //创建订单商品表的操作 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); //订单Id orderItem.setProduceId(product.getId()); //商品Id orderItem.setPurchasePrice(product.getPrice()); //购买价格 orderItem.setPurchaseNum(count); //购买数量 orderItemMapper.insert(orderItem); return order.getId(); }catch (Exception e) { e.printStackTrace(); } finally { redisTemplate.delete(key + productId); } return "创建失败"; } |
修改controller:TOrderController
package com.ss.demo.controller; import com.ss.demo.service.ITOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 前端控制器 * </p> */ @RestController @RequestMapping("/order") public class TOrderController { @Autowired private ITOrderService orderService;
@PostMapping("/create") public String createOrder(Integer productId, Integer count) { //return orderService.createOrder(productId, count); return orderService.createOrderRedis(productId,count); } } |
启动服务9091,9090
使用Jmeter记性测试
把数据库所有数据复原
分布式锁解决方案_Redis实现的分布式锁原理【这种问题是在极端情况下可能会出现】
Redis分布式锁误删除问题解决方案
设置超时时间远大于业务执行时间,但是会带来性能问题
删除锁的时候要判断,是不是自己的,如果是再删除
修改service实现类:TorderServiceImpl
@Autowired
private RedisTemplate<String ,Object> redisTemplate;
/**
* Redis操作
* @param productId
* @param count
* @return
*/
@Transactional
@Override
public String createOrderRedis(Integer productId, Integer count) {
String key = "lock:";
Boolean result = redisTemplate.opsForValue().setIfAbsent(key + productId, Thread.currentThread().getId() + "", 10, TimeUnit.SECONDS);
//如果说我能获得所,也就是为true
if(!result) {
return "不准许重复下单";
}
try {
//根据商品id获取商品信息
Product product = productMapper.selectById(productId);
if(product == null) {
throw new RuntimeException("购买商品不存在");
}
log.info(Thread.currentThread().getName() +"库存数量" + product.getCount());
//校验库存
if(count > product.getCount()) {
throw new RuntimeException("库存不足");
}
//更新库存
product.setCount(product.getCount() - count);
//更新操作
productMapper.updateById(product);
//创建订单操作
TOrder order = new TOrder();
order.setOrderStatus(1);
order.setReceiverName("张三");
order.setReceiverMobile("12345678765");
//设置订单价格【商品单价*商品数量】
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
orderMapper.insert(order); //插入订单操作
//创建订单商品表的操作
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId()); //订单Id
orderItem.setProduceId(product.getId()); //商品Id
orderItem.setPurchasePrice(product.getPrice()); //购买价格
orderItem.setPurchaseNum(count); //购买数量
orderItemMapper.insert(orderItem);
return order.getId();
}catch (Exception e) {
e.printStackTrace();
} finally {
//获得我们线程的Id也就是我们的表示
Integer lockId= (Integer) redisTemplate.opsForValue().get(key + productId);
//获取当前线程Id
String threadId = Thread.currentThread().getId() + "";
//判断表示是否一致
if(lockId.equals(threadId)) {
//如果一致则删除
redisTemplate.delete(key + productId);
}
}
return "创建失败";
} |
启动服务9091,9090
使用Jmeter记性测试
把数据库所有数据复原
分布式锁解决方案_Redis分布式锁不可重入问题
Setnx在极端情况下会出现以下问题
不可重入:在同一个线程中,无法多次获得同一把锁
不可重试:锁只能获取一次,不能重试
超时释放:虽然可以防止死锁,但是因为业务执行失效件特别长也会存在锁的释放有安全问题
主从一致性:主从同步回存在延时特性,造成主机从机数据不一致,或者主机宕机后主机和从机数据不一致