分布式锁Redis、ZooKeeper 和数据库实现分布式锁的优缺点、实现方式以及适用场景

核心概念:一个合格的分布式锁需要什么?

在比较具体实现之前,我们必须先了解一个健壮的分布式锁应具备的特性:

  1. 互斥性:在任意时刻,只有一个客户端能持有锁。

  2. 安全性:不会发生死锁。即使一个客户端在持有锁期间崩溃,没有主动解锁,也能保证后续其他客户端能够加锁。

  3. 容错性:只要大部分(超过一半)的锁服务节点存活,客户端就能正常获取和释放锁。

  4. 避免惊群效应:当锁被释放时,多个等待的客户端中只有一个能成功获得锁。

  5. 可重入性:同一个客户端在已经持有锁的情况下,可以再次成功获取锁。


三种实现方式的详细比较

特性 Redis ZooKeeper 数据库(如MySQL)
实现复杂度 中等 较低 (但需处理重试、超时)
性能 最高(内存操作) 中等(ZK需要共识协议) 最低(磁盘IO)
一致性保证 (AP,异步复制) (CP,基于ZAB协议) (依赖数据库事务)
避免死锁机制 Key过期时间 临时节点(客户端断开自动删除) 超时时间(需额外定时任务清理)
锁唤醒机制 Pub/Sub客户端自旋 Watch机制(天然事件通知) 主动轮询(性能差)
可重入性 需客户端逻辑实现 原生支持(同一Session) 需客户端逻辑实现
主要缺点 主从切换可能导致锁失效 性能不如Redis,有广播风暴风险 性能最差,数据库压力大,易死锁

他们是如何实现的?

1. Redis 实现

Redis 实现分布式锁的核心命令是 SET key value NX PX milliseconds

  • NX:仅当 Key 不存在时才设置。这保证了互斥性。

  • PX:设置 Key 的过期时间(毫秒)。这保证了安全性,避免了死锁。

基础实现流程:

  1. 加锁

    bash 复制代码
    SET lock_resource_name my_random_value NX PX 30000
    • my_random_value 必须是全局唯一的值(如UUID),用于标识加锁的客户端。这至关重要,它用于在释放锁时验证这是自己加的锁,防止误删其他客户端的锁。

    • 30000 是锁的自动过期时间,单位毫秒。

  2. 业务操作:执行需要受锁保护的业务逻辑。

  3. 释放锁:使用 Lua 脚本保证原子性。

    Lua 复制代码
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    else
        return 0
    end
    • 脚本先比较当前锁的值是否与自己设置的值相等,相等才删除。GETDEL 操作在 Lua 脚本中是原子的。

高级方案 - Redlock算法:

为了克服 Redis 主从架构下(主节点宕机,锁信息未同步到从节点导致锁失效)的问题,Redis 作者提出了 Redlock 算法。它需要多个(通常为5个)独立的 Redis 主节点(非集群)。

  1. 客户端获取当前毫秒级时间戳 T1。

  2. 依次向 N 个 Redis 实例发送加锁命令(使用相同的 Key 和随机值)。

  3. 只有当客户端从超过半数(N/2+1) 的节点上成功获取锁,且总耗时小于锁的过期时间,才认为加锁成功。

  4. 锁的实际有效时间 = 初始有效时间 - 获取锁的总耗时。

  5. 如果加锁失败,客户端会向所有 Redis 实例发起释放锁的请求。

优缺点:

  • 优点:性能极高,实现相对简单,社区支持好(如 Redisson 客户端)。

  • 缺点:基础模式在主从故障切换时不安全;Redlock 算法复杂,性能有损耗,且存在争议(如 Martin Kleppmann 的批评)。

2. ZooKeeper 实现

ZooKeeper 的数据模型类似于文件系统,它的临时顺序节点是实现分布式锁的核心。

