当前位置: 首页 > news >正文

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

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

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

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

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

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

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

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


三种实现方式的详细比较

特性RedisZooKeeper数据库(如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. 加锁

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

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

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

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

    if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
    elsereturn 0
    end
    • 脚本先比较当前锁的值是否与自己设置的值相等,相等才删除。GET 和 DEL 操作在 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. 创建锁表

    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. 加锁:向表中插入一条记录。

    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. 释放锁

    DELETE FROM distributed_lock WHERE lock_key = 'resource_1' AND lock_value = 'my_uuid';

优缺点:

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

  • 缺点

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

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

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

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

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

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

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

1. 资源争用场景

库存扣减与超卖防止

// 伪代码示例
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. 分布式定时任务调度

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

@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. 防止重复操作

用户重复提交订单

@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. 分布式环境下的初始化操作

配置加载或缓存预热

@Service 
public class ConfigService {private volatile boolean initialized = false;@PostConstructpublic 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 客户端(推荐)
<!-- Maven 依赖 -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.27.0</version>
</dependency>

配置类:

@Configuration
public class RedissonConfig {@Value("${redis.host:localhost}")private String redisHost;@Value("${redis.port:6379}")private String redisPort;@Beanpublic 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);}
}

服务类:

@Service
public class RedisDistributedLockService {@Autowiredprivate 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();}}
}

使用示例:

@Service
public class ProductService {@Autowiredprivate 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 框架(推荐)
<!-- Maven 依赖 -->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.5.0</version>
</dependency>

配置类:

@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();}@Beanpublic InterProcessMutex interProcessMutex(CuratorFramework curatorFramework) {// 这是一个示例bean,实际使用时根据不同的锁路径创建return new InterProcessMutex(curatorFramework, "/locks");}
}

服务类:

@Service
public class ZkDistributedLockService {@Autowiredprivate 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;}}
}

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

@Service
public class ConfigSyncService {@Autowiredprivate 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. 数据库分布式锁在微服务中的实现

锁表结构:

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='分布式锁表';

服务类:

@Service
@Transactional
public class DatabaseDistributedLockService {@Autowiredprivate 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性能要求极高,允许极低概率的锁失效
金融交易账户余额变更、交易处理ZooKeeper 或 Redis + 事务补偿强一致性要求,不能出现重复操作
定时任务报表生成、数据归档Redis 或 ZooKeeperRedis性能好,ZooKeeper更可靠
配置管理配置热更新、服务发现ZooKeeper天然的一致性协调能力
工作流控制审批流程、状态机Redis性能好,支持复杂的锁类型
简单业务低频操作、内部管理数据库无需引入新组件,维护简单

微服务架构中的最佳实践

1. 锁的粒度控制

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

2. 超时时间设置

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

3. 故障恢复机制

@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. 监控与告警

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

总结

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

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

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

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

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

http://www.dtcms.com/a/545676.html

相关文章:

  • 《创作一周年有感》
  • Rust:异步锁(Mutex、RwLock)的设计
  • EG1195S 带使能降压开关电源控制芯片技术解析
  • 关于解决stm32cubeIDE打开现有工程失败的方法:
  • 代码随想录 669.修剪二叉搜索树
  • 单细胞转录组测序上游——cellranger
  • 下模板做网站阿里巴巴网页版
  • 组态软件SCADA在化工行业的应用
  • 移动商城 网站建设方法方式无锡做网站专业的公司
  • seo网站推广教程网红营销策略
  • 《考研408数据结构》第六章(5.5树的应用)复习笔记
  • 关于电子商务网站建设的论文飞飞影视做的网站
  • MiniMax-M2 在SCNet超算平台尝鲜(4卡不够,未完成)
  • Java 基本数据类型详解:从理论到实践
  • 自建大模型推理引擎中 KV Cache 的有效设计
  • 0010.static修饰的全局变量被无意间修改
  • 误入网站退不了怎么做制作音乐排行榜网页设计
  • 前端低代码开发实践:配置驱动与可视化搭建
  • godot4.4 如何让游戏画面没有透视【正交相机】
  • 电子商务平台 网站 建设方式Wordpress游戏rpg
  • 仓颉语言中Channel通道的深度解析:从原理到高并发实践
  • 数据网站建设多少钱重庆平台网站建设工作
  • 企业网站管理系统使用教程微信小程序开发文档下载
  • MATLAB复杂曲线曲面造型及导函数实现
  • OpenAI首发AI浏览器,互联网流量格局如何重塑
  • 【1.3】costas环的MATLAB仿真与测试
  • 使用FormData上传图片和JSON数据注意事项
  • HBase 核心架构和增删改查
  • 网站建设尚品网站怎么做gps定位
  • js中如何隐藏eval关键字?