🔐 分布式锁的实现方式:多人抢厕所的艺术!

41 阅读13分钟

📖 开场:火车站的厕所

想象火车站只有一个厕所 🚽:

没有锁(并发问题)

小明推门进去 → 正在使用 🚶
小红也推门进去 → 尴尬了!😱

有锁(互斥)

小明进去 → 反锁门 🔒
小红推门 → 锁着的,等待 ⏰
小明出来 → 开锁 🔓
小红进去 → 反锁门 🔒 ✅

这就是锁的作用:保证同一时间只有一个人使用资源!


🤔 什么是分布式锁?

单机锁 vs 分布式锁

单机锁(Java的synchronized)

┌─────────────┐
│   JVM1      │
│             │
│  Thread1 ───┼─→ synchronized(lock) {
│  Thread2 ───┼─→     // 只有一个线程能进入Thread3 ───┼─→ }
│             │
└─────────────┘

特点:只能在一个JVM内有效 ✅

分布式锁(跨JVM)

┌─────────────┐
│   JVM1      │──┐
│  Thread1    │  │
└─────────────┘  │
                 │     ┌──────────────┐
┌─────────────┐  │     │   Redis      │
│   JVM2      │──┼────→│   锁中心     │
│  Thread2    │  │     └──────────────┘
└─────────────┘  │
                 │     只有一个线程能获取锁 🔒
┌─────────────┐  │
│   JVM3      │──┘
│  Thread3    │
└─────────────┘

特点:跨JVM、跨服务器 ✅

为什么需要分布式锁?

场景:秒杀扣减库存

商品库存:10件

没有分布式锁:
┌─────────┐     ┌─────────┐     ┌─────────┐
│ Server1 │     │ Server2 │     │ Server3 │
│  读库存 │     │  读库存 │     │  读库存 │
│   10   │     │   10   │     │   10   │
│  -1    │     │  -1    │     │  -1    │
│  写回9 │     │  写回9 │     │  写回9 │
└─────────┘     └─────────┘     └─────────┘

结果:实际卖了3件,库存显示9件(应该是7件)❌

有分布式锁:
Server1获取锁 → 读10 → -1 → 写回9 → 释放锁
    ↓
Server2获取锁 → 读9 → -1 → 写回8 → 释放锁
    ↓
Server3获取锁 → 读8 → -1 → 写回7 → 释放锁

结果:实际卖了3件,库存7件 ✅

🎯 分布式锁的四种实现方式

方式1:基于数据库(最简单)📦

原理

利用数据库的唯一索引
    ↓
插入成功 → 获取锁成功 🔒
插入失败(主键冲突)→ 获取锁失败 ❌
    ↓
删除记录 → 释放锁 🔓

建表

CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) NOT NULL COMMENT '锁名称',
    holder VARCHAR(64) NOT NULL COMMENT '持有者',
    expire_time BIGINT NOT NULL COMMENT '过期时间戳',
    PRIMARY KEY (lock_name)  -- ⭐ 唯一索引保证互斥
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实现代码

@Component
@Slf4j
public class DatabaseDistributedLock {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * ⭐ 获取锁
     * 
     * @param lockName 锁名称
     * @param holder 持有者(通常是UUID)
     * @param expireSeconds 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockName, String holder, int expireSeconds) {
        try {
            long expireTime = System.currentTimeMillis() + expireSeconds * 1000;
            
            // ⭐ 插入锁记录
            String sql = "INSERT INTO distributed_lock (lock_name, holder, expire_time) VALUES (?, ?, ?)";
            jdbcTemplate.update(sql, lockName, holder, expireTime);
            
            log.info("获取锁成功: lockName={}, holder={}", lockName, holder);
            return true;
            
        } catch (DuplicateKeyException e) {
            // ⭐ 主键冲突,说明锁已被占用
            log.info("获取锁失败(锁已被占用): lockName={}", lockName);
            
            // ⭐ 检查是否过期,如果过期则尝试删除
            tryDeleteExpiredLock(lockName);
            
            return false;
        }
    }
    
    /**
     * ⭐ 释放锁
     */
    public void unlock(String lockName, String holder) {
        String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND holder = ?";
        int rows = jdbcTemplate.update(sql, lockName, holder);
        
        if (rows > 0) {
            log.info("释放锁成功: lockName={}, holder={}", lockName, holder);
        } else {
            log.warn("释放锁失败(锁不存在或不属于当前持有者): lockName={}, holder={}", lockName, holder);
        }
    }
    
    /**
     * ⭐ 删除过期的锁
     */
    private void tryDeleteExpiredLock(String lockName) {
        long now = System.currentTimeMillis();
        String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND expire_time < ?";
        int rows = jdbcTemplate.update(sql, lockName, now);
        
        if (rows > 0) {
            log.info("删除过期的锁: lockName={}", lockName);
        }
    }
}

