当我们开始开发项目部署运行时,项目规模不大,只是在一个JVM实例中运行,对同一资源的并发访问用JDK自带的锁机制就可以解决资源同时访问的问题。而随着项目的不断发展,单体应用已经无法满足日益增长的访问需求,我们开始考虑多台部署,提高接收客户端的连接请求,提高项目的吞吐量。一台变多台,其中不可避免的问题就是如何控制解决不同线程对同一资源的并发访问。其中一种手段就是使用redis进行分布式锁的控制。

  我们可以在获取访问资源锁之前判断redis中是否存在对应代表该资源锁key的value,如果存在,则说明已经被获取,反之还没有客户端获取该资源对应的锁,可以进行获取锁。

     boolean lock = false;
try {
lcok = getLock(taskId); //获取锁
if (lock) {
doSomething(); //业务逻辑
}
} finally {
if (lock) {
releaseLock(taskId); //释放锁
}
}
 public static boolean getLock(String taskId) {
if (existsKey(taskId)) {
return false;
} else {
setKey(taskId);
return true;
}
}

  上面的部分实现代码给了一个大概的解决思路,看起来没有问题的,但是仔细看看还是存在问题滴,存在什么问题呢?

  当正在执行doSomething()方法时,突然系统宕机挂掉了,无法执行释放锁的操作,redis中对应的资源key的锁一直存在,之后运行代码就会出现问题。另一个问题就是执行getLock(taskId)方法时,该方法不是原子性的,有可能同时两个线程都判断为不存在该资源锁,都执行了setKey方法,导致同时获得锁资源的情况。

  如何解决上面的两个问题呢?从Redis官方API中有SET my_key my_value NX PX milliseconds的方法,得到了解决方案。它提供了一个只有在某个key不存在的情况下才会设置key的值的原子命令,该命令也能设置key值过期时间。其中,NX表示只有当键key不存在的时候才会设置key的值,PX表示设置键key的过期时间,单位是毫秒。

  到现在是否完全解决了并发获取锁的问题了呢?系统可能存在这种情况,当客户端A获取锁之后,执行业务代码的时间超过了之前设置的过期时间,导致锁的自动释放,而客户端B刚好获得新的资源锁,但客户端A恰好执行完业务操作,释放锁的时候,该锁是客户端B重新获得的锁,导致出现问题。这时,我们想到可以在设置key值时给定一个随机数,在释放资源锁的同时,判断是否和之前设置的value值相同,相同则释放,反之不释放。

     if(getKey(taskId)==random_value){
deleteKey(taskId);
}

  很可惜,上面的整个if操作也不是原子性的,getKey方法和deleteKey方法之间由于某种原因而延迟1秒钟操作了,而这1秒内刚好设置的的超时时间而锁释放,被新的客户端获得锁,1秒之后执行deleteKey方法又会误删除新客户端的锁,问题依旧存在。接下来我们只要想办法解决上面判断的原子性就能解决误删除锁的问题。Redis可以使用Lua脚本保证操作的原子性。

 if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

  其中ARGV[1]表示设置key时指定的随机值。由于Lua脚本的原子性,在Redis执行该脚本的过程中,其他客户端的命令都需要等待该Lua脚本执行完才能执行,所以不会出现上面所说的误删除锁问题。至此,使用Redis实现分布式锁的方案就相对完善了。上述分布式锁的实现方案中,都是针对单节点Redis而言的。

  

最新文章

  1. How can i use iptables save on centos 7?
  2. [知识整理]Java集合
  3. DICOM:DICOM3.0网络通信协议(续)
  4. 【P1351】构建双塔
  5. kthread_stop引起的OOP
  6. ASP.NET 之 检测到在集成的托管管道模式下不适用的ASP.NET设置
  7. HDU 4571 Travel in time ★(2013 ACM/ICPC长沙邀请赛)
  8. Quartz Scheduler 开发指南(1)
  9. 【KMP】Oulipo
  10. Unity中的关节
  11. 时间TDateTime相当于是Double,即双精度数64位,终于查到它用11位表示e,53位表示精度(整数小数一起),最前面一位表示正负
  12. [HNOI2009]图的同构
  13. 20165223《网络对抗技术》Exp5 MSF基础应用
  14. 浅谈Web开发中的定时任务
  15. python学习日记(基础数据类型及其方法01)
  16. python 全栈开发,Day76(Django组件-cookie,session)
  17. jvm本地实战
  18. linux 后台执行nohup 命令,终端断开无影响
  19. [java] DOS编译 .java 文件得到 .class 文件 并执行 以及使用外部 .jar包 时的命令
  20. V-rep学习笔记:力传感器

热门文章

  1. Impala简介
  2. c# 给文件/文件夹 管理用户权限
  3. mac终端主机与用户名的修改
  4. input、textarea等输入框输入中文时,拼音在输入框内会触发input事件的问题
  5. 剑指Offer-14:输入一个链表,输出该链表中倒数第k个结点。
  6. create table常用命令
  7. System.Web.Mvc.IActionFilter.cs
  8. 让delphi2010能有delphi7的版面布局
  9. spss logistic回归分析结果如何分析
  10. Java之RabbitMQ(一)与SpringBoot整合