1 LUA+SETNX+EXPIRE

先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。

  • setnx(key, value)

    setnx 的含义就是 SET if Not Exists,该方法是原子的。如果 key 不存在,则设置当前 keyvalue 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0

  • expire(key, seconds)

    expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。

1.1 使用Lua脚本(SETNX+EXPIRE)

可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),加解锁代码如下:

/*** 使用Lua脚本,脚本中使用setnex+expire命令进行加锁操作*/
public boolean lock(Jedis jedis, String key, String uniqueId, int seconds) {String luaScript = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +"redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";Object result = jedis.eval(luaScript, Collections.singletonList(key),Arrays.asList(uniqueId, String.valueOf(seconds)));return result.equals(1L);
}/*** 使用Lua脚本进行解锁操纵,解锁的时候验证value值*/
public boolean unlock(Jedis jedis, String key, String value) {String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +"return redis.call('del',KEYS[1]) else return 0 end";return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
}

1.2 STW

如果在写文件过程中,发生了 FullGC,并且其时间跨度较长, 超过了锁超时的时间, 那么分布式就自动释放了。在此过程中,client2 抢到锁,写了文件。client1 的FullGC完成后,也继续写文件,注意,此时 client1 的并没有占用锁,此时写入会导致文件数据错乱,发生线程安全问题。这就是STW导致的锁过期问题。STW导致的锁过期问题,如下图所示:

STW导致的锁过期问题,大概的解决方案有:

  • 方案一: 模拟CAS乐观锁的方式,增加版本号(如下图中的token)

​ 此方案如果要实现,需要调整业务逻辑,与之配合,所以会入侵代码。

  • 方案二:watch dog自动延期机制

    客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?简单!只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。Redission采用的就是这种方案, 此方案不会入侵业务代码。

2 SET-NX-EX

方案SET key value [EX seconds] [PX milliseconds] [NX|XX]

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value
  • XX :只在键已经存在时,才对键进行设置操作

客户端执行以上的命令:

  • 如果服务器返回 OK ,那么这个客户端获得锁
  • 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试

2.1 加锁

使用redis命令 set key value NX EX max-lock-time 实现加锁。

Jedis jedis = new Jedis("127.0.0.1", 6379);
private static final String SUCCESS = "OK";/*** 加锁操作* @param key 锁标识* @param value 客户端标识* @param timeOut 过期时间*/public Boolean lock(String key,String value,Long timeOut){String var1 = jedis.set(key,value,"NX","EX",timeOut);if(LOCK_SUCCESS.equals(var1)){return true;}return false;}
  • 加锁操作 jedis.set(key,value,"NX","EX",timeOut)【保证加锁的原子操作】
  • keyrediskey值作为锁的标识,value在作为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】
  • 通过timeout设置过期时间保证不会出现死锁【避免死锁】
  • NX:只有这个key不存才的时候才会进行操作,if not exists
  • EX:设置key的过期时间为秒,具体时间由第5个参数决定,过期时间设置的合理有效期需要根据业务具体决定,总的原则是任务执行time*3

2.2 解锁

使用redis命令 EVAL 实现解锁。

Jedis jedis = new Jedis("127.0.0.1", 6379);
private static final String SUCCESS = "OK";/*** 加锁操作* @param key 锁标识* @param value 客户端标识* @param timeOut 过期时间*/public Boolean lock(String key,String value,Long timeOut){String var1 = jedis.set(key,value,"NX","EX",timeOut);if(LOCK_SUCCESS.equals(var1)){return true;}return false;}
  • luaScript 这个字符串是个lua脚本,代表的意思是如果根据key拿到的value跟传入的value相同就执行del,否则就返回0【保证安全性】
  • jedis.eval(String,list,list);这个命令就是去执行lua脚本,KEYS的集合就是第二个参数,ARGV的集合就是第三参数【保证解锁的原子操作】

2.3 重试

如果在业务中去拿锁如果没有拿到是应该阻塞着一直等待还是直接返回,这个问题其实可以写一个重试机制,根据重试次数和重试时间做一个循环去拿锁,当然这个重试的次数和时间设多少合适,是需要根据自身业务去衡量的。