使用示例

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private DatabaseDistributedLock lock;
    
    /**
     * 扣减库存
     */
    public boolean deductStock(Long productId, int quantity) {
        String lockName = "stock:" + productId;
        String holder = UUID.randomUUID().toString();
        
        try {
            // ⭐ 获取锁(超时时间10秒)
            if (!lock.tryLock(lockName, holder, 10)) {
                log.warn("获取锁失败,放弃扣减库存");
                return false;
            }
            
            log.info("获取锁成功,开始扣减库存");
            
            // ⭐ 业务逻辑(扣减库存)
            int stock = getStock(productId);
            if (stock < quantity) {
                log.warn("库存不足: stock={}, need={}", stock, quantity);
                return false;
            }
            
            setStock(productId, stock - quantity);
            log.info("扣减库存成功: productId={}, quantity={}, remaining={}", 
                productId, quantity, stock - quantity);
            
            return true;
            
        } finally {
            // ⭐ 释放锁
            lock.unlock(lockName, holder);
        }
    }
    
    private int getStock(Long productId) {
        // 从数据库读取库存
        return 100;
    }
    
    private void setStock(Long productId, int stock) {
        // 写入数据库
    }
}

优缺点

优点 ✅:

  • 实现简单(利用数据库特性)
  • 不需要额外的中间件
  • 可以借助数据库事务

缺点 ❌:

  • 性能差(数据库操作慢)
  • 单点故障(数据库挂了,锁服务不可用)
  • 过期时间不好控制(需要定时任务清理)
  • 不可重入(同一个线程无法再次获取锁)

适用场景

  • 并发量不高
  • 已有数据库,不想引入新中间件
  • 对性能要求不高

方式2:基于Redis(最常用)⭐⭐⭐

原理

利用Redis的SETNX命令(SET if Not eXists)
    ↓
SETNX成功 → 获取锁成功 🔒
SETNX失败(key已存在)→ 获取锁失败 ❌
    ↓
DEL key → 释放锁 🔓

简单实现(有问题)❌

public boolean tryLock(String lockKey, String holder) {
    // ⭐ SETNX:key不存在时设置,返回1;存在时不设置,返回0
    Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, holder);
    return Boolean.TRUE.equals(success);
}

public void unlock(String lockKey) {
    redisTemplate.delete(lockKey);
}

问题1:没有过期时间

获取锁成功 → 业务代码异常 → 没有释放锁
    ↓
锁永远无法释放 → 死锁!💀

问题2:释放了别人的锁

线程A获取锁 → 业务处理慢(超时)→ 锁过期自动释放
    ↓
线程B获取锁
    ↓
线程A处理完成 → 释放锁(实际释放的是线程B的锁)❌

正确实现(Lua脚本)✅

