目录
Redisson
整合Redisson
RLock
RReadWriteLock
RSemaphore
RCountDownLatch
优化三级分类缓存
缓存一致性问题
双写模式
失效模式
脏数据解决
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直接查看当前计数状态
典型应用场景
-
分布式任务同步:等待多个分布式任务完成
-
系统初始化:等待所有服务初始化完成
-
批量处理:等待所有子任务处理完成
-
测试协调:分布式测试中的线程协调
优化三级分类缓存
@Overridepublic Map<String, List<Level2CategoryVo>> getLevel2AndLevel3Category() {//1.先从缓存中获取 catalogJsonValueOperations<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;}
缓存一致性问题
双写模式
修改数据库,并修改缓存。
失效模式
修改数据库后,删除缓存,下一次查询时缓存数据。
脏数据解决
俩种模式都会产生暂时的脏数据,解决方案:
- 读写锁
- 过期时间,保证最终一致性
- 一致性要求高的数据,不存入缓存,应直接查询数据库