/*** 重试机制* @param key 锁标识* @param value 客户端标识* @param timeOut 过期时间* @param retry 重试次数* @param sleepTime 重试间隔时间* @return*/
public Boolean lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime){Boolean flag = false;try {for (int i=0;i<retry;i++){flag = lock(key,value,timeOut); if(flag){break; } Thread.sleep(sleepTime); } }catch (Exception e){ e.printStackTrace(); } return flag;
}

3 Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。

3.1 特性功能

  • 支持 Redis 单节点(single)模式、哨兵(sentinel)模式、主从(Master/Slave)模式以及集群(Redis Cluster)模式
  • 程序接口调用方式采用异步执行和异步流执行两种方式
  • 数据序列化,Redisson 的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在 Redis 里的读取和存储
  • 单个集合数据分片,在集群模式下,Redisson 为单个 Redis 集合类型提供了自动分片的功能
  • 提供多种分布式对象,如:Object Bucket,Bitset,AtomicLong,Bloom Filter 和 HyperLogLog 等
  • 提供丰富的分布式集合,如:Map,Multimap,Set,SortedSet,List,Deque,Queue 等
  • 分布式锁和同步器的实现,可重入锁(Reentrant Lock),公平锁(Fair Lock),联锁(MultiLock),红锁(Red Lock),信号量(Semaphonre),可过期性信号锁(PermitExpirableSemaphore)等
  • 提供先进的分布式服务,如分布式远程服务(Remote Service),分布式实时对象(Live Object)服务,分布式执行服务(Executor Service),分布式调度任务服务(Schedule Service)和分布式映射归纳服务(MapReduce)

3.2 Watch dog

总体的Redisson框架的分布式锁类型大致如下:

  • 可重入锁
  • 公平锁
  • 联锁
  • 红锁
  • 读写锁
  • 信号量
  • 可过期信号量
  • 闭锁(/倒数闩)

3.3 实现方案

添加依赖

<!-- 方式一:redisson-java -->
<dependency>  <groupId>org.redisson</groupId> <artifactId>redisson</artifactId>   <version>3.11.4</version>
</dependency><!-- 方式二:redisson-springboot -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.11.4</version>
</dependency>

定义接口

import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;public interface DistributedLocker {RLock lock(String lockKey);RLock lock(String lockKey, int timeout);RLock lock(String lockKey, TimeUnit unit, int timeout);boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);void unlock(String lockKey);void unlock(RLock lock);}

实现分布式锁

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;import java.util.concurrent.TimeUnit;public class RedissonDistributedLocker implements DistributedLocker{private RedissonClient redissonClient;@Overridepublic RLock lock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.lock();return lock;}@Overridepublic RLock lock(String lockKey, int leaseTime) {RLock lock = redissonClient.getLock(lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);return lock;}@Overridepublic RLock lock(String lockKey, TimeUnit unit ,int timeout) {RLock lock = redissonClient.getLock(lockKey);lock.lock(timeout, unit);return lock;}@Overridepublic boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {RLock lock = redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, unit);} catch (InterruptedException e) {return false;}}@Overridepublic void unlock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.unlock();}@Overridepublic void unlock(RLock lock) {lock.unlock();}public void setRedissonClient(RedissonClient redissonClient) {this.redissonClient = redissonClient;}}

3.4 高可用的RedLock(红锁)原理

RedLock算法思想是不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题。

更多JAVA、高并发、微服务、架构、解决方案、中间件的总结在:https://github.com/yu120/lemon-guide

