如何在高并发下,保证共享数据的一致性

高并发环境下,要保证共享数据的一致性,核心在于通过一系列严谨的、从数据库到应用架构层面的“并发控制”机制,来确保多个并行的操作,在逻辑上,能够像“串行”执行一样,产生一个确定的、符合业务规则的正确结果。一套全面、健壮的数据一致性保障体系,其构建必须系统性地涵盖五大关键策略:运用“锁”机制保障操作的“互斥性”、通过“事务”确保一系列操作的“原子性”、在分布式系统中选择合适的“一致性模型”、利用“消息队列”等工具实现“最终一致性”、以及在架构设计层面采用“无锁”或“数据分片”等策略。其中,通过“事务”确保一系列操作的“原子性”,是保障数据一致性的“基石”。

如何在高并发下,保证共享数据的一致性如何在高并发下,保证共享数据的一致性

一个事务,就如同一个“保险箱”,它将一系列独立的数据库读写操作,都包裹在了一个“要么全部成功、要么全部失败”的、不可分割的单元之中。如果在事务执行的过程中,发生了任何错误,整个事务,都会被“回滚”到操作开始前的原始状态,从而,从根本上,杜绝了数据,因为“操作执行了一半”而被遗留在一种“中间的、不一致的”残缺状态的风险。

一、问题的根源、并发下的“数据混沌”

在探讨“如何保证”之前,我们必须首先,深刻地,理解“为何会不一致”。在单用户、低并发的环境中,数据的一致性,通常是自然而然的。然而,一旦进入高并发的世界,多个用户或进程,在几乎同一微秒,对同一个共享数据,进行“读取-修改-写回”的操作时,一场“数据的混沌”风暴,就将不可避免地降临

1. 经典的“商品库存”场景

这是一个最能直观地,揭示并发问题所在的经典案例:

场景:一件热门商品,其在数据库中的库存,只剩下最后1件。

并发事件:在几乎完全相同的时间点,用户A和用户B,都点击了“购买”按钮。这两个请求,被分配到了服务器的两个不同线程(线程A和线程B)上,并行地开始处理。

一个未经保护的、灾难性的执行时序

时刻1:线程A,从数据库中,读取商品库存,得到值为1

时刻2:线程A,在内存中,进行逻辑判断:1 > 0,库存充足,可以购买。

时刻3此时,发生了一次线程切换! 操作系统,暂停了线程A的执行,转而去执行线程B。

时刻4:线程B,也从数据库中,读取商品库存。因为线程A的修改,尚未被写回,所以,它读取到的值,依然1

时刻5:线程B,在内存中,也进行了逻辑判断:1 > 0,库存充足,可以购买。

时刻6:线程B,执行“写回”操作,将库存修改为1 - 1 = 0,并更新到数据库中。然后,为用户B,创建了订单。

时刻7线程再次切换,回到线程A。

时刻8:线程A,从上次被暂停的地方,继续执行。它基于自己早已“过时”的、在时刻1读取到的旧数据,执行“写回”操作,将库存,再次,修改为1 - 1 = 0,并为用户A,也创建了订单。

最终结果:一件商品,被成功地,卖给了两个人。数据库中的库存,变为了-1(如果字段允许负数)。数据,进入了严重的“不一致”状态。

2. 一致性的“定义”

数据一致性,是指数据,在其生命周期中的任何时间点,都必须,处于一种符合预设业务规则的、有效的、逻辑自洽的状态。例如,“商品库存,永远不能为负数”,就是一条业务规则。

二、方案一、“单体”世界中的“强一致性”

对于那些运行在“单个”数据库服务器上的、传统的“单体”应用而言,我们通常,会追求一种最高级别的、可立即验证的“强一致性”。

1. “锁”机制:从“悲观”到“乐观”

“锁”,是实现并发控制的、最基础、也最直接的工具。

悲观锁:它秉持一种“悲观”的哲学,即“假设冲突,总是会发生”。因此,它在对数据,进行任何操作之前,会先** preemptively**地,将这条数据“锁定”。

实现方式:例如,在数据库中,使用SELECT ... FOR UPDATE语句。当一个事务,执行了这条语句后,数据库,就会将该行数据锁定。任何其他的事务,如果也想修改这一行,就必须“排队等待”,直到前一个事务,完成并“释放”锁为止。

