分布式锁实现原理与最佳实践(1)

简介: 分布式锁实现原理与最佳实践

image.png

在单体的应用开发场景中涉及并发同步时,大家往往采用Synchronized(同步)或同一个JVM内Lock机制来解决多线程间的同步问题。而在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁。接下来本文将为大家分享分布式锁的最佳实践。


01 超卖问题复现


1.1 现象

存在如下的几张表:

商品表

image.png

image.png

订单表

image.png

订单item表

image.png

商品的库存为1,但是并发高的时候有多笔订单。

✪ 错误案例一:数据库update相互覆盖

直接在内存中判断是否有库存,计算扣减之后的值更新数据库,并发的情况下会导致相互覆盖发生:

@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {
    Product product = productMapper.selectByPrimaryKey(purchaseProductId);
    // ... 忽略校验逻辑
    //商品当前库存
    Integer currentCount = product.getCount();
    //校验库存
    if (purchaseProductNum > currentCount) {
        throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
    }
    // 计算剩余库存
    Integer leftCount = currentCount - purchaseProductNum;
    // 更新库存
    product.setCount(leftCount);
    product.setGmtModified(new Date());
    productMapper.updateByPrimaryKeySelective(product);
    Order order = new Order();
    // ... 省略 Set
    orderMapper.insertSelective(order);
    OrderItem orderItem = new OrderItem();
    orderItem.setOrderId(order.getId());
    // ... 省略 Set
    return order.getId();
}

✪ 错误案例二:扣减串行执行,但是库存被扣减为负数

在 SQL 中加入运算避免值的相互覆盖,但是库存的数量变为负数,因为校验库存是否足够还是在内存中执行的,并发情况下都会读到有库存:

@Transactional(rollbackFor = Exception.class)
public Long createOrder() throws Exception {
    Product product = productMapper.selectByPrimaryKey(purchaseProductId);
    // ... 忽略校验逻辑
    //商品当前库存
    Integer currentCount = product.getCount();
    //校验库存
    if (purchaseProductNum > currentCount) {
        throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
    }
    // 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存
    productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());
    Order order = new Order();
    // ... 省略 Set
    orderMapper.insertSelective(order);
    OrderItem orderItem = new OrderItem();
    orderItem.setOrderId(order.getId());
    // ... 省略 Set
    return order.getId();
}

✪ 错误案例三:使用 synchronized 实现内存中串行校验,但是依旧扣减为负数

因为我们使用的是事务的注解,synchronized加在方法上,方法执行结束的时候锁就会释放,此时的事务还没有提交,另一个线程拿到这把锁之后就会有一次扣减,导致负数。

@Transactional(rollbackFor = Exception.class)
public synchronized Long createOrder() throws Exception {
    Product product = productMapper.selectByPrimaryKey(purchaseProductId);
    // ... 忽略校验逻辑
    //商品当前库存
    Integer currentCount = product.getCount();
    //校验库存
    if (purchaseProductNum > currentCount) {
        throw new Exception("商品" + purchaseProductId + "仅剩" + currentCount + "件,无法购买");
    }
    // 使用 set count =  count - #{purchaseProductNum,jdbcType=INTEGER}, 更新库存
    productMapper.updateProductCount(purchaseProductNum,new Date(),product.getId());
    Order order = new Order();
    // ... 省略 Set
    orderMapper.insertSelective(order);
    OrderItem orderItem = new OrderItem();
    orderItem.setOrderId(order.getId());
    // ... 省略 Set
    return order.getId();
}


1.2 解决办法

从上面造成问题的原因来看,只要是扣减库存的动作,不是原子性的。多个线程同时操作就会有问题。

  • 单体应用:使用本地锁 + 数据库中的行锁解决
  • 分布式应用:
  • 使用数据库中的乐观锁,加一个 version 字段,利用CAS来实现,会导致大量的 update 失败
  • 使用数据库维护一张锁的表 + 悲观锁 select,使用 select for update 实现
  • 使用Redis 的 setNX实现分布式锁
  • 使用zookeeper的watcher + 有序临时节点来实现可阻塞的分布式锁
  • 使用Redisson框架内的分布式锁来实现
  • 使用curator 框架内的分布式锁来实现
