lua mysql 死锁_Redis通过Lua脚本实现分布式锁实践

分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下Redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的。

我们先来看一个无锁的情况下会导致什么问题:

这是一个普通的更新用户年龄的功能,各层代码如下,访问controller层,一个更新,一个查询

b3db6bbf0f5701f224f76d9871f424c1.png

这是service层,我们使用contdownlatch发令枪来模拟线程同时并发的情况,发令枪设为32,即32个线程同时去请求修改年龄,

e7d7a5ae36f928b5c2a9f9a8cd5c993c.png

这里使用线程池来提交多线程任务,看代码知道,这里我们已经有了判断年龄的操作,当查询用户查询大于0时,才去调更新用户年龄-1的方法,等下看看有没有用

023312b1d490f74714efca2c2cba738b.png

这里是sql,可以看到两个sql,一个查询用户年龄,一个会执行用户年龄每次减1 ,

e5badd679bf1468ceb2762d957a54931.png

这里是用户数据,我们可以看到,用户UID为UR12324的用户,他的年龄是30,接着我们来调32个线程来操作减他年龄

47b09bf8aa3359b3a32050e09dce279d.png

我们请求下这个方法

d55ae00dae4669869c4a14d04f675ea9.png

然后看看结果:

f972386e347e9c9aa6638c73619f210f.png

50fa713f6acd9274c7c5da9eef8bf5fb.png

可以看到库中年龄已被减为-2,在未加锁的情况下,查询较验并没有什么作用,此时如果加个synchronized或lock锁肯定能避免这种情况,但我们本文讨论的是多实例或分布式环境中,此加锁方式仍然会产生问题,感兴趣的可以试下是不是

下面我们开始实现一个redis分布式锁,来避免这种情况发生,先说说实现思路:

1,线程请求访问前先调用加锁的方法,加锁就去里生成一个随机数同时保存在线程本地变量和redis的某key中,此key设有效期为200ms,具体值根据业务执行时间自行调整,加锁成功;

2.其它线程试着访问拿出它本地变量与redis中某key进行比较,如果不一致,则说明有锁,此线程休眠一段时间,再试着加锁;

3.加锁成功的线程在操作结束后删掉它持有锁(用lua实现,保证原子性,在它比对和删除锁的过程中,其它线程不会加锁成功),让其它线程再次加锁以执行任务;

说明:锁的时间为200ms可预防线程挂掉之后死锁,200ms后会自动释放

下面看看我们写的锁代码:

片段1:使用redislock 实现lock来复写它的方法

6aea38d043a5f7d6d2c10963bfa7a938.png

片段2:试着加锁的方法

76f0783b30f307ead8fafdb140dca51a.png

片段3:解锁方法,此处首先从线程本地变量获取它的随机数,然后调用lua脚本,与redis中key相比较,如果相同则删除,否则返回0;

d1348773fa9b7692df18d2c79b164593.png

此为lua脚本方法,用此方法可以保证判断和删除的原子性,在此过程中没有线程可以操作此key

43a54b19e581c9e5994817c64b27d60e.png

到此为止,我们锁基本写完,来测试下有没有用:

0eccdc452b01e5c83af48b4ce0cb8c45.png

我们在此方法前后分别加入加锁和解锁方法,使用方式和lock锁一样, 我们重新把年龄恢复到30后来测试一下吧

先看看日志

0c508ed83889a75abb86251cb3e4ba55.png

这里可以看到各个线程争夺锁的情况,再看看执行结果

2b0ea75de53f2895963804939dd5cf5b.png

5f82f49baa396873451791f12b313d46.png

这里我们可以看到虽然是32个线程并发执行,但此值并不会变为负数,加锁成功.

我们可以看到最后2个线程并没有执行方法

b56eddcf3af3753c67396303338a0927.png

19b693b1d704785b4b8faceacc18fd2d.png

在具体生产环境中,比如典型的用户余额扣减,我们可以用用户UID作KEY,这样就不会造成100个用户,500个线程争夺一个锁的情况发生,100个用户会有100个锁,此时假如每个用户5个请求,一个锁只处理5个线程

大大提高锁的效率.

此时说明加锁成功,大家可以在分布式环境中测试更明显,有关极端情况下解锁失败后应该做什么也可以由我们自己决定,比redission要灵活,带锁的redis最好是单实例,在集群中可能会出问题,有机会我们再用zk实现下。

0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值