用户只点了一次提交,因为网络超时客户端自动重试,结果同一笔订单被下了两次、款也扣了两次,我对着接口没做幂等设计这个坑排查大半天的复盘

一个让我对分布式与网络环境下的不确定性彻底敬畏的架构坑,后怕在出问题的不是某段写错的逻辑,而是一个我压根没考虑过的场景:同一个请求被处理了不止一次,而这在真实网络世界里是必然。用户投诉只下了一单却被扣两次款收到两份货,查日志确有两条几乎同时、内容完全一样的下单记录。下单接口逻辑朴实:扣库存、扣款、创建订单、发货。问题在于:用户点提交,请求到服务器其实已成功处理,但响应网络包回来路上超时丢失,客户端没收到以为失败就自动重试又发一次一模一样的请求,服务器又从头执行一遍。深究幂等性才明白:幂等是一个操作执行一次还是多次效果都相同(设状态/删除是幂等,扣款/库存-1/插订单不幂等);网络环境下重复请求是必然(客户端超时重试而超时不等于失败、用户手抖重复点、MQ 为不丢消息普遍 at-least-once 至少投递一次、网关重试);我的接口做的是不幂等操作却默认每请求只来一次,重复请求一来就重复扣款下单。解决之道不是祈祷它不发生而是让接口幂等。这篇从故障现场、幂等性与重复请求真相、正解(幂等键+SETNX 去重、数据库唯一约束兜底、状态机条件更新 WHERE status=UNPAID、乐观锁、把相对操作转绝对/流水)、分布式其他不确定性(丢失/乱序/延迟/超时≠失败/部分失败/并发/时钟)、操作幂等性速查表、重试与幂等是必须配套的孪生设计、设计接口决策图与铁律,到附上一个可复用的幂等注解+AOP 切面(注意业务失败要删键)。核心领悟:从单机走向分布式最根本的思维转变是抛弃对确定性的依赖、拥抱并主动应对不确定性,exactly-once 是奢望能依赖的常是 at-least-once 即可能多次;为失败而设计——不去消除不确定性而是承认它构建能在不确定中保持正确的有韧性系统,幂等不阻止重复而是让重复无害;重试解决丢失却引入重复、需幂等配套,引入每个机制都要追问它的副作用与配套;横切关注点用 AOP/中间件统一抽离让正确且一致成为默认。

用户只点了一次提交,因为网络超时客户端自动重试,结果同一笔订单被下了两次、款也扣了两次,我对着接口没做幂等设计这个坑排查了大半天的复盘

这是一个让我对"分布式与网络环境下的不确定性"彻底敬畏的架构坑。它最让人后怕的地方在于:出问题的不是某段写错的逻辑,而是一个我压根没考虑过的场景——"同一个请求,被发送/处理了不止一次"。而在真实的网络世界里,这恰恰是必然会发生的常态。

事情起于用户投诉:有人反映自己明明只下了一单,却被扣了两次款、收到了两份货。我查日志,发现确实有两条几乎同时的、内容完全一样的下单记录。我把下单接口的代码拎出来,逻辑朴实无华:

// 下单接口(有问题的版本)
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest req) {
    // 1. 扣减库存
    inventoryService.deduct(req.getProductId(), req.getQuantity());
    // 2. 扣款
    paymentService.charge(req.getUserId(), req.getAmount());
    // 3. 创建订单
    Order order = orderService.create(req);
    // 4. 发起发货
    shipmentService.ship(order.getId());
    return Result.ok(order);
}

这段代码,在"每个请求只来一次"的理想世界里,完全正确。可现实是:用户点了提交,请求发到服务器,服务器其实已经成功处理了,但返回响应的网络包在回来的路上超时丢失了;客户端没收到响应,以为失败,于是自动重试,又发了一次一模一样的请求——服务器收到后,又老老实实地从头执行了一遍:再次扣库存、再次扣款、再次创建订单、再次发货。于是,一次用户操作,变成了两次实际执行。我盯着那两条重复记录,意识到问题的本质:我的接口,默认了"每个请求只会来一次"这个根本不成立的假设。

第一件事:看清真相——网络环境下"重复请求"是必然,接口必须幂等

