分布式锁+分布式下的缓存问题
- 当微服务模式时,多个相同服务之间对于数据库需要保持数据一致性.此时需要从本地锁 演变为 分布式锁.
- 本博文通过进阶的形式 不断提出问题以及解决思路,一步一步完善代码,实现具有高可靠性的分布式锁功能.
使用redis的set命令带NX(not exist)参数实现分布式锁
NX:只有当不存在时,才可以set;成功set会返回OK,不成功返回null
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段二 独立加上分布式锁的过期时间
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
//2、设置过期时间
stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段三 原子占锁和设置过期时间
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock",30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段四 删锁进行权限uuid匹配
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
String lock = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lock)){
//删除自己的锁
stringRedisTemplate.delete("lock");
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段五 lua脚本 删锁原子操作
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段六 最终结果
不论业务是否正确完成都删除自己建立的锁
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = null;
try {
dataFromDB = this.getDataFromDB();
}finally {
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDBWithRedisLock();
}
}
使用Redisson框架实现分布式锁
如果手动使用redis实现分布式锁,在每一次业务处理前都需要编写原子上锁和lua脚本原子解锁语句,过于繁琐,使用Redisson分布式锁的框架能更加方便实现.Reddison底层实现了JUC包下的方法,因此可用无缝对接JUC的使用.
引入依赖
<!--引入redis的分布式锁 分布式对象框架redison-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
配置redissonClient客户端
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson的使用 都是通过RedissonClient对象
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() throws IOException{
//1、创建配置 redisson包下的config
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.231.1:6379");
//2、根据config创建出RedissonClient示例
return Redisson.create(config);
}
}
使用redisson锁实现分布式锁
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedissonLock() {
//1、占分布式锁。redisson
/**
* 锁的名字 决定了锁的粒度,越细越快
* 锁的粒度约定: 具体缓存的是某个数据 例如 11号商品 product-11-lock
*/
RLock lock = redissonClient.getLock("catalogJson-lock");
lock.lock();
Map<String, List<Catelog2Vo>> dataFromDB = null;
try{
//加锁成功 执行业务
dataFromDB = this.getDataFromDB();
}finally{
lock.unlock();
}
return dataFromDB;
}