@Component
@Slf4j
public class RedisDistributedLock {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * ⭐ 获取锁
     * 
     * @param lockKey 锁的key
     * @param holder 持有者标识(UUID)
     * @param expireSeconds 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String holder, int expireSeconds) {
        // ⭐ SETNX + 过期时间(原子操作)
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, holder, expireSeconds, TimeUnit.SECONDS);
        
        if (Boolean.TRUE.equals(success)) {
            log.info("获取锁成功: lockKey={}, holder={}", lockKey, holder);
            return true;
        } else {
            log.info("获取锁失败(锁已被占用): lockKey={}", lockKey);
            return false;
        }
    }
    
    /**
     * ⭐ 释放锁(Lua脚本,保证原子性)
     * 
     * 逻辑:
     * 1. 检查锁是否是自己的(holder匹配)
     * 2. 是自己的才删除
     */
    public void unlock(String lockKey, String holder) {
        // ⭐ Lua脚本
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        // 执行脚本
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            holder
        );
        
        if (result != null && result == 1) {
            log.info("释放锁成功: lockKey={}, holder={}", lockKey, holder);
        } else {
            log.warn("释放锁失败(锁不存在或不属于当前持有者): lockKey={}, holder={}", lockKey, holder);
        }
    }
    
    /**
     * ⭐ 尝试获取锁(带重试)
     * 
     * @param lockKey 锁的key
     * @param holder 持有者标识
     * @param expireSeconds 过期时间(秒)
     * @param retryTimes 重试次数
     * @param retryInterval 重试间隔(毫秒)
     * @return 是否获取成功
     */
    public boolean tryLockWithRetry(String lockKey, String holder, int expireSeconds,
                                     int retryTimes, long retryInterval) {
        for (int i = 0; i < retryTimes; i++) {
            if (tryLock(lockKey, holder, expireSeconds)) {
                return true;
            }
            
            // 重试前等待
            if (i < retryTimes - 1) {
                try {
                    Thread.sleep(retryInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }
        }
        
        return false;
    }
}

使用示例

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private RedisDistributedLock lock;
    
    /**
     * 扣减库存
     */
    public boolean deductStock(Long productId, int quantity) {
        String lockKey = "stock:lock:" + productId;
        String holder = UUID.randomUUID().toString();
        
        try {
            // ⭐ 获取锁(10秒过期,重试3次,每次间隔100ms)
            if (!lock.tryLockWithRetry(lockKey, holder, 10, 3, 100)) {
                log.warn("获取锁失败,放弃扣减库存");
                return false;
            }
            
            log.info("获取锁成功,开始扣减库存");
            
            // ⭐ 业务逻辑
            int stock = getStockFromRedis(productId);
            if (stock < quantity) {
                log.warn("库存不足");
                return false;
            }
            
            setStockToRedis(productId, stock - quantity);
            log.info("扣减库存成功");
            
            return true;
            
        } finally {
            // ⭐ 释放锁
            lock.unlock(lockKey, holder);
        }
    }
    
    private int getStockFromRedis(Long productId) {
        String stock = redisTemplate.opsForValue().get("stock:" + productId);
        return stock != null ? Integer.parseInt(stock) : 0;
    }
    
    private void setStockToRedis(Long productId, int stock) {
        redisTemplate.opsForValue().set("stock:" + productId, String.valueOf(stock));
    }
}

Redisson实现(推荐)⭐⭐⭐

Redisson = Redis官方推荐的Java客户端,内置了分布式锁

@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setPassword("yourpassword");
        
        return Redisson.create(config);
    }
}