优缺点数据一致性保障极高,但因为“串行化”了并行操作,所以,会牺牲掉一部分的“并发性能”

乐观锁:它秉持一种“乐观”的哲学,即“假设冲突,是小概率事件”。它不会在操作前加锁,而是在**最终“提交更新”**的那一刻,去检查,数据,是否已经被其他线程所修改。

实现方式:通常,会在数据表中,增加一个“版本号”字段。

线程A,读取数据时,会同时读取其“版本号”(例如,v=1)。

当线程A,准备写回数据时,它的更新语句,会变成:UPDATE products SET stock = 0, version = 2 WHERE id = ? AND version = 1

如果,在此期间,线程B,已经抢先一步,修改了库存,那么,该行数据的版本号,就已经变成了2。此时,线程A的UPDATE语句,会因为version = 1这个条件不满足,而更新失败(影响行数为0)。

优缺点并发性能,远高于悲观锁。但如果“冲突”频繁发生,那么,大量的“重试”操作,反而会降低整体性能。

2. 数据库“事务”:原子性的保障

事务,是将一系列独立的数据库读写操作,打包成一个“不可分割”的、“要么全部成功,要么全部失败”的“原子”单元的、最核心的机制。 一个符合ACID标准(即原子性、一致性、隔离性、持久性)的事务,能够从根本上,保障在一个操作序列中的数据一致性。 在开篇的“商品库存”案例中,只要我们将“读取库存 -> 判断库存 -> 修改库存”这三个步骤,完整地,包裹在一个“事务”中,并配合“悲观锁”,那么,当线程A,在事务中,执行SELECT ... FOR UPDATE时,线程B的SELECT ... FOR UPDATE请求,就会被阻塞,直到线程A的整个事务,提交回滚为止。

三、挑战的“升维”:分布式系统的一致性

当我们的业务,发展到需要将数据,分散存储在多个独立的服务器(即“分布式系统”)上时,一致性的保障,其难度,会呈指数级上升。

1. CAP定理的“三难困境”

CAP定理,是分布式系统领域的“基石”理论。它指出,任何一个分布式系统,在以下三个核心指标中,最多,只能同时,满足其中两个

一致性:任何一次读操作,都能读取到“最新的”写操作的结果。

可用性:任何一次请求,都能收到一个“非错误”的响应,但不保证数据是最新的。

分区容错性:系统在遇到“网络分区”(即节点间的网络连接中断)时,依然能够继续对外提供服务。

在现实世界的、一个必然会遇到网络故障的分布式系统中,“分区容错性”,是一个必须被保障的基础。因此,我们,常常被迫地,在“一致性”和“可用性”之间,做出一个艰难的“权衡取舍”。

2. BASE理论与“最终一致性”

为了应对这种“三难困境”,许多大型的互联网系统,都选择,放弃“强一致性”,而遵循“BASE理论”,追求一种更具弹性和扩展性的“最终一致性”。

BASE理论,即基本可用软状态最终一致

最终一致性,意味着,系统,并不保证,在数据写入后的“任何时刻”,所有节点上的数据,都是完全一致的。但它“承诺”,在经过了一个短暂的“不一致窗口期”之后,所有节点上的数据,最终,都将会,达到一个一致的状态。

四、方案二、“分布式”世界中的“最终一致性”

1. 分布式事务的“两阶段提交”与“补偿事务”

为了在分布式环境下,实现多个独立服务之间的“事务性”操作,业界发展出了“两阶段提交”、“补偿事务”等复杂的分布式事务协议。它们,通过引入一个“协调者”的角色,来尽力地,模拟传统单体数据库的事务行为。但因为其实现的复杂性和对性能的巨大影响,在许多互联网场景下,团队更倾向于,采用基于“消息”的、更松耦合的最终一致性方案。

2. 核心模式:基于“消息队列”的异步通信

消息队列(如Kafka, RabbitMQ),是实现最终一致性的、最常用、也最可靠的“核心基础设施”。

场景:在一个电商系统中,“订单服务”,在创建了一个新订单后,需要通知“库存服务”,去扣减库存,并通知“积分服务”,去为用户增加积分。

最终一致性的实现

“订单服务”,在自己的本地数据库事务中,成功地,创建了订单。

然后,它并不直接地,去远程调用“库存服务”和“积分服务”的接口(因为,这些调用,可能会失败或超时)。

