目录
基本原理和不同实现方式对比
满足分布式系统或集群模式下多进程可见且互斥的锁。
Redis分布式锁实现思路
两个基本方法::
获取锁:
释放锁:
实现Redis分布式锁版本1
实现
package com.hmdp.utils;public interface ILock {/*** 尝试获取锁* @param timeoutSec 锁持有的超时时间,过期后自动释放* @return true代表获取锁成功; false代表获取锁失败*/boolean tryLock(long timeoutSec);/*** 释放锁*/void unlock();}
@Resourceprivate StringRedisTemplate stringRedisTemplate;@Transactionalpublic Result createVoucherOrder(Long voucherId) {//一人一单Long userId = UserHolder.getUser().getId();//创建锁对象SimpleRedisLock redisLock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);//尝试获取锁boolean isLock = redisLock.tryLock(1200);if(!isLock){//获取锁失败return Result.fail("不允许重复下单!");}try {int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if(count > 0){//用户已经购买过return Result.fail("用户已经购买过!");}//5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if(!success) {return Result.fail("库存不足!");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//6.2用户idvoucherOrder.setUserId(userId);//6.3代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);//7.返回订单idreturn Result.ok(orderId);} finally {redisLock.unlock();}}
测试
打断点,通过PostMan进行测试两个用户




通过JMeter测试高并发
Redis分布式锁误删问题

极端情况:
业务阻塞导致锁提前释放,线程1醒来后,释放了线程2的锁,然后线程3加入了触发并行。
解决Redis分布式锁误删

需求:修改之前分布式锁实现,满足:
- 在获取锁时存入线程标识(可以用UUID)
- 在释放锁时先获取锁线程标识,判断与当前标识是否一致
```java
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + “-“;
@Override public boolean tryLock(long timeoutSec) { //获取线程标识 String threadId = ID_PREFIX + Thread.currentThread().getId(); //获取锁 Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); //避免自动拆箱空指针 return Boolean.TRUE.equals(success); }
@Override public void unlock() { //获取线程标识 String threadId = ID_PREFIX + Thread.currentThread().getId(); //获取锁中标识 String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); if(threadId.equals(id)){ //释放锁 stringRedisTemplate.delete(KEY_PREFIX + name); } }
<a name="hxGJM"></a>## Redis分布式锁原子性问题<br />判断锁标识和释放是两个动作,这两个动作之间产生了阻塞。<br />解决方法:保证这两个动作的原子性。<a name="ergjA"></a>## Lua脚本解决多条脚本原子性问题**Redis的Lua脚本**<br />Redis提供Lua脚本功能,在一个脚本编写多条Redis命令,确保多条命令原子性。<br />Redis提供调用函数<br />语法如下:<br /><br />例如要执行set name jack,则脚本如下<br /><br />例如要先执行set name Rese,再执行get name<br /><br />写好脚本后,用Redis命令调用脚本<br /><br />例如,要执行redis.call('set','name','jack')脚本<br /><br />脚本中key、value可以作为参数传递。key类型参数放入keys数组,其他参数放入argv数组。<br /><br />释放锁业务流程:1. 获取锁线程标识1. 判断是否一致1. 如果一致释放1. 不一致什么都不做```lua-- KEYS[1]是锁的key,ARGV[1]是线程标识-- 获取锁中线程标识 get key,比较线程标识与锁中标识是否一致if(redis.call('get', KEYS[1]) == ARGV[1]) then-- 释放锁 del keyreturn redis.call('del', KEYS[1])endreturn 0
Java调用Lua脚本改造分布式锁
需求:基于Lua脚本实现分布式锁释放锁逻辑
提示:RedisTemplate调用Lua脚本API如下
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}@Overridepublic void unlock(){//调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}
Redisson功能介绍
基于setnx实现的分布式锁问题:
Redisson
一个在Redis基础上实现的Java驻内存数据网格。不仅提供一系列分布式Java对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
Redissson快速入门
引入依赖
<!--redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>
配置Redisson客户端
@Configurationpublic class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");// 创建RedissonClient对象return Redisson.create(config);}}
使用Redisson分布式锁
@Resourceprivate RedissonClient redissonClient;@Transactionalpublic Result createVoucherOrder(Long voucherId) {//一人一单Long userId = UserHolder.getUser().getId();//创建锁对象RLock redisLock = redissonClient.getLock("lock:order:" + userId);//尝试获取锁boolean isLock = redisLock.tryLock();if(!isLock){//获取锁失败return Result.fail("不允许重复下单!");}try {......//7.返回订单idreturn Result.ok(orderId);} finally {redisLock.unlock();}}
Redisson可重入锁原理
Redisson锁重试和WatchDog机制
反复听->源码课:https://www.bilibili.com/video/BV1cr4y1671t?p=67&spm_id_from=pageDriver
RedissonLock.java




