与单机锁不同,分布式锁更加复杂,需要考虑网络延迟、服务阻塞等,通常具有如下特点:
利用数据库主键唯一的特性,可以基于唯一主键保证多次 *** 作只有一次成功。在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。释放锁时,直接删除数据库记录即可。
此方案存在的问题是强依赖数据库,容易形成热点,数据库锁表导致的超时会影响性能,或者数据库宕机会导致服务不可用。并且,数据库本身没有失效机制,如果任务崩溃会导致数据库中的锁不能被释放。数据库插入 *** 作本身没有阻塞机制,故无法实现分布式锁的阻塞等待,任务线程可能需要重复尝试插入。由于唯一主键的存在,持有锁的线程也无法重复获得锁,其他线程竞争锁的过程中也无法根据优先级进行分配。
在数据库中为表增加一个版本号字段,每次 *** 作时判断版本号,只有版本号一致才能进行对应的修改,修改后版本号加 1,通过 CAS 的方式进行修改。
此实现会增加数据库 *** 作的次数,高并发情况下可能性能不好。
for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行。我们可以认为获得排他锁的线程即获得分布式锁,任务执行完成后通过 commit 来释放锁。for update 语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。
但是 MySQL 会对查询进行优化,即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。
setnx 的含义就是 SET if Not Exists,主要有两个参数 setnx(key, value)。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。setnx 命令不能设置 key 的超时时间,只能通过 expire() 来设置。
锁的实现步骤:
这个方案如果在第一步 setnx 执行成功后,在 expire() 命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题。
这个方案是对上一个方案的优化版本。
getset() 命令主要有两个参数 getset(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么首次执行的返回值是 null。
锁的实现步骤:
这个方案在任务处理超时或发生宕机时,无需担心锁超时问题,下次请求可以判断出实际上锁已经超时了。
zookeeper 由多个节点构成(单数),采用 zab 一致性协议。因此可以将 zk 看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
zookeeper 数据是目录树的形式,每个目录称为 znode, znode 中可存储数据(一般不超过 1M),还可以在其中增加子节点。
子节点有三种类型。
zookeeper 提供了 Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。
可以利用临时节点与 watch 机制实现分布式锁。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除 *** 作后再去争锁。临时节点好处在于当进程挂掉后锁的节点自动删除不会发生死锁。
缺点在于所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
一个可行的优化方案是上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。watch 事件到来后,再次判断是否序号最小。取锁成功则执行代码,最后释放锁(删除该节点)。
性能上可能没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。zookeeper 中创建和删除节点只能通过 Leader 服务器来执行,然后将数据同步到所有的 Follower 机器上。
分布式锁比较复杂,也比较容易发生死锁。目前主流的实现方式包括:
分布式锁及其常见实现方式 - 程序之心
数据库锁的产生原因:数据库和 *** 作系统一样,是一个多用户使用的共享资源。当多个用户并发地存取数据 时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发 *** 作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并 发控制的一个非常重要的技术。在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严 重影响应用的正常执行。
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两 种基本的锁类型来对数据库的事务进行并发控制。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)