微服务-分布式锁(二)-Redis方案相关推荐

  1. SpringCloud 微服务分布式 笔记(二)

    SpringCloud 微服务分布式 本篇是结合上一篇代码进行修改学习 点击

  2. Spring Cloud Alibaba微服务项目中集成Redis实现分布式事务锁实践

    引言 我们知道同一个进程里面为了解决资源共享而不出现高并发的问题可以通过高并发编程解决,通过给变量添加volatile关键字实现线程间变量可见:通过synchronized关键字修饰代码块.对象或者方 ...

  3. spring cloud微服务分布式云架构 - 整合企业架构的技术点(二)

    点击上面 免费订阅本账号! 本文作者:it菲菲 原文:https://yq.aliyun.com/articles/672231 点击阅读全文前往 spring cloud本身提供的组件就很多,但我们 ...

  4. (二)spring cloud微服务分布式云架构 - 整合企业架构的技术点

    spring cloud本身提供的组件就很多,但我们需要按照企业的业务模式来定制企业所需要的通用架构,那我们现在需要考虑使用哪些技术呢? 下面我针对于spring cloud微服务分布式云架构做了以下 ...

  5. (二)微服务分布式云架构 - spring cloud整合企业架构的技术点

    spring cloud本身提供的组件就很多,但我们需要按照企业的业务模式来定制企业所需要的通用架构,那我们现在需要考虑使用哪些技术呢? 下面我针对于spring cloud微服务分布式云架构做了以下 ...

  6. (二)spring cloud微服务分布式云架构-整合企业架构的技术点

    spring cloud本身提供的组件就很多,但我们需要按照企业的业务模式来定制企业所需要的通用架构,那我们现在需要考虑使用哪些技术呢?Spring Cloud大型企业分布式微服务云架构源码请加一七九 ...

  7. 谷粒商城微服务分布式基础篇二—— Spring Cloud Alibaba、Nacos注册与发现

    文章目录 Spring Cloud Alibaba--微服务开发 Spring Cloud Alibaba是什么?  主要功能  组件 为什么不使用Spring Cloud 使用 Nacos Disc ...

  8. 分布式锁用 Redis 还是 Zookeeper?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:jianfeng 来源:石杉的架构笔记 为什么用分布式锁? ...

  9. redis 分布式锁 看门狗_漫谈分布式锁之Redis实现

    笔耕墨耘,深研术道. 01写在前面Redis是一个高性能的内存数据库,常用于数据库.缓存和消息中间件.它提供了丰富的数据结构,更适合各种业务场景:基于AP模型,Redis保证了其高可用和高性能. 本文 ...

最新文章

  1. R语言包_reshape2
  2. 2016企业开发趋势:Lightbend关于JVM开发者的调查
  3. 要做的题目-要用到hadoop资源
  4. 直接使用Berkeley DB的Memory Pool 功能
  5. SpringBoot+Vue使用Get请求时提示:Error parsing HTTP request header
  6. day15 Ui自动化元素的定位
  7. currentTitle的用法
  8. 基于LVS对LAMP做负载均衡集群
  9. java图片的导出,并压缩
  10. web前端-----跨域
  11. 说说单节点集群里安装hive、3\5节点集群里安装hive的诡异区别(版本搭配)
  12. 非常好的关于android安全的博客
  13. 共享文件夹只能连接20人_英语正能量 | 快乐可以与人共享,苦难却只能自己坚强...
  14. usbcan系列便携式can分析仪
  15. Multisim卸载后重新安装不上解决方案
  16. 办公office 2019软件有哪些
  17. 筚路蓝缕,以启山林 | 做RPA行业的坚守者与创新者——数据猿专访云扩科技CTO史秋芳
  18. 电影《战狼2》的可视化分析
  19. eclipse的32位和64位版本(亲测有效)
  20. 个人中端深度学习工作站配置(硬件系统篇)

热门文章

  1. STL--list的模拟实现
  2. elementUI 表格宽度自适应、不换行
  3. P2749 夜空繁星
  4. 2022-1-17第三章机器学习基础--网格搜索超参数优化、决策树、随机森林
  5. 大学学python用记笔记吗_python笔记(1)-关于我们应不应该继续学习
  6. 我的校招季大概也是结束了。
  7. 什么是DAOstack
  8. 计算机基础实训项目二 Word 综合应用,计算机应用基础-实训项目二 Word 综合应用[优质文档]...
  9. Java中 字节转 KB/MB/GB
  10. sqlsugar模糊查询