分布式锁

简介: 分布式锁是分布式系统中实现跨节点资源互斥访问的关键机制,常用于解决多进程、多机器环境下的并发控制问题。它依赖外部存储(如Redis、ZooKeeper)协调锁状态,确保全局唯一性和原子性操作。常见实现包括基于Redis的单点锁与RedLock算法、ZooKeeper的临时顺序节点及数据库唯一索引。适用于任务调度、缓存重建和库存管理等场景。设计时需关注可重入性、锁超时、续租及异常处理,并权衡性能与可靠性。

分布式锁是在分布式系统中实现跨节点资源互斥访问的一种机制,用于解决多进程、多机器环境下的并发控制问题。与单机锁(如synchronized)不同,分布式锁需要依赖外部共享存储(如Redis、ZooKeeper)来协调不同节点间的锁状态。

核心原理

  1. 全局唯一性
    通过共享存储(如Redis的SETNX命令)确保同一时间只有一个客户端能获取锁。

  2. 原子性操作
    锁的获取和释放必须是原子操作,避免竞态条件。例如:

    # Redis的SET命令同时设置值和过期时间(原子操作)
    SET lock_key "value" NX PX 30000  # 30秒过期
    
  3. 锁超时机制
    防止锁持有者崩溃后锁无法释放,通过设置过期时间自动失效。

分布式锁的实现方式

1. 基于Redis的实现

  • 单点Redis

    import redis
    import time
    
    class RedisLock:
        def __init__(self, redis_client, lock_key, expire_time=30):
            self.redis = redis_client
            self.lock_key = lock_key
            self.expire_time = expire_time
            self.uuid = str(uuid.uuid4())  # 唯一标识锁持有者
    
        def acquire(self):
            return self.redis.set(self.lock_key, self.uuid, nx=True, ex=self.expire_time)
    
        def release(self):
            script = """
            if redis.call("get", KEYS[1]) == ARGV[1] then
                return redis.call("del", KEYS[1])
            else
                return 0
            end
            """
            return self.redis.eval(script, 1, self.lock_key, self.uuid)
    
  • RedLock算法(多节点Redis):
    向多个独立的Redis节点同时获取锁,超过半数成功则认为获取成功,提高可靠性。

2. 基于ZooKeeper的实现

  • 创建临时顺序节点,最小节点持有者获得锁,其他节点监听前一个节点的删除事件。

    public class ZkLock implements AutoCloseable {
         
        private final CuratorFramework client;
        private final String lockPath;
        private String currentPath;
        private String previousPath;
    
        public ZkLock(CuratorFramework client, String lockPath) {
         
            this.client = client;
            this.lockPath = lockPath;
        }
    
        public void acquire() throws Exception {
         
            if (currentPath == null) {
         
                currentPath = client.create()
                    .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                    .forPath(lockPath + "/lock-");
            }
    
            List<String> children = client.getChildren().forPath(lockPath);
            Collections.sort(children);
    
            if (currentPath.endsWith(children.get(0))) {
         
                return; // 当前节点是最小节点,获取锁成功
            }
    
            // 监听前一个节点
            previousPath = lockPath + "/" + children.get(children.indexOf(currentPath.substring(lockPath.length() + 1)) - 1);
            CountDownLatch latch = new CountDownLatch(1);
            NodeCache cache = new NodeCache(client, previousPath);
            cache.getListenable().addListener(() -> {
         
                if (cache.getCurrentData() == null) {
         
                    latch.countDown();
                }
            });
            cache.start();
    
            // 检查前一个节点是否已删除
            if (client.checkExists().forPath(previousPath) == null) {
         
                return;
            }
    
            latch.await(); // 等待前一个节点释放锁
        }
    
        @Override
        public void close() throws Exception {
         
            client.delete().forPath(currentPath);
        }
    }
    

3. 基于数据库的实现

  • 通过唯一索引或INSERT ... ON DUPLICATE KEY UPDATE实现:

    -- 创建锁表
    CREATE TABLE distributed_lock (
        lock_key VARCHAR(64) PRIMARY KEY,
        expire_time DATETIME NOT NULL
    );
    
    -- 获取锁
    INSERT INTO distributed_lock (lock_key, expire_time)
    VALUES ('resource_key', NOW() + INTERVAL 30 SECOND)
    ON DUPLICATE KEY UPDATE expire_time = NOW() + INTERVAL 30 SECOND;
    

分布式锁的典型应用场景

  1. 分布式任务调度
    防止多个节点同时执行同一任务。

  2. 缓存失效重建
    避免缓存击穿时大量请求同时重建缓存。

  3. 库存扣减
    保证分布式系统中库存操作的原子性。

