目录
Redisson
提供redis分布式锁(Distributed locks with Redis)的java客户端
整合Redisson
引入
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
程序化配置
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}"+":"+"${spring.redis.port}")
private String singleAddress;
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
/*
SingleServer() 单节点模式
redis:// redis 连接协议
*/
config.useSingleServer()
.setAddress("redis://"+singleAddress);
return Redisson.create(config);
}
}
RLock
可重入锁(Reentrant Lock)它允许同一个线程多次获取同一把锁而不会产生死锁。
RLock lock = redisson.getLock("myLock");
// 尝试获取锁,最多等待10秒,锁有效期30秒
boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
try {
// 业务代码
} finally {
lock.unlock();
}
}
可重入性:同一线程可多次获取同一把锁
锁续期:内置看门狗机制自动续期
公平锁:支持公平锁和非公平锁
锁释放:确保只有锁持有者能释放锁
RReadWriteLock
读写锁,它允许多个读操作同时进行,但写操作是排他的。
RReadWriteLock rwLock = redisson.getReadWriteLock("myReadWriteLock");
RLock readLock = rwLock.readLock(); // 获取读锁
RLock writeLock = rwLock.writeLock(); // 获取写锁
writeLock.lock(); // 先获取写锁
try {
// 写操作...
// 保持写锁的同时获取读锁(锁降级)
readLock.lock();
try {
// 读操作...
} finally {
// 注意:这里不能释放写锁
}
// 可以继续持有写锁做其他操作
} finally {
writeLock.unlock(); // 最后释放写锁
// 此时仍持有读锁
}
读写分离:
多个线程可以同时持有读锁
写锁是排他的,有写锁时不能有读锁或其他写锁
可重入性:
读锁和写锁都支持可重入
同一线程可以多次获取同一把读锁或写锁
锁降级:
支持将写锁降级为读锁
但不支持读锁升级为写锁(会死锁)
公平性选择:
支持公平和非公平两种模式
RSemaphore
信号量,它允许多个线程/进程在分布式环境中协调对共享资源的访问
// 获取信号量实例(初始许可数为5)
RSemaphore semaphore = redisson.getSemaphore("mySemaphore");
semaphore.trySetPermits(5); // 初始化许可数量
// 获取1个许可(阻塞直到可用)
semaphore.acquire();
try {
// 访问受限资源
accessLimitedResource();
} finally {
// 释放许可
semaphore.release();
}
资源限制:控制同时访问特定资源的线程/进程数量
分布式支持:跨JVM、跨服务器的协调能力
公平性选择:支持公平和非公平两种模式
可重入:支持同一线程多次获取许可
超时机制:支持尝试获取许可的超时设置
典型应用场景
限流控制:限制系统并发请求数
资源池管理:如数据库连接池控制
任务调度:限制同时执行的任务数量
API访问限制:控制第三方API调用频率
RCountDownLatch
闭锁,它允许一个或多个线程等待其他线程完成操作后再继续执行。
RCountDownLatch latch = redisson.getCountDownLatch("myLatch");
// 初始化计数器为5
latch.trySetCount(5);
// 减少计数器(每个工作线程完成后调用)
latch.countDown();
//获取当前计数
long remaining = latch.getCount();
// 等待计数器归零(阻塞)
latch.await();
// 带超时的等待
boolean reached = latch.await(10, TimeUnit.SECONDS);
一次性使用:计数器归零后不能重置(与JDK的CountDownLatch一致)
等待/通知机制:线程可以等待计数器归零
可视化监控:可通过Redis直接查看当前计数状态
典型应用场景
分布式任务同步:等待多个分布式任务完成
系统初始化:等待所有服务初始化完成
批量处理:等待所有子任务处理完成
测试协调:分布式测试中的线程协调
优化三级分类缓存
@Override
public Map<String, List<Level2CategoryVo>> getLevel2AndLevel3Category() {
//1.先从缓存中获取 catalogJson
ValueOperations<String,String> valueOperations = redisTemplate.opsForValue();
String catalogJson = valueOperations.get("catalogJson");
Map<String, List<Level2CategoryVo>> res = null;
if(StringUtils.isEmpty(catalogJson)){
//缓存中无对应数据,查询数据库
res = getCatalogJsonFromDBWithRedissonLock();
}else{
res = JSON.parseObject(catalogJson,new TypeReference<Map<String, List<Level2CategoryVo>> >(){});
}
return res;
}
/**
* 获取 Redisson 分布式锁,查询数据库
* @return
*/
private Map<String, List<Level2CategoryVo>> getCatalogJsonFromDBWithRedissonLock(){
Map<String, List<Level2CategoryVo>> res = null;
RLock lock = redisson.getLock("CatalogJson-Lock");
lock.lock();
//成功获取到锁
try {
res = getCatalogJsonFromDB();
}finally {
lock.unlock();
}
return res;
}
缓存一致性问题
双写模式
修改数据库,并修改缓存。
失效模式
修改数据库后,删除缓存,下一次查询时缓存数据。
脏数据解决
俩种模式都会产生暂时的脏数据,解决方案:
- 读写锁
- 过期时间,保证最终一致性
- 一致性要求高的数据,不存入缓存,应直接查询数据库