我去深入理解了"幂等性(Idempotency)"这个概念,以及它在分布式系统里为何如此重要,才彻底明白这个坑的根源——在网络和分布式环境里,"一个请求被重复发送/处理"不是偶然的意外,而是必然会发生的常态;任何"会产生副作用(扣款、下单)"的接口,都必须被设计成"幂等"的

幂等性与重复请求的真相

# 1. 什么是"幂等(Idempotent)":
#    一个操作, 不管执行【一次】还是【多次】, 产生的【效果(对系统状态的影响)都相同】。
#    - 幂等的例子: "把订单状态设为已支付"(设几次都是已支付)、"删除id=5的记录"
#    - 不幂等的例子: "扣款100元"(扣一次少100, 扣两次少200!)、"库存-1"、"插入一条订单"

# 2. 为什么网络环境下"重复请求"是【必然】:
#    a) 客户端重试: 请求超时/没收到响应, 客户端不知道"到底成没成", 通常会重试
#       —— 而"超时"不等于"失败"! 可能服务端已成功, 只是响应在回来路上丢了。
#    b) 用户重复操作: 用户点了没反应, 手一抖又点一次; 刷新页面重复提交。
#    c) 消息队列: MQ 为保证"不丢消息", 通常是"at-least-once(至少投递一次)",
#       即【宁可重复投递, 也不丢】—— 所以消费者必然会收到重复消息。
#    d) 网关/代理重试、负载均衡重试等。

# 3. 我的接口的问题:
#    它做的是"扣款、库存-1、插入订单"这些【不幂等】的操作;
#    而它又默认"每个请求只来一次"——这个假设在网络环境下【根本不成立】;
#    一旦重复请求到来, 就重复扣款、重复下单。

# 4. 核心结论:
#    既然"重复请求"在分布式/网络环境下【无法避免】,
#    那么解决之道就不是"祈祷它不发生", 而是【让接口能正确处理重复请求】——
#    即: 把接口设计成【幂等】的: 同一个请求来多少次, 效果都和来一次一样。

# 核心: 网络/分布式环境下重复请求(客户端重试/用户重复点/MQ至少投递一次)是必然;
#   有副作用的接口必须设计成幂等——同一请求执行多次, 对系统的最终效果和执行一次相同。

真相大白,我醍醐灌顶。原来"幂等"的意思是:一个操作,不管执行一次还是多次,对系统状态产生的效果都相同("把订单设为已支付"设几次都一样,是幂等的;"扣款 100""库存-1""插入一条订单"执行几次效果就翻几倍,是不幂等的)。而在网络环境下,"重复请求"是必然:客户端超时会重试(而超时不等于失败,可能服务端已成功、只是响应丢了)、用户会手抖重复点、消息队列为了不丢消息普遍是"at-least-once(至少投递一次)"即宁可重复也不丢、网关也会重试。而我的接口做的恰恰是"扣款、库存-1、插入订单"这些不幂等的操作,却默认了"每个请求只来一次"这个在网络环境下根本不成立的假设——重复请求一来,就重复扣款、重复下单。核心结论是:既然重复请求无法避免,解决之道就不是"祈祷它不发生",而是让接口能正确处理重复请求——把接口设计成幂等的:同一个请求来多少次,效果都和来一次一样。

第二件事:正解——用幂等键 + 唯一约束/状态机,让重复请求只生效一次

搞懂了原理,正解就清晰了:给每个请求一个唯一的"幂等键",服务端用它去重(已处理过就直接返回上次结果),并辅以数据库唯一约束、状态机等多道防线

// ====== 正解一(核心): 幂等键 + 去重(Redis SETNX 或 唯一索引) ======
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest req,
                          @RequestHeader("Idempotency-Key") String idemKey) {
    // ★ 客户端为每次"业务操作"生成一个唯一的幂等键(如UUID), 重试时用【同一个】键
    // 服务端用这个键去重:
    Boolean isFirst = redis.setIfAbsent("idem:" + idemKey, "1", Duration.ofHours(24));
    if (!Boolean.TRUE.equals(isFirst)) {
        // 这个键已存在 → 是重复请求 → 直接返回(或返回上次的结果), 不再执行业务!
        return Result.ok(getCachedResult(idemKey));
    }
    // 第一次, 正常执行业务...
    Order order = doCreateOrder(req);
    cacheResult(idemKey, order);   // 缓存结果供重复请求返回
    return Result.ok(order);
}