取而代之,它,向一个高可用的“消息队列”中,发送一条包含了订单信息的、可靠的“订单已创建”的消息。

“库存服务”和“积分服务”,则作为“消费者”,分别地,“订阅”了这个消息。

当它们,收到这条消息后,再各自地,在自己的服务内部,执行“扣减库存”和“增加积分”的操作。

优点:这种模式,通过“消息中间件”,将原本紧耦合的、同步的调用,进行了解耦,极大地,提升了系统的“可用性”和“弹性”。即便“库存服务”暂时宕机,也不会影响到“订单服务”的正常运行。

五、在流程与实践中“抉择”

一致性,并非一个“越高越好”的单一指标。在不同的业务场景下,我们需要,有策略地,选择不同“级别”的一致性模型

需要“强一致性”的场景:所有与“金钱”和“交易”直接相关的核心流程,例如,支付、下单、核心库存等。在这些场景下,我们必须,不计代价地,采用数据库的“事务”和“悲观锁”等机制,来保障数据的绝对一致。

可接受“最终一致性”的场景:大量的、非交易核心的场景,例如,用户动态的更新、文章点赞数的显示、非核心信息的跨系统同步等。在这些场景下,短暂的数据不一致,对用户体验的影响很小,我们可以,放心地,采用基于“消息队列”的最终一致性方案,来换取系统更高的可用性可扩展性

常见问答 (FAQ)

Q1: “强一致性”和“最终一致性”哪个更好?

A1: 两者并无绝对的“好坏”之分,只有“适用场景”的不同。“强一致性”,提供了最可靠的数据保证,但牺牲了部分的“性能”和“可用性”。而“最终一致性”,则通过接受一个短暂的“不一致”窗口,来换取系统极高的“可用性”和“可扩展性”。

Q2: 什么是“ACID”?

A2: “ACID”,是数据库事务,必须具备的四个核心特性:原子性(一个事务,是不可分割的工作单元,要么全做,要么全不做)、一致性(事务,必须使数据库,从一个一致性状态,转变到另一个一致性状态)、隔离性(并发执行的事务之间,不应相互干扰)和持久性(一旦事务提交,其结果,就是永久性的)。

Q3: 为什么说在分布式系统中,我们必须在“一致性”和“可用性”之间做出选择?

A3: 这是由“CAP定理”所决定的。因为,在分布式系统中,“网络分区”(即网络故障)是不可避免的。当网络分区发生时,一个节点,为了保证“一致性”,就必须拒绝那些可能“过时”的读写请求,从而,牺牲了“可用性”。反之,如果它为了保证“可用性”,而继续处理请求,那么,它所处理的数据,就可能是“过时”的,从而,牺牲了“一致性”。

Q4: “乐观锁”和“悲观锁”有什么区别?

A4: 两者的核心区别,在于对“并发冲突”的“假设”不同。“悲观锁”,假设冲突“很可能会发生”,所以,在操作数据“之前”,就先将其“锁定”。而“乐观锁”,则假设冲突“很少会发生”,所以,它在操作前“不加锁”,而只是在最终“提交更新”时,才去“检查”,在此期间,数据是否已被其他线程修改过。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:百晓生,转转请注明出处:https://www.chuangxiangniao.com/p/638958.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月12日 12:45:22
下一篇 2025年11月12日 12:45:52