使用Redisson

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 扣减库存
     */
    public boolean deductStock(Long productId, int quantity) {
        // ⭐ 获取锁对象
        RLock lock = redissonClient.getLock("stock:lock:" + productId);
        
        try {
            // ⭐ 尝试获取锁
            // tryLock(等待时间, 过期时间, 时间单位)
            boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
            
            if (!acquired) {
                log.warn("获取锁失败");
                return false;
            }
            
            log.info("获取锁成功");
            
            // ⭐ 业务逻辑
            int stock = getStockFromRedis(productId);
            if (stock < quantity) {
                return false;
            }
            
            setStockToRedis(productId, stock - quantity);
            return true;
            
        } catch (InterruptedException e) {
            log.error("获取锁被中断", e);
            return false;
        } finally {
            // ⭐ 释放锁(自动检查是否是自己的锁)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Redisson的Watch Dog机制 🐕

问题:业务处理时间超过锁的过期时间怎么办?

解决:Redisson的Watch Dog自动续期

获取锁(过期时间10秒)
    ↓
业务处理中...(5秒)
    ↓
Watch Dog检测到锁还在持有,自动续期到10秒
    ↓
业务处理中...(5秒)
    ↓
Watch Dog再次续期到10秒
    ↓
业务处理完成
    ↓
释放锁,停止Watch Dog ✅

实现原理

// 获取锁时,不指定过期时间,Redisson会使用默认的30秒,并启动Watch Dog
RLock lock = redissonClient.getLock("myLock");
lock.lock();  // ⭐ 不指定过期时间,启动Watch Dog

// Watch Dog会每10秒(30秒的1/3)检查一次
// 如果锁还在持有,自动续期到30秒

优缺点

优点 ✅:

  • 性能高(Redis内存操作)
  • 可以设置过期时间(防止死锁)
  • 支持自动续期(Redisson的Watch Dog)
  • 支持可重入锁(Redisson)

缺点 ❌:

  • Redis单机有单点故障风险
  • Redis主从切换时可能丢锁(见Redlock算法)

适用场景

  • 高并发场景(秒杀、抢购)⭐⭐⭐
  • 对性能要求高
  • 大部分分布式锁场景(最常用)

方式3:基于Zookeeper(最可靠)🦓

原理

利用Zookeeper的临时顺序节点
    ↓
创建临时顺序节点:/locks/lock_0000000001
                    /locks/lock_0000000002
                    /locks/lock_0000000003
    ↓
序号最小的节点获取锁 🔒
其他节点监听前一个节点(watch机制)
    ↓
最小节点删除(业务完成或客户端断开)
    ↓
下一个节点收到通知,获取锁 ✅

实现代码(Curator)

依赖

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.2.1</version>
</dependency>

配置

@Configuration
public class ZookeeperConfig {
    
    @Bean
    public CuratorFramework curatorFramework() {
        // 重试策略:最多重试3次,每次间隔1秒
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        
        CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("localhost:2181")
            .sessionTimeoutMs(5000)
            .connectionTimeoutMs(5000)
            .retryPolicy(retryPolicy)
            .build();
        
        client.start();
        return client;
    }
}

使用

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private CuratorFramework curatorFramework;
    
    /**
     * 扣减库存
     */
    public boolean deductStock(Long productId, int quantity) {
        String lockPath = "/locks/stock/" + productId;
        
        // ⭐ 创建可重入锁
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);
        
        try {
            // ⭐ 尝试获取锁(最多等待3秒)
            if (!lock.acquire(3, TimeUnit.SECONDS)) {
                log.warn("获取锁失败");
                return false;
            }
            
            log.info("获取锁成功");
            
            // ⭐ 业务逻辑
            int stock = getStock(productId);
            if (stock < quantity) {
                return false;
            }
            
            setStock(productId, stock - quantity);
            return true;
            
        } catch (Exception e) {
            log.error("处理失败", e);
            return false;
        } finally {
            // ⭐ 释放锁
            try {
                lock.release();
            } catch (Exception e) {
                log.error("释放锁失败", e);
            }
        }
    }
}

优缺点

优点 ✅:

  • 可靠性高(Zookeeper的CP特性)
  • 自动删除临时节点(客户端断开连接)
  • 公平锁(按顺序获取锁)
  • 不会丢锁(强一致性)

缺点 ❌:

  • 性能较Redis差(磁盘IO)
  • 需要部署和维护Zookeeper集群
  • 实现相对复杂

适用场景

  • 对可靠性要求极高
  • 不能容忍丢锁
  • 已有Zookeeper集群

方式4:Redlock算法(Redis集群)🔴

原理

问题:Redis单机有单点故障

Redlock算法:在多个独立的Redis实例上获取锁

N个独立的Redis实例(N=5)

