Vue3 乐观更新:先改变界面,也要准备撤销

背景 用户点击后立即看到结果,体验很好;但失败回滚若恢复整份旧快照,可能覆盖期间已经成功的另一次修改。 核心判断 给操作分配版本或请求序号,响应只能提交到匹配的状态。 按业务项记录逆操作,避免整页数据回滚。 金额、库存等强约束操作由服务端结果裁决,界面明确 pending 状态。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 // DTO 列表只有原始字段;本例将更新串行化,防止旧回滚覆盖新操作。 async function update(action: Action) { if (pending.value) return; const before = items.value.map(item => ({ ...item })); pending.value = true; items.value = applyLocal(items.value, action); try { items.value = await api.update(action); } catch (error) { items.value = before; showError(error); } finally { pending.value = false; } } 边界与误区 structuredClone 不适用于所有 Vue 响应式对象;快照应从可克隆的原始 DTO 构造。乐观更新不等于后端幂等,也不能解决并发写冲突。 验证方式 示例将单次操作期间的后续操作串行化。用两次修改乱序成功、失败和超时验证回滚,不让一次旧失败清掉新结果。 小结 保存可回滚版本并给变更分配序号,失败时只撤销仍属于当前操作的部分。

2026年10月4日 · 1 分钟 · BvBeJ

PostgreSQL 事务隔离:先说明异常,再选择级别

背景 先说需要避免哪一种业务异常,再讨论隔离级别,远比直接选择最高级别有效。库存扣减和账户转账关注的约束并不相同。 核心判断 用并发事务复现丢失更新、写偏差等风险。 能通过原子 SQL、唯一约束或行锁表达的规则,尽量交给数据库。 Serializable 冲突可能导致事务失败,调用方准备有限重试。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 BEGIN ISOLATION LEVEL SERIALIZABLE; UPDATE accounts SET balance = balance - 10 WHERE id = 1; COMMIT; 边界与误区 示例只有一次扣款,不是完整转账。重试必须从事务开始重新执行,事务外已经发生的副作用也要考虑幂等。 验证方式 使用两个连接安排读写交错,验证业务不变量;对 serialization failure 注入重试,并检查重试预算与外部副作用是否重复。 小结 从业务不变量出发选择隔离级别,并为 serialization failure 准备有限重试。

2026年9月19日 · 1 分钟 · BvBeJ

对象存储一致性设计:读后写、覆盖写与多地域复制

先回答三个问题 新写对象是否读后可见? 覆盖写是否存在短暂旧读? 跨地域复制的延迟窗口是多少? 业务协议建议 写入时带版本号(ETag/VersionId)。 读取时声明期望版本,不满足则重试或降级。 删除采用 tombstone 而非立即硬删。 典型流程 PUT(v2) -> 返回version=v2 -> GET(if-match=v2) -> 成功 多地域策略 强一致需求走单主写入。 多主写入必须引入冲突解决策略(LWW 或 CRDT)。 对外暴露“可见性延迟”SLO,避免误判故障。 小结 对象存储的一致性不是文档注释,而是业务协议的一部分。把版本语义显式化,读写行为才可预测。

2026年5月11日 · 1 分钟 · BvBeJ

Kafka Exactly-Once 的边界:事务语义与业务幂等如何配合

常见误解 启用 Kafka EOS 后,很多团队默认“不会重复消费”。实际上,数据库写入、HTTP 调用等外部副作用仍可能重复。 EOS 真正保证什么 Producer 端幂等写入(同一会话内去重)。 事务内“写输出 + 提交消费位点”原子性。 只在 Kafka 生态内部有效。 仍需业务补齐 下游写库使用幂等键(业务主键或事件 ID)。 外部调用提供去重令牌。 失败重试具备可重入语义。 处理流程建议 consume -> validate -> execute(idempotent) -> produce -> commit offsets 任何一步失败都可重试,且不会造成不可逆双写。 小结 EOS 不是银弹,而是基础设施层的“至少一次到一次”的收敛器。业务副作用一致性仍需你自己设计。

2026年5月7日 · 1 分钟 · BvBeJ

Go 实现 Raft 时的日志压缩与快照传输细节

为什么会“越跑越慢” Raft 日志长期不压缩会拖慢重放与追赶;但压缩过于激进又会让慢 follower 频繁走全量快照,网络和磁盘双杀。 压缩策略建议 用“日志条目数 + 磁盘占用”双阈值触发快照。 快照频率按写入速率自适应,而不是固定分钟数。 仅在 commit index 安全推进后截断日志。 安装快照时的工程点 分块传输并带 offset 校验。 最后一块再原子切换状态机数据目录。 快照应用期间限制客户端写入峰值。 Go 侧接口设计 type SnapshotStore interface { Create(meta SnapshotMeta) (io.WriteCloser, error) Open(id string) (io.ReadCloser, SnapshotMeta, error) List(limit int) ([]SnapshotMeta, error) } 常见故障 截断日志早于快照持久化完成,导致节点重启后状态缺口。 快照元数据未纳入 WAL,断电恢复后索引错位。 对慢 follower 缺少限流,拖累 leader。 小结 Raft 的可用性不只来自选主,更来自“日志、快照、复制窗口”三者的节奏匹配。压缩策略设计得当,集群才能长期平稳运行。

2026年5月2日 · 1 分钟 · BvBeJ