Skip to content

分布式锁

当多个 Redis 部署在多个服务器上,形成集群,那么就需要实现分布式锁,来控制共享资源的访问,提高效率。

问题描述

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁主流的实现方案:

  • 基于数据库实现分布式锁

  • 基于缓存(Redis 等)

  • 基于 Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  • 性能:Redis 最高

  • 可靠性:zookeeper 最高

本内容,我们就基于 Redis 实现分布式锁。

分布式锁指令

使用命令

sh
set <key> <value> <nx / xx> <px millisecond / ex second>
  • nxxx 二选一:
    • nx:只在键不存在时,才对键进行设置操作
    • xx:只在键已经存在时,才对键进行设置操作
  • px millisecondex second 二选一:
    • px millisecond:设置键的过期时间为 millisecond 毫秒
    • ex second:设置键的过期时间为 second 秒

上面的命令中

sh
set <key> <value> <px second>

等价于

sh
setex <key> <second> <value>

sh
set <key> <value> <nx>

等价于

sh
setnx <key> <value>

所以综合就是:

sh
setnx <key> <second> <value>

例子:

sh
set name "kele" nx px 10000

可以写成:

sh
setnx name 10000 "kele"

注意:Redis 实现分布式锁的指令是 setnx,该指令的功能是:

  • 如果插入的 key 没有存在 Redis,则将 key-value 存入 Redis

  • 如果插入的 key 已经存在 Redis,则 value 失效,无法重新覆盖原来的 value

这样就实现了分布式锁:key 存在则代表有人操作,其他人无法操作。

总结

代码总结

  • 加锁(setnx 指令)
  • 添加过期时间(setnx 指令加时间)
  • 添加唯一标识如:uuid(将 uuid 放入 Reids,然后操作时获取 uuid,添加 if 判断)
  • 添加原子性,用 LUA 语言实现(第 2、3 步用 LUA 语言编写)

分布式锁总结

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁

  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁

  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

  • 加锁和解锁必须具有原子性