相关推荐

  • PHP高并发:连接池优化方案

    php连接池优化可通过复用数据库连接提升高并发性能。1.选择合适的连接池实现:使用第三方库如doctrine dbal或laravel database获取完善的连接池管理功能;2.配置合理的连接池参数:设置最大连接数、最小空闲连接数、连接超时时间并监控连接泄漏;3.优化数据库查询:使用索引、避免全…

    2025年12月10日 好文分享
    000
  • 面对高并发场景,PHP 怎样优化以从容应对?

    php 通过以下方式优化以应对高并发:1. 配置 php-fpm,合理设置进程参数;2. 优化代码,减少数据库查询次数;3. 使用 redis 缓存;4. 实施负载均衡和异步处理。 引言 面对高并发场景,PHP 怎样优化以从容应对?这是许多开发者在构建大规模应用时常常会遇到的问题。高并发意味着系统需…

    2025年12月10日
    000
  • 探索 PHP 函数在高并发场景中的应用和优化

    在高并发场景中优化 php 函数的应用:使用缓存:存储频繁执行函数的结果以避免重复执行。多进程/多线程:并行执行耗时函数,以提高处理能力。减少函数调用次数:合并相似函数调用或使用循环替代嵌套函数调用。避免递归:尽可能使用循环替代递归调用,以节省堆栈空间。利用 php 扩展:启用 opcache 或 …

    2025年12月9日
    000
  • PHP 函数在高并发应用中的实战经验

    对于高并发应用,php 函数的优化至关重要。优化方式包括:使用函数式编程提高并发性,例如柯里化和组合。利用 pcntl_fork() 等函数进行并发处理。缓存经常调用的函数结果,以提升性能。 PHP 函数的高并发应用优化实战 对于高并发应用,函数的优化是至关重要的。PHP 提供了丰富的函数,但如何合…

    2025年12月9日
    000
  • 提升 PHP 函数在高并发下的稳定性

    提升 php 函数在高并发下的稳定性可以通过:1、缓存计算结果;2、限制并发请求;3、异步执行任务;4、使用 cdn 托管静态内容;5、优化代码性能。 提升 PHP 函数在高并发下的稳定性 问题背景 PHP 作为一种解释性语言,在高并发环境下可能会遇到性能问题和稳定性问题。尤其是当某个函数被频繁调用…

    2025年12月9日
    000
  • 什么是最终用户许可协议(EULA)和NFT许可?两者在所有权上有何区别?

    EULA规定用户仅获非独占使用权,禁止反向工程与非法使用,软件按“现状”提供,开发者免责,违约可终止协议;NFT许可允许持有者控制代币并自由交易,部分支持商业利用,但版权仍归创作者所有,条款可通过智能合约更新,高价值NFT或附带链外权益;二者核心差异在于EULA仅授使用权且无所有权,依赖中心化执行,…

    2025年12月9日
    000
  • Allora (ALLO)币是什么?工作原理、代币经济学介绍

    allora 是一个自我改进的去中心化人工智能网络,它利用社区构建的机器学习模型进行精准的、情境感知的预测。allora 由 nick emmons 和 kenny peluso 于 2019 年创立,并获得了 polychain capital、framework ventures 和 block…

    2025年12月9日
    000
  • 瑞波币最新价格查询_瑞波币官方网站入口

    瑞波(ripple)是一个旨在连接全球银行、支付提供商和数字资产交易所的开放支付网络,其原生数字货币被称为瑞波币(xrp)。与许多主流加密货币不同,xrp专注于为金融机构提供一种高效、低成本的跨境支付解决方案,凭借其极快的交易确认速度和高度的可扩展性,在全球支付领域展现了巨大的潜力,成为了数字货币市…

    2025年12月9日
    000
  • 瑞波币XRP官网导航 瑞波币App使用入口

    binance币安交易所 注册入口: APP下载: 欧易OKX交易所 注册入口: APP下载: 火币交易所: 注册入口: APP下载: 为了帮助用户准确获取瑞波币(XRP)及其底层技术的相关信息,本文将系统梳理其官方网站的关键入口和移动端应用的使用路径。通过本指南,您可以清晰地了解如何访问核心资源,…

    2025年12月9日
    000
  • 狗狗币价格预测:多头能否引发 0.25 美元的突破?一文分析

    狗狗币(Dogecoin)是什么?值得投资吗? ‍ 狗狗币(Dogecoin)诞生于2013年12月,由软件开发者Billy Markus与Jackson Palmer共同推出,是迷因币(Meme Coin)的鼻祖。 当时两人认为加密货币氛围过于严肃,于是以轻松幽默的心态创造了狗狗币,并采用网络爆红…

    2025年12月9日 好文分享
    000
  • 突然就“推理 Agent 元年”了,再聊 AI Chat 与 AI Agent

    今年 3 月份,我们还在以为 ai agent 的新纪元需要等到“泛 agi”,依靠大模型自身的能力和与之相辅相成的一系列技术的发展,诸如 rag、调用链等,去将大模型的能力更深入地“外置”给 agent 单元体。 然而到了下半年,随着大模型自身推理能力的爆发,以及生态中 MCP、ACP、A2A、上…

    2025年12月6日 行业动态
    000
  • Go语言中枚举的惯用实现方式

    本文深入探讨了Go语言中实现枚举的惯用方法,重点介绍了iota关键字的机制与应用。通过详细的代码示例,文章阐述了iota在常量声明中的重置、递增特性及其在生成系列相关常量时的强大功能,并演示了如何结合自定义类型创建类型安全的枚举,以满足如表示DNA碱基等特定场景的需求。 引言:Go语言中的枚举需求 …

    2025年12月3日 后端开发
    000
  • Go 程序沙盒化:构建安全隔离环境的策略与实践

    本文探讨了 Go 程序沙盒化的核心策略与实践。针对运行不可信 Go 代码的需求,文章阐述了通过限制或伪造标准库包(如 unsafe、net、os 等)、严格控制运行时环境(如 GOMAXPROCS)以及禁用 CGO 和汇编代码等手段来构建安全隔离环境的方法。强调沙盒设计需根据具体安全需求定制,并提醒…

    2025年12月2日 后端开发
    000
  • mysql持续交付如何实现_mysql数据库devops

    将MySQL数据库变更纳入版本控制并使用Flyway等工具管理迁移脚本,实现与应用代码同步;通过CI/CD流水线自动化测试、灰度发布和回滚机制,确保数据库交付高效、安全、可追溯。 在现代软件开发中,MySQL数据库的持续交付(Continuous Delivery)是DevOps实践的重要组成部分。…

    2025年12月2日 数据库
    000
  • Go与C++ DLL互操作:SWIG在Windows平台上的兼容性考量与实践

    本文深入探讨了在Windows环境下使用SWIG将Go语言与C++ DLL集成的挑战,特别是当遇到“adddynlib: unsupported binary format”错误时。核心问题在于SWIG在Windows上对Go语言的DLL绑定,其官方兼容性主要集中在32位系统。文章提供了详细的集成流…

    2025年12月2日 后端开发
    100
  • Go语言编译产物体积探秘:静态链接与运行时机制解析

    Go语言编译的二进制文件体积相对较大,主要源于其默认采用静态链接,将完整的Go运行时、类型信息、反射支持及错误堆栈追踪等核心组件打包到最终可执行文件中。即使是简单的”Hello World”程序也概莫能外,这种设计旨在提供独立、高效且无外部依赖的运行环境。 go语言的设计哲学…

    2025年12月2日 后端开发
    000
  • Go语言日期与时间处理详解:time 包核心机制与实践

    Go语言通过其内置的time包提供了一套强大且精确的日期时间处理机制。它以Time结构体为核心,能够以纳秒级精度表示时间瞬间,且在内部表示中不考虑闰秒。time包依赖IANA时区数据库处理复杂的时区和夏令时规则,确保全球时间信息的准确性。本文将深入探讨Time结构体的设计、时区管理,并提供实际应用示…

    2025年12月2日 后端开发
    000
  • 使用 Go 构建时添加 Git Revision 信息到二进制文件

    在软件开发过程中,尤其是在部署后进行问题排查时,快速确定运行中的二进制文件对应的源代码版本至关重要。本文将介绍一种在 Go 语言构建过程中嵌入 Git Revision 信息的方法,以便在程序运行时方便地获取版本信息。 利用 ldflags 在构建时设置变量 Go 语言的 go build 命令提供…

    2025年12月2日 后端开发
    200
  • 深入理解Go语言gc编译器与C语言调用约定的差异

    Go语言的gc编译器不采用与C语言兼容的调用约定,主要是因为Go独特的协程栈(split stacks)机制使其无法直接与C代码互操作,因此保持调用约定兼容性并无实际益处。然而,gccgo作为Go的另一个编译器实现,在特定条件下可以实现与C语言兼容的调用约定,因为它能支持C语言的栈分割特性,从而提供…

    2025年12月2日 后端开发
    000
  • Go应用中嵌入Git修订版本号的实践指南

    本教程详细阐述了如何在Go语言编译的二进制文件中嵌入当前Git修订版本号。通过利用go build命令的-ldflags -X选项,我们可以在不修改源代码的情况下,将项目的Git提交哈希值注入到可执行文件中,从而实现部署后二进制文件的版本追溯和故障排查,提升软件的可维护性与透明度。 在软件开发和部署…

    2025年12月2日 后端开发
    000

发表回复

登录后才能评论
关注微信