分布式锁
序言分布式锁特性常用实现方案基于数据库实现基于数据库实现-乐观锁基于数据库实现-悲观锁基于数据库实现-总结基于redis实现实现思想基于redis实现-原理基于redis实现-总结基于 ZooKeeper 实现基于 ZooKeeper 实现-原理基于ZooKeeper实现-curator基于ZooKeeper实现-总结总结来源
序言
在单机时代,虽然不存在分布式锁,但也会面临资源互斥的情况,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就需要对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在 Java 中 synchronize/Lock 等。
但是到了分布式系统的时代,这种线程之间的锁机制,就没作用了,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。因此,为了解决这个问题,「分布式锁」就出现了。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
分布式锁特性
常用实现方案
基于数据库实现
基于数据库实现-乐观锁
乐观锁的特点先进行业务操作,不到万不得已不去拿锁。“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。
乐观锁机制是在数据库表中引入一个版本号(version)字段来实现
当我们要从数据库中读取数据的时候,同时把这个 version 字段也读出来,如果要对读出来的数据进行更新后写回数据库,则需要将 version 加 1,同时将新的数据与新的 version 更新到数据表中,且必须在更新的时候同时检查目前数据库里 version 值是不是之前的那个 version ,如果是,则正常更新。如果不是,则更新失败,说明在这个过程中有其它的进程去更新过数据了
乐观锁遵循的两点法则:
- 锁服务要有递增的版本号 version
- 每次更新数据的时候都必须先判断版本号对不对,然后再写入新的版本号
基于数据库实现-悲观锁
悲观锁的特点是先获取锁,再进行业务操作,即*“悲观”*的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作
通常所说的“一锁二查三更新”即指的是使用悲观锁。
当数据库执行
select for update
时会获取被 select
中的数据行的行锁并发执行的
select for update
选中同一行则会发生排斥(需要等待行锁被释放),以此达到锁的效果select for update
获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用基于数据库实现-总结
基于redis实现
基于 Redis 实现的锁机制,主要是依赖 Redis 自身的原子操作
实现思想
- 获取锁的时候,使用
setnx
加锁,并使用expire
命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID ,通过此在释放锁的时候进行判断
- 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁
- 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行
del
进行锁释放
基于redis实现-原理
基于redis实现-总结
基于 ZooKeeper 实现
基于 ZooKeeper ,就是使用它的临时有序节点来实现的分布式锁。
实现思想:
(1)创建一个目录 mylock;
(2)线程A想获取锁就在 mylock 目录下创建临时顺序节点;
(3)获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
基于 ZooKeeper 实现-原理
基于ZooKeeper实现-curator
基于ZooKeeper实现-总结
总结
分布式锁有很多种,”相对主流的有三种”只是个人愚见。
分布式锁千变万化,具体的实现方案按照自己的场景及条件实现。
本质还是针对同步资源的合理占用,最理想的方式就是不用加锁就可以避免并发操作
来源
Loading...