分布式锁
文章目录
有用到 php laravel 框架 redisLock, 发现还是有一些可以改进的地方, 在网络查找了一些资料, 有文章用 java 开发, 比如对锁的过期时间开启线程, 定时不断的去增加过期时间, 每次增加10s, 直到处理完
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。 分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。” 所以,很多系统在设计之初就要对这三者做出取舍。 在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系 统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
目标
- 效率:使用分布式锁可以避免不同节点重复相同的工作,这些工作会浪费资源。比如用户付了钱之后有可能不同节点会发出多封短信。
- 正确性:加分布式锁同样可以避免破坏正确性的发生,如果两个节点在同一条数据上面操作,比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误,造成损失。
如何避免缓存击穿?
使用分布式锁:请求发现缓存不存在后,去查询 DB前,使用锁,保证有且只有一个请求去查询 DB ,并更新到缓存。流程如下:
- 1、获取锁,直到成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
- 2、再去缓存中。如果存在值,则直接返回;如果不存在,则继续往下执行如果成功获取到锁的话,就可以保证只有一个请求去数据源更新数据,并更新到 缓存中了。
- 3、查询 DB ,并更新到缓存中,返回值。
特点
- 互斥性: 和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
- 锁超时: 和本地锁一样支持锁超时,防止死锁。
- 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。
- 高效可用: 加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
- 支持阻塞和非阻塞: 和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
- 支持公平锁和非公平锁(可选): 公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。
常见的分布式锁
- MySQL
- Zk
- Redis
- 自研分布式锁:如谷歌的Chubby
Redis分布式锁简单实现
redis有很高的性能, redis命令对此支持较好, 实现起来比较方便
使用命令
SETEX key value
EXPIRE key seconds
DEL key [key...]
实现思想
- 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
- 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行del进行锁释放,自己只能删除自己加的锁。
概念解析
- 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁
- 自旋
为什么使用redis+lua?
1、什么是Lua? Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能
2、Redis嵌入lua的优势 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用
Redis当中使用lua
在Lua脚本中采用如下两个不同的Lua函数进行Redis命令调用
redis.call()
redis.pcall()
redis.call()类似于redis.pcall(),唯一的区别是如果Redis命令调用将导致错误,redis.call()将引发Lua错误,反过来会强制EVAL返回错误到命令调用者,而 redis.pcall将捕获错误并返回一个Lua错误码。
参考
文章作者 小叨
上次更新 2020-02-11