用户投诉"改过的资料又变回旧的"|异地多活的写冲突到底怎么解

简介: 从同城双活上线三个月的用户投诉切入,先分清容灾与多活、钉死"两个写入点必然产生冲突"这个前提,拆出写冲突的三种来源与可量化的冲突率统计,逐个拆解单元化、最后写入胜出、跨中心共识、冲突检测与合并四个方案的机制与代价,给出唯一性约束三种解法与自增号段的扩容陷阱,并配时钟漂移监控与会话粘性的避坑清单。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

去年一个同城双活的项目上线三个月,客服开始集中收到一类投诉。用户说,我上午改过的收货地址,下午又变回旧的了。让他再改一次,过两天可能又回去。运维把链路查了个遍。两个中心都在正常写,同步也在正常跑,日志里一条报错都没有。

我一开始怀疑是缓存,把两个中心的缓存全清了,问题照旧。后来把那条记录的历史版本拉出来看。它一天里被改了四次,两个中心各改两次,互相覆盖。最后留下的是哪一版,取决于同步的到达顺序,而这个顺序每次都不同。

那次之前,我以为双活就是两套库都开着,谁接流量都行。那次之后我才明白,"两个地方都能写"这六个字有多重。

先分清容灾和多活

这两个词经常被混着用,但它们不是一回事。

容灾是备着不用。主备、两地三中心都属于这个路子,金仓这类国产库在这一档也有方案。备用中心平时不接写入,只在主中心挂了以后接管。数据是主中心同步过去的,只有一条写入链路。这种形态下不存在写冲突,因为写的源头只有一个。

多活是两边都在写。同城双活、异地多活都属于这一类。只要有两个地方同时接受写入,冲突就是必然的,不是概率。这跟你配置得好不好没关系。"两个写入点"这个前提本身就带着矛盾。

很多人上多活,第一反应是"多一个中心多一层保险"。它换来的可用性提升,代价是问题从数据库搬进了业务代码。这笔账要提前算。

维度 容灾 多活
备用中心接不接写 不接 接
写入点 一个 多个
写冲突 不存在 必然存在
解决的问题 主中心故障后能顶上来 两个中心都在承载业务
典型形态 主备、两地三中心 同城双活、异地多活
主要代价 备用资源平时闲置 一致性复杂度上移到业务

01-写入点对比.png

冲突只有三种来源

实际遇到的冲突看着五花八门,归到根上就三类。

第一类是用户投诉里最常见的那种。用户在离自己近的中心改资料。另一个中心的副本被别的流程也改了。两条修改各自往对面同步,谁覆盖谁看的是到达顺序。

第二类最隐蔽。两个中心各查本地,都查不到重复,就都写进去了。等数据同步过来才发现有两条一模一样的唯一键。

第三类最基础。发号本身是写操作。只要两个中心都在发号,撞号就一定会发生。

冲突来源 具体表现 为什么必然发生
同一行并发改 两个中心同时改同一行,后同步的覆盖先同步的 两边都能写,就没有唯一的先后顺序
跨中心唯一约束 两边同时建了编号相同的单据,本地校验都通过 唯一性要看全局,本地看不到对方
自增与序列 两个中心发出来同一个主键 ID 各自独立发号,没有共享的号源

冲突率是可以算出来的。取一个时间窗口,看同一主键被两个中心都改过的比例。这个占比就是你的冲突率。

-- 写冲突率:同一主键在同一分钟窗口内被两个中心都改过的比例
SELECT
  SUM(CASE WHEN center_cnt > 1 THEN 1 ELSE 0 END) / COUNT(*) AS conflict_ratio
FROM (
  SELECT
    pk_id,
    FLOOR(UNIX_TIMESTAMP(modify_ts) / 60) AS win,
    COUNT(DISTINCT center_id) AS center_cnt
  FROM change_log
  GROUP BY pk_id, win
) t;

这个窗口的粒度需要根据表的更新频率来定,高频表用分钟,低频表用小时。粒度太细会把正常的先后修改误判成冲突,太粗又会漏掉真正的并发写。这个数摸清之前,任何多活方案都只停在纸面上。

