分布式锁,万字长文,从入门到实战,看这一篇就够了

1. 分布式锁是什么

作为一名在互联网大厂摸爬滚打多年的架构师,见证了无数系统从单体应用演变为分布式架构的过程,也踩过不少分布式环境下的坑。

其中,分布式锁就是一个既简单又复杂的技术点,今天想和大家深入探讨一下这个话题。

分布式锁,顾名思义,就是在分布式环境下实现的锁机制。在单机系统中,我们可以使用语言提供的锁机制(如Java的synchronized或ReentrantLock)来控制共享资源的访问。

但在分布式系统中,这些本地锁就无能为力了,因为多个服务实例可能分布在不同的机器上,本地锁只能控制单机内的资源访问。
在这里插入图片描述

那么分布式锁到底有什么用途呢?简单来说,当多个分布式服务实例需要对共享资源进行互斥访问时,就需要用到分布式锁。典型的场景包括:

  1. 避免重复处理:如定时任务在多个服务实例上同时触发,但只需要一个实例处理就行。

  2. 数据一致性保护:多个服务实例可能同时修改同一份数据,需要加锁防止数据被污染。

  3. 秒杀场景:有限商品在短时间内面临大量用户抢购,需要确保不超卖。

  4. 业务流程控制:某些业务操作需要按顺序执行,分布式锁可以确保这种顺序性。

2. 分布式锁的核心特性

作为从业多年的架构师,我认为一个合格的分布式锁至少要满足以下几个特性:

2.1 互斥性

这是最基本的,在任何时候,只有一个客户端能持有锁。这就像一个会议室,当有人在使用时,需要挂上"使用中"的牌子,其他人看到后就会等待。

2.2 可重入性

同一个客户端可以多次获取同一把锁,这是避免死锁的必要特性。就像我进入自己的办公室后反锁了门,之后我可以随意在办公室和外面走动,而不需要每次都开新锁。

2.3 锁超时

即使持有锁的客户端崩溃,锁也会在一定时间后自动释放,防止死锁。这就好比图书馆的借阅系统,即使借书人忘记归还,系统也会在到期后将书籍标记为可借。

2.4 高可用

锁服务不应成为系统的单点故障。就像公司的门禁系统,如果只有一个门禁系统,它故障了会导致所有人都无法正常工作,所以应该有备用系统。

2.5 高性能

获取和释放锁的操作应该尽可能快,不应成为系统的性能瓶颈。就像高速公路的收费站,如果处理缓慢,会导致大量车辆排队等待。

3. 分布式锁的实现方式

经过多年的项目实践,我发现主流的分布式锁实现主要有以下几种:

3.1 基于数据库的实现

这是最容易理解的实现方式,利用数据库的唯一索引特性来实现互斥。

-- 创建锁表
CREATE TABLE distributed_lock (
    lock_key VARCHAR(64) NOT NULL,
    lock_value VARCHAR(128) NOT NULL,
    expire_time TIMESTAMP NOT NULL,
    PRIMARY KEY (lock_key)
);

-- 获取锁(使用INSERT语句)
INSERT INTO distributed_lock (lock_key, lock_value, expire_time) 
VALUES ('order_process', 'client_123', NOW() + INTERVAL '30 SECOND');
-- 如果插入成功,则获取锁成功;如果因主键冲突插入失败,则获取锁失败

-- 获取锁的另一种方式(使用SELECT FOR UPDATE)
BEGIN TRANSACTION;
SELECT * FROM distributed_lock WHERE lock_key = 'order_process' FOR UPDATE;
-- 如果记录不存在,则插入一条记录
INSERT INTO distributed_lock (lock_key, lock_value, expire_time) 
VALUES ('order_process', 'client_123', NOW() + INTERVAL '30 SECOND');
COMMIT;

-- 释放锁
DELETE FROM distributed_lock WHERE lock_key = 'order_process' AND lock_value = 'client_123';

-- 锁续期
UPDATE distributed_lock 
SET expire_time = NOW() + INTERVAL '30 SECOND' 
WHERE lock_key = 'order_process' AND lock_value = 'client_123';

优点:

  • 实现简单,易于理解。
  • 不需要引入额外的组件。
  • 适合读写频率不高的场景。

缺点:

  • 性能较差,特别是在高并发场景下。
  • 数据库可能成为单点故障。
  • 没有内置的自动过期机制,需要额外开发。

3.2 基于Redis的实现

Redis凭借其高性能和原子操作特性,成为实现分布式锁的热门选择。我们先来看一个简单的实现:

import redis.clients.jedis.Jedis;
import java.util.Collections;

public class RedisLock {
   
   
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;
    
    private Jedis jedis;
    
    public RedisLock(Jedis jedis) {
   
   
        this.jedis = jedis;
    }
    
    /**
     * 获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(用于锁的释放验证)
     * @param expireTime 锁的过期时间(毫秒)
     * @return 是否成功获取锁
     */
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
   
   
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, 
                                 SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }
    
    /**
     * 释放分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(确保释放的是自己的锁)
     * @return 是否成功释放锁
     */
    public boolean releaseLock(String lockKey, String requestId) {
   
   
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), 
                                  Collections.singletonList(requestId));
        return RELEASE_SUCCESS.equals(result);
    }
}

