Fork me on GitHub

实现分布式锁的三种方式

类似的文章网上一搜一大把,实现方式也无非这三种,不过自己还是总结一下吧,实际应用中只采用过缓存来实现

数据库实现

1.基于数据库表唯一性实现

通过增删操作,借助数据库唯一索引的唯一性或主键唯一性,来实现

缺点:

  1. 数据库单点问题,如果数据库挂了,会导致业务系统不可用

  2. 获取锁后,没有失效时间,如果解锁失败,就会导致锁记录始终在数据库中,其他线程则无法获取锁

  3. 锁是非阻塞,没有机制保证失败后的阻塞重试

  4. 锁非重入,同一个线程在没有释放之前,无法再次获取该锁

缺点的解决方案:

  1. 单点问题,可以通过主从数据库的方式解决,数据双向同步,主库挂了从库上

  2. 没有失效时间问题,可以通过定时任务去清理长时间没有释放的锁

  3. 非阻塞问题,问题是没有重试,代码中加入循环重试即可

  4. 锁非重入问题,在数据库表中加入获取锁的线程信息的字段,线程在获取锁之前先查询是否通过当前机器信息和线程信息可以获取,如果可以则获取锁

2.基于数据库排它锁实现

基于通过数据库提供的排它锁,来实现

通过在 select 语句后追加 for update,数据库会在查询过程中给数据库表增加排它锁,当某条记录在查询时被加上了排它锁,其他线程查询将无法在查询时加上排它锁

只有当获取到锁的连接 connection 提交 commit 时,才会将锁释放

select * from lock_table where lock_method = '**' for update;

相较第一种实现的优点:

  1. 阻塞锁,for update 执行成功后立即返回,若失败,则 while(true) 处于阻塞状态,不断重试直到成功

  2. 锁失效问题,如果数据库发生宕机,数据库会将排它锁释放

缓存实现

通过 Redis 中 SETNX 方法实现

SETNX 是 Redis 提供的原子操作,如果指定 Key 存在,那么 set 失败,如果不存在 set 成功并返回,利用这个操作便可实现一个分布式锁

set 成功便成功获取锁,失败便是获取锁失败,删除缓存则释放掉锁

锁失效时间的问题,可以通过设置缓存的过期时间解决

缺点:

  1. 锁失效时间,通过缓存设置过期时间并不太靠谱,如果设置过长,过大请求量时吞吐量低,如果设置过短,耗时长的方法将出现并发问题

  2. 重入问题,和数据库解决方法相同,获取锁时将线程信息保存,再次获取时先查询,查询不到再尝试 set 操作

Zookeeper 实现

zookeeper 原理还不熟,之后更新...

2019-08-13,终于更新啦 -> 详解Zookeeper原理与应用场景

参考资料

  1. 分布式锁的几种实现方式~:http://www.hollischuang.com/archives/1716

  2. 分布式锁的三种实现的对比:https://www.jianshu.com/p/c2b4aa7a12f1

  3. MySQL 共享锁与排它锁:https://www.cnblogs.com/boblogsbo/p/5602122.html

  4. 阻塞和非阻塞,同步和异步:https://www.cnblogs.com/George1994/p/6702084.html
posted @ 2018-04-20 17:39  郑斌blog  阅读(922)  评论(3编辑  收藏  举报