实现流程(排他锁):

  1. 加锁

    • 客户端在指定的锁节点(如 /locks/my_lock)下创建一个临时顺序节点 ,假设为 /locks/my_lock/seq-00000001

    • ZooKeeper 会保证这个节点在客户端会话(Session)结束时(如连接断开)被自动删除。这天然地避免了死锁

    • 客户端获取 /locks/my_lock 下的所有子节点,并判断自己创建的子节点是否为序号最小的一个。

    • 如果是,则成功获取锁。

    • 如果不是,则对序号排在自己前面的那个节点 设置 Watch 监听。

  2. 业务操作:执行需要受锁保护的业务逻辑。

  3. 锁释放/监听

    • 当持有锁的客户端完成操作或会话结束时,临时节点会被删除。

    • 监听该节点的下一个客户端会收到 ZooKeeper 的通知,然后再次检查自己是否是最小节点,如果是,则成功获取锁。

优缺点:

  • 优点:锁强一致,安全可靠(临时节点防死锁),有天然的等待队列机制(Watch),避免了惊群效应。

  • 缺点:性能比 Redis 差,因为每次写操作都需要在集群内达成共识。添加和删除节点会触发大量 Watch 事件,存在广播风暴风险。

3. 数据库实现

通常有两种方式:基于数据库表的唯一索引乐观锁

