FantasticMao 技术笔记
BlogGitHub
  • README
  • C & Unix
    • C
      • 《C 程序设计语言》笔记
      • C 语言中的陷阱
      • CMake 示例
      • GNU make
      • LLVM Clang
      • Nginx 常用模块
      • Vim 常用命令
    • Unix-like
      • 《深入理解计算机系统》笔记
      • 《UNIX 环境高级编程》笔记 - UNIX 基础知识
      • 《UNIX 环境高级编程》笔记 - 文件 IO
      • 《UNIX 环境高级编程》笔记 - 标准 IO 库
      • 《鳥哥的 Linux 私房菜》笔记 - 目录配置
      • 《鳥哥的 Linux 私房菜》笔记 - 认识与学习 bash
      • 《鳥哥的 Linux 私房菜》笔记 - 任务管理
      • OpenWrt 中的陷阱
      • iptables 工作机制
  • Go
    • 《A Tour of Go》笔记
    • Go vs C vsJava
    • Go 常用命令
    • Go 语言中的陷阱
  • Java
    • JDK
      • 《Java 并发编程实战》笔记 - 线程池的使用
      • 设计模式概览
      • 集合概览
      • HashMap 内部算法
      • ThreadLocal 工作机制
      • Java Agent
    • JVM
      • 《深入理解 Java 虚拟机》笔记 - Java 内存模型与线程
      • JVM 运行时数据区
      • 类加载机制
      • 垃圾回收算法
      • 引用类型
      • 垃圾收集算法
      • 垃圾收集器
    • Spring
      • Spring IoC 容器扩展点
      • Spring Transaction 声明式事务管理
      • Spring Web MVC DispatcherServlet 工作机制
      • Spring Security Servlet 实现原理
    • 其它
      • 《Netty - One Framework to rule them all》演讲笔记
      • Hystrix 设计与实现
  • JavaScript
    • 《写给大家看的设计书》笔记 - 设计原则
    • 《JavaScript 权威指南》笔记 - jQuery 类库
  • 数据库
    • ElasticSearch
      • ElasticSearch 概览
    • HBase
      • HBase 数据模型
    • Prometheus
      • Prometheus 概览
      • Prometheus 数据模型和指标类型
      • Prometheus 查询语法
      • Prometheus 存储原理
      • Prometheus vs InfluxDB
    • Redis
      • 《Redis 设计与实现》笔记 - 简单动态字符串
      • 《Redis 设计与实现》笔记 - 链表
      • 《Redis 设计与实现》笔记 - 字典
      • 《Redis 设计与实现》笔记 - 跳跃表
      • 《Redis 设计与实现》笔记 - 整数集合
      • 《Redis 设计与实现》笔记 - 压缩列表
      • 《Redis 设计与实现》笔记 - 对象
      • Redis 内存回收策略
      • Redis 实现分布式锁
      • Redis 持久化机制
      • Redis 数据分片方案
      • 使用缓存的常见问题
    • MySQL
      • 《高性能 MySQL》笔记 - Schema 与数据类型优化
      • 《高性能 MySQL》笔记 - 创建高性能的索引
      • 《MySQL Reference Manual》笔记 - InnoDB 和 ACID 模型
      • 《MySQL Reference Manual》笔记 - InnoDB 多版本
      • 《MySQL Reference Manual》笔记 - InnoDB 锁
      • 《MySQL Reference Manual》笔记 - InnoDB 事务模型
      • B-Tree 简述
      • 理解查询执行计划
  • 中间件
    • gRPC
      • gRPC 负载均衡
    • ZooKeeper
      • ZooKeeper 数据模型
    • 消息队列
      • 消息积压解决策略
      • RocketMQ 架构设计
      • RocketMQ 功能特性
      • RocketMQ 消息存储
  • 分布式系统
    • 《凤凰架构》笔记
    • 系统设计思路
    • 系统优化思路
    • 分布式事务协议:二阶段提交和三阶段提交
    • 分布式系统的技术栈
    • 分布式系统的弹性设计
    • 单点登录解决方案
    • 容错,高可用和灾备
  • 数据结构和算法
    • 一致性哈希
    • 布隆过滤器
    • 散列表
  • 网络协议
    • 诊断工具
    • TCP 协议
      • TCP 报文结构
      • TCP 连接管理
由 GitBook 提供支持
在本页
  • 单机版实现
  • 分布式实现
  • 参考资料
  1. 数据库
  2. Redis

