redisson为redis的分布式解决方案,对redis进行了封装,经常应用于分布式锁场景。
redis常见问题
- 缓存穿透:程序中没有缓存x值,当大量请求获取一个不存在的x值时,由于缓存中没有,大量请求直接访问数据库,数据库压力陡增,从而出现穿透问题;
解决:将查询结果为x值的数据缓存到redis中; - 缓存雪崩:大量缓存同一个时间内失效,这时来了一大波请求,都怼到数据库上,数据库处理不过来崩了;
解决:在设置数据失效时间时,增加一个随机数; - 缓存击穿:大量请求同时访问同一个正好过期的缓存数据,导致原本能命中缓存数据的请求直接访问数据库,数据库崩了;
解决:添加分布式锁;
实例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.0</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
//创建配置
Config config = new Config();
//可以用"redis://"来启用SSL连接,useSingleServer表示单例模式
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
//根据config创建出RedissonClient实例
return Redisson.create(config);
}
}
分布式锁、写锁、读锁、CountDownLatch
@RestController
public class HelloController {
@Autowired
private RedissonClient redisson;
@Autowired
private RedisTemplate redisTemplate;
/**
* lock.lock(10, TimeUnit.SECONDS); //10秒自动解锁;解锁时间一定要大于业务操作时间
* 问题:如果指定解锁时间,在锁时间到了以后,不会自动续期
* 1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
* 2、如果我们未指定超时时间,就使用30*1000【lockWatchdogTimeout看门狗默认的时间】,只要占锁成功,就会
* 启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒就会自动续期,续成30s
*/
@RequestMapping("/hello")
public String hello(){
//1、获取一把锁,只要锁的名字一样,那就是同一把锁
RLock lock = redisson.getLock("redisson-lock");
//2、加锁,默认加的锁都是30s时间
lock.lock(); //阻塞式等待
//1)、锁的自动续期;如果业务超长,运行期间自动给锁续上新的30s;不用担心业务时间长,锁自动过期被删掉
//2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
//lock.lock(30, TimeUnit.SECONDS); 指定时间,并手动解锁
try {
System.out.println("加锁成功,执行业务. Thread:"+Thread.currentThread().getId());
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("释放锁. Thread:"+Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
/**
* 写锁保证一定能读到最新数据,修改期间,写锁是一个排他锁(互诉锁,独享锁)。读锁是一个共享锁,写锁没释放,读就必须等待
* 写 + 读 (写的时候进行读操作):等待写锁释放
* 写 + 写 (写的时候进行写操作):阻塞方式
* 读 + 写 (读的时候进行写操作):等待读锁释放
* 读 + 读 :相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功。
*/
@RequestMapping("/write")
public String writeLock(){
RReadWriteLock lock = redisson.getReadWriteLock("w-lock");
String str = "";
RLock rLock = lock.writeLock();
try {
// 改数据加写锁,读数据加读锁
rLock.lock();
str = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("writerValue",str);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return str;
}
@RequestMapping("/read")
public String readLock(){
RReadWriteLock lock = redisson.getReadWriteLock("r-lock");
RLock rLock = lock.readLock();
String str = "";
try {
//加读锁
rLock.lock();
str = redisTemplate.opsForValue().get("writerValue").toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return str;
}
@RequestMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); // 等待闭锁都完成
return "各部门已下班,关门了。";
}
@RequestMapping("/go/{id}")
public String go(@PathVariable("id") String id){
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数减1
return id + "部门下班了。";
}
}
分布式锁的应用还需深入实践。