一、整合redisson作为分布式锁等功能框架
1、引入依赖
<!-- 用redisson作为所有分布式锁,分布式对象等功能框架-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.5</version>
</dependency>
2、配置redission
@Configuration
public class MyRedisConfig {
@Value("${ipAddr}")
private String ipAddr;
// redission通过redissonClient对象使用 // 如果是多个redis集群,可以配置
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
//1、创建配置
Config config = new Config();
// 创建单例模式的配置
config.useSingleServer().setAddress("redis://" + ipAddr + ":6379");//rediss代表安全模式
//2、根据Config创建出RedissonClient示例
return Redisson.create(config);
}
}
———————————————————————————————————————————
二、分布式锁Redisson-lock锁测试
1)、加锁解锁
@ResponseBody
@RequestMapping("/index/hello")
public String hello() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("my-lock");
//2、加锁
//阻塞式等待。默认加的锁都是30s时间
//模拟超长时间,两次请求,第二次会被阻塞,等待第一次请求之后才能执行
lock.lock(10, TimeUnit.SECONDS);//或lock.lock();看门狗
//1)、RLock锁有看门狗机制 会自动帮我们续期,锁的自动续期,如果业务超长,运行期间会自动给锁续上新的30s。不用担心业务时间长,锁会自动过期被删除掉
//2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除
//总结看门狗1)自动续期 2)自动解锁
try {
System.out.println("加锁成功,执行业务...."+Thread.currentThread().getId());
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3、解锁
System.out.println("释放锁...."+Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
2)、看门狗原理、如何解决死锁
1、RLock锁有看门狗机制 会自动帮我们续期,默认30秒自动过期
2、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间,没有看门狗
//10秒自动解锁,自动解锁一定要大于业务的执行时间
//lock.lock(10, TimeUnit.SECONDS);在锁时间到了以后,不会自动续期
//如果设置了解锁时间,业务还没执行完,解锁了,另一个业务还可以进来加锁 就会报错
lock.lock(10, TimeUnit.SECONDS);
3、看门狗机制:如果我们未指定锁的超时时间,就是使用30*1000【LockWatchdogTimeout看门狗的默认时间】,只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期,续成30s[每隔internalLockLeaseTime【看门狗时间】/3 (10s)]
4、最佳实战:使用lock.lock(30, TimeUnit.SECONDS); //时间设置大一点,使用指定时间加锁,省掉整个续期时间,然后手动解锁
———————————————————————————————————————————
三、读写锁 writeLock readLock
概念:改数据加写锁,读数据加读锁,想要读取数据,就必须等待写锁释放,保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁、独享锁),读锁是一个共享锁,写锁没释放,读就必须等待
读+读:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁,他们都会同时加锁
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁,写也需要等待
*只要有写的存在,都必须等待
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 读写锁
*/
@GetMapping("/index/write")
@ResponseBody
public String writeValue() {
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = lock.writeLock();
String s = "";
try {
rLock.lock();
s = UUID.randomUUID().toString();
Thread.sleep(3000);
stringRedisTemplate.opsForValue().set("writeValue", s);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
@GetMapping("/index/read")
@ResponseBody
public String readValue() {
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = lock.readLock();
String s = "";
rLock.lock();
try {
s = stringRedisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
———————————————————————————————————————————
四、闭锁,全部执行才能解锁
@Resource
private RedissonClient redissonClient;
/**
* 放假锁门
* 5个班全部走完,我们才能锁大门
* 闭锁 只有设定的人全通过才关门
*/
@ResponseBody
@GetMapping("/index/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
// 设置这里有5个人
//等待执行五次才能解锁
door.trySetCount(5);
door.await();
//执行五次才能执行以下业务
return "5个人全部通过了...";
}
@ResponseBody
@GetMapping("/index/go/{id}")
public String go(@PathVariable("id") Long id) throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
// 每访问一次相当于出去一个
door.countDown();
return id + "走了";
}
———————————————————————————————————————————
五、信号量测试(秒杀),也可以用作限流
@Resource
private RedissonClient redissonClient;
/**
* 尝试获取车位 [信号量]
* 信号量:也可以用作限流
*/
@ResponseBody
@GetMapping("/index/park")
public String park() {
RSemaphore park = redissonClient.getSemaphore("park");
//park.cquire();每执行一次锁就+1,获取一个值,占用一个车位
boolean acquire = park.tryAcquire();
if(acquire){
//执行业务
}else{
return "error";//当前流量过大 请稍等,也可以用作分布式限流
}
return "获取车位 =>" + acquire;
}
/**
* 尝试获取车位
*/
@ResponseBody
@GetMapping("/index/go/park")
public String goPark() {
RSemaphore park = redissonClient.getSemaphore("park");
park.release();
return "ok => 车位+1";
}
———————————————————————————————————————————
六、实战
/**
* redisson 微服务集群锁
* 缓存中的数据如何与数据库保持一致
*/
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithRedissonLock() {
// 这里只要锁的名字一样那锁就是一样的
// 关于锁的粒度 具体缓存的是某个数据 例如: 11-号商品 product-11-lock
RLock lock = redissonClient.getLock("CatelogJson-lock");
lock.lock();
Map<String, List<Catelog2Vo>> data;
try {
data = getDataFromDB();
} finally {
lock.unlock();
}
return data;
}
———————————————————————————————————————————
七、缓存数据一致性-失效模式
我们系统的一致性解决方案:
1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
2、读写数据的时候,加上分布式的读写锁。
经常写,经常读