当前位置: 首页 > news >正文

浅谈redis分布式锁

浅谈redis分布式锁

分布式锁介绍

分布式锁,顾名思义,分布式系统中的锁,当多个进程不在同一个系统中时,用分布式锁控制各个进程对共享资源的访问,通过互斥来保持一致性。

使用场景:电商中某商品的秒杀活动,接口中的幂等性校验等

分布式锁的特性

(1)互斥性。锁的目的是获取共享资源的使用权,则需保证在并发情况下,同一时刻只有一个线程能获得锁
(2)加锁解锁对称性。线程使用加锁获得对共享资源的使用后,使用完毕必须解锁,即,加锁解锁需为同一个线程
(3)防止死锁。假如由系统等原因出现宕机情况导致线程获取到锁后来不及解锁,而其他线程无法获取到锁,此情况为死锁,避免此情况,有必要设置锁的有效时间,确保系统出故障,在超出锁的有效时间后其他线程能获取到锁。
(4)锁粒度尽量小。锁的颗粒度要尽量小,避免导致大量线程同时为获取同一个锁而造成阻塞
(5)可重入性。同一个线程在锁使用期间可以重复拿到同一个资源的锁。

常用的三种分布式锁

(1) 基于数据库表主键唯一性原理、排他锁实现分布式锁
(2) 基于ZK临时有序节点实现的分布式锁
(3) 基于redis中setnx命令的原子性实现的分布式锁

Redis分布式锁的阶段性进展

基于redis为目前最常见的锁及之前介绍的redis原理,简单介绍下redis分布式锁

实现一个简易的Redis分布式锁

阶段性1、利用redis的setnx和expire命令设置一个简单的redis锁,代码如下:

        String thread = "thread";Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", thread,30, TimeUnit.SECONDS);if(lock){//(1)加锁成功,处理handle("此处执行业务逻辑");//(2)处理完毕后解锁Object lockValue = redisTemplate.opsForValue().get("lock");if(thread.equals(lockValue)){//根据值对比判断是此线程的锁后删除锁redisTemplate.delete("lock");// 删除锁}}else{// 加锁失败: 重试或者直接返回获取锁失败retryOrReturn();}

以上代码看着没什么问题,实现了针对某个线程获取锁,且设置超时时间,并且根据值对比判断,只能此线程解锁。然而,忽略一个问题,由于在获取锁并删除这个过程中并非原子性,假如线程A删除锁的时候,锁已超时,自动解锁,同时其他线程B获取到锁,此时A把B持有的锁给删除。
那么如何实现锁对比判断和删除是一个原子性呢?见阶段性2

引入LUA

