有这样一个需求,一个系统部署在两套服务器上访问同一个数据库,访问某个表单时需要确定没有没有其他请求在访问,如果有其他请求在访问,则给出相关提示,退出请求。
这里如果是同一个系统上的请求,可以利用加锁的机制进行同步控制,显然在两个系统下这点是无法做到的,同步就是不同的执行过程在操作同一资源时做的一些控制,比如谁可以操作这个资源就需要先拿到这个资源的访问权,如下图示:
根据同步控制的思想,设计一个简单的分布式锁方案,既然系统A和系统B都要访问公共资源,能不能再设定一个公共的中间变量,系统A、B在访问公共资源之前先查看这个公共的中间变量的状态,如果这个公共中间变量的状态符合进一步获取公共资源的条件,则进行公共资源的操作,反之不能操作公共资源;根据以上思想,这里设计一个最简单的同步访问控制功能,还有很大的修改余地,但是为我们进一步学习打开了一个细缝,redis可以为我们提供什么帮助呢?
redis的string类型数据方法中有一个操作:
set key value ex nseconds nx:如果key不存在,则将key设置为value,同时设置超时时间为nseconds秒;如果key存在,则不进行操作
根据这个特性设定一个key值,如果key值不存在,则可以取到公共资源,同时给key赋值;这时候其他请求在访问资源之前给key赋值失败,则不能访问资源;待资源访问完毕,再将key值删除,则其他请求可以访问资源了;删除key的时候也不能乱删,需要根据value进行判断删除,以免A的请求结束后将B的key值删掉了,为保证操作原子性,可用LUA脚本进行删除操作:
if redis.call('get',KEYS[1])==ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
根据以上方案,实现如下:
1、引入pom redis依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.14.RELEASE</version> </dependency>
2、封装redis锁
@Component public class RedisLockUtil { @Autowired private RedisTemplate<String, Serializable> redisTemplate; //删除脚本,判断要删除的val是指定的val后删除key值 private final String RELEASE_LOCK_SCRIPT="if redis.call('get',KEYS[1]) == ARGV[1] " + "then return redis.call('del',KEYS[1]) else return 0 end"; private final DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_SCRIPT,Long.class); /** * 加锁,设置key值 * @param key * @param val * @return */ public boolean lock(String key,Serializable val){ //如果不存在该k-v,则设置该k-v并返回true,否则返回false,数值的失效时间设置为2分钟 if(redisTemplate.opsForValue().setIfAbsent(key,val,2, TimeUnit.MINUTES)){ return true; } return false; } /** * 删除k-v值,为了避免误删,需要判断值是否一样 * @param key */ public void unlock(String key,Serializable val){ long result = redisTemplate.execute(redisScript, Collections.singletonList(key),val); System.out.println(result); } }
这里的lock方法没有做while重试操作,设置失败后立马返回false;以上工具定义好后可以在系统中的某个方法中使用了:
@Service public class RedisLockUtilTest { @Autowired private RedisLockUtil redisLock;
public void test(String xxx){ String randomVal=UUID.randomUUID().toString();
//加锁
redisLock.lock(xxx,randomVal);
//todo xxx逻辑操作
//解锁
redisLock.unLock(xxx,randomVal);
}
}
以上是一个redis分布式锁的简单设计,还有很多需要改进的地方。。。