背景
多进程和多线程中有时候需要加锁,分布式锁是不同主机的不同线程之间需要加锁,加锁的目的就是保证数据一致性,防止数据竞争,单机中有很多保证数据一致性的手段,比如互斥锁、信号量、条件变量等等,那么多主机多线程就需要分布式锁
分布式锁是个啥
分布式锁其实算一种资源,存储在网络主机上,根据不同的手段来实现互斥加锁、解锁。有几个基础的操作,加锁、解锁、网络的交互、而且要求加锁和解锁是一个对象
特点
特点主要有四个
- 互斥性,因为要保证数据一致性,防止数据竞争,因此需要互斥性,仅有一个节点持锁
- 锁超时,不能集群中一个节点加上锁后,就一直上锁,这样的话如果加锁节点崩溃了,那么这个锁就一直加锁了,其他节点就不能访问资源了,因此要给加锁设置一个时间,超过时间自动释放,如果还想继续用,自己续锁(添加加锁时间)
- 可用性,可用性就是在合理的时间内得到正确的回复,如果不保证可用性,存储锁的节点如果崩溃了,那么锁就没有了,其他节点再加锁就会出现不对的情况,可用性根本法则就是 copy,也就是有多个备份点
- 容错性,在可用性的基础上,存储锁节点崩溃后,保证锁是由正常行为的(加锁或解锁状态,谁给其加锁的等等),也就是节点崩溃后替换上来的节点和不崩溃的状态要一模一样,主要是使用 raft 一致性算法(半数),redis 的 redlock
类型
主要有两个类型
- 可重入锁/非可重入锁,也就是是否是递归锁
- 公平锁/非公平锁,也就是互斥锁和自旋锁的区别,公平体现在互斥锁加锁动作获取不到锁,就在阻塞队列排队了,而自旋锁一直在轮询尝试加锁
实现的重点
- 锁本质是一个节点上的资源,需要一个节点存储;要保证可用性,避免锁失效
- 加锁和解锁需要是同一个节点
- 互斥语义
- 加锁解锁行为是网络通信,需要锁超时
- 获取已持有锁方法
- 主动轮询持锁 非公平锁
- 被动通知
- 广播 非公平锁(不是按照排队的顺序获得的)
- 排队单独通知 公平锁
- 是否允许同一对象多次加锁
- 重入锁
- 非重入锁
分布式样例
mysql
mysql 实现分布式主要是利用数据库唯一键的约束来实现互斥性,构建一个表来实现分布式锁。这个表来存储所有的分布式锁。
看看是否可以满足分布式锁的特性
- 可用性取决于使用的 mysql 的可用性,如果不是 mysql 集群,仅有一个 mysql 单点,那单点崩溃了,就不可用了
- 加锁和解锁需要是同一个节点,owner_id 字段来实现加锁和解锁是一个节点
- 互斥性,唯一键值保证了互斥语义的实现
- 锁超时,mysql 本身没有实现定时机制,因此需要自己单独开一个线程不断轮询的判断锁是否超时
- 获取已持有锁方法,依然需要自己有一个线程,不断地轮询判断能否可以加锁,因此是非公平锁
- 加一个 count 字段来记录加锁次数,只有当次数为 0 时,才释放锁。因此可以根据实现,来实现重入锁和非重入锁,配合 owner_id 字段来实现
mysql 实现分布式锁的场景(效率最低,最不完备)
- 当主机中仅有 mysql 时,主要业务用不到其他的组件
- 仅有少量的业务使用分布式锁
redis
redis 是一种内存数据库,一半是用来做缓存的,官方有分布式锁的样例
看看是否可以满足分布式锁的特性
- 可用性,redis 是可以实现可用性的,redis 本事有哨兵模式和 cluster 模式,可以保证数据的可用性,但是容错是不能保证的,因为主从复制是采用异步复制的方式的,因此容错性是不行的。
- 同步复制,集群所有节点全部写入了,再返回成功
- 异步复制,raft 一致性协议,半数写入就可以返回成功
- 加锁和解锁需要是同一个节点,本身有实现此类机制的命令实现,lua 脚本可以实现原子操作,并且可以实现锁超时和互斥性
- redlock 解决容错性,redlock 就是有多个 redis 进程,加锁的时候对每个进程进行加锁,半数以上的进程加锁成功,加锁才成功,解锁的时候对每个进程都解锁,半数解锁成功才算成功
redis 实现分布式锁的场景(效率最高)
- 我们业务用到了 redis,比如做缓存什么的,有 redis 可以使用
- 我们有 redis 集群,那么可以使用其中一个或多个节点实现分布式锁
etdc
完备性最高的分布式锁
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于