关于Redis分布式锁的分享

背景

  解决进程互斥访问共享资源
  对于三种分布式锁方式(mysql,redis,zookeeper)性能redis最高;可靠性zookeeper最高;
  本文简单介绍redis是如何在分布式情况系加锁的

1,reids分布式锁基本实现

保证已存在时不能重新设值;删除key后可以重新设值.

setnx 【lockKey】【value】
//加锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lockKey", "1");
//删锁
redisTemplate.delete("lockKey");

2,设置过期时间

某些情况下,需要防止某线程长时间占据资源,为锁设置过期时间。

set 【key】 【value 】nx ex 【seconds】
//加锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lockKey", "1", 300, TimeUnit.SECONDS);
//删锁
redisTemplate.delete("lockKey");

3,设置唯一的value

在分布式情况下,存在某下情况锁被其他线程删除的情况:线程A因为某些原因(服务器卡顿……)超出过期时间后,自动释放锁。释放锁后,线程B获得锁,B执行操作中,A修复并完成操作手动释放锁,导致B锁被A释放;
解决:使用唯一值区分线程,防止误删,如uuid

set 【key】 【uuid】nx ex 【seconds】
//加锁
String uuid = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lockKey", uuid, 300, TimeUnit.SECONDS);
//删锁       
if (uuid.equals(ops.get("lockKey"))) {
	redisTemplate.delete("lockKey");
      //访问redis获取锁值后,删除锁之前,锁过期了,其它线程已创建了新锁,删除错误;
      //解决:保证查锁,删锁原子操作(使用LUA脚本)
}

这样在释放锁时,需要先校验自己的uuid,匹配键值是自己设置锁时的uuid,才能释放锁,这样就不会造成错误释放锁的情况。

4,保证原子性

特殊的,uuid已经校验成功,此时将释放锁,巧合的是正好此时,锁过期了,B快一步加上了锁,A释放锁,此时释放的是B的锁。
原因:不是原子操作;
解决方法:必须保证原子性的执行删除操作,使用lua脚本

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),Collections.singletonList("lockKey"),uuid);

简单应用示例

public Data getDataFromDbWithRedisLock() {
   	String uuid = UUID.randomUUID().toString();
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    Boolean locked = ops.setIfAbsent("Lock", uuid, 300, TimeUnit.SECONDS);
    Data data = null;
    if (locked) {
        try{
            data = this.getDataFromDb();//查数据库
        }finally{
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Collections.singletonList("Lock"),uuid);
        }
        return data ;
    } else {
        //加锁失败,其他线程占用锁,延迟自旋,消耗cpu资源
        try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
        return getDataFromDbWithRedisLock();
    }
}

5,保证一致性

对于要求强一致性的场景,如交易,该种方案还是有漏洞的.
原因:

  redis主从架构采用的是异步复制,当master节点拿到了锁,但是锁还未同步到slave节点,
  此时master节点挂了,发生故障转移,slave节点被选举为master节点,丢失了锁。
  这样其他线程就能够获取到该锁,显然是有问题的。
  因此,上述基于redis实现的分布式锁只是满足了AP,并没有满足C。

解决:
Redlock (Distributed locks with Redis) https://redis.io/topics/distlock

RedLock是基于redis实现的分布式锁,它能够保证以下特性:
      互斥性:在任何时候,只能有一个客户端能够持有锁;
      避免死锁:当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
      容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁;

使用Redisson或者Spring Cache解决一致性问题,这里不再详细介绍。

Redisson简单介绍

  Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)
  充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。
  使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。
  同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
  基于NIO的Netty框架不仅能作为Redis底层驱动客户端,具备提供对Redis各种组态形式的连接功能,对Redis命令能以同步发送、
  异步形式发送、异步流形式发送或管道形式发送的功能,LUA脚本执行处理,以及处理返回结果的功能,还在此基础上融入了更高级的应用方案,
不但将原生的Redis Hash,List,Set,String,Geo,HyperLogLog等数据结构封装为Java里大家最熟悉的映射(Map),列表(List),
集(Set),通用对象桶(Object Bucket),地理空间对象桶(Geospatial Bucket),基数估计算法(HyperLogLog)等结构,在这基础上还提供了Redis原本没有的分布式数据结构。分布式的多值映射(Multimap),本地缓存映射(LocalCachedMap),有序集(SortedSet),计分排序集(ScoredSortedSet),字典排序集(LexSortedSet),列队(Queue),阻塞队列(Blocking Queue),有界阻塞列队(Bounded Blocking Queue),双端队列(Deque),阻塞双端列队(Blocking Deque),阻塞公平列队(Blocking Fair Queue),延迟列队(Delayed Queue), 布隆过滤器(Bloom Filter),原子整长形(AtomicLong),原子双精度浮点数(AtomicDouble),BitSet等 不仅如此,Redisson还实现了Redis文档中提到像分布式锁Lock这样的更高阶应用场景。
  事实上Redisson并没有不止步于此,在分布式锁的基础上还提供了联锁(MultiLock),读写锁(ReadWriteLock),公平锁(Fair Lock),
红锁(RedLock),信号量(Semaphore),可过期性信号量(PermitExpirableSemaphore)和闭锁(CountDownLatch)这些实际当中对多线程高并发应用至关重要的基本部件。正是通过实现基于Redis的高阶应用方案,使Redisson成为构建分布式系统的重要工具。
  Redisson广泛的使用了承载于Redis订阅发布功能之上的分布式话题(Topic)功能。使得即便是在复杂的分布式环境下,Redisson的各个实例仍然具有能够保持相互沟通的能力。在以这为前提下,结合了自身独有的功能完善的分布式工具,Redisson进而提供了像分布式远程服务(Remote Service),分布式执行服务(Executor Service)和分布式调度任务服务(Scheduler Service),这样适用于不同场景的分布式服务。使得Redisson成为了一个基于Redis的Java中间件(Middleware)。
  Redisson Node的出现作为驻内存数据网格的重要特性之一,使Redisson能够独立作为一个任务处理节点,以系统服务的方式运行并自动加入Redisson集群, 具备集群节点弹性增减的能力。然而在真正意义上让Redisson发展成为一个完整的驻内存数据网格的,还是具有将基本上任何复杂、多维结构的对象都能变为分布式对象的分布式实时对象服务(Live Object Service),以及与之相结合的,在分布式环境中支持跨节点对象引用(Distributed Object Reference)的功能。这些特色功能使Redisson具备了在分布式环境中, 为Java程序提供了堆外空间(Off-Heap Memory)储存对象的能力。
  Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。如果您现在正在使用其他的Redis的Java客户端,希望Redis命令和Redisson对象匹配列表能够帮助您轻松的将现有代码迁徙到Redisson里来。如果目前Redis的应用场景还仅限于作为缓存使用,您也可以将Redisson轻松的整合到像Spring和Hibernate这样的常用框架里。除此外您也可以间接的通过Java缓存标准规范JCache API (JSR-107)接口来使用Redisson。
  Redisson生而具有的高性能,分布式特性和丰富的结构等特点恰巧与Tomcat这类服务程序对会话管理器(Session Manager)的要求相吻合。利用这样的特点,Redisson专门为Tomcat提供了会话管理器(Tomcat Session Manager)。
……

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值