企业网站模板建站怎么用,公众号怎么运营起来,统计局宣传工作总结 网站建设,国内最新十大新闻theme: smartblue
分布式锁是什么#xff1f;为什么要有这个技术#xff0c;解决了什么问题#xff1f;
讲到分布式锁#xff0c;我们会很容易联想到单机锁。在java多线程编程中#xff0c;通常我们会使用锁来保护共享变量#xff0c;来保证线程安全。这里面的锁作用范…
theme: smartblue
分布式锁是什么为什么要有这个技术解决了什么问题
讲到分布式锁我们会很容易联想到单机锁。在java多线程编程中通常我们会使用锁来保护共享变量来保证线程安全。这里面的锁作用范围是同一个进程中。如果是多个进程应该怎么保护共享资源这就是分布式锁来解决的问题。分布式解决的是在多个进程如果确保对共享资源操作的正确性。 那我们应该如何实现分布式锁? 需要借助其他第三方服务来解决比如mysql、zookeeper、Redis等。
不同实现方案
mysql
利用数据库的唯一约束特性来实现锁的互斥性。比如创建一张锁表表中包含锁名称用于区分不同的业务锁、锁定状态等字段。当某个服务要获取锁时向表中插入一条对应锁名称的记录如果插入成功则表示获取到锁若插入失败违反唯一约束则说明锁已被其他服务占有。释放锁时只需删除对应的记录即可。
优点 实现相对简单理解容易借助数据库的稳定性和持久性保证锁的可靠性适用于并发量不是特别巨大的场景。缺点 性能相对较差获取锁和释放锁涉及数据库操作有一定的 I/O 开销存在单点故障问题如果数据库出现故障整个分布式锁机制将受到影响可能会出现死锁情况比如获取锁的服务崩溃没来得及释放锁。
基于 Zookeeper 实现分布式锁
Zookeeper 是一个分布式协调服务它通过节点的创建和删除以及节点的顺序性等特性来实现分布式锁。通常利用临时顺序节点来实现当一个服务要获取锁时在 Zookeeper 指定路径下创建一个临时顺序节点然后获取该路径下所有子节点判断自己创建的节点是否是序号最小的节点如果是则表示获取到锁如果不是则监听序号比自己小的节点的删除事件当监听的节点被删除时再次判断自己是否为最小节点来获取锁。释放锁就是删除对应的临时顺序节点。在 Java 中可以通过 Curator 等框架来方便地操作 Zookeeper 实现分布式锁。
优点 可靠性高基于 Zookeeper 的分布式一致性协议如 ZAB 协议能保证在集群环境下锁的一致性具备高可用和容错性Zookeeper 集群可以应对节点故障等情况可以实现阻塞式获取锁方便处理需要等待锁释放的业务场景。缺点 性能相比 Redis 稍差一些因为涉及到节点的创建、查询、监听等一系列操作有一定的网络开销和 Zookeeper 服务器的处理成本实现相对复杂需要对 Zookeeper 的相关概念和操作比较熟悉代码的编写和理解成本相对较高。
Redis分布式锁方案
一款优秀分布式锁应该具有什么特点
互斥性
含义在任意时刻对于同一个被保护的资源只能有一个客户端能够获取到分布式锁其他客户端若尝试获取需等待当前持有锁的客户端释放锁之后才有机会获取。这确保了在分布式环境下针对共享资源的并发访问能够按照顺序依次进行避免多个客户端同时操作资源而导致数据不一致等问题。
可重入性
一个已经获取到分布式锁的客户端在锁未释放期间如果再次请求获取该锁仍然能够成功获取并且锁的释放次数需要和获取次数严格匹配才能最终真正释放锁。这在一些具有递归调用或者嵌套调用逻辑的业务场景中非常重要避免自己把自己 “锁死” 而无法继续后续操作。类似java多线程中的可冲入锁概念
阻塞特性与超时机制
阻塞特性 含义当客户端尝试获取锁但当前锁已被其他客户端持有时该客户端能够阻塞等待直到获取到锁或者超时为止而不是立刻返回失败结果。这样可以保证在高并发情况下客户端有机会获取到锁去操作资源不用频繁地发起获取锁的请求去轮询。 超时机制 含义为了防止某个客户端获取锁后由于异常情况比如进程崩溃、网络故障等一直没有释放锁导致其他客户端无限期等待需要给锁设置一个超时时间一旦超过这个时间锁会自动释放让其他客户端有机会获取锁。
具体实现
单机版本的分布式锁
最简单的分布式锁 setnx 这个命令代表如果key不存在然后才设置。操作完成后del删除这个key就行。 但是有一个很大的问题客户端1拿到锁之后如果没有释放锁或者进程挂了没机会释放锁。其他客户端就永远没有机会拿到锁。 如何解决 很自然的一个思路给这个锁设置一个有效时间类似锁设置一个超时时间过了这个时间自动释放锁给其他客户端一个机会获取临界资源。我们可以使用redis 提供的 SET key 1 EX 10 NX 该命令是原子性的。 看起来很完美了但是我们再细想一下。首先就是这个过期时间现实中很难设置准确设置太长导致资源浪费设置太短又没法确保客户端执行完业务代码。我们有没有一种机制能够自动给锁延长时间呢答案是存在的我们开启一个守护线程我们通常称为看门狗线程定时检测这个锁的失效时间如果锁快要过期了操作共享资源还没有完成我们就可以自动给锁进行续期重新设置过期时间。 这个时候我们再想一下还存在什么问题 客户端释放锁会无脑释放并不会检查是不是自己的锁我们需要在客户端加锁的时候设置一个唯一标识比如UUID线程id等。释放锁的时候先判断一下是不是自己的锁是自己的锁才会进行释放。这个时候会设计到redis不同命令。redis通过lua脚本来确保原子性。 大概流程是 加锁。 设置锁并且设置一个唯一标识来标识这个锁 操作共享资源释放锁lua脚本先判断是不是自己的锁然后再del锁。
分布式场景下的锁
如果有多台redis实例如何实现呢 1、客户端先获取当前时间戳t1。 2、客户端依次向n个redis实例发起加锁命令并且每一个请求设置超时时间这个时间要远小于锁的有效时间不然你还没设置锁就到期了没有意义如果某个实例加锁失败就立即向下一个实例申请加锁。 3、如果超过一般以上的redis实例加锁成功就再次获取当前时间戳t2。t2-t1看一下是否超过了锁的超时时间没有代表客户端加锁成功否则失败。 4、加锁成功就可以操作共享资源。 5、如果加锁失败就需要向全部节点释放锁的请求。