这个简单的Redis锁实现利用了SET key value NX PX milliseconds命令,该命令保证了操作的原子性:只有当key不存在时才会设置成功,并同时设置过期时间。释放锁时,使用Lua脚本保证了检查锁的拥有者和删除锁的原子性。

不过,这个简单实现存在一些问题:

  1. 单点故障:如果Redis宕机,所有锁操作都会失败。
  2. 锁的过期问题:如果持有锁的客户端在操作完成前崩溃,且操作耗时超过了锁的过期时间,就有可能导致其他客户端获取锁并开始操作,造成数据不一致。

为了解决这些问题,Redis官方推荐使用Redis Redlock算法,这是一种更可靠的分布式锁实现方式。

Redlock算法的核心思想是:

  1. 客户端依次向N个独立的Redis节点获取锁。
  2. 如果客户端能够在大多数节点(N/2+1)获取锁,且获取锁的总耗时小于锁的过期时间,则认为获取锁成功。
  3. 如果获取锁失败,客户端应该向所有节点发起释放锁的操作。

这种实现提高了系统的可用性,即使部分Redis节点出现故障,也能正常工作。在实际项目中,我一般推荐使用成熟的开源库,如Redisson,它已经实现了Redlock算法。

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

public class RedissonLockExample {
   
   
    
    public static void main(String[] args) {
   
   
        // 配置Redisson
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://127.0.0.1:6379")
              .setPassword("password");
        
        // 创建Redisson客户端
        RedissonClient redisson = Redisson.create(config);
        
        // 获取锁对象
        RLock lock = redisson.getLock("myLock");
        
        try {
   
   
            // 尝试获取锁,最多等待100秒,锁有效期为30秒
            boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);
            
            if (isLocked) {
   
   
                try {
   
   
                    // 执行业务逻辑
                    System.out.println("获取锁成功,执行业务逻辑");
                    Thread.sleep(15000); // 模拟业务操作耗时
                } finally {
   
   
                    // 释放锁
                    lock.unlock();
                    System.out.println("业务执行完毕,释放锁");
                }
            } else {
   
   
                System.out.println("获取锁失败");
            }
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            // 关闭Redisson客户端
            redisson.shutdown();
        }
    }
}

Redisson不仅实现了基本的分布式锁功能,还提供了锁的自动续期、可重入锁、读写锁、公平锁等高级特性,让开发者可以更专注于业务逻辑。

3.3 基于ZooKeeper的实现

作为一个分布式协调服务,ZooKeeper的临时节点和有序节点特性使其非常适合实现分布式锁。

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.TimeUnit;

public class ZookeeperLockExample {
   
   
    
    public static void main(String[] args) {
   
   
        // 创建CuratorFramework客户端
        CuratorFramework client = CuratorFrameworkFactory.newClient(
            "localhost:2181",
            new ExponentialBackoffRetry(1000, 3)
        );
        client.start();
        
        // 创建分布式锁
        InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock");
        
        try {
   
   
            // 尝试获取锁,最多等待10秒
            if (lock
### 关于ESP8266的全面学习资料 #### ESP8266简介与功能详解 ESP8266是一款专为物联网(IoT)设计的Wi-Fi模块,具备强大的网络连接能力。此款微控制器不仅支持TCP/IP协议栈还提供了一系列用于处理字符串的API接口以及最佳实践指导[^1]。 对于基于ESP8266的应用开发而言,理解其内置函数库至关重要。这些库涵盖了从基本的数据类型定义到复杂的网络编程等多个方面: - **字符串操作**:包括创建、复制、拼接等常见任务; - **内存管理**:动态分配释放堆空间的方法; - **串口通讯**:通过UART与其他设备交换数据的技术细节; - **网络安全特性**:确保传输过程中的信息安全措施; - **性能调优技巧**:提高应用程序运行效率的方式; - **错误恢复机制**:当遇到意外情况时如何优雅地应对并恢复正常服务; 以上各项内容均能在官方文档中找到详尽描述,并配有实际案例供参考学习。 #### 示例代码展示 为了更好地掌握上述知识点,在这里给出一段简单的Python风格伪代码来说明怎样利用ESP8266发送HTTP请求获取网页内容: ```python import urequests as requests # 导入轻量级http客户端库 def fetch_webpage(url): try: response = requests.get(url) # 发送GET请求至指定URL if response.status_code == 200: # 如果状态码表示成功,则打印页面主体部分 print(response.text) response.close() # 记得关闭连接以节省资源 except Exception as e: print(f"Error occurred while fetching webpage: {e}") ``` 这段代码展示了如何使用`urequests`这个专门为MicroPython环境定制的小型化版本requests库来进行标准HTTP GET请求的操作流程。它能有效地简化原本较为繁琐的过程,让开发者可以更专注于业务逻辑本身而不是底层实现。 另外,还有更多实用的例子可以在开源项目【ESP8266-Demos】里发现,该项目包含了多个不同场景下的具体实施方案,非常适合用来加深理解练习技能[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值