四个方案,逐个说

单元化:让一份数据只从一个地方写

这是核心系统里最主流的做法,叫 Set 化。

思路很直接。既然冲突来自"两个地方都能写",那就规定每份数据只有一个地方能写。按一个业务维度把流量切开,比如按用户 ID 取模,前一半走中心 A,后一半走中心 B。同一个用户的所有写请求,永远落到同一个中心。另一边只放只读副本,不接写。

同一行数据的写入点只剩一个,冲突从源头就没了。难点不在数据库,在路由。接入层要保证同一个用户的请求每次都被分到同一个单元。这个判断不能在各个应用里各写各的,要在接入层统一做。有一个应用漏了这条规则,自己连了另一个中心写,前面的设计全白费。

单元化还有一笔代价,跨单元访问。用户 A 要转钱给用户 B,两人如果分属两个单元,这笔操作就跨了单元。要么在应用层拆成两次单元内调用,要么走一条专门的跨单元通道。业务切得越干净,这类调用越少。切不干净,跨单元调用就会变成新的瓶颈。所以单元化的前提,是业务能被干净地切开。

最后写入胜出:简单,但会静默丢数据

这个方案叫 Last Write Wins。每条写入带一个时间戳,两个中心的数据撞上,谁的时间戳新谁留下。

实现简单,代价是静默丢数据。被覆盖的那条修改不报错,不留痕,就那么没了。用户看到自己的修改失效,系统这边什么都不知道。

它还依赖时钟。时钟一漂,谁"最后"写就说不准。普通做法用 NTP 同步物理时钟,但物理时钟会回拨、会跳变,网络一抖偏差就上去。工程上用得多的是混合逻辑时钟,把物理时间和一个逻辑计数器拼起来,保证单调递增。它能处理同一物理时刻内的先后排序,处理不了时钟本身已经漂错的情况。

用最后写入胜出,就得配监控。持续测两个中心的时间偏差,超过阈值立刻告警。

-- 时钟漂移监控:偏差超过 50 毫秒的记录
SELECT
  center_id,
  drift_ms,
  check_ts
FROM center_clock_drift
WHERE ABS(drift_ms) > 50
ORDER BY ABS(drift_ms) DESC;

50毫秒这个阈值对同城双活是合理的,因为同城RTT通常在1毫秒以内。异地多活的跨城RTT通常30到50毫秒,漂移阈值要按实际RTT来定,不能直接照搬。原则是:漂移量不能超过跨中心RTT的量级,否则时间戳排序就会失去意义。

跨中心共识同步写:把跨城延迟写进响应时间

这个方案的思路是,写入不落单边,要等多数派确认才算成功。靠 Paxos 或 Raft 这类共识协议来做。

好处是一致性强,不用操心覆盖。代价是延迟。同城两个数据中心的网络往返大概 1 毫秒,异地通常 30 到 50 毫秒。写入要等多数派,多数派里必然有跨城的那一个,这几十毫秒就直接进了响应时间。

更麻烦的是抖动放大。跨城网络一抖,P99 立刻跟着跳,业务侧的感受是"偶尔卡一下"。这种卡顿在应用日志里看不出来,得去看网络延迟分布才找得到。这套方案适合写入量不大、一致性要求极高的场景。放在高并发的交易链路上,跨城延迟会直接吃掉响应预算。

冲突检测与合并:适合能合的数据

前三个方案都在想办法避免冲突,这个方案承认冲突,然后把两条并发修改合起来。

工具是版本向量。给每条数据挂一份各中心的修改计数,合并时对比向量,能判断两次修改是"有先后"还是"并发"。有先后就取新的,并发才需要业务介入。

能合并的数据还能做自动合并,这类专门的数据结构叫 CRDT。计数器可以合并,集合可以合并,某些形态的寄存器也可以合并。两个中心各给一篇文章点了赞,合并时把计数加起来就行。

余额不行。余额的两个并发修改,语义上未必能相加。A 中心扣了 100,B 中心也扣了 100,用户实际只该被扣一次。这类数据的正确性很难靠合并算法保证,得把账户的写入点收敛到一边。绕一圈,又回到了单元化。

