一、线程锁、进程锁 在介绍分布式锁之前,我们先来简单介绍下线程锁、进程锁。
线程锁
线程锁,主要用来解决的问题是:保护临界区域。
使用方式:lock(mutex)、unlock(mutex)。
进程锁
为了控制同一操作系统中多个进程访问一个共享资源。
nginx中的accept锁,就是一种进程锁,它采用的实现方式是:共享内存+信号量。
二、分布式锁
2.1 分布式锁的实现方案
分布式锁的常见实现方案有:
1)数据库 redis,mysql
2)zookeeper
高效性比较:zookeeper > redis > mysql
2.2、分布式锁有哪些特征
1)互斥性
2)可重入性:一个进程允许递归获取锁(需要递归释放)
3)分布式锁需要解决锁超时(进程crash掉,需要考虑释放分布式锁)
4)高效、高可用(redis、zookeeper)。
5)公平锁(按获取锁顺序执行)和非公平锁(随机执行)
图1
文章图片
高可用,指的是数据中心crash掉后 ,进程依然可以获取到锁,并能够正确释放锁。
redis主从复制:主redis收到写操作,通知从redis方式实现数据同步,实现的是最终一致性(可能一秒rdb或者一个操作aof)。
zookeeper:使用zab协议,实现强一致性。
【后端服务|分布式锁的实现原理】分布式中,主要考虑CAP,c一致性,a可用性,p分区容错性(三者占二)。
2.3、redis实现分布式锁
1、redis运行原理
redis用作缓存数据库,也是数据结构数据库,kv数据库(hash)。
图2
文章图片
热点数据(常用)存放在redis中。
操作redis,实际是操作redis中的数据结构,rpc远程调用。
数据请求方式:请求回应+监听发布。
按请求顺序来执行命令,同样返回按照执行顺序。
比如:
请求:set hello world
回应:+OK
2、redis实现分布式锁
1)获取锁
2)执行逻辑
3)释放锁
设置锁:setnx()只有在key不存在的时候,才能设置成功。成功返回(+ok),失败返回(nil)。
释放锁:del key
结合 图1,引出几个问题。
问题1 进程a、b、c、d同时竞争锁,若产生了死锁,如何解决?
给进程的锁设置超时时间:set(“lock”, 四元组, “NX”, “EX”, 30) ,比如进程A超时了,进程B就可以获取到锁。
问题2 来自问题1的设置超时解决方案:进程A超时后,还需要释放锁吗?怎么释放?
可以对进程A的锁进行超时叠加:set(“lock”, 四元组, “NX”, “EX”, 30)超时后,可以依次续时间:20s,10s。
问题3 同样来自问题1的设置超时解决方案:当A设置锁超时之后,B可以获取到锁,如果B还没执行完逻辑而A执行完后,A释放了锁,这时C也可以获取锁。
我们可以通过增加是哪个进程标识,表明是哪个进行在使用锁。这样A就不能随意释放锁。
问题4 来自问题3的设置超时解决方案:如何对一个进程进行唯一标识?
1)方案1:使用 “PID” 作为进程唯一标识。缺陷:PID是一个递增ID,最大值有上限,约是32970,当ID值增加到最大值的时候,系统就会重新从1开始分配PID,没法区分,因此仅使用PID并不合适。
2)方案2:使用 “IP:PORT:PID” 作为进程唯一标识:会产生一种bug–可能通过批处理执行过很多命令,1270pid轮询重启后又变成1270,这个时候没法区分该进程是否有重启。
3)方案3:使用 “IP:PORT:STARTTIME:PID” 作为进程唯一标识,starttime是以ms单位来存储。
2.4、zookeeper实现分布式锁
zookeeper主要处理协调管理功能:统一命名、管理集群、配置中心(配置更新)、分布式锁。
图3
文章图片
集成zookeeper之后,一般都是配置一个Web后台。可以在Web后台对zookeeper进行配置更新。Zookeeper就可以调用批处理程序,比如重启等。
zookeeper通过数据模型+监听机制,来驱动所有的功能。
1、数据模型
类似文件系统存储方式
/root
/usr/local
特征:
1)持久的:持久化(写到硬盘中,重启后读到内存中)
2)短暂的:与连接相关,连接断开后,连接创建的节点自动删除
3)顺序的(如图4)
图4
文章图片
2、监听机制
可监听节点的变化(创建,删除,值的变化,子节点的变化(创建或删除))。
1)获取锁:创建同名节点,只有一个能创建成功。
2)释放锁:delete节点,断开连接自动删除,锁超时。
结合 图3,引出几个问题。
问题1 如果有多个进程都去创建,只有一个创建成功了,假设a创建成功了,bcd什么时候去创建呢?
可以使用监听机制,当节点失效的时候,都去竞争锁。小于10个进程竞争的时候,可以使用这种方式;而如果有大量进程获取锁,就会惊群效应,浪费网络资源。
问题2 来自问题1,如何解决惊群问题?
可以采用公平锁:
1)创建节点,产生短暂顺序节点。比如abcd依次创建,得到的短暂顺序节点0、1、2、3。
2)每一个进程都有一个序号 。 假设序号为0,就是它获取锁;假设序号不为0,只需要监听比它小一节的节点即可。
比如:b节点监听a节点,只要a释放,b就能拿到锁。以此类推:c监听b节点,d监听c节点。
问题3 来自问题2,进程a当前获取到锁,如果进程b崩溃,进程c如何能拿到锁?
改为进程c监听进程a。
推荐阅读
- Redis|一文看懂redis知识全景--redis核心技术与实战系列
- 分布式|Redis 主从复制、哨兵、集群的部署
- redis|Redis (主从复制+哨兵+集群)
- 运维|Redis集群(一)
- 数据库|redis之主从复制、哨兵模式、群集模式
- 项目实战系列|个性化广告推荐系统实战系列(六)(实时推荐产生结果)
- Redis|17 为什么 CPU 结构也会影响 Redis的性能()
- 数据清洗|redis作为MongoDB的缓存在线实时去重
- 数据库|2022年面试复盘大全400道(Redis+ZK+Nginx+数据库+分布式+微服务)