分布式锁的设计要点

  1. 可重入性
    同一客户端可多次获取同一把锁,需记录获取次数。

  2. 锁的续租
    通过定时任务延长锁的过期时间,防止任务执行时间过长导致锁提前释放。

  3. 异常处理

    • 锁持有者崩溃时,通过过期时间自动释放锁。
    • 释放锁时验证锁的所有者,防止误释放。

与单机锁的对比

特性 单机锁 分布式锁
作用范围 同一进程内 跨进程、跨机器
实现方式 JVM内置(如synchronized) 依赖外部存储(Redis、ZooKeeper)
可靠性 取决于外部存储的可靠性
性能 高(无网络开销) 低(网络调用开销)
复杂度 高(需处理网络分区等)

注意事项

  1. 网络分区问题
    当发生网络分区时,可能导致多个节点同时认为自己持有锁(如Redis的脑裂问题)。

  2. 锁的粒度
    避免使用全局锁,尽量细化锁的粒度以提高并发度。

  3. 性能权衡
    分布式锁引入网络开销,需根据业务场景选择合适的实现方案。

总结

分布式锁是分布式系统中实现资源互斥的关键机制,常用Redis、ZooKeeper或数据库实现。设计时需考虑原子性、可重入性、锁超时和异常处理等问题。在选择实现方案时,需根据业务场景权衡性能、可靠性和复杂度。合理使用分布式锁能有效避免多节点并发带来的数据一致性问题。

目录
相关文章
|
7月前
|
存储 安全 Java
Java HashMap 全面解析:原理、用法与实战要点
本文深入解析Java中HashMap的底层原理与使用实践,涵盖其“数组+链表+红黑树”的结构演变、哈希计算、扩容机制及线程安全问题,详解常用方法、性能优化与最佳实践,助力开发者高效掌握这一核心数据结构。
1768 11
|
机器学习/深度学习 编解码 BI
RegNet架构复现--CVPR2020
在这项工作中,我们**提出了一种新的网络设计范式**。我们的目标是帮助促进对网络设计的理解,并发现跨环境通用的设计原则。我们不是专注于设计单个网络实例,而是设计参数化网络群体的网络设计空间。整个过程类似于经典的网络手动设计,但提升到了设计空间级别。使用我们的方法,我们探索了网络设计的结构方面,并**得出了一个由简单、规则的网络组成的低维设计空间,我们称之为** ==RegNet==。
2788 0
RegNet架构复现--CVPR2020
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,日期时间格式化是前后端交互的常见痛点。本文详细解析了 **@DateTimeFormat** 和 **@JsonFormat** 两个注解的用法,分别用于将前端传入的字符串解析为 Java 时间对象,以及将时间对象序列化为指定格式返回给前端。通过完整示例代码,展示了从数据接收、业务处理到结果返回的全流程,并总结了解决时区问题和全局配置的最佳实践,助你高效处理日期时间需求。
2207 0
|
存储 算法 安全
HashMap的实现原理,看这篇就够了
关注【mikechen的互联网架构】,10年+BAT架构经验分享。深入解析HashMap,涵盖数据结构、核心成员、哈希函数、冲突处理及性能优化等9大要点。欢迎交流探讨。
HashMap的实现原理,看这篇就够了
解决IDEA中 Could not autowire. No beans of 'xxxx' type found 的错误提示
解决IDEA中 Could not autowire. No beans of 'xxxx' type found 的错误提示
13155 3
解决IDEA中 Could not autowire. No beans of 'xxxx' type found 的错误提示
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
803 29
JVM简介—1.Java内存区域
|
存储 关系型数据库 索引
什么是聚簇索引及其优缺点?
聚簇索引并不是单独的索引类型,而是一种数据存储方式。 B+树索引分为聚簇索引和非聚簇索引,主键索引就是聚簇索引的一种,非聚簇索引有复合索引、前缀索引、唯一索引。 在innodb存储引擎中,表数据本身就是按B+树组织的一个索引结构,聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚簇索引的叶子节点成为数据页。 Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。 非聚簇索引又称为辅助索引,InnoDB访问数据需要两次查找,辅助索引叶子节点存储的不再是行
|
Java Maven 开发工具
【IntelliJ IDEA】使用Maven方式构建Spring Boot Web 项目(超详细)1
【IntelliJ IDEA】使用Maven方式构建Spring Boot Web 项目(超详细)
1321 2
|
设计模式 Java 关系型数据库
设计模式——设计模式简介和七大原则
设计模式的目的和核心原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则、合成复用原则
设计模式——设计模式简介和七大原则