1. 悲观锁与乐观锁

1.1 悲观锁

  • 核心思想:默认一定会存在资源竞争,提前锁定资源独占使用,其他请求阻塞等待,使用完毕再释放锁
  • 特点:先上锁,执行业务,阻塞其他并发请求

1.2 乐观锁

  • 核心思想:默认并发冲突极少发生,不主动加排他锁,所有请求并行执行,出现冲突后再做重试、回滚处理
  • 特点:执行业务在前,冲突处理在后,全程不阻塞其他请求

特别注意:纯版本号乐观锁,作用是解决多人并发修改冲突,无法用于拦截单人重复提交;误用会触发不必要的并发修改异常,业务语义错乱。


2. Redis 分布式锁

异步弱一致、看门狗续命、自旋抢锁、存在丢锁风险 存储唯一uuid标识锁持有者,防止误删除他人锁

2.1 数据结构

2.1.1 简易普通分布式锁(String 类型)

  • 结构:String Key → Value
  • Key:lock:stock:1001
  • Value:唯一随机 UUID(锁持有者标识)
  • 附带:expire 自动过期时间
  • 无内置版本号,value仅做身份标识,无序无递增时序

2.1.2 Redisson 可重入分布式锁(Hash 类型)

  • Hash Key:lock:stock:1001
  • Hash Field:客户端ID + 线程ID
  • Hash Value:锁重入次数
  • 附带:全局过期时间

示例: lock:stock:1001 ├─ client001-thread1 : 2 // 重入2次 └─ client002-thread2 : 0

2.2 实现流程

2.2.1 抢锁

  1. 生成唯一随机token;
  2. 执行原子命令 SETNX key token EX 过期时间

key:锁名称 value:唯一随机标识 NX:不存在才创建 EX:过期时间秒 SET lock:order:1001 uuid_8888 NX EX 30 命令返回成功 = 抢到锁,失败 = 未抢到。

2.2.2 释放锁

普通释放分为查询校验token、删除锁两步,非原子操作存在安全隐患 使用Lua脚本整合两步操作,保证原子性执行

1 -- 校验当前锁持有者,一致才删除
2if redis.call('get', KEYS[1]) == ARGV[1] then
3    return redis.call('del', KEYS[1])
4else
5    return 0
6end

3. etcd 分布式锁

Raft 强一致、租约续命、监听唤醒、可靠性极高 全局递增revision版本号,版本号最小者获取锁,未抢到锁则监听等待,不占用CPU自旋

3.1 数据结构

Key : “/lock/order” – 锁前缀,实际为前缀+唯一ID Value : “client-uuid” – 客户端标识 Lease : 租约 ID (TTL 自动续约) – 心跳保活,异常自动释放锁

底层元数据(etcd 内部 MVCC 属性)

  • create_revision:创建key全局递增版本号,抢锁排队核心依据
  • mod_revision:key最后一次修改版本号
  • version:key累计修改次数

排队核心规则:所有客户端使用相同前缀写入key,etcd保证create_revision全局单调递增,版本号最小客户端持有锁,其余客户端按版本顺序排队等待。

3.2 实现流程

3.2.1 抢锁(Campaign)

  1. 客户端生成唯一标识与TTL租约Lease
  2. 以统一前缀写入带租约的唯一key
  3. etcd分配全局唯一create_revision版本号
  4. 获取前缀下所有key,按版本号升序排序
  5. 自身版本号最小,直接获取锁执行业务
  6. 版本号非最小,监听前一个更小版本key的删除/过期事件,挂起等待
  7. 前置锁释放后,监听触发唤醒,重新校验抢占锁

3.2.2 锁续约(Keepalive)

  • 持有锁期间,定时发送心跳刷新租约,证明客户端正常在线
  • 客户端宕机、网络断开,续约中断,租约到期自动删除key,锁自动释放

3.2.3 释放锁(Resign)

  • 主动释放:业务执行完成,删除key或撤销租约
  • 被动释放:程序异常崩溃,租约超时,etcd自动清理锁,唤醒排队客户端

