超时之后再重试,会发生什么
一句话总结
超时不是请求失败的意思,而是不知道结果的意思。 如果错过了这个差异,重新尝试会造成重复结算和重复发送。
为什么需要这个?
结算关联中最常见的事故是这样的。
- 我们发送了结算请求
- 3秒超时——没有回复
- 看来失败了,再试一次
- 在结算公司两次批准了
3次的判断是错误的。没有收到回复和没有处理是不同的。 请求已经到达,处理也完成了,可能只有回复丢失了。
在分布系统中,这个被称为不确定的结果(indeterminate)。 不是成功也不是失败,而是第三种状态,处理这种状态的方法是不同的。
可以重试和不能重试的东西
核心问题是**“这个运算是等价的吗”**。即使两次发送相同的请求 如果结果和第一次发送的一样,那就毫无意义了。
| 计算 | 错误? | 重新尝试 |
|---|---|---|
GET /orders/123 |
是的 | 自由地 |
PUT /orders/123 {status:"PAID"} |
是的(用相同的值) | 安全 |
DELETE /orders/123 |
是的(没有第二个) | 安全 |
POST /orders {…} |
不 | 危险 — 需要安全带 |
POST /payments {amount:1000} |
不 | 危险 |
扣除余额balance -= 1000 |
不 | 危险 |
HTTP方法的兼容性是规范,而不是保证。服务器PUT从
增加计数器的话,那个PUT不是很重要。不是文件
需要确认动作。
橙色钥匙
为了安全地重新尝试不正确计算的运算,客户端每次请求时 制作一个唯一的密钥发送给你。
POST /payments
Idempotency-Key: 3f9a1c7e-2b44-4c9d-a1f8-0d6e2b7c5a11
{"order_id": 123, "amount": 1000}
服务器会保存这个密钥,如果用同样的密钥再次访问,**不会重新处理。 将保存的结果原封不动地退还给您。**无论尝试几次,结算一次。
制作密钥时有需要注意的地方。重新尝试时必须使用相同的密钥。 每次尝试都创建新的UUID的话毫无意义。键是“这个业务 关于“行为”是一个,关于“这个HTTP请求”不是一个。
重新尝试政策
随便重新尝试的话会增加障碍。下游很慢,所以超时了。 如果全部立即重新尝试,负载就会增加一倍,完全崩溃。
- 指数倒计时——1秒、2秒、4秒、8秒。增加间隔,让有时间呼吸。
- 震动 — 这里随机混合。否则所有客户端都会 在同一时间同时重新尝试(thundering herd)。
- 上限 — 设定最大次数和最大总时间。禁止无限重试。
- 电路断路器—连续失败超过临界值后,一段时间内完全不尝试。 这是给下游恢复时间的装置。
然后区分要重试哪些错误。
| 回复 | 重新尝试 |
|---|---|
| 拒绝连接,超时 | 否(当有闪烁或闪烁键时) |
| 429 Too Many Requests | 是 —Retry-After遵守 |
| 500、502、503、504 | 一般是 |
| 400, 422(请求错误) | 不 — 无论发送多少次都一样 |
| 401, 403 | 否 — 需要修改资格证明 |
调查时的信号
如果怀疑有重复,请这样查找。
SELECT order_id, count(*) FROM payments
GROUP BY 1 HAVING count(*) > 1;
还有那些重复的件子的created_at看看间隔。间隔和重新尝试后退
如果相似的话(1秒、2秒、4秒),原因几乎确定了。
重新尝试增加障碍的方式
如果再试用得当,可以掩盖暂时性的失败;如果使用不当,**小障碍会变成大障碍。 培养。**培养的路线是确定的。
每层重新尝试的话会乘以。客户3次,网关3次,服务 如果尝试3次,一次用户请求就会以27次的负载到达上游。上游 开始缓慢的事情会让上层更慢。原则是只在第一层重新尝试 是,通常与用户最近的楼层是那个位置。
如果以相同的间隔重新尝试的话,就会变成波浪。障碍中失败的请求正好1秒 后面一次性回来。本想恢复的服务再次淹没在波浪中。智秀 在白化上混合随机震动的理由就是这个。
delay = min(cap, base * 2 ** attempt) * (0.5 + random.random() * 0.5)
如果没有断路器,重新尝试不会停止。 上游完全死掉的时候 再试无意义,只会消耗资源。失败率超过临界值时,暂时试行本身 停止,偶尔只发送一个,确认恢复。快速失败对用户来说也是 好多了 — 比起等了30秒后的错误,立即出现的错误更容易重试。
**传播期限(deadline)。**如果用户等待5秒,该请求就会过期。 所有区间都需要知道剩余的时间。剩余时间是200ms,但3秒的呼叫 重新开始是浪费。gRPC的deadline或通过头信息传递的到期时间各 确认楼层。
不会再次尝试无法重试的错误。 400·401·404·409 会重试几次
也可以发送。值得再试的只有429·502·503·504和连接错误,
在429中Retry-After因为附着着,所以保持那个价值。
**对于重新尝试的请求,会留下标记。**在日志和头中放入尝试编号的话,障碍 在调查中,将“请求增加了3倍”分为用户增加了还是重新尝试。
在现场相遇的样子
- 月末结算金额不符→因超时重新尝试而产生的重复。
- 通知发送了两三次→发送API上没有备用键。
- 下游变慢后整个系统崩溃了→没有断电和回路断路器。
在接下来的实习中要做的事情
针对处理完毕但只丢失响应的结算API进行三次结算。
멱등키 없이 주문 4건 → 기록 7건
비즈니스 행위당 키 하나 주문 4건 → 기록 4건
시도마다 새 키 주문 4건 → 기록 7건 ← 키를 붙였는데도 소용없다
第三个是这个实训的核心。光是贴上钥匙的事实就什么都不能 不保证。