分布式锁实现:Redis、Zookeeper、数据库三种方案对比
分布式锁实现:Redis、Zookeeper、数据库三种方案对比
摘要:分布式锁是分布式并发控制的重要手段,理解不同方案的优缺点对选择合适的锁实现至关重要。本文将深入剖析数据库、Redis、Zookeeper三种主流实现方案的原生原理与代码实战,助你在面试与实战中游刃有余。
一、引言
在单体架构时代,我们可以利用Java原生的synchronized关键字或ReentrantLock类轻松解决并发问题,因为它们共享同一个JVM内存模型。然而,随着微服务架构的普及,业务场景从单机多线程转变为多机多进程,跨JVM的并发控制成为了新的挑战。
想象一个典型的电商秒杀场景:用户下单扣减库存。如果两台服务器同时读取到库存剩余1,并进行扣减,就会出现“超卖”现象。此时,就需要一把跨越进程边界的“锁”——分布式锁来协调多个服务实例的行为。
本文将从原理、实战、优缺点对比三个维度,深度解析三种主流的分布式锁实现方案。
二、核心概念与理论基础
在深入实现细节之前,我们需要明确一个合格的分布式锁必须具备的核心特性:
- 互斥性:这是锁的最基本属性,任意时刻,只有一个客户端能持有锁。
- 防死锁:锁必须有超时机制,防止客户端持有锁后宕机导致锁无法释放,造成系统死锁。
- 安全性:锁只能被持有它的客户端释放,不能被其他客户端误删。
- 高可用与高性能:加锁和解锁的开销要小,且锁服务本身要具备高可用性(如集群部署)。
三、方案一:基于数据库的实现
3.1 技术原理
利用数据库的ACID特性,最简单的方式是通过唯一索引来实现互斥。
原理:创建一张锁表,包含lock_key(锁资源名)字段并设置唯一约束。当需要获取锁时,插入一条记录;释放锁时,删除该记录。数据库会自动保证插入操作的原子性和唯一性,插入成功即代表获取锁成功。
3.2 实战代码
首先,定义数据库表结构:
CREATE TABLE `distributed_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`lock_key` varchar(64) NOT NULL COMMENT '锁资源标识',
`holder_info` varchar(255) NOT NULL COMMENT '持有者信息(如UUID+线程ID)',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_lock_key` (`lock_key`) -- 唯一索引是互斥性的核心
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Java实现代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class DatabaseDistributedLock {
@Autowired
private JdbcTemplate jdbcTemplate;
// 当前线程的唯一标识,用于防止误删
private static final ThreadLocal<String> THREAD_HOLDER = ThreadLocal.withInitial(() ->
UUID.randomUUID().toString() + "-" + Thread.currentThread().getId()
);
/**
* 尝试获取锁
* @param lockKey 锁的key
* @return 是否获取成功
*/
public boolean tryLock(String lockKey) {
try {
// 利用唯一索引冲突机制,插入成功即获取锁
String sql = "INSERT INTO distributed_lock (lock_key, holder_info) VALUES (?, ?)";
int affectedRows = jdbcTemplate.update(sql, lockKey, THREAD_HOLDER.get());
return affectedRows > 0;
} catch (Exception e) {
// 捕获唯一索引冲突异常,表示锁已被占用
return false;
}
}
/**
* 释放锁
* @param lockKey 锁的key
*/
public void unlock(String lockKey) {
// 必须同时匹配 lockKey 和 holder_info 才能删除,防止误删其他线程的锁
String sql = "DELETE FROM distributed_lock WHERE lock_key = ? AND holder_info = ?";
jdbcTemplate.update(sql, lockKey, THREAD_HOLDER.get());
THREAD_HOLDER.remove();
}
}
3.3 优缺点分析
- 优点:实现简单,利用现有数据库设施,无需引入第三方组件,易于理解。
- 缺点:
- 性能瓶颈:数据库通过磁盘IO存储数据,并发能力远不如内存数据库。
- 无阻塞重试机制:插入失败直接返回,需要业务层自行实现轮询重试。
- 死锁风险:如果服务宕机,数据库记录可能残留。虽然可以通过定时任务清理过期记录,但这增加了系统复杂度。
四、方案二:基于Redis的实现
4.1 技术原理
Redis基于内存操作,性能极高,是目前最流行的分布式锁实现方案。其核心在于SET命令的扩展

浙公网安备 33010602011771号