3.3 安全性与高可靠保证

3.3.1 宕机自动释锁

依托租约机制,杜绝死锁问题,客户端异常无需人工干预,超时自动释放锁。

3.3.2 Raft集群强一致性

  • 加锁、释锁所有写操作,必须集群半数以上节点确认才可生效
  • 避免网络分区脑裂问题,杜绝双客户端同时持有同一把锁
  • 集群节点2n+1架构,最多容忍n台节点故障

4. ZooKeeper 分布式锁

ZAB 原子广播、临时顺序节点、Watch 通知、天然公平锁

4.1 数据结构

  • 锁路径前缀:/lock/order
  • 节点类型:临时顺序节点(Ephemeral Sequential),自动生成全局递增序号
  • 临时节点特性:客户端会话断开,节点自动删除
  • 节点数据:存储客户端IP、线程ID,用于重入校验与日志排查

4.2 实现流程

4.2.1 抢锁

  1. 客户端在统一锁路径下创建临时顺序节点
  2. 获取路径下所有子节点,按序号升序排列
  3. 自身节点序号最小,成功获取分布式锁
  4. 序号非最小,监听前置序号节点删除事件,进入等待状态
  5. 前置节点释放删除,监听事件触发,唤醒客户端重新抢占锁

4.2.2 锁释放

  • 主动释放:业务结束,手动删除自身创建节点
  • 被动释放:客户端宕机、网络中断,会话超时自动清空临时节点,自动移交锁权限

4.2.3 可重入性支持

节点内记录持有者线程信息与重入次数,本地完成重入判断,动态更新节点计数即可实现锁重入。

4.3 安全性与一致性强保证

4.3.1 宕机无死锁

临时节点绑定客户端会话生命周期,程序崩溃会话超时,锁强制释放,彻底规避死锁。

4.3.2 ZAB协议集群一致性

基于ZAB原子广播协议,所有锁相关写操作,过半节点确认才生效,集群一致性极强,杜绝脑裂重复加锁。

4.3.3 公平无惊群

顺序节点全局自增有序,客户端依次排队监听,不会出现大量客户端同时唤醒争抢锁的惊群效应。


5. 分布式锁归属:悲观锁 VS 乐观锁

核心结论 业务应用层面:Redis、etcd、ZooKeeper 分布式锁,全部属于悲观锁 中间件底层层面:三者内核均搭载乐观锁CAS版本机制,依托底层乐观原语封装上层悲观锁。

5.1 应用视角:属于悲观锁

悲观锁核心逻辑:预判冲突,预先加锁独占资源 分布式锁标准使用流程:

  1. 尝试抢占锁,失败则阻塞/重试等待
  2. 获取锁后执行复杂跨服务业务逻辑
  3. 业务完成主动释放锁

同一资源同一时间仅一个客户端持有锁,其余请求全部拦截阻塞,完全契合悲观锁设计思想。

5.2 中间件底层:依托乐观锁原语

底层依靠版本号、CAS比较交换实现数据一致性,是构建分布式悲观锁的基础。

  • etcd:内置MVCC多版本控制,依托revision版本号实现事务乐观校验,上层结合租约+监听封装排队悲观锁
  • ZooKeeper:节点自带dataVersion数据版本,支持版本号校验更新的乐观操作,依靠临时顺序节点实现排队悲观锁
  • Redis:底层依靠SETNX原子CAS指令,搭配Lua脚本实现抢占逻辑;原生支持WATCH乐观事务,极少用于封装分布式锁

5.3 总结与架构思考

  1. 业务调用Lock、Unlock方法,使用形态为悲观分布式锁
  2. 分布式悲观锁 = 底层CAS乐观原子指令 + 上层循环阻塞重试逻辑
  3. 架构优化思路:高并发场景尽量规避分布式悲观锁,降低阻塞损耗
    • 采用消息队列实现异步最终一致性
    • 引入CRDT无冲突复制数据类型,从数据层面规避锁竞争,彻底消除分布式锁性能瓶颈