// ====== 正解二: 数据库唯一约束(最可靠的兜底) ======
// 给订单表的"业务唯一标识"(如 幂等键/业务订单号)加【唯一索引】:
//   ALTER TABLE orders ADD UNIQUE KEY uk_idem (idempotency_key);
// 重复请求插入时, 数据库会因唯一约束冲突而失败 → 捕获冲突, 当作"已处理"返回。
// → 这是最后一道防线: 即使前面的去重失效, 数据库也不会出现两条重复订单。

// ====== 正解三: 状态机流转(用状态变化保证幂等) ======
// 把操作变成"状态流转", 并用条件更新保证只流转一次:
//   UPDATE orders SET status='PAID' WHERE id=? AND status='UNPAID';
//   ↑ 只有当前是UNPAID才会更新成功(影响行数=1); 重复执行时status已是PAID,
//     影响行数=0, 自然不会重复扣款。检查影响行数即可判断"是不是我这次改的"。

// ====== 正解四: 乐观锁(版本号) ======
//   UPDATE account SET balance=balance-100, version=version+1
//   WHERE id=? AND version=?   // version不匹配则更新失败, 防止基于旧状态的重复操作

// ====== 把"不幂等操作"转成"幂等操作"的思路 ======
// - 不幂等: balance = balance - 100  (相对操作, 执行几次减几次)
// - 幂等:   balance = 900            (绝对赋值, 设几次都是900) —— 若业务允许
// - 或: 用唯一的"流水记录"+"一笔流水只记一次"来保证扣款只发生一次

// 核心: 客户端带幂等键、服务端据此去重(已处理直接返回); 配合数据库唯一约束兜底、
//   状态机条件更新、乐观锁; 多道防线让"同一业务操作"无论请求几次, 都只真正生效一次。

修复的核心,是"用幂等键去重 + 唯一约束兜底 + 状态机,让重复请求只生效一次"正解一(核心):幂等键 + 去重——客户端为每次业务操作生成一个唯一幂等键(重试时用同一个),服务端用 setIfAbsent(Redis SETNX)去重:键已存在就是重复请求、直接返回上次结果不再执行业务正解二:数据库唯一约束——给业务唯一标识加唯一索引,重复插入会因冲突失败,这是最可靠的最后一道防线,即使前面去重失效也不会出现两条重复订单正解三:状态机流转——UPDATE ... SET status='PAID' WHERE id=? AND status='UNPAID',只有当前是 UNPAID 才更新成功,重复执行影响行数为 0、不会重复扣款正解四:乐观锁(版本号)还有一个思路是把不幂等操作转成幂等操作(balance=balance-100 相对操作改成绝对赋值或唯一流水记录)。归根结底:客户端带幂等键、服务端据此去重,配合数据库唯一约束兜底、状态机条件更新、乐观锁;多道防线让同一业务操作无论请求几次都只真正生效一次。

第三件事:分布式系统里其他必须考虑的"不确定性"

排查后我把分布式系统里其他必须考虑的"不确定性"也系统梳理了一遍,它们和"重复请求"一样,都是网络环境的常态。

分布式系统必须考虑的不确定性

# 1. 重复(本文): 请求/消息会重复。→ 幂等设计。

# 2. 丢失: 请求/响应/消息可能丢。→ 重试 + 幂等(重试又带来重复, 所以二者要配套)。

# 3. 乱序: 消息到达顺序可能和发送顺序不同。→ 业务上不依赖顺序, 或用序号/版本排序。

# 4. 延迟: 网络有不可预测的延迟。→ 设超时, 别假设"立即完成"。

# 5. 超时≠失败: 这是最坑的! 超时只代表"我没收到响应", 不代表"操作没成功"。
#    → 所以不能简单"超时就重试一个不幂等操作"; 必须配幂等。

# 6. 部分失败: 多步操作中, 前几步成功、后面失败(扣了款没发货)。
#    → 用事务/Saga/最终一致性/对账补偿来处理。

# 7. 并发: 多个请求同时操作同一资源(超卖)。→ 锁/乐观锁/原子操作。

# 8. 时钟不同步: 各机器时间有偏差。→ 别强依赖跨机器的绝对时间顺序。

# 共同根源(分布式的本质难点): 网络是【不可靠】的(会延迟、丢包、乱序、重复),
#   各节点是【独立且可能部分失败】的; 单机时代"调用必成功、顺序确定"的直觉, 在这里全部失效。