方案 机制 一致性 延迟代价 数据风险 适用条件
单元化 按业务维度切流量,一份数据只一个中心写 强 跨单元访问要绕路 无 业务切得干净
最后写入胜出 时间戳打标,谁新谁赢 最终一致 低 静默丢写 冲突少、可容忍丢
跨中心共识 多数派确认才算成功 强 跨城 RTT 进响应 无 写入量低、强一致刚需
冲突检测与合并 版本向量、CRDT 最终一致 低 看合并语义 数据本身可合并

02-四方案取舍.png

唯一性约束是最难的一块

三类冲突里,唯一性约束要单独拎出来说,因为它没有又好又省的办法。

我见过的做法大多是号段预分配。它把冲突概率压到"段用完的那一刻",日常运行不受影响。但要接受号的浪费,也得有一套段用完时的续领流程。

解法 做法 代价
集中分配 一个中心统一发号,别的中心来取 发号中心成了新单点,跟多活的初衷相反
号段预分配 每个中心预领一段号,段之间不重叠 段用完要重分,各中心用量不均时会浪费
跨中心校验 写入前先去其他中心查一遍 多一次跨中心往返,本质还是分布式锁

还有一种是最终一致加补偿。先写进去,事后对账发现重复再合并。这要求业务能容忍短暂重复,比如允许两个用户看到同一个订单号几分钟。

自增主键也有类似的坑。传统做法是给两个中心的自增步长错开。

-- 两个中心的自增步长错开,避免撞号
-- 中心 A
SET @@auto_increment_increment = 2;
SET @@auto_increment_offset   = 1;   -- 产出 1,3,5,7...
-- 中心 B
SET @@auto_increment_increment = 2;
SET @@auto_increment_offset   = 2;   -- 产出 2,4,6,8...
-- 加第三个中心时步长要改成 3,新号段会和已有数据撞在一起

两个中心时它够用。要扩第三个中心,步长得从 2 改成 3,历史号段立刻重叠。这种分法从设计上就没给扩容留余地。

避坑清单

最后写入胜出依赖时钟,必须配时钟同步,并且持续监控漂移量。漂移阈值要提前定,超了就告警。别等用户来投诉,才发现两边的时间早就差了半秒。

单元化的路由必须在接入层统一做,不能靠各个应用自己判断。这条规则只要有一个应用没遵守,数据就会从那个缺口漏到另一个中心去写。

最后一条是我自己搞错的。我一开始以为每个中心都能写、读写都走本地就完事了。没想到用户在自己中心刚写完,下一次请求被负载均衡甩到另一个中心,读到的是同步还没到的旧数据。用户以为没保存成功,又点了一次保存。后来补上了会话粘性,让同一个用户的读写固定走同一个中心。

写在最后

多活的第一步不是加中心,是找一个能把写入干净切开的维度。找不到这个维度,硬上多活只是把一致性问题从数据库搬到了业务代码里。

容灾和多活也不是一回事。容灾解决的是"主中心挂了怎么办",多活解决的是"两个中心都别闲着"。前者是底线,后者是能力的进阶。我的做法是先有容灾,再谈多活。顺序反过来,风险会高很多。

我现在判断一个系统该不该上多活,先问三个问题。写入能不能按一个维度切开。切不开的那部分占比多少。业务能不能容忍短暂的重复或者丢失。三个答案基本就定了走哪条路,或者干脆不走。

