温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Redis中怎么实现一个分布式文件夹锁

发布时间:2021-06-18 17:57:28 来源:亿速云 阅读:161 作者:Leah 栏目:大数据

Redis中怎么实现一个分布式文件夹锁,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

思考和调研

分布式锁常见的三种实现方式:数据库、zookeeper/etcd(临时有序节点)、redis(setnx/lua脚本),各有千秋。

数据库实现分布式锁

实现

原理简单易实现,创建一张lock表,存储锁定的资源、上锁对象、获取锁的资源、获取锁时间等,获取锁时查询该资源是否存在记录,存在且未过失效时间则获取锁失败,不存在则插入一条数据并且获取锁成功;释放锁则更简单,删除锁数据即可。

缺点
  • 释放锁删除数据时,会出现死锁情况

优点
  • 实现简单

zookeeper/etcd实现分布式锁

详见zookeeper总结

redis实现分布式锁

详见Redis总结

分布式文件夹锁实现过程

基于开文处所列情况,要覆盖所有复杂情况很难,但是实现基本的文件夹锁是必须的,故选择了redis+lua脚本,具体代码如下

java获取锁工具类

/**
 * redis工具类
 */
public class RedisLockUtils {

    static final Long SUCCESS = 1L;
    static final String LOCKED_HASH = "cs:lockedHashKey";
    static final String GET_LOCK_LUA_RESOURCE = "/lua/getFileLock.lua";
    static final String RELEASE_LOCK_LUA_RESOURCE = "/lua/releaseFileLock.lua";
    static final Logger LOG = LoggerFactory.getLogger(RedisLockUtils.class);
    
    /**
     * 获取文件夹锁
     * @param redisTemplate
     * @param lockProjectId
     * @param lockKey
     * @param requestValue
     * @param expireTime 单位:秒
     * @return
     */
    public static boolean getFileLock(RedisTemplate redisTemplate, Long lockProjectId, String lockKey, String requestValue, Integer expireTime) {

        LOG.info("start run lua script,{{}} start request lock",lockKey);
        long start = System.currentTimeMillis();
        DefaultRedisScript<String> luaScript =new DefaultRedisScript<>();
        luaScript.setLocation(new ClassPathResource(GET_LOCK_LUA_RESOURCE));
        luaScript.setResultType(String.class);

        Object result = redisTemplate.execute(
                luaScript,
                Arrays.asList(lockKey, LOCKED_HASH + lockProjectId),
                requestValue,
                String.valueOf(expireTime),
                String.valueOf(System.currentTimeMillis())
        );
        boolean getLockStatus = SUCCESS.equals(result);

        LOG.info("{{}} cost time {} ms,request lock result:{}",lockKey,(System.currentTimeMillis()-start), getLockStatus);
        return getLockStatus;
    }

    /**
     * 释放文件夹锁
     * @param redisTemplate
     * @param lockProjectId
     * @param lockKey
     * @param requestValue
     * @return
     */
    public static boolean releaseFileLock(RedisTemplate redisTemplate, Long lockProjectId, String lockKey, String requestValue) {

        DefaultRedisScript<String> luaScript =new DefaultRedisScript<>();
        luaScript.setLocation(new ClassPathResource(RELEASE_LOCK_LUA_RESOURCE));
        luaScript.setResultType(String.class);

        Object result = redisTemplate.execute(
                luaScript,
                Arrays.asList(lockKey, LOCKED_HASH + lockProjectId),
                requestValue
        );
        boolean releaseLockStatus = SUCCESS.equals(result);

        LOG.info("{{}}release lock result:{}", lockKey, releaseLockStatus);
        return releaseLockStatus;
    }

}

lua脚本

获取文件夹锁
入参说明

requestKey为请求锁的路径,requestValue为请求锁的value,应为请求锁时生成的UUID,确保解锁人只能为上锁人,lockedKeys为存放所有锁的哈希表的key,这里用常量加项目id的方式,确保一个项目的所有锁存在一个哈希表里面,expireTime为锁的过期时间,nowTime为当前时间,由于lua脚本里面获取当前时间消耗性能且获取的是redis服务器上的当前时间,可能不准确。

思路说明

首先,通过GET key判断是否有人正在操作这个文件夹,若有人在操作则直接返回0(获取锁失败),否则获取存放该项目锁的哈希表里面的所有key,遍历所有key,通过lua脚本的string.find函数对比该key和请求的key是否存在包含或被包含关系,若存在包含关系且未失效,则返回0(获取锁失败),否则则可获取锁,设置key和过期时间及存入哈希表(哈希表内存放请求锁的key和请求时间),最后返回1(获取锁成功)。

例如请求上图中项目下的C文件夹的锁,请求路径为:项目/A/C,当另一个人想操作D文件夹,请求路径为:项目/A/C/D,此时查询到存储这个项目所有锁定key的哈希表,里面包含项目/A/C这个key,这两个key通过lua函数string.find发现项目/A/C/D包含项目/A/C,且未到过期时间,则获取锁失败,否则获取锁成功。

local requestKey=KEYS[1]
local lockedKeys=KEYS[2]
local requestValue=ARGV[1]
local expireTime=ARGV[2]
local nowTime=ARGV[3]
if redis.call('get',requestKey)
then
    return 0
end
local lockedHash = redis.call('hkeys',lockedKeys)
for i=1, #lockedHash do
    if string.find(requestKey,lockedHash[i]) or string.find(lockedHash[i],requestKey)
    then
        local lockTime = redis.call('hget',lockedKeys,lockedHash[i])
        if (nowTime-lockTime) >= expireTime * 1000
        then
            redis.call('hdel',lockedKeys,lockedHash[i])
        else
            return 0
        end
    end
end
redis.call('set',requestKey,requestValue)
redis.call('expire',requestKey,expireTime)
redis.call('hset',lockedKeys,requestKey,nowTime)
return 1
释放文件夹锁
入参说明

requestKey为请求锁的路径,requestValue为请求锁的value,应为请求锁时生成的UUID,确保解锁人只能为上锁人,lockedKeys为存放所有锁的哈希表的key,这里用常量加项目id的方式,确保一个项目的所有锁存在一个哈希表里面。

local requestKey=KEYS[1]
local lockedKeys=KEYS[2]
local requestValue=ARGV[1]
if redis.call('get', requestKey) == requestValue
then
    redis.call('hdel', lockedKeys,requestKey)
    return redis.call('del',requestKey)
else
    return 0
end

优点

  1. 灵活,锁定的范围可以随requestKey变化而变化

  2. 性能不错,经测试除了第一次lua脚本未缓存耗时较长,第二次之后则在10ms左右可得到请求结果

缺点

  1. 可靠性依赖redis

  2. 不是可重入锁

  3. 维护成本较高,需熟知redis的5种数据结构及lua脚本

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI