一、自己模拟一个分布式锁
/**
* @Description 自己模拟一个分布式锁
* 加锁使用setIfAbsent setnx(setIfPresent setex)
* 值不要写固定字符串 使用大字符串如 UUID
* 解锁使用lua脚本
*/
public Map<String, List<Category2Vo>> getCategoryJsonFromDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
//原子加锁 原子解锁
System.out.println("获取分布式锁成功");
Map<String, List<Category2Vo>> dataFromDb;
try {
//加锁成功 执行业务
dataFromDb = getDataFromDb();
} finally {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
//获取值对比 对比成功删除 必须是一个原子操作 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)){
// //如果是自己的锁 删除
// stringRedisTemplate.delete("lock");
// }
return getDataFromDb();
} else {
System.out.println("获取分布式锁失败 等待重试");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCategoryJsonFromDbWithRedisLock();
}
}
二、Redisson 完成分布式锁
1.导入依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.1</version>
</dependency>
2.配置
@Configuration
public class MyRedissonConfig {
/**
* config.useSingleServer() 单节点模式
* @Description 所有对Redisson操作都是通过RedissonClient对象
* @Param destroyMethod = "shutdown" 销毁方法 服务停止销毁
* @return org.redisson.api.RedissonClient
*/
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() throws IOException {
//创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.0.105:6379");
//创建实例
return Redisson.create(config);
}
}
3.使用
3.1 可重入锁
redisson默认就是可重入锁
@Autowired
RedissonClient redisson;
@ResponseBody
@GetMapping("/hello")
public String hello() {
//获取一把锁 只要名字一样就是一把锁
RLock lock = redisson.getLock("MY-LOCK");
//lock.lock();
//加锁 阻塞式等待 默认过期时间30s
//锁自动续期 如果业务超长 运行期间自动续上新的30s 不用担心业务时间长锁自动过期被删除
//加锁的业务只要运行完成就不会给当前锁续期 即使不手动解锁 默认30s删除
lock.lock(10, TimeUnit.SECONDS);
//10s自动解锁 自动解锁时间一定要大于业务执行时间
//问题 lock.lock(10, TimeUnit.SECONDS); 锁时间到了以后不会自动续期
//1.如果传了锁的超时时间 就发给reids执行脚本 进行占锁 默认超时时间就是我们指定的时间
//2.如果没传锁的超时时间 就使用30*1000 LockWatchdogTimeout看门狗的默认时间
//只要占锁成功 就会启动一个定时任务 (重新给锁设定过期时间 新的过期时间就是看门狗的默认时间)
//定时任务时间 = internalLockLeaseTime(看门狗时间 )/ 3 10s
//最佳实战 lock.lock(30, TimeUnit.SECONDS); 省掉了整个续期操作 自动解锁给长一点 手动解锁
try {
System.out.println("加锁成功 执行业务" + Thread.currentThread().getId());
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
System.out.println("释放锁" + Thread.currentThread().getId());
}
return "hello";
}
3.2 公平锁
redisson.getFairLock()
3.3 读写锁
/**
* 保证一定能读的最新数据
* 修改期间 写锁是一个排他锁(互斥锁) 读锁是一个共享锁
* 写锁没释放 读锁就必须等待
* 读 + 读 相当于无锁 并发读只会在reids中记录好当前的读锁 都会同时加锁成功
* 写 + 读 等待写锁释放
* 写 + 写 阻塞方式
* 读 + 写 有读锁 写也需要等待
*/
@ResponseBody
@GetMapping("/read")
public String read() {
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = lock.readLock();
rLock.lock();
try {
System.out.println("读锁加锁成功 执行业务" + Thread.currentThread().getId());
s = redisTemplate.opsForValue().get("writeValue");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();//解锁
System.out.println("读锁释放锁" + Thread.currentThread().getId());
}
return s;
}
@ResponseBody
@GetMapping("/write")
public String write() {
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = lock.writeLock();
rLock.lock();
try {
System.out.println("写锁加锁成功 执行业务" + Thread.currentThread().getId());
s = UUID.randomUUID().toString();
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue", s);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();//解锁
System.out.println("写锁释放锁" + Thread.currentThread().getId());
}
return s;
}
3.4 闭锁
/**
* @Description 放假锁门 闭锁
*/
@ResponseBody
@GetMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch countDownLatch = redisson.getCountDownLatch("door");
countDownLatch.trySetCount(5);
countDownLatch.await();//等待闭锁都完成
return "放假了";
}
@ResponseBody
@GetMapping("/gogogo/{id}")
public String gogogo(@PathVariable("id") Long id){
RCountDownLatch countDownLatch = redisson.getCountDownLatch("door");
countDownLatch.countDown();//计数减一
return "ok"+id;
}
3.5 信号量
可以用来限流 比如当前服务只能承受1w的并发请求
设置1w个信号量
让所有服务先获取信号量 能获取到证明有空闲线程来处理 否则等待
/**
* @Description 模拟停车 限流 信号量
*/
@ResponseBody
@GetMapping("/park")
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.acquire();//获取一个信号量 获取一个值 阻塞式获取 一定要获取
park.tryAcquire();//有就获取 没有就算了
return "ok";
}
@ResponseBody
@GetMapping("/go")
public String go(){
RSemaphore park = redisson.getSemaphore("park");
park.release();//释放一个信号量 释放一个值
return "ok";
}