基于唯一索引(悲观锁):

  1. 创建锁表

    sql 复制代码
    CREATE TABLE `distributed_lock` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `lock_key` varchar(64) NOT NULL,
      `lock_value` varchar(255) NOT NULL,
      `expire_time` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_lock_key` (`lock_key`)
    );
  2. 加锁:向表中插入一条记录。

    sql 复制代码
    INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES ('resource_1', 'my_uuid', NOW() + INTERVAL 30 SECOND);
    • 利用数据库的唯一约束,只有一个客户端能插入成功,成功即代表获取锁。

    • lock_value 的作用同 Redis,用于安全释放锁。

    • expire_time 用于防止死锁,需要一个定时任务来清理过期的锁。

  3. 业务操作:执行需要受锁保护的业务逻辑。

  4. 释放锁

    sql 复制代码
    DELETE FROM distributed_lock WHERE lock_key = 'resource_1' AND lock_value = 'my_uuid';

优缺点:

  • 优点:实现简单,直接利用现有数据库,理解成本低。

  • 缺点

    • 性能最差,数据库 IO 开销大,容易成为系统瓶颈。

    • 数据库单点问题(虽然可以用主从,但主从延迟又会带来锁一致性问题)。

    • 需要处理超时和清理过期锁,实现不优雅。

    • 不具备可重入性和自动唤醒功能。

微服务架构中的分布式锁需求

在微服务架构中,随着服务被拆分成多个独立的进程,传统的单机锁机制无法满足跨服务的同步需求,分布式锁成为必需的基础组件。

微服务中常见的分布式锁场景

1. 资源争用场景

库存扣减与超卖防止

java 复制代码
// 伪代码示例
public boolean deductStock(Long productId, Integer quantity) {
    String lockKey = "stock_lock:" + productId;
    DistributedLock lock = distributedLockService.tryLock(lockKey, 3000L);
    
    try {
        if (lock != null) {
            // 查询库存
            Integer currentStock = stockService.getStock(productId);
            if (currentStock >= quantity) {
                // 扣减库存
                stockService.updateStock(productId, currentStock - quantity);
                return true;
            }
        }
        return false;
    } finally {
        if (lock != null) {
            lock.unlock();
        }
    }
}
2. 分布式定时任务调度

确保集群中只有一个实例执行任务

java 复制代码
@Component
public class ScheduledReportTask {
    
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void generateDailyReport() {
        String lockKey = "task:generate_daily_report";
        if (distributedLockService.tryLock(lockKey, 3600L)) { // 锁1小时
            try {
                // 生成日报逻辑
                reportService.generateDailyReport();
            } finally {
                distributedLockService.unlock(lockKey);
            }
        }
    }
}
3. 防止重复操作

用户重复提交订单

java 复制代码
@Service
public class OrderService {
    
    public CreateOrderResult createOrder(CreateOrderRequest request) {
        String lockKey = "order_create:" + request.getUserId();
        
        // 5秒内防止同一用户重复提交
        if (!distributedLockService.tryLock(lockKey, 5000L)) {
            throw new BusinessException("请求过于频繁,请稍后再试");
        }
        
        try {
            // 创建订单逻辑
            return doCreateOrder(request);
        } finally {
            distributedLockService.unlock(lockKey);
        }
    }
}
4. 分布式环境下的初始化操作

配置加载或缓存预热

java 复制代码
@Service 
public class ConfigService {
    
    private volatile boolean initialized = false;
    
    @PostConstruct
    public void initConfig() {
        String lockKey = "config_initialization";
        if (distributedLockService.tryLock(lockKey, 60000L)) {
            try {
                // 双重检查,防止重复初始化
                if (!initialized) {
                    loadGlobalConfig();
                    warmUpCache();
                    initialized = true;
                }
            } finally {
                distributedLockService.unlock(lockKey);
            }
        }
    }
}

三种方案在微服务中的详细实现

1. Redis 分布式锁在微服务中的最佳实践

使用 Redisson 客户端(推荐)
java 复制代码
<!-- Maven 依赖 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.0</version>
</dependency>

配置类:

java 复制代码
@Configuration
public class RedissonConfig {
    
    @Value("${redis.host:localhost}")
    private String redisHost;
    
    @Value("${redis.port:6379}")
    private String redisPort;
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://" + redisHost + ":" + redisPort)
              .setDatabase(0)
              .setConnectionPoolSize(64)
              .setConnectionMinimumIdleSize(24)
              .setIdleConnectionTimeout(10000)
              .setConnectTimeout(10000)
              .setTimeout(3000);
        return Redisson.create(config);
    }
}

服务类:

java 复制代码
@Service
public class RedisDistributedLockService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 可重入锁
     */
    public boolean tryReentrantLock(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * 公平锁 - 按照请求顺序获得锁
     */
    public boolean tryFairLock(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getFairLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * 读写锁 - 读读不互斥,读写、写写互斥
     */
    public boolean tryWriteLock(String lockKey, long waitTime, long leaseTime) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);
        RLock writeLock = rwLock.writeLock();
        try {
            return writeLock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

使用示例:

java 复制代码
@Service
public class ProductService {
    
    @Autowired
    private RedisDistributedLockService lockService;
    
    public void updateProductInventory(Long productId, Integer delta) {
        String lockKey = "product_inventory:" + productId;
        
        // 尝试获取锁,最多等待2秒,锁持有时间10秒
        if (lockService.tryReentrantLock(lockKey, 2000, 10000)) {
            try {
                // 业务逻辑
                productRepository.updateInventory(productId, delta);
                
                // 模拟耗时操作
                Thread.sleep(500);
                
            } catch (Exception e) {
                log.error("更新库存失败", e);
            } finally {
                lockService.unlock(lockKey);
            }
        } else {
            throw new BusinessException("系统繁忙,请稍后重试");
        }
    }
}

2. ZooKeeper 分布式锁在微服务中的实现

使用 Curator 框架(推荐)
XML 复制代码
<!-- Maven 依赖 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.5.0</version>
</dependency>

配置类:

java 复制代码
@Configuration
public class CuratorConfig {
    
    @Value("${zookeeper.connect-string:localhost:2181}")
    private String connectString;
    
    @Bean(initMethod = "start", destroyMethod = "close")
    public CuratorFramework curatorFramework() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        return CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(60000)
                .connectionTimeoutMs(15000)
                .retryPolicy(retryPolicy)
                .namespace("microservice-locks")
                .build();
    }
    
    @Bean
    public InterProcessMutex interProcessMutex(CuratorFramework curatorFramework) {
        // 这是一个示例bean,实际使用时根据不同的锁路径创建
        return new InterProcessMutex(curatorFramework, "/locks");
    }
}

服务类:

javascript 复制代码
@Service
public class ZkDistributedLockService {
    
    @Autowired
    private CuratorFramework curatorFramework;
    
    private final Map<String, InterProcessMutex> lockMap = new ConcurrentHashMap<>();
    
    /**
     * 获取互斥锁
     */
    public boolean tryAcquireMutex(String lockPath, long timeout, TimeUnit unit) {
        try {
            InterProcessMutex lock = lockMap.computeIfAbsent(lockPath, 
                path -> new InterProcessMutex(curatorFramework, path));
            return lock.acquire(timeout, unit);
        } catch (Exception e) {
            log.error("获取ZooKeeper锁失败", e);
            return false;
        }
    }
    
    /**
     * 释放锁
     */
    public void releaseMutex(String lockPath) {
        try {
            InterProcessMutex lock = lockMap.get(lockPath);
            if (lock != null && lock.isAcquiredInThisProcess()) {
                lock.release();
            }
        } catch (Exception e) {
            log.error("释放ZooKeeper锁失败", e);
        }
    }
    
    /**
     * 获取读写锁
     */
    public boolean tryAcquireWriteLock(String lockPath, long timeout, TimeUnit unit) {
        try {
            InterProcessReadWriteLock rwLock = new InterProcessReadWriteLock(curatorFramework, lockPath);
            return rwLock.writeLock().acquire(timeout, unit);
        } catch (Exception e) {
            log.error("获取ZooKeeper写锁失败", e);
            return false;
        }
    }
}

使用示例 - 配置中心数据同步:

java 复制代码
@Service
public class ConfigSyncService {
    
    @Autowired
    private ZkDistributedLockService lockService;
    
    public void syncGlobalConfig() {
        String lockPath = "/config/sync/global";
        
        if (lockService.tryAcquireMutex(lockPath, 5, TimeUnit.SECONDS)) {
            try {
                // 只有获得锁的服务实例执行配置同步
                log.info("开始同步全局配置...");
                configService.syncFromCentral();
                log.info("全局配置同步完成");
                
            } finally {
                lockService.releaseMutex(lockPath);
            }
        } else {
            log.info("其他服务实例正在执行配置同步,跳过本次执行");
        }
    }
}

3. 数据库分布式锁在微服务中的实现

锁表结构:

sql 复制代码
CREATE TABLE `distributed_lock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `lock_key` varchar(255) NOT NULL COMMENT '锁定的资源key',
  `lock_value` varchar(255) NOT NULL COMMENT '锁的值(UUID)',
  `expire_time` datetime NOT NULL COMMENT '锁过期时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_lock_key` (`lock_key`),
  KEY `idx_expire_time` (`expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表';

服务类:

java 复制代码
@Service
@Transactional
public class DatabaseDistributedLockService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private static final String INSERT_SQL = 
        "INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES (?, ?, ?)";
    
    private static final String DELETE_SQL = 
        "DELETE FROM distributed_lock WHERE lock_key = ? AND lock_value = ?";
    
    private static final String CLEAN_EXPIRED_SQL = 
        "DELETE FROM distributed_lock WHERE expire_time < NOW()";
    
    /**
     * 尝试获取锁
     */
    public boolean tryLock(String lockKey, long expireMillis) {
        String lockValue = UUID.randomUUID().toString();
        LocalDateTime expireTime = LocalDateTime.now().plus(expireMillis, ChronoUnit.MILLIS);
        
        try {
            // 清理过期锁
            jdbcTemplate.update(CLEAN_EXPIRED_SQL);
            
            // 尝试插入获取锁
            int affected = jdbcTemplate.update(INSERT_SQL, lockKey, lockValue, expireTime);
            return affected > 0;
            
        } catch (DuplicateKeyException e) {
            // 锁已被其他线程持有
            return false;
        }
    }
    
    /**
     * 释放锁
     */
    public boolean unlock(String lockKey, String lockValue) {
        int affected = jdbcTemplate.update(DELETE_SQL, lockKey, lockValue);
        return affected > 0;
    }
    
    /**
     * 带重试的锁获取
     */
    public boolean tryLockWithRetry(String lockKey, long expireMillis, int maxRetries, long retryInterval) {
        for (int i = 0; i < maxRetries; i++) {
            if (tryLock(lockKey, expireMillis)) {
                return true;
            }
            
            try {
                Thread.sleep(retryInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return false;
    }
}

微服务场景下的选型矩阵

场景分类 具体场景 推荐方案 理由
高并发业务 秒杀、抢购、库存扣减 Redis 性能要求极高,允许极低概率的锁失效
金融交易 账户余额变更、交易处理 ZooKeeperRedis + 事务补偿 强一致性要求,不能出现重复操作
定时任务 报表生成、数据归档 RedisZooKeeper Redis性能好,ZooKeeper更可靠
配置管理 配置热更新、服务发现 ZooKeeper 天然的一致性协调能力
工作流控制 审批流程、状态机 Redis 性能好,支持复杂的锁类型
简单业务 低频操作、内部管理 数据库 无需引入新组件,维护简单

微服务架构中的最佳实践

1. 锁的粒度控制

java 复制代码
// 好的实践 - 细粒度锁
String lockKey = "order:" + orderId;

// 不好的实践 - 粗粒度锁  
String lockKey = "order_lock"; // 所有订单操作都串行化

2. 超时时间设置

java 复制代码
// 根据业务操作预估合理超时时间
long timeout = calculateBusinessTimeout(); // 动态计算
distributedLockService.tryLock(lockKey, timeout);

3. 故障恢复机制

java 复制代码
@Service
public class ResilientLockService {
    
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public void doWithLock(String lockKey, Runnable businessLogic) {
        // 获取锁并执行业务逻辑
        if (tryLock(lockKey)) {
            try {
                businessLogic.run();
            } finally {
                unlock(lockKey);
            }
        }
    }
}

4. 监控与告警

java 复制代码
@Component
public class LockMonitor {
    
    @EventListener
    public void handleLockAcquisitionFailure(LockAcquisitionFailureEvent event) {
        // 记录锁获取失败指标
        metrics.increment("lock.acquisition.failure");
        
        // 触发告警
        if (event.getFailureCount() > threshold) {
            alertService.sendAlert("分布式锁获取异常频繁");
        }
    }
}

总结

在微服务架构中选择分布式锁方案时:

  • Redis:适用于绝大多数业务场景,性能优秀,生态成熟

  • ZooKeeper:适用于对一致性要求极高的核心业务场景

  • 数据库:适用于简单场景或作为过渡方案

推荐策略 :在微服务架构中,可以基于 Redis 构建主要的分布式锁能力,对于特别关键的业务(如资金交易)使用 ZooKeeper 作为补充,形成多层次的锁策略。同时,无论选择哪种方案,都要做好监控、熔断和降级准备,确保锁服务不会成为系统的单点故障。

相关推荐
沐浴露z6 小时前
Kafka Consumer 消费流程详解
java·分布式·kafka
starandsea6 小时前
kafka添加压缩配置后失败
分布式·kafka·linq
zyh2005043010 小时前
RabbitMQ概述
分布式·消息队列·rabbitmq·消息中间件·amqp
武子康12 小时前
Java-164 MongoDB 认证与权限实战:单实例与分片集群 整体认证配置实战 最小化授权/错误速查/回滚剧本
java·数据库·分布式·mongodb·性能优化·系统架构·nosql
大G的笔记本14 小时前
Redis 分布式锁如何保证同一时间只有一个客户端持有锁
数据库·redis·分布式
飞鱼&16 小时前
Kafka(文件)数据存储、清理机制、高性能设计
分布式·kafka
herobrineAC7891 天前
Hyperopt 强大的分布式参数优化框架全解析
分布式·其他
明达智控技术1 天前
MR30系列分布式I/O在造型机产线的应用
分布式·物联网·自动化
Moniane1 天前
A2A+MCP构建智能体协作生态:下一代分布式人工智能架构解析
人工智能·分布式·架构