阶段性2、引入LUA删除锁
引入LUA,在获取锁的value值,对比是否一致,假如相等则删除,此段过程保证其原子性。代码如下:

        String lockKey = "lock";String thread = "thread";Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, thread,30, TimeUnit.SECONDS);if(lock){//(1)加锁成功,处理handle("此处执行业务逻辑");//(2)处理完毕后解锁Object lockValue = redisTemplate.opsForValue().get("lock");//LUA脚本String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 原子删除Object lock1 = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList(lockKey, thread));}else{// 加锁失败: 重试或者直接返回获取锁失败retryOrReturn();}

框架Redission

以上结合redis+Lua的实现,在框架redission中均一实。Redission,在一个提供redis基础功能实现的情况下,还提供了一系列的分布式服务。从而使使用者的开发能够集中的专注于业务逻辑上。
除redission具备了锁的锁的互斥性和可重入性等基本功能,还增加了其他功能,比如引入Watch Dog解决了一个关于锁的自动续期问题,以及引入Red Lock增加了一些更安全的锁实现
简易代码如下:

       RLock lock = redisson.getLock ("lock");//获取锁或阻塞等待lock.lock ();try {handle("此处执行业务逻辑");} finally {//释放锁,已封装获取到此线程的锁并封装lock.unlock ();}

看门狗(Watch Dog)

场景:假如线程A获取到锁后,由于执行的逻辑耗时比较长, 在运行期间超出了默认的超时时间(30s)范围。则出现在线程A未执行完毕正常释放锁的情况下,由超时解锁,线程B重新获取到锁,引发故障。
针对此场景,引入Redission的Watch Dog实现自动续期,保证在线程A使用期间,不会超时而引发其他多个线程同时持有锁的情况
原理:看门狗相当于是一个定时任务,线程一旦加锁成功,会对应启动一个看门狗(属于后台线程)。每10s观察线程是否还持有锁,如果有,则延迟锁的的持有时间,将时间重置到30s,直至线程主动解锁或者系统故障看门狗不执行
注意:如果指定超时时间,不会自动续签时间,此时需保证线程执行业务逻辑的时间务必大于指定超时时间。

关于红锁(RedLock)

场景:假设在集群中,有多个redis master节点,这些节点是完全独立的,其中一个master获取到锁后发生故障,此时还未来得及发生主从复制,即key未来得及同步到从节点上。而此时通过哨兵选举,其一slave节点升级为master节点。那么此时会出现,后续应用会申请到同一个锁,则此时同一个锁被获取了不只一次,导致出现问题。
针对以上情况,引入红锁,利用多个 Redission node 最终 组成 RedLock分布式锁,Redission node 是互相独立的,不存在任何复制或者其他隐含的分布式协调机制。解决主从结构下存在的安全问题。

RedLock特点
加锁过程中,在一个redis集群中,依次从N个reidis节点上获取锁(需要相同的key和value),并且至少半数以上(N/2+1)的redis节点获取到锁,才算是获取锁成功,否则获取失败。红锁以节点组的方式解决单个节点出现故障的情况。
** 场景**:假设redis集群中五个主节点,客户端申请获取锁的请求到了redis节点(节点三个A\B\C获取到锁)并成功执行setnx操作,此时假如其中一节点A宕机,则返回给客户端的响应失败,在客户端层面看,是获取锁超时而失败,但是在redis集群看来是获取锁成功。然后客户端在释放锁时,也会对那些获取锁失败的redis节点发起同样的请求。

RedLock弊端:在加锁/解锁多个节点,其过程均耗费时间,性能较低。针对红锁,假如全部redis重启,也可能会出现锁失效的问题。
因此是否使用红锁,需结合实际场景使用

http://www.lryc.cn/news/168993.html

相关文章:

  • 【Python保姆级教程】List容器
  • 微服务保护-授权规则
  • v-if失效原因
  • Chrome 基于 Wappalyzer 查看网站所用的前端技术栈
  • python的装饰器
  • P2P协议的传输艺术
  • 辅助驾驶功能开发-功能规范篇(21)-4-XP行泊一体方案功能规范
  • 家政服务小程序上门服务小程序预约上门服务维修保洁上门服务在线派单技师入口
  • LeetCode精选100题-【3数之和】-2
  • springboot集成mybatis-plus
  • 再想一想GPT
  • Blazor前后端框架Known-V1.2.15
  • Tomcat 的部署和优化
  • 后端中间件安装与启动(Redis、Nginx、Nacos、Kafka)
  • 【电子元件】常用电子元器件的识别之电阻器
  • 指针和数组笔试题讲解(2)
  • MapReduce YARN 的部署
  • vue 引入zTree
  • 链队列的基本操作(带头结点,不带头结点)
  • 深入学习 Redis Cluster - 基于 Docker、DockerCompose 搭建 Redis 集群,处理故障、扩容方案
  • C现代方法(第3、4章)笔记
  • R语言绘制染色体变异位置分布图,RIdeogram包
  • Vue知识系列(7)每天10个小知识点
  • 5分钟就能实现的API监控,有什么理由不做呢?
  • Jmeter引入外部jar包以满足加密数据的Post请求
  • 了解冒泡排序
  • 群辉 Synology NAS Docker 安装 RustDesk-server 自建服务器只要一个容器
  • 为什么要有override
  • Linux界的老古董
  • 安卓逆向 - Xposed入门教程