`

用Redis实现分布式锁完善思路

阅读更多

Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。这系列的命令非常有用,这里讲使用SETNX来实现分布式锁。

用SETNX实现分布式锁

利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字foo的锁,客户端使用下面的命令进行获取:

SETNX lock.foo <current Unix time + lock timeout + 1>

  •  如返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。
  •  如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。

解决死锁

上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lock.foo的值,说明该锁已失效,可以被重新使用。

发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次,当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景:

  1.  C0操作超时了,但它还持有着锁,C1和C2读取lock.foo检查时间戳,先后发现超时了。
  2.  C1 发送DEL lock.foo
  3.  C1 发送SETNX lock.foo 并且成功了。
  4.  C2 发送DEL lock.foo
  5.  C2 发送SETNX lock.foo 并且成功了。

这样一来,C1,C2都拿到了锁!问题大了!

幸好这种问题是可以避免D,让我们来看看C3这个客户端是怎样做的:

  1. C3发送SETNX lock.foo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0
  2. C3发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。
  3. 反之,如果已超时,C3通过下面的操作来尝试获得锁:
    GETSET lock.foo <current Unix time + lock timeout + 1>
  4. 通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。
  5. 如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值,这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。

注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

示例伪代码

根据上面的代码,我写了一小段Fake代码来描述使用分布式锁的全过程:

 

# get lock
lock = 0
while lock != 1:
    timestamp = current Unix time + lock timeout + 1
    lock = SETNX lock.foo timestamp
    if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):
        break;
    else:
        sleep(10ms)
 
# do your job
do_job()
 
# release
if now() < GET lock.foo:
    DEL lock.foo

是的,要想这段逻辑可以重用,使用python的你马上就想到了Decorator,而用Java的你是不是也想到了那谁?AOP + annotation?行,怎样舒服怎样用吧,别重复代码就行。

 

注:以上原文转自:http://www.jeffkit.info/2011/07/1000/

 

上面是一个很不错的思路,但也有所不足:

1. 释放锁的时候(release代码块),如果有线程操作超时,并正确执行完成do_job代码后,可能后面已经有新的请求获得了锁权限,此时lock.foo的值已被新的线程请求更新,应该就是大于now()的,这样的话会导致锁被意外释放。所以这里的判断条件应该改一改,如timestamp == GET lock.foo,这样的话,每个线程都只能释放自己加的锁,如果自己超时了,那么锁被更新成别的线程后,也不会把别的线程控制的锁释放掉。

 

以上所说的情况是在所有分布式环境均在同一个服务器上或多个服务器但时间是完全同步的情况下可用,否则的话now()取出的时间不同步,可能导致锁有很大的安全问题,或者直接导致锁无效。

 

这样的情况下,要再完善这个思路就可以考虑在SETNX调用成功后,为key添加一个expires

整个思路就简单很多了,以上代码简单改变下:

 

# get lock
timestamp = '';
while true:
    #randomInt 防止并发值重复
    timestamp = current Unix time + '#' + randomInt ;
    # 当key被上一个锁删除或过期时,以下操作都可以这么判断
    if (SETNX lock.foo timestamp) == 1:
        EXPIRE lock.foo timeout;
        break;
    else:
        sleep(10ms)
 
# do your job
do_job()
 
# release
if timestamp == GET lock.foo:
    DEL lock.foo

 

 

 

 

分享到:
评论

相关推荐

    Python实现的redis分布式锁功能示例

    然而,这个简单的示例提供了一个基本的思路,展示了如何使用Python和Redis来实现分布式锁。对于更复杂的场景,可以参考更成熟的分布式锁库,如`redlock-py`,它提供了更完善的解决方案和容错机制。

    抽奖系统后台 springboot+mybatis redis队列处理高并发.zip

    6. **分布式锁**:在抽奖过程中,为了防止同一用户多次抽奖或重复发奖,可以利用Redis的分布式锁功能。当用户发起抽奖请求时,系统会尝试获取锁,只有获取成功后才允许抽奖,这样能保证抽奖的公平性。 7. **监控与...

    解决框架耦合问题的分布式管道设计模式实现.pdf

    针对大数据分析对图书馆管理服务带来的挑战,本文最后提出了基于大数据技术提升图书馆服务质量的创新思路,构建图书馆信息资源共享平台,全面掌握用户需求,完善图书馆信息资源组织管理程序,更新服务方式,提升图书...

    面向电商网站的分布式爬虫系统开发.pdf

    本文主要讲述的是面向电商网站的分布式爬虫系统开发过程,重点分析了该系统的设计思路、功能实现以及应对电商网站反爬虫策略的方法。分布式爬虫系统是为了解决集中式爬虫无法高效采集大规模数据的局限性而提出的。...

    大型分布式电商系统架构是如何从0开始演进的?

    例如,通过分布式缓存Redis或Memcached提高服务抗压能力,利用分布式消息队列RabbitMQ或Kafka处理异步任务和解耦,以及使用Hystrix实现服务间的熔断保护。 6. **性能优化**:为了提供良好的用户体验,系统性能至关...

    秒杀项目代码,秒刀房代码,Java源码.zip

    Java中的Redis或Zookeeper可以实现分布式锁,确保同一时刻只有一个请求能够修改库存。 3. **缓存策略**:为减轻数据库压力,可采用缓存技术,如Redis,将热门商品信息和用户状态暂存于内存中,提高查询速度。同时,...

    详解基于Scrapy的IP代理池搭建

    4. 分布式部署:将代理IP池部署在多台服务器上,通过Scrapy-Redis实现分布式爬取,提高整体爬取效率。 通过以上步骤,我们可以构建一个功能完善的IP代理池,为爬虫提供稳定、高效的IP资源,有效应对网站的反爬策略...

    【spring-boot-seckill分布式秒杀系统 v1.0】从0到1构建的java秒杀系统源码+安装说明

    4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库中间件实现数据的读写分离。 优化思路 1、分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的...

    高并发Web架构实现思路——java版

    ### 高并发Web架构实现思路——Java版 #### 一、引言 随着互联网的快速发展,Web应用系统面临着巨大的挑战,尤其是如何处理高并发、海量数据的情况。传统的Web架构已经无法满足当前的需求,这就需要我们探索新的...

    distbtsync:一个方便的SDK,用于基于Spring框架的分布式方案中的同步

    2. **Redis集成**:作为分布式的键值存储系统,Redis被广泛用作分布式锁和缓存。distbtsync充分利用Redis的特性,提供了一套基于Redis的同步机制,如分布式锁服务,确保多节点间的操作互斥,防止数据冲突。 3. **...

    Eclipse开发分布式商城系统+完整视频代码及文档

    规格参数介绍及实现思路.avi │ 07.规格参数关联的数据库.avi │ 08.商品规格参数模板列表.avi │ 09.判断规格参数模板是否存在.avi │ 10.保存规格参数模板.avi │ 11.展示规格参数模板.avi │ 12.保存规格参数.avi...

    mybatis - 使用Spring+Springmvc+Mybatis实现秒杀商品案例.zip

    在本项目中,我们主要探讨...以上就是使用Spring、SpringMVC和MyBatis实现秒杀商品案例的主要技术点和设计思路。实际项目中,还需要考虑更多的细节,如错误处理、日志记录、安全性等,以打造一个健壮且高效的秒杀系统。

    徐长龙分布式服务的管理

    ### 徐长龙分布式服务管理的关键知识点 #### 一、背景与个人介绍 - **演讲者**: 徐长龙,微博广告平台部门成员,@thinkpc。 - **身份**: PHP微服务Dora-RPC的作者,具有四年PHP基础架构经验。 #### 二、互联网...

    课程设计基于Java实现的校园快递代拿系统源码+sql数据库+项目详细说明.zip

    【资源说明】 ...1. 一个大坑就是rocketmq,我是现学现用的,曾因为理论不完善而导致代码多次出现大改, 1.1. 主要坑有两个,一个两阶段提交如何确保事务最终一致性(其实就是这学期nosql的理论实践)

    spring-boot-seckill分布式秒杀系统 v1.0

    4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库中间件实现数据的读写分离。 spring-boot-seckill分布式秒杀系统优化思路 1、分流、分流、分流,重要的事情说三...

    wireless.zip

    ##redis分布式锁 &gt;首先要了解的乐观锁是什么? &gt; 数据库乐观锁,数据库增加一个版本标识 &gt;redis乐观锁通过watch命令监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务失败。也可以调用...

    spring-boot-seckill分布式秒杀系统-其他

    4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库中间件实现数据的读写分离。 优化思路 1、分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的...

    Redis集群方案

    为了解决这些挑战,可以考虑采用社区成熟的Redis集群解决方案,如Twemproxy(Twitter的Redis代理)或Codis(阿里巴巴开源的Redis集群管理工具),它们提供了更完善的分布式和故障恢复机制,减轻了开发和运维的压力。...

    SpringCloud实现SSO 单点登录的示例代码

    本文将通过一个简单的示例,介绍如何使用SpringCloud和Zuul结合Redis来实现SSO。 首先,SSO的基本思路如下: 1. **请求拦截**:所有对分布式系统的请求都会被Zuul边缘服务的过滤器拦截。 2. **过滤逻辑**:如果...

    京东秒杀.zip

    这里可能用到了分布式事务解决方案,如两阶段提交(2PC)、补偿事务(TCC)或基于分布式锁的策略。同时,库存的更新操作需要在数据库层面采用行级锁或者乐观锁来避免并发问题。 此外,秒杀系统还需要具备良好的扩展...

Global site tag (gtag.js) - Google Analytics