目录
全局ID生成器编辑
实现优惠卷下单
优惠卷超卖问题
乐观锁
一人一单
分布式锁
分布锁的实现
基于Redis的分布锁
Redis的Lua脚本
再次改进Redis的分布锁
基于Redis的分布锁优化
Redisson分布式框架
引入依赖
Redisson可重入锁原理
Redisson分布锁原理编辑
全局ID生成器
@Component
public class RedisIdWorker {
/**
* 开始时间戳
*/
private static final long BEGIN_TIMESTAMP = 1640995200L;
/**
* 序列号的位数
*/
private static final int COUNT_BITS= 32;
private StringRedisTemplate stringRedisTemplate;
public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public long nextId(String keyPrefix){
//1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
//2.生成序列号
//2.1获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
//2.2自增长
Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
实现优惠卷下单
优惠卷超卖问题
乐观锁
一人一单
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Override
public Result seckillVoucher(Long voucherId) {
//1.查询优惠卷
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
if ( voucher.getBeginTime().isAfter(LocalDateTime.now()) ) {
// 秒杀尚未开始
return Result.fail ("秒杀尚未开始!");
}
//3.判断秒杀是否已经结束
if ( voucher.getEndTime().isBefore(LocalDateTime.now()) ) {
return Result.fail ("秒杀已结束!");
}
//4.判断库存是否充足
if ( voucher.getStock()<1 ) {
//库存不足
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
//获取代理对象(事务)事务生效
//获取锁之后再创建事务
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
//事务提交完再释放锁
return proxy.createVoucherOrder(voucherId);//事务能够
//可以避免事务没提交就释放锁的安全问题
}
}
@Transactional//加入事务
public Result createVoucherOrder(Long voucherId) {
//TODO 6.一人一单
Long userId = UserHolder.getUser().getId();
// userId.toString()底层代码是创建对象,所以有可能还是不一样的
// synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
//TODO 6.1查询订单
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
//TODO 6.2判断是否存在
if ( count > 0 ) {
//用户已经买过了
return Result.fail("用户已经买过了一次了");
}
//5.扣减库存
boolean sucess = seckillVoucherService.update()
.setSql("stock = stock-1")//set stock = stock-1
.gt("stock", "0")//可以解决超卖 where id ?and stock >0
.eq("voucher_id", voucherId).update();//
// .eq("stock",voucher.getStock()).update();//where id ?and stock = ?
if ( !sucess ) {
//扣减不足
return Result.fail("库存不足");
}
//6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//6.1订单di
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//6.2用户id
// Long userId = UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3代金卷id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回订单
return Result.ok(orderId);
}
}
分布式锁
分布锁的实现
基于Redis的分布锁
//TODO 分布锁
//TODO 创建锁对象
SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//获取锁
boolean isLock = lock.tryLock(1200);
if ( !isLock ){
//获取锁失败,返回错误或重试
return Result.fail("不允许重复下单");
}
try {
//获取代理对象(事务)事务生效
//TODO 获取锁之后再创建事务
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
//TODO 事务提交完再释放锁
return proxy.createVoucherOrder(voucherId);//事务能够
//TODO 可以避免事务没提交就释放锁的安全问题
} finally {
//释放锁
lock.unlock();
}
// }
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
private static final String KEY_PREFIX="lock:";
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标识
long threadId = Thread.currentThread().getId();
String key = KEY_PREFIX + name;
//获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(key, threadId + "", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
// //防止拆箱
// return BooleanUtil.isTrue(success);
}
@Override
public void unlock() {
//释放锁
stringRedisTemplate.delete(KEY_PREFIX+name);
}
}
Redis的Lua脚本
再次改进Redis的分布锁
基于Redis的分布锁优化
Redisson分布式框架
引入依赖
@Resource
private RedissonClient redissonClient;
//TODO 分布锁
//TODO 创建锁对象
// SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
// TODO 使用工具Redisson
RLock lock = redissonClient.getLock("lock:order" + userId);
//获取锁
//TODO
boolean isLock = lock.tryLock();
if ( !isLock ){
//获取锁失败,返回错误或重试
return Result.fail("不允许重复下单");
}
try {
//获取代理对象(事务)事务生效
//TODO 获取锁之后再创建事务
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
//TODO 事务提交完再释放锁
return proxy.createVoucherOrder(voucherId);//事务能够
//TODO 可以避免事务没提交就释放锁的安全问题
} finally {
//释放锁
lock.unlock();
}
// }
Redisson可重入锁原理
Redisson分布锁原理
leaseTime:释放时间
Redisson分布锁主从一致性问题