一、简介
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
二、作用
分布式对象:分布式对象简单来说就是存储在Redis中的Java对象。
Redisson允许你在分布式环境中创建Java对象,并将它们存储在Redis中。这样,不同的Java应用程序或服务就能够共享和访问这些对象,实现数据共享和数据同步。
分布式集合:简单来说就是将集合放在Redis中,并且可以多个客户端对集合进行操作。
Redisson支持常见的分布式数据结构,如List、Set、Map、Queue等,使得多个Java应用程序可以并发地访问和修改这些集合。
分布式锁:通过Redisson,你可以轻松地实现分布式锁,确保在分布式环境中的并发操作的正确性和一致性。
缓存:通过Redisson能够轻松的基于redis实现项目中的缓存。
三 、使用
1.使用RdissonClient客户端方式
1.1 引入redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.37.0</version>
</dependency>
1.2 配置类
package com.xzl.xmall.product.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 描述:
*
* @author: xuzili
* @date: 2024/10/31
*/
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://ip:port").setPassword("");
return Redisson.create(config);
}
}
2. 使用分布式锁
特点:
- 锁自动续期,锁默认有效期为30秒,在业务执行期间会自动续期,不用担心锁过期;
- 锁自动释放,当业务执行完成,即使没有手动释放锁,锁也会在30s后自动释放,不会造成死锁。
@Autowired
private RedissonClient redisson;
@GetMapping("/lock")
public String lock() {
//redisson锁
RLock lock = redisson.getLock("lock");
log.info("线程{}等待锁", Thread.currentThread().getName());
lock.lock();
log.info("线程{}获取锁", Thread.currentThread().getName());
try {
log.info("线程{}执行业务逻辑", Thread.currentThread().getName());
Thread.sleep(30000);
} catch (InterruptedException e) {
log.error("线程{}执行业务逻辑失败", Thread.currentThread().getName());
e.printStackTrace();
} finally {
lock.unlock();
log.info("线程{}释放锁", Thread.currentThread().getName());
}
return "lock";
}
3. 使用读写锁
特点:读锁和写锁是互斥的,写锁和写锁是互斥的,读锁和读锁是共享的 使用场景:大量读,少量写的情况下
3.1 读锁
/**
* ReadWriteLock读写锁,
* 读 + 读 共享
* 写 + 读 不共享
* 写 + 写 不共享
*
*
*/
@GetMapping("/read/lock")
public String readLock() {
RLock readLock = redisson.getReadWriteLock("ReadWriteLock").readLock();
readLock.lock();
log.info("线程{}获取读锁", Thread.currentThread().getName());
try {
log.info("线程{}执行业务逻辑", Thread.currentThread().getName());
Thread.sleep(10000);
} catch (InterruptedException e) {
log.error("线程{}执行业务逻辑失败", Thread.currentThread().getName());
e.printStackTrace();
} finally {
readLock.unlock();
log.info("线程{}释放读锁", Thread.currentThread().getName());
}
return "readLock";
}
3.2 写锁
@GetMapping("/write/lock")
public String writeLock() {
RLock writeLock = redisson.getReadWriteLock("ReadWriteLock").writeLock();
writeLock.lock();
log.info("线程{}获取写锁", Thread.currentThread().getName());
try {
log.info("线程{}执行业务逻辑", Thread.currentThread().getName());
Thread.sleep(10000);
} catch (InterruptedException e) {
log.error("线程{}执行业务逻辑失败", Thread.currentThread().getName());
e.printStackTrace();
} finally {
writeLock.unlock();
log.info("线程{}释放写锁", Thread.currentThread().getName());
}
return "writeLock";
}
4. 闭锁
特点:
- 计数器:
CountDownLatch内部有一个计数器,用于记录需要等待的事件数量。 - 阻塞等待:调用
await()方法的线程会阻塞,直到CountDownLatch的计数器减到0。 - 事件触发:其他线程可以通过调用
countDown()方法来减少计数器的值,表示一个事件已经发生。 - 一次性:
CountDownLatch只能使用一次,计数器的值到达0后,不能再增加。
使用场景:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行。二元闭锁(包括两个状态)可以用来表示“资源R已经被初始化”,而所有需要R的操作都必须先在这个闭锁上等待。
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
- 等待直到某个操作的所有参与者都就绪在继续执行。(例如:多人游戏中需要所有玩家准备才能开始)
例子:图书馆现在有五个人在看书,保安需要等这五个人离开,才能关门
/**
* 闭锁,等待计数器为0,才结束等待;
*
* 模拟图书馆闭馆,等待所有用户离开,才闭馆
*/
@GetMapping("/close")
public String close() {
RCountDownLatch countDownLatch = redisson.getCountDownLatch("countDownLatch");
// 设置闭锁的计数器为5,模拟图书馆有五个用户
countDownLatch.trySetCount(5);
try {
log.info("等待所有用户离开");
countDownLatch.await();
log.info("所有用户离开,闭馆");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "闭馆";
}
模拟有人离开
@GetMapping("/leave")
public String leave() {
RCountDownLatch countDownLatch = redisson.getCountDownLatch("countDownLatch");
// 计数器减1,模拟用户离开
countDownLatch.countDown();
log.info("离开一个用户");
return "离开一个用户";
}
三、原理 tryAcquireAsync尝试获取锁
private RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture<Long> ttlRemainingFuture;
if (leaseTime > 0) {
ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);
ttlRemainingFuture = new CompletableFutureWrapper<>(s);
CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// lock acquired
if (ttlRemaining == null) {
if (leaseTime > 0) {
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
tryLockInnerAsync执行lua脚本:
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return evalWriteSyncedNoRetryAsync(getRawName(), LongCodec.INSTANCE, command,
"if ((redis.call('exists', KEYS[1]) == 0) " +
"or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
- KEYS[1]: getLock("name")的锁名称
- ARGV[2]: uuid + 线程id
- ARGV[1]: key过期时间
如果key不存在或者指定的键的哈希表中存在指定的uuid + 线程id字段,则将key哈希表中的指定字段的值+1,设置key的过期时间;
"if ((redis.call('exists', KEYS[1]) == 0) " +
"or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;
如果key存在,则返回key的过期时间
"return redis.call('pttl', KEYS[1]);",
看门狗逻辑:
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = getServiceManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
CompletionStage<Boolean> future = renewExpirationAsync(threadId);
future.whenComplete((res, e) -> {
if (e != null) {
if (getServiceManager().isShuttingDown(e)) {
return;
}
log.error("Can't update lock {} expiration", getRawName(), e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// reschedule itself
renewExpiration();
} else {
cancelExpirationRenewal(null, null);
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
// internalLockLeaseTime / 3,过期时间的三分之一,也就是每隔十秒执行一次
ee.setTimeout(task);
}