获取锁的步骤:
1. 获取当前时间戳t1
2. 依次尝试在N个Redis实例上获取锁(设置很短的超时时间,如5ms)
3. 获取当前时间戳t2
4. 计算获取锁的耗时:t2 - t1
5. 判断是否成功:
   - 在大多数实例(N/2+1=3个)上获取到锁
   - 获取锁的耗时 < 锁的有效时间
6. 如果成功,锁的有效时间 = 原有效时间 - 获取锁的耗时
7. 如果失败,释放所有已获取的锁

实现(Redisson支持)

@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // ⭐ 配置多个Redis实例
        config.useReplicatedServers()
            .addNodeAddress(
                "redis://127.0.0.1:6379",
                "redis://127.0.0.1:6380",
                "redis://127.0.0.1:6381",
                "redis://127.0.0.1:6382",
                "redis://127.0.0.1:6383"
            );
        
        return Redisson.create(config);
    }
}

使用

@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean deductStock(Long productId, int quantity) {
        // ⭐ 获取RedLock(Redisson自动实现Redlock算法)
        RLock lock1 = redissonClient.getLock("lock1:" + productId);
        RLock lock2 = redissonClient.getLock("lock2:" + productId);
        RLock lock3 = redissonClient.getLock("lock3:" + productId);
        
        // ⭐ RedissonRedLock
        RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3);
        
        try {
            if (!redLock.tryLock(3, 10, TimeUnit.SECONDS)) {
                return false;
            }
            
            // 业务逻辑
            return true;
            
        } catch (InterruptedException e) {
            return false;
        } finally {
            redLock.unlock();
        }
    }
}

优缺点

优点 ✅:

  • 高可用(多个Redis实例)
  • 不会因为单个Redis故障而丢锁

缺点 ❌:

  • 实现复杂
  • 需要多个独立的Redis实例(成本高)
  • 性能比单Redis差(需要在多个实例上操作)

适用场景

  • 对可用性要求极高
  • 不能容忍Redis单点故障
  • 预算充足(多个Redis实例)

📊 四种方式对比

实现方式性能可靠性复杂度成本适用场景
数据库⭐ 低⭐⭐ 中⭐ 低⭐ 低并发低,简单场景
Redis⭐⭐⭐ 高⭐⭐ 中⭐⭐ 中⭐⭐ 中大部分场景(推荐)
Zookeeper⭐⭐ 中⭐⭐⭐ 高⭐⭐⭐ 高⭐⭐⭐ 高可靠性要求极高
Redlock⭐⭐ 中⭐⭐⭐ 高⭐⭐⭐ 高⭐⭐⭐ 高高可用 + 高可靠

🎓 面试题速答

Q1: 分布式锁有哪些实现方式?

A: 四种方式!

  1. 数据库:唯一索引实现互斥
  2. Redis:SETNX + Lua脚本(最常用)⭐
  3. Zookeeper:临时顺序节点(最可靠)
  4. Redlock:多个Redis实例(高可用)

推荐使用Redis(Redisson),性能和可靠性的最佳平衡


Q2: Redis分布式锁如何实现?

A: 核心:SETNX + 过期时间 + Lua脚本

// 1. 获取锁(SETNX + 过期时间,原子操作)
redisTemplate.opsForValue()
    .setIfAbsent(lockKey, holder, 10, TimeUnit.SECONDS);

// 2. 释放锁(Lua脚本,保证原子性)
String script = 
    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
    "    return redis.call('del', KEYS[1]) " +
    "else " +
    "    return 0 " +
    "end";

关键点

  • 设置过期时间(防止死锁)
  • Lua脚本释放锁(防止释放别人的锁)
  • holder标识(UUID,确保只释放自己的锁)

Q3: Redis分布式锁有什么问题?

A: 三个主要问题

  1. 单点故障

    • 问题:Redis挂了,锁服务不可用
    • 解决:Redlock算法(多个Redis实例)
  2. 主从切换丢锁

    • 问题:主库获取锁后,还没同步到从库就挂了,从库升主后,锁丢失
    • 解决:Redlock算法
  3. 业务超时

    • 问题:业务处理时间超过锁的过期时间
    • 解决:Redisson的Watch Dog自动续期

