有这样一个需求,一个系统部署在两套服务器上访问同一个数据库,访问某个表单时需要确定没有没有其他请求在访问,如果有其他请求在访问,则给出相关提示,退出请求。

这里如果是同一个系统上的请求,可以利用加锁的机制进行同步控制,显然在两个系统下这点是无法做到的,同步就是不同的执行过程在操作同一资源时做的一些控制,比如谁可以操作这个资源就需要先拿到这个资源的访问权,如下图示:

 

 

根据同步控制的思想,设计一个简单的分布式锁方案,既然系统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分布式锁的简单设计,还有很多需要改进的地方。。。

posted on 2022-01-09 23:11  Judy518  阅读(43)  评论(0编辑  收藏  举报