分布式锁

简介: 分布式锁是分布式系统中实现跨节点资源互斥访问的关键机制,常用于解决多进程、多机器环境下的并发控制问题。它依赖外部存储(如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 Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1167 0
|
7月前
|
消息中间件 canal 存储
如何解决并发环境下双写不一致的问题?
在并发环境下,“双写不一致”指数据库与缓存因操作顺序或执行时机差异导致数据不匹配。解决核心是保证操作的原子性、顺序性或最终一致性。常见方案包括延迟双删、加锁机制、binlog同步、版本号机制和读写锁分离,分别适用于不同一致性要求和并发场景,需根据业务需求综合选择。
582 0
|
2月前
|
JSON API PHP
免费ICP备案查询API接口详细教程
本文介绍“接口盒子”提供的免费ICP备案查询API,支持通过域名快速获取备案信息,如主办单位、备案号、审核时间等。基于离线库查询,适合非实时场景,开发者可免费调用并集成至应用,需注册获取ID和KEY,另有付费优享版供高并发需求使用。
347 2
|
7月前
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
703 3
|
机器学习/深度学习 数据采集 人工智能
TeleAI 开源星辰语义大模型-TeleChat2!
2024.9.20 中国电信人工智能研究院(TeleAI)开源TeleChat2-115B模型,该模型是首个完全国产算力训练并开源的千亿参数模型。
|
11月前
|
存储 关系型数据库 索引
什么是聚簇索引及其优缺点?
聚簇索引并不是单独的索引类型,而是一种数据存储方式。 B+树索引分为聚簇索引和非聚簇索引,主键索引就是聚簇索引的一种,非聚簇索引有复合索引、前缀索引、唯一索引。 在innodb存储引擎中,表数据本身就是按B+树组织的一个索引结构,聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚簇索引的叶子节点成为数据页。 Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。 非聚簇索引又称为辅助索引,InnoDB访问数据需要两次查找,辅助索引叶子节点存储的不再是行
|
IDE 开发工具
【开发IDE升级】如何对IDEA版本进行升级
本文介绍了如何将 IntelliJ IDEA Ultimate 从 2020.2.2 版本升级到 2022.3.2 版本。主要内容包括准备工作、卸载旧版本和安装新版本的步骤。首先,从官网下载所需版本并备份旧版配置;接着,通过 Uninstall.exe 卸载旧版,保留配置和插件;最后,安装新版并完成激活。详细的操作步骤和截图帮助用户顺利完成升级过程。
14126 1
【开发IDE升级】如何对IDEA版本进行升级
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
SQL 算法 Java
(二十六)MySQL分库篇:Sharding-Sphere分库分表框架的保姆级教学!
前面《MySQL主从原理篇》、《MySQL主从实践篇》两章中聊明白了MySQL主备读写分离、多主多写热备等方案,但如果这些高可用架构依旧无法满足业务规模,或业务增长的需要,此时就需要考虑选用分库分表架构。
6273 4
|
缓存 网络协议 算法
(二)Java网络编程之爆肝HTTP、HTTPS、TLS协议及对称与非对称加密原理!
作为一名程序员,尤其是Java程序员,那必须得了解并掌握HTTP/HTTPS相关知识。因为在如今计算机网络通信中,HTTP协议的作用功不可没,无论是日常上网追剧、冲���、亦或是接口开发、调用等,必然存在HTTP的“影子”在内。尤其对于WEB开发者而言,HTTP几乎是每天会打交道的东西。
481 10