Redis 实现分布式锁

单机版实现

实现单实例的 Redis 分布式锁的具体算法如下:

  1. 获取锁的过程:使用 SET key value NX PX milliseconds 命令,尝试在 Redis 中设置一个唯一的随机值。若设值成功则表示获取锁成功,若设值失败则表示获取锁失败;

  2. 释放锁的过程:当且仅当 key 存在且 key 对应的 value 值和获取锁时设置的值相等时,删除 key,表示释放锁成功,否则表示释放锁失败。这段逻辑的代码需要使用 Lua 脚本执行。

在这个实现算法中,有几点需要注意:

  • 获取锁时,需要设置一个 key 的过期时间,这是为了避免在客户端崩溃时造成死锁的状况;

  • 获取锁时,需要设置一个唯一的随机值,并且需要在释放锁时被比对,这是为了避免释放由其它客户端创建的锁;

  • 为了避免产生竞态条件,获取锁和释放锁都需要有原子性的保障。所以在设置锁时需要使用 SET key value NX PX milliseconds 命令,而不是先 SET key value NX 再 EXPIRE key seconds,而且释放锁时也需要使用 Lua 脚本。

关键实现步骤如下:

public class DistributedRedisLock implements Lock {
    private final JedisPool jedisPool;
    private final String lockKey;
    private final String randomString;
    private final long millisecondsToExpire;

    public DistributedRedisLock(JedisPool jedisPool, String key, TimeUnit unit, long duration) {
        this.jedisPool = jedisPool; // 使用 JedisPool 获取线程安全的 Redis 连接
        this.lockKey = key;
        this.randomString = UUID.randomUUID().toString();
        this.millisecondsToExpire = unit.toMillis(duration);
    }

    @Override
    public void lock() {
        try (Jedis jedis = jedisPool.getResource()) {
            while (true) {
                String result = jedis.set(lockKey, randomString, SetParams.setParams().px(millisecondsToExpire).nx());
                if (result != null) {
                    break;
                } else {
                    try {
                        TimeUnit.MILLISECONDS.sleep(50);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                }
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        try (Jedis jedis = jedisPool.getResource()) {
            while (!Thread.currentThread().isInterrupted()) {
                String result = jedis.set(lockKey, randomString, SetParams.setParams().px(millisecondsToExpire).nx());
                if (result != null) {
                    break;
                } else {
                    TimeUnit.MILLISECONDS.sleep(50);
                }
            }
        }
    }

    @Override
    public boolean tryLock() {
        try (Jedis jedis = jedisPool.getResource()) {
            String result = jedis.set(lockKey, randomString, SetParams.setParams().px(millisecondsToExpire).nx());
            return result != null;
        }
    }

    @Override
    public boolean tryLock(long time, @Nonnull TimeUnit unit) throws InterruptedException {
        try (Jedis jedis = jedisPool.getResource()) {
            long startTime = System.nanoTime();
            long expireTime = startTime + unit.toNanos(time);
            while (!Thread.currentThread().isInterrupted()) {
                String result = jedis.set(lockKey, randomString, SetParams.setParams().px(millisecondsToExpire).nx());
                if (result != null) {
                    return true;
                } else if (System.nanoTime() >= expireTime) {
                    return false;
                } else {
                    TimeUnit.MILLISECONDS.sleep(50);
                }
            }
            return false;
        }
    }

    @Override
    public void unlock() {
        try (Jedis jedis = jedisPool.getResource()) {
            final String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
            jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(randomString));
        }
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}

分布式实现

在使用单实例的 Redis 分布式锁的情况下,如果唯一的 Redis 实例挂了,那么分布式锁功能则完全失效了。为此 Redis 社区提出了一种多实例 Redis 的分布式锁算法——Redlock。

Redlock 的大致思路是假设存在 2n+1 台 Redis 实例,当应用获取锁时,需在限定的超时时间内向这些 Redis 实例批量地设置相同的 key 和 value,如果某台 Redis 实例在超时时间内没有设值成功,则应该尽快跳过这台实例。最终,如果在 >n 台实例上设值成功,则表示获取锁成功,否则表示获取锁失败,并且需要删除所有 Redis 实例上的 key。当应用释放锁时,只是简单地向所有 Redis 实例发送释放锁的命令。

参考资料

最后更新于1年前

Distributed locks with Redis