Redis分布式锁实现理解

redis中,通过对key值的独占来实现分布式锁看似简单快捷,但实际上存在许多挑战。确保锁资源的安全和及时释放是分布式锁实现的核心问题。以下是逐层分析redis实现分布式锁的过程,以及存在的问题和解决方案。

方案1:setnx

通过setnx命令设置key的方式实现独占锁。

并发线程抢占锁资源

setnx an_special_lock 1

如果1抢占到当前锁,并发线程中的当前线程执行

if(成功获取锁)execute business_method()

释放锁

del an_special_lock

存在的问题很明显:从抢占锁到并发线程中当前线程操作,再到最后的释放锁,并不是一个原子性操作。如果最后的锁没有被成功释放(del an_special_lock),即2到3之间发生了异常,就会导致其他线程永远无法重新获取锁。

方案2:setnx + expire key

为了避免方案1中的这种情况,需要对锁资源加一个过期时间,比如10秒钟。一旦从占锁到释放锁的过程中发生异常,可以保证过期之后锁资源的自动释放。

并发线程抢占锁资源

setnx an_special_lock 1

设置锁的过期时间

expire an_special_lock 10

如果1抢占到当前锁,并发线程中的当前线程执行

if(成功获取锁)execute business_method()

释放锁

del an_special_lock

通过设置过期时间(expire an_special_lock 10),避免了占锁到释放锁的过程中发生异常而导致锁无法释放的问题。但是仍旧存在问题:在并发线程抢占锁成功到设置锁的过期时间之间发生了异常,即这里的1到2之间发生了异常,锁资源仍旧无法释放。方案2虽然解决了方案1中锁资源无法释放的问题,但同时引入了一个非原子操作,同样无法保证set key到expire key的原子性执行。因此,目前的问题集中在:如何使得设置一个锁&&设置锁超时时间,即这里的1到2操作,保证以原子的方式执行?

方案3:set key value ex 10 nx

Redis 2.8之后加入了一个set key && expire key的原子操作:set an_special_lock 1 ex 10 nx。

并发线程抢占锁资源,原子操作

set an_special_lock 1 ex 10 nx

Metronic Bootstrap后台模板 Metronic Bootstrap后台模板

Metronic是一套精美的响应式后台管理模板,基于强大的Twitter Bootstrap框架实现。Metronic拥有简洁优雅的Metro UI风格界面,自适应屏幕分辨率大小,兼容PC端和手机移动端。全套模板,包含仪表盘、侧边栏菜单、布局宣传片、电子邮件模板、UI特性、按钮、标签、表格布局、表单组件、多文件上传、悬浮窗文件上传、时间表、博客、新闻、关于我们、联系我们、日历、用户配置文件、锁屏、

Metronic Bootstrap后台模板 275 查看详情 Metronic Bootstrap后台模板

如果1抢占到当前锁,并发线程中的当前线程执行

if(成功获取锁)business_method()

释放锁

del an_special_lock

目前,加锁&&设置锁超时成为一个原子操作,可以解决当前线程异常之后,锁可以得到释放的问题。

但是仍旧存在问题:如果在锁超时之后,比如10秒之后,execute_business_method()仍旧没有执行完成,此时锁因过期而被动释放,其他线程仍旧可以获取an_special_lock的锁,并发线程对独占资源的访问仍无法保证。

方案4:业务代码加强

到目前为止,方案3仍旧无法完美解决并发线程访问独占资源的问题。笔者能够想到解决上述问题的办法就是:设置business_method()执行超时时间,如果应用程序中在锁超时之后仍无法执行完成,则主动回滚(放弃当前线程的执行),然后主动释放锁,而不是等待锁的被动释放(超过expire时间释放)。如果无法确保business_method()在锁过期放之前得到成功执行或者回滚,则分布式锁仍是不安全的。

并发线程抢占锁资源,原子操作

set an_special_lock 1 ex 10 nx

如果抢占到当前锁,并发线程中的当前线程执行

if(成功获取锁)business_method()

在应用层面控制,业务逻辑操作在Redis锁超时之前,主动回滚

释放锁

del an_special_lock

方案5:RedLock – 解决单点Redis故障

截止目前,(假设)可以认为方案4解决了“占锁”&&“安全释放锁”的问题,仍旧无法保证“锁资源的主动释放”:Redis往往通过Sentinel或者集群保证高可用,即便是有了Sentinel或者集群,但是面对Redis的当前节点的故障时,仍旧无法保证并发线程对锁资源的真正独占。具体说就是,当前线程获取了锁,但是当前Redis节点尚未将锁同步至从节点,此时因为单节点的故障造成锁的“被动释放”,应用程序的其它线程(因故障转移)在从节点仍旧可以占用实际上并未释放的锁。Redlock需要多个Redis节点,RedLock加锁时,通过多数节点的方式,解决了Redis节点故障转移情况下,因为数据不一致造成的锁失效问题。其实现原理,简单地说就是,在加锁过程中,如果实现了多数节点加锁成功(非集群的Redis节点),则加锁成功,解决了单节点故障,发生故障转移之后数据不一致造成的锁失效。而释放锁的时候,仅需要向所有节点执行del操作。

