[TOC]

本地加锁原理

解决并发写竞争的方法:

  • 原子操作
  • 加锁

本地加锁实现:

  • 加锁:使用 CAS 尝试将锁变量由 0 改成 1
  • 释放锁:将锁变量由 1 改成 0

todo: AQS/ReentrantLock 源码分析

分布式锁实现就是将锁变量放到共享存储系统(Redis)上,使不同的客户端可以操作该锁变量。

单节点 Redis 分布式锁

实现分布式锁需要考虑的问题:

  • 加锁/释放锁操作的原子性(单命令或 Lua 脚本)
  • 连接超时(和远程调用一样,需要考虑调用超时情况)
  • 加锁后客户端发生异常,锁能够自动超时释放,避免死锁
  • 释放锁时,只有持有锁的客户端才能释放锁,避免锁被意外释放
  • 可靠性,需要考虑实例宕机故障的情况,通过多 Redis 节点和算法保证锁的可靠性

Redis 单节点分布式锁实现:

# 加锁(Redis 命令)
# id 可以唯一标识客户端的值
# NX 当键不存在时,才会进行设置(通过设置是否成功隐式表示锁变量)
# PX 过期时间(毫秒),EX 过去时间(秒) 
SET lock_key id NX PX 10000
# 释放锁(Lua 脚本)
# KEYS[1]: lock_key
# ARGV[1]: id
if redis.call("get", KEYS[1]) == ARGV[1] then 
  return redis.call("del",KEYS[1])
else 
  return 0end

todo: 了解 Redisson 分布式锁实现

多节点 Redis 分布式锁

为了避免单节点 Redis 故障,提高锁的可靠性,就需要使用多节点 + 分布式锁算法实现。

Redis RedLock 算法:

# 加锁
1. 获取开始时间
2. 尝试对每个节点进行加锁(需要设置连接超时,并且需要远远小于锁的有效时间,一般是几十毫秒)
3. 判断是否对超过半数的节点(N/2 + 1)加锁成功,否则加锁失败
4. 重新计算锁的有效剩余时间(最初有效时间-获取锁的总耗时),如果判断剩余时间能完成操作,加锁成功;否则加锁失败。
    (第四步是为了防止业务还没执行完,锁就过期自动释放的情况) 
# 释放锁
1. 依次对所有节点执行释放锁操作

RedLock 保证了只要半数以上的节点是正常的,锁就能正常工作。

todo:

  1. 了解 Redis RedLock 算法
  2. 评论区提示 RedLock 已被不官方推荐使用

参考

  • 《Redis 核心技术与实战》