Q4: Redisson的Watch Dog是什么?

A: Watch Dog = 自动续期机制 🐕

// 获取锁时不指定过期时间
RLock lock = redissonClient.getLock("myLock");
lock.lock();  // ⭐ 默认30秒,启动Watch Dog

// Watch Dog每10秒(30秒的1/3)检查一次
// 如果锁还在持有,自动续期到30秒

作用

  • 防止业务处理时间过长,锁自动过期
  • 自动续期,不需要手动设置很长的过期时间

原理

  • 后台线程定时检查
  • 如果锁还在持有,重置过期时间

Q5: Zookeeper分布式锁有什么优势?

A: 最大优势:可靠性高!

  1. 临时节点

    • 客户端断开连接,节点自动删除
    • 不会出现死锁
  2. 强一致性

    • Zookeeper的CP特性(一致性优先)
    • 不会丢锁
  3. 公平锁

    • 按照创建顺序获取锁
    • 避免饥饿

劣势

  • 性能较Redis差
  • 需要部署和维护Zookeeper集群

Q6: 如何选择分布式锁的实现方式?

A: 根据场景选择!

场景推荐方式理由
秒杀、抢购Redis(Redisson)性能高 ⭐⭐⭐
扣减库存Redis性能 + 可靠性 ⭐⭐
订单处理Redis性能足够 ⭐⭐
金融交易Zookeeper可靠性第一 ⭐⭐⭐
配置更新Zookeeper强一致性 ⭐⭐⭐
简单定时任务数据库简单,成本低 ⭐

总结

  • 大部分场景:Redis(Redisson)⭐⭐⭐
  • 高可靠场景:Zookeeper
  • 简单场景:数据库

🎬 总结

        分布式锁四种实现方式

┌─────────────────────────────────────┐
│          数据库锁                    │
│  唯一索引 + INSERT                   │
│  ⭐ 简单,性能差                     │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│         Redis锁(推荐)⭐⭐⭐         │
│  SETNX + Lua脚本                    │
│  ⭐⭐⭐ 高性能,常用                  │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│       Zookeeper锁                   │
│  临时顺序节点                        │
│  ⭐⭐⭐ 最可靠,性能稍差              │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│        Redlock算法                  │
│  多个Redis实例                       │
│  ⭐⭐⭐ 高可用,成本高                │
└─────────────────────────────────────┘

    大部分场景用Redis(Redisson)!✅

🎉 恭喜你!

你已经完全掌握了分布式锁的四种实现方式!🎊

核心要点

  1. 数据库:简单,性能差
  2. Redis:常用,推荐 ⭐⭐⭐
  3. Zookeeper:可靠,复杂
  4. Redlock:高可用,成本高

下次面试,这样回答

"分布式锁有四种实现方式:数据库、Redis、Zookeeper和Redlock算法。

最常用的是Redis实现,使用SETNX命令获取锁,设置过期时间防止死锁,用Lua脚本释放锁保证原子性。推荐使用Redisson框架,它内置了Watch Dog自动续期机制,解决了业务处理时间过长的问题。

Zookeeper实现可靠性最高,利用临时顺序节点,客户端断开连接节点自动删除,不会死锁,而且是公平锁。但性能较Redis差,需要维护Zookeeper集群。

Redlock算法解决了Redis单点故障问题,在多个独立的Redis实例上获取锁,大多数实例(N/2+1)获取成功才算成功。

我们项目的秒杀系统使用Redisson实现分布式锁,扣减库存时保证并发安全,QPS达到10万。"

面试官:👍 "很好!你对分布式锁理解很全面!"


本文完 🎬

上一篇: 194-消息队列如何保证消息有序性.md
下一篇: 196-Redis实现分布式锁的细节和问题.md

作者注:写完这篇,我都想去火车站当厕所管理员了!🚽
如果这篇文章对你有帮助,请给我一个Star⭐!