Redlock需要多个Redis节点,由于从一台Redis实例转为多台Redis实例,Redlock实现的分布式锁,虽然更安全了,但是必然伴随着效率的下降。

至此,从方案1到方案2到方案3到方案4到方案5,依次解决了前一步的问题,但仍旧是一个非完美的分布式锁实现。

以下通过一个简单的测试来验证Redlock的效果。

案例是一个典型的对数据库“存在则更新,不存在则插入”的并发操作(这里忽略数据库层面的锁),通过对比是否通过Redis分布式锁控制来看效果。

#!/usr/bin/env python3import redisimport sysimport timeimport uuidimport threadingfrom time import ctime, sleepfrom redis import StrictRedisfrom redlock import Redlockfrom multiprocessing import Poolimport pymssqlimport random

class RedLockTest:_connection_list = None_lock_resource = None_ttl = 10 # ttl

def __init__(self, *args, **kwargs):    for k, v in kwargs.items():        setattr(self, k, v)def get_conn(self):    try:        # 如果当前线程获取不到锁,重试次数以及重试等待时间        conn = Redlock(self._connection_list, retry_count=100, retry_delay=10)    except:        raise    return conndef execute_under_lock(self, thread_id):    conn = self.get_conn()    lock = conn.lock(self._lock_resource, self._ttl)    if lock:        self.business_method(thread_id)        conn.unlock(lock)    else:        print("try later")''' 模拟一个经典的不存在则插入,存在则更新,起多线程并发操作实际中可能是一个非常复杂的需要独占性的原子性操作 '''def business_method(self, thread_id):    print(" thread -----{0}------ execute business method begin".format(thread_id))    conn = pymssql.connect(host="127.0.0.1", server="SQL2014", port=50503, database="DB01")    cursor = conn.cursor()    id = random.randint(0, 100)    sql_script = ''' select 1 from TestTable where Id = {0} '''.format(id)    cursor.execute(sql_script)    if not(cursor.fetchone()):        sql_script = ''' insert into TestTable values ({0},{1},{1},getdate(),getdate()) '''.format(id, thread_id)    else:        sql_script = ''' update TestTable set LastUpdateThreadId ={0} ,LastUpdate = getdate() where Id = {1} '''.format(thread_id, id)    cursor.execute(sql_script)    conn.commit()    cursor.close()    conn.close()    print(" thread -----{0}------ execute business method finish".format(thread_id))

if name == "main":redis_servers = [{"host": "...", "port": 9000, "db": 0},{"host": "...", "port": 9001, "db": 0},{"host": "...", "port": 9002, "db": 0},]lock_resource = "mylock"ttl = 2000 # 毫秒redlock_test = RedLockTest(_connection_list=redis_servers, _lock_resource=lock_resource, _ttl=ttl)

# redlock_test.execute_under_lock(redlock_test.business_method)threads = []for i in range(50):    # 普通的并发模式调用业务逻辑的方法,会产生大量的主键冲突    # t = threading.Thread(target=redlock_test.business_method, args=(i,))    # Redis分布式锁控制下的多线程    t = threading.Thread(target=redlock_test.execute_under_lock, args=(i,))    threads.append(t)begin_time = ctime()for t in threads:    t.setDaemon(True)    t.start()for t in threads:    t.join()

测试1:简单多线程并发

简单地起多线程执行测试的方法,测试中出现两个很明显的问题:

  1. 出现主键冲突(而报错)
  2. 从打印的日志来看,各个线程在测试的方法中存在交叉执行的情况(日志信息的交叉意味着线程的交叉执行)

Redis分布式锁实现理解

测试2:Redis锁控制下多线程并发

Redlock的Redis分布式锁为三个独立的Redis节点,无需做集群。

Redis分布式锁实现理解

当加入Redis分布式锁之后,可以看到,虽然是并发多线程操作,但是在执行实际的测试的方法的时候,都是独占性地执行,从日志也能够看出来,都是一个线程执行完成之后,另一个线程才进入临界资源区。

Redis分布式锁实现理解

Redlock相对安全地解决了一开始分布式锁的潜在问题,与此同时,也增加了复杂度,同时在一定程度上降低了效率。

以上就是Redis分布式锁实现理解的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/815292.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月27日 00:38:38
下一篇 2025年11月27日 00:44:25

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何解决本地图片在使用 mask JS 库时出现的跨域错误?

    如何跨越localhost使用本地图片? 问题: 在本地使用mask js库时,引入本地图片会报跨域错误。 解决方案: 要解决此问题,需要使用本地服务器启动文件,以http或https协议访问图片,而不是使用file://协议。例如: python -m http.server 8000 然后,可以…

    2025年12月24日
    200
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • 使用 Mask 导入本地图片时,如何解决跨域问题?

    跨域疑难:如何解决 mask 引入本地图片产生的跨域问题? 在使用 mask 导入本地图片时,你可能会遇到令人沮丧的跨域错误。为什么会出现跨域问题呢?让我们深入了解一下: mask 框架假设你以 http(s) 协议加载你的 html 文件,而当使用 file:// 协议打开本地文件时,就会产生跨域…

    2025年12月24日
    200
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    300

发表回复

登录后才能评论
关注微信