你手上的系统,多活的流量是按什么维度切的?评论区聊聊。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
|
19天前
|
人工智能 监控 IDE
Jetbrains 正式官宣:全新AI IDE正式发布!
JetBrains AIR 是全球首个AI原生IDE,定位为“智能体调度台”而非AI供应商。它不内置模型,而是通过ACP协议统一管理Codex、Copilot等第三方智能体,支持多任务并行、实时会话监控、一键子任务与Worktree开发,强调确定性、可审查性与开发者主权。(239字)
168 1
|
3月前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
5815 214
|
20天前
|
人工智能
分支功能:实现脚本多场景覆盖
分支功能支持“一份代码、多组回显”测试,为主流场景(主线)及各类异常分支(如命令失败、字段为空、权限不足等)分别生成模拟回显并自动验证,提前暴露边缘问题。覆盖全面、操作高效,大幅提升网络脚本鲁棒性。项目地址:https://qingxun.online
|
1月前
|
存储 关系型数据库 MySQL
对账差了三毛钱,查完我把全部金额字段从DOUBLE改成了DECIMAL
一次财务对账差三毛钱的排查,牵出金额字段用浮点数的老坑。从IEEE 754为什么存不准0.1讲起,用同一批金额把FLOAT、DOUBLE、DECIMAL三种类型实测对比,再给出金额字段的选型、聚合与改表做法,附避坑清单。
|
1月前
|
数据采集 JavaScript 前端开发
房产数据对比爬虫:同时爬取链家+贝壳+安居客,做房价横向对比
本文详解如何突破链家/贝壳(JS渲染+字体反爬)与安居客(动态Token+请求头校验)的差异化反爬机制,基于适配器模式构建跨平台爬虫架构,结合站大爷隧道代理实现IP隔离,并通过小区名+户型+面积模糊匹配,完成房价横向对比分析,助力识别虚高挂牌与低价引流。
340 1
|
1月前
|
SQL Java 数据库连接
1万行插入13秒到0.9秒:ORM批量插入只差一个参数
从一次列表接口慢的排障讲起,发现2000多条一模一样的N+1查询。文章拆开ORM生成慢SQL的三类典型病:N+1懒加载、逐条批量插入(只差一个rewriteBatchedStatements参数)、隐式转换让索引白建。给出JOIN/批量IN/@BatchSize的取舍、MyBatis与JPA各自的修法,以及用performance_schema按SQL指纹抓N+1、测试环境打印真实SQL的协作办法。
|
1月前
|
SQL 运维 算法
订单表上亿行,我按用户ID拆成128片之后怎么样了
从单表几千万行慢查询的痛点出发,讲清垂直拆分与水平拆分的区别、分片键怎么选、分片算法(hash取模/range/一致性hash)怎么权衡,以及分库分表带来的分布式ID、跨片查询、分布式事务等问题,给出避免过度拆分的避坑清单。
|
21天前
|
编解码 人工智能 弹性计算
租用GPU服务器如何收费?来看看阿里云GPU服务器,怎么样?可以吧!
阿里云GPU云服务器(EGS)支持包年包月、按量付费、抢占式实例三种计费模式,适配AI训练/推理、视频转码、图形渲染及科学仿真等场景;提供T4/A10/L20/V100等多款GPU规格,价格因配置、地域及时长而异,推荐按业务负载精准选型。(239字)
|
1月前
|
人工智能 安全 IDE
Qoder CN v1.4.1完整实战指南:Agent式AI编程全流程拆解,从本地部署到企业级落地实操教程
Qoder CN v1.4.1最大的价值,是把AI编码从“代码片段生成”升级为完整Agent式工程任务执行。借助Quest任务引擎、RepoWiki记忆知识库、MCP工具扩展、Hooks安全机制,开发者只需要描述业务需求,智能体自主完成需求拆解、多文件修改、命令执行、测试验证完整链路。同时兼容CLI、IDE、插件多种形态,深度对接百炼平台各类订阅方案,既可以满足个人开发者提升开发效率,也具备完整权限管控、安全拦截、团队知识库能力,支撑企业规模化落地AI研发。
379 1
|
22天前
|
人工智能 缓存
阿里云AI模型的Token如何计费?输入、输出及免费100 Tokens费用说明
2026年阿里云通义千问(Qwen)系列模型Token收费标准公布:按输入/输出分别计价,覆盖Qwen3-Max、Qwen3.5-Plus、Qwen-Long、Qwen3-Omni-Flash(多模态)、通义点金及全妙轻应用等。支持Batch调用5折、上下文缓存优惠及90天内免费额度(各100万Token起),精准报价以阿里云百炼官网为准。(239字)
353 0

热门文章

最新文章