目录
相关文章
|
7月前
|
NoSQL 算法 安全
分布式锁—1.原理算法和使用建议
本文主要探讨了Redis分布式锁的八大问题,包括非原子操作、忘记释放锁、释放其他线程的锁、加锁失败处理、锁重入问题、锁竞争问题、锁超时失效及主从复制问题,并提供了相应的优化措施。接着分析了Redis的RedLock算法,讨论其优缺点以及分布式专家Martin对其的质疑。此外,文章对比了基于Redis和Zookeeper(zk)的分布式锁实现原理,包括获取与释放锁的具体流程。最后总结了两种分布式锁的适用场景及使用建议,指出Redis分布式锁虽有性能优势但模型不够健壮,而zk分布式锁更稳定但部署成本较高。实际应用中需根据业务需求权衡选择。
|
10月前
|
运维 NoSQL 算法
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
本文深入探讨了基于Redis实现分布式锁时遇到的细节问题及解决方案。首先,针对锁续期问题,提出了通过独立服务、获取锁进程自己续期和异步线程三种方式,并详细介绍了如何利用Lua脚本和守护线程实现自动续期。接着,解决了锁阻塞问题,引入了带超时时间的`tryLock`机制,确保在高并发场景下不会无限等待锁。最后,作为知识扩展,讲解了RedLock算法原理及其在实际业务中的局限性。文章强调,在并发量不高的场景中手写分布式锁可行,但推荐使用更成熟的Redisson框架来实现分布式锁,以保证系统的稳定性和可靠性。
601 0
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
|
存储 调度
分布式锁设计问题之云存储的最佳实践中保障分布式锁的容错能力如何解决
分布式锁设计问题之云存储的最佳实践中保障分布式锁的容错能力如何解决
184 0
|
11月前
|
分布式计算 DataWorks 数据处理
产品测评 | 上手分布式Python计算服务MaxFrame产品最佳实践
MaxFrame是阿里云自研的分布式计算框架,专为大数据处理设计,提供高效便捷的Python开发体验。其主要功能包括Python编程接口、直接利用MaxCompute资源、与MaxCompute Notebook集成及镜像管理功能。本文基于MaxFrame最佳实践,详细介绍了在DataWorks中使用MaxFrame创建数据源、PyODPS节点和MaxFrame会话的过程,并展示了如何通过MaxFrame实现分布式Pandas处理和大语言模型数据处理。测评反馈指出,虽然MaxFrame具备强大的数据处理能力,但在文档细节和新手友好性方面仍有改进空间。
|
监控 NoSQL Java
分布式锁实现原理问题之ZooKeeper的观察器(Watcher)特点问题如何解决
分布式锁实现原理问题之ZooKeeper的观察器(Watcher)特点问题如何解决
176 0
|
NoSQL Java Redis
分布式锁实现原理问题之使用Redis的setNx命令来实现分布式锁问题如何解决
分布式锁实现原理问题之使用Redis的setNx命令来实现分布式锁问题如何解决
308 0
|
12月前
|
人工智能 分布式计算 数据处理
云产品评测:MaxFrame — 分布式Python计算服务的最佳实践与体验
阿里云推出的MaxFrame是一款高性能分布式计算平台,专为大规模数据处理和AI应用设计。它提供了强大的Python编程接口,支持分布式Pandas操作,显著提升数据处理速度(3-5倍)。MaxFrame在大语言模型数据处理中表现出色,具备高效内存管理和任务调度能力。然而,在开通流程、API文档及功能集成度方面仍有改进空间。总体而言,MaxFrame在易用性和计算效率上具有明显优势,但在开放性和社区支持方面有待加强。
196 9
|
存储 Dubbo Java
分布式 RPC 底层原理详解,看这篇就够了!
本文详解分布式RPC的底层原理与系统设计,大厂面试高频,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 RPC 底层原理详解,看这篇就够了!
|
机器学习/深度学习 存储 运维
分布式机器学习系统:设计原理、优化策略与实践经验
本文详细探讨了分布式机器学习系统的发展现状与挑战,重点分析了数据并行、模型并行等核心训练范式,以及参数服务器、优化器等关键组件的设计与实现。文章还深入讨论了混合精度训练、梯度累积、ZeRO优化器等高级特性,旨在提供一套全面的技术解决方案,以应对超大规模模型训练中的计算、存储及通信挑战。
679 4
|
NoSQL Java API
分布式锁的实现原理与应用场景,5 分钟彻底搞懂!
本文详细解析了分布式锁的实现原理与应用场景,包括线程锁、进程锁和分布式锁的区别,以及分布式锁的四种要求和三种实现方式(数据库乐观锁、ZooKeeper、Redis)。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式锁的实现原理与应用场景,5 分钟彻底搞懂!

热门文章

最新文章