redis中怎么实现一个分布式文件夹锁,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
创新互联建站服务项目包括容城网站建设、容城网站制作、容城网页制作以及容城网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,容城网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到容城省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
分布式锁常见的三种实现方式:数据库、zookeeper/etcd(临时有序节点)、redis(setnx/lua脚本),各有千秋。
原理简单易实现,创建一张lock表,存储锁定的资源、上锁对象、获取锁的资源、获取锁时间等,获取锁时查询该资源是否存在记录,存在且未过失效时间则获取锁失败,不存在则插入一条数据并且获取锁成功;释放锁则更简单,删除锁数据即可。
释放锁删除数据时,会出现死锁情况
实现简单
详见zookeeper总结
详见Redis总结
基于开文处所列情况,要覆盖所有复杂情况很难,但是实现基本的文件夹锁是必须的,故选择了redis+lua脚本,具体代码如下
/** * 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(); DefaultRedisScriptluaScript =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 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; } }
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
灵活,锁定的范围可以随requestKey
变化而变化
性能不错,经测试除了第一次lua脚本未缓存耗时较长,第二次之后则在10ms左右可得到请求结果
可靠性依赖redis
不是可重入锁
维护成本较高,需熟知redis的5种数据结构及lua脚本
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注创新互联行业资讯频道,感谢您对创新互联的支持。