# 核心: 分布式/网络环境充满不确定性(重复/丢失/乱序/延迟/超时≠失败/部分失败/并发);
#   设计时必须假设"任何环节都可能出问题", 用幂等/重试/超时/补偿/锁等手段主动应对, 而非假设理想。

排查让我把分布式的其他不确定性也梳理清了。一、重复(本文)。二、丢失(请求/消息可能丢,要重试+幂等配套)。三、乱序(消息到达顺序可能变)。四、延迟(要设超时)。五、超时≠失败(最坑!超时只代表没收到响应、不代表没成功,所以不能简单重试不幂等操作)。六、部分失败(扣了款没发货,要用事务/Saga/对账补偿)。七、并发(超卖,要锁/乐观锁)。八、时钟不同步它们的共同根源是:网络是不可靠的(会延迟、丢包、乱序、重复),各节点独立且可能部分失败;单机时代"调用必成功、顺序确定"的直觉在这里全部失效核心是:设计时必须假设"任何环节都可能出问题",用幂等/重试/超时/补偿/锁主动应对,而非假设理想下面这张图,是这次重复下单的成因与解法:

第四件事:常见操作的幂等性与实现方式速查表

这次踩坑后,我把常见操作的幂等性、以及怎么让它幂等整理成一张表,设计接口时对照。

操作 天然幂等? 让它幂等的方式
查询 GET ✓ 天然幂等 无副作用, 不用处理
删除 DELETE id=5 ✓ 天然幂等 删几次结果都是"没了"
全量更新 PUT(绝对赋值) ✓ 天然幂等 设几次都是同一个值
新增 POST(插入订单) ✗ 不幂等 幂等键+唯一索引去重
扣款/加减(相对操作) ✗ 不幂等 幂等键+流水表/状态机
库存-1 ✗ 不幂等 状态机/乐观锁/唯一约束
发MQ消息触发动作 ✗ 不幂等 消费端用消息ID去重

这张表把"什么操作要特别处理幂等"钉死了。核心规律是:"查询、删除、全量赋值"这类操作天然幂等(做几次结果一样);而"新增、相对的加减(扣款/库存-1)"这类操作天生不幂等(做几次累加几次),必须用幂等键、唯一约束、状态机等手段额外赋予它幂等性它给我的最大启发是:设计接口时,要先有意识地判断"这个操作天然幂等吗?"——对那些天生不幂等、又会产生重要副作用(尤其涉及钱、库存、订单)的操作,幂等设计不是"锦上添花的优化",而是"必须做的、关乎数据正确性的基本保障"这其实呼应了 RESTful 设计里对 HTTP 方法的语义约定:GET、PUT、DELETE 被设计为幂等的,而 POST 不是——这不是随意规定,而是反映了这些操作"本质上幂不幂等"的语义;理解这层语义,能帮你在设计 API 时,就自觉地为不幂等的操作(POST 类)考虑幂等保障养成"设计每个接口时都先问一句它幂不幂等、会不会因重复调用而出错"的习惯——是写出健壮的、经得起网络环境考验的接口的前提。

第五件事:重试与幂等,是一对必须配套的"孪生设计"

这次也让我深刻理解了"重试"和"幂等"之间密不可分的关系。

组合 结果 评价
不重试 + 不幂等 请求丢了就真丢了, 但不会重复 可靠性差
重试 + 不幂等(本文) 重试导致重复执行, 重复扣款 ✗ 灾难! 最危险的组合
不重试 + 幂等 没充分利用幂等带来的安全重试能力 浪费
重试 + 幂等 丢了能重试补救, 重复也不会出错 ✓ 既可靠又安全

这张表道出了"重试"与"幂等"的辩证关系。核心是:"重试"是为了对抗"请求可能丢失"(提高可靠性),但重试必然带来"请求可能重复";而"幂等"正是为了让"重复"变得安全无害——所以,重试和幂等,是一对必须成对出现的孪生设计:有重试就必须有幂等(否则重试会导致重复执行的灾难),有幂等才敢放心地重试(否则不敢重试、可靠性上不去)我犯的最危险的错,正是落在了"重试 + 不幂等"这个最糟糕的组合里。它给我的深刻启发是:在系统设计中,很多机制不是孤立存在的,而是互相依赖、必须配套的;一个机制(重试)在解决一个问题(丢失)的同时,往往会引入一个新问题(重复),而这个新问题需要另一个配套机制(幂等)来解决;只引入前者而忘了后者,常常会制造出比原问题更严重的新麻烦这让我形成了一个重要的设计意识:当我引入一个机制来解决某个问题时,要习惯性地追问一句"这个机制本身,会引入什么新的问题?它需要什么配套设计来兜住?";系统设计的成熟,很大程度上体现在能否看到这些"机制之间的连锁反应",并成体系地、配套地去设计,而非头痛医头、引入一个补丁又埋下一个新坑把"重试与幂等"当成不可分割的一对来设计、并培养"追问每个机制的副作用与配套"的意识——是这个重复下单的坑,教给我的关于"系统化设计"的宝贵一课。

第六件事:设计有副作用的接口时,我现在的判断习惯

现在每当我设计一个会产生副作用的接口,我都会按这张图先想清楚:

这张图的精髓,是"有副作用且天生不幂等的接口,必须做幂等设计"纯查询天然幂等不用管;有副作用就先判断它天然幂等吗,删除/全量赋值天然幂等,新增/加减不幂等就必须做幂等:客户端带唯一幂等键、服务端去重、数据库唯一约束兜底涉及状态流转/并发再加状态机条件更新/乐观锁这套习惯,让我设计接口时,从"假设请求只来一次"变成了"假设请求会重复、主动保证幂等"——核心始终是:网络环境重复请求必然,有副作用的接口必须幂等;幂等键去重+唯一约束兜底,重复请求只生效一次。

我立下的几条规矩

这场"重复下单重复扣款"的事故,换来了我做架构设计时,刻进骨子里的几条铁律:

  1. 网络环境下重复请求是必然。客户端重试、用户重复点、MQ 至少投递一次。
  2. 超时不等于失败。可能已成功只是响应丢了,所以重试要配幂等。
  3. 有副作用的接口必须幂等。尤其涉及钱、库存、订单。
  4. 幂等键去重是核心。客户端带唯一键,服务端 SETNX/唯一索引去重。
  5. 数据库唯一约束是最后防线。即使前面去重失效,也不会出现两条。
  6. 重试与幂等必须配套。有重试就有重复,有幂等才敢重试。
  7. 引入机制先问它的副作用。每个机制都可能引入需配套解决的新问题。

附:一个可复用的幂等处理切面/装饰器

这次踩坑后,我把"幂等处理"抽象成了一个可复用的组件(注解/切面),让任何需要幂等的接口,加一个注解就能拥有幂等能力,不必每个接口都手写一遍去重逻辑:

// ====== 定义一个幂等注解 ======
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    long expireSeconds() default 86400;   // 幂等键的有效期
}

// ====== 用 AOP 切面统一处理所有 @Idempotent 标注的方法 ======
@Aspect
@Component
public class IdempotentAspect {
    @Autowired private StringRedisTemplate redis;

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint pjp, Idempotent idempotent) throws Throwable {
        // 1. 从请求头取幂等键
        HttpServletRequest req = ((ServletRequestAttributes)
            RequestContextHolder.currentRequestAttributes()).getRequest();
        String key = req.getHeader("Idempotency-Key");
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("缺少 Idempotency-Key 请求头");
        }

        String redisKey = "idem:" + key;
        // 2. SETNX 去重: 第一次能设置成功, 重复请求设置失败
        Boolean isFirst = redis.opsForValue()
            .setIfAbsent(redisKey, "PROCESSING", Duration.ofSeconds(idempotent.expireSeconds()));

        if (!Boolean.TRUE.equals(isFirst)) {
            // 重复请求: 直接拒绝或返回提示(更完善的做法是返回上次缓存的结果)
            throw new DuplicateRequestException("重复请求, 已忽略");
        }

        try {
            Object result = pjp.proceed();    // 第一次, 执行真正的业务
            redis.opsForValue().set(redisKey, "DONE", Duration.ofSeconds(idempotent.expireSeconds()));
            return result;
        } catch (Throwable e) {
            redis.delete(redisKey);           // ★ 业务失败要删除键, 允许客户端重试!
            throw e;
        }
    }
}

// ====== 用起来极简: 任何接口加个注解即可 ======
@Idempotent
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest req) {
    return Result.ok(doCreateOrder(req));   // 业务代码干干净净, 幂等由切面统一保障
}

// 核心: 把幂等逻辑(取键→SETNX去重→成功标记/失败删除)抽成注解+切面, 一次实现处处复用;
//   注意业务失败时要删除幂等键, 否则客户端永远无法重试这个失败的操作。

这个幂等切面,是我这次踩坑后最有价值的工程沉淀之一。它把"幂等处理"这件每个写接口都可能要面对的事,从"每个接口里手写一遍去重代码(容易写错、容易漏)",升级成了"加一个 @Idempotent 注解就自动拥有"的能力——业务代码因此变得干干净净,只专注于业务本身,而幂等这个"横切关注点"被统一、可靠地交给了切面。它还藏着一个我特意处理的关键细节:业务执行失败时,必须删除幂等键——否则一个失败的操作会被"幂等键"永久挡住,客户端再也无法重试它,把"防重复"变成了"防重试",反而坑了用户这正是我想用这个组件,留给自己也分享给你的核心思想:对于"幂等、限流、鉴权、日志、事务"这类横切关注点(cross-cutting concern)"——它们散布在很多接口里、和具体业务无关、却又至关重要——最好的做法,是用 AOP(切面)、中间件、装饰器等机制,把它们从分散的业务代码里抽离出来、统一实现、声明式地应用这样做的价值是双重的:一方面,业务代码得以保持纯粹、专注,不被这些通用逻辑污染;另一方面,这些通用逻辑被集中、一致、正确地实现一次,避免了"每个开发者各写一遍、各有各的 bug、有人还忘了写"的混乱(我最初的坑,正是因为幂等是"需要每个接口自觉去写"的)把横切关注点用统一的机制抽离、让"正确且一致"成为默认——这,是我用一次幂等缺失的事故,换来的、关于"如何优雅地管理系统中那些通用而关键的能力"的实用架构智慧。

写在最后

回头看,这场由"接口没做幂等"引发的、重复下单扣款的事故,真正教给我的,远不止"加个幂等键"这一个技巧。它让我完成了一次从"单机思维"到"分布式思维"的、根本性的认知升级。我栽跟头,根源是我把在"单机、本地调用"环境里养成的直觉,想当然地搬到了"网络、分布式"环境里。在单机世界里,"调用一个方法"是确定的:它要么成功、要么失败,而且只会执行一次;我从没需要担心"这个方法会不会被莫名其妙地执行两遍"。可一旦跨越了网络,这一切确定性都崩塌了:网络会延迟、会丢包、会让响应消失;一个请求,可能成功了你却不知道、可能被自动重试、可能被重复投递。"恰好执行一次(exactly-once)"在分布式世界里是极难实现的奢望,我们能依赖的,往往只是"至少一次"——而"至少一次"就意味着"可能多次"。这让我领悟到一个深刻的认知:从单机编程走向分布式系统,最大的、也最根本的思维转变,是要抛弃对"确定性"的依赖,转而拥抱并主动应对"不确定性"——你必须假设"任何远程调用都可能失败、超时、重复、乱序",并把你的系统设计成"即使在这些坏情况下,依然能保证最终正确"的这其实是分布式系统设计的核心哲学:"为失败而设计(Design for failure)"——不是去消除不确定性(那不可能),而是承认它、拥抱它,并构建出能在不确定性中依然保持正确的、有韧性的系统;幂等,正是这种哲学最基础、最典型的体现之一——它不去阻止"重复"发生(阻止不了),而是让"重复"变得无害从"假设一切如我所愿"到"假设一切都可能出错并为之设计"——这,是我用一次重复扣款的事故,换来的、关于架构、关于分布式系统、也关于工程成熟度的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次设计一个写接口时,先问自己一句"它要是被调了两次,会出事吗?",那我对着那两条重复订单复盘的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我训练的模型离线评估准确率高达 95%,信心满满地上了线,真实表现却暴跌到 70%,我对着在划分训练测试集之前就标准化整个数据集造成的数据泄漏这个坑排查大半天的复盘

2026-6-2 10:19:10

技术教程

我在一个异步方法上用 .Result 同步等了一下结果,整个请求就这么永远卡死了,没有任何报错也没有超时,我对着 async 配 .Result 造成的死锁这个坑排查大半天的复盘

2026-6-2 10:30:28

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索