API幂等性设计全攻略:幂等键、分布式去重与支付安全接口
摘要
- 幂等性是分布式系统的「安全带」:网络重试、消息重复、用户双击,都可能导致接口被多次调用
- 幂等键(Idempotency Key)是HTTP API幂等的标准做法,Stripe、支付宝、微信支付均已采用
- 实现幂等有3层防线:客户端幂等键 → 服务端去重表 → 数据库唯一约束
- 支付、下单、库存扣减是幂等性要求最高的3类接口,面试必考
- 本文提供从原理到Go实现的完整方案,含Redis + PostgreSQL双保险架构
目录
什么是幂等性,为什么必须做
一句话定义
同一个请求无论执行多少次,结果都和执行一次相同,且不会有多余的副作用。
数学表达:f(f(x)) = f(x)
为什么分布式系统一定会有重复请求
这不是「如果」的问题,而是「什么时候」的问题:
| 重复来源 | 典型场景 | 发生概率 |
|---|---|---|
| 用户重复点击 | 支付按钮、提交订单 | 每天都会遇到 |
| 网络超时重试 | 客户端自动重试、网关重试 | 生产环境常态 |
| 消息队列重复投递 | Kafka at-least-once、RabbitMQ重投 | 设计如此 |
| 负载均衡重试 | Nginx/Envoy上游超时重试 | 配置不当时频繁 |
| 微服务调用链重试 | 下游超时,上游重发 | 链路越长越常见 |
不做幂等的真实代价
2025年某二手交易平台爆出「重复扣款」事故:用户点击支付后网络抖动,客户端自动重试3次,服务端没有幂等校验,导致同一订单扣款3次。涉及金额超过200万元,监管部门介入调查。
幂等性不是优化项,是支付/交易类系统的准入门槛。
幂等性的4种等级
Level 0:天然幂等(HTTP GET/PUT/DELETE)
GET /api/users/123 → 查询,天然幂等
PUT /api/users/123 → 全量更新,天然幂等
DELETE /api/users/123 → 删除,天然幂等
POST /api/orders → 创建,NOT幂等!
POST和PATCH默认不幂等,因为每次调用可能创建新资源或产生新副作用。
Level 1:业务层幂等
通过业务逻辑判断:「这个订单已经创建过了,直接返回之前的结果」。
func CreateOrder(ctx context.Context, req CreateOrderRequest) (*Order, error) {
existing, err := db.GetOrderByClientOrderId(ctx, req.ClientOrderId)
if err == nil {
return existing, nil // 已存在,直接返回
}
return db.CreateOrder(ctx, req)
}
优点:简单直接。缺点:并发时两个请求可能同时通过检查。
Level 2:幂等键 + 去重表
客户端传唯一幂等键,服务端用去重表记录「这个键是否已处理过」。
Level 3:数据库唯一约束(最终防线)
在数据库层加唯一索引,即使应用层有漏洞,数据库也会拒绝重复插入。
CREATE UNIQUE INDEX idx_orders_idempotency_key
ON orders (idempotency_key)
WHERE idempotency_key IS NOT NULL;
生产推荐:Level 2 + Level 3 组合使用。
幂等键设计规范
幂等键的生成规则
| 规则 | 说明 | 示例 |
|---|---|---|
| 客户端生成 | 由调用方在请求前生成UUID | 550e8400-e29b-41d4-a716-446655440000 |
| 唯一性 | 同一业务操作使用同一个键 | 重试时必须复用原键 |
| 作用域 | 通常绑定 user_id + 操作类型 | 不同用户可以用相同格式的键 |
| 有效期 | 24-72小时 | 过期后可复用(极少发生) |
| 传递方式 | HTTP Header | Idempotency-Key: <uuid> |
Stripe的幂等键规范(行业标杆)
POST /v1/charges
Idempotency-Key: uuid-v4-string
Content-Type: application/json
{"amount": 2000, "currency": "usd", "source": "tok_visa"}
Stripe的处理逻辑:
- 收到请求,检查幂等键是否已存在
- 不存在 → 执行业务逻辑,存储结果,返回201
- 已存在且参数相同 → 返回缓存的结果(200)
- 已存在但参数不同 → 返回409 Conflict
幂等键 vs 请求ID vs 链路追踪ID
| 字段 | 用途 | 生命周期 | 是否用于去重 |
|---|---|---|---|
| Idempotency-Key | 业务幂等 | 24-72h | 是 |
| X-Request-ID | 请求追踪 | 单次请求 | 否 |
| Trace-ID | 分布式链路追踪 | 单次调用链 | 否 |
三者不要混用。Trace-ID每次请求都不同,不能用于幂等。
3层幂等防线架构
客户端 服务端 数据库
│ │ │
│ Idempotency-Key │ │
├────────────────────────→│ │
│ │ Layer 1: Redis 快速去重 │
│ │ SETNX idem:{key} → 成功/失败 │
│ │ │ │
│ │ ▼ │
│ │ Layer 2: 幂等记录表 │
│ │ INSERT idempotency_records │
│ │ │ │
│ │ ▼ │
│ │ Layer 3: 业务表唯一约束 │
│ │ INSERT orders (UNIQUE key) ────→│
│ │ │
│ ←── 返回结果 ──────────│ │
Layer 1:Redis SETNX 快速拦截
func (s *IdempotencyService) TryAcquire(ctx context.Context, key string, ttl time.Duration) (bool, error) {
redisKey := fmt.Sprintf("idem:%s", key)
acquired, err := s.redis.SetNX(ctx, redisKey, "processing", ttl).Result()
if err != nil {
return false, fmt.Errorf("redis setnx: %w", err)
}
return acquired, nil
}
SETNX成功 → 首次请求,继续处理SETNX失败 → 重复请求,查缓存结果返回- TTL 设为 24-72h,防止Redis内存泄漏
Layer 2:幂等记录表
CREATE TABLE idempotency_records (
id BIGSERIAL PRIMARY KEY,
idempotency_key VARCHAR(64) NOT NULL,
request_hash VARCHAR(64) NOT NULL, -- 请求体哈希,防参数篡改
response_status INT,
response_body JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'processing',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
CONSTRAINT uq_idempotency_key UNIQUE (idempotency_key)
);
CREATE INDEX idx_idem_expires ON idempotency_records (expires_at);
状态流转:processing → completed / failed
Layer 3:业务表唯一约束
ALTER TABLE orders ADD COLUMN idempotency_key VARCHAR(64);
CREATE UNIQUE INDEX idx_orders_idem ON orders (idempotency_key);
即使Layer 1和Layer 2都失效(Redis宕机、应用bug),数据库唯一约束是最后防线。
Go生产级幂等中间件实现
幂等中间件(Gin框架)
type IdempotencyMiddleware struct {
redis *redis.Client
db *sql.DB
ttl time.Duration
}
func (m *IdempotencyMiddleware) Handle() gin.HandlerFunc {
return func(c *gin.Context) {
key := c.GetHeader("Idempotency-Key")
if key == "" {
c.Next()
return
}
ctx := c.Request.Context()
cached, err := m.getCachedResponse(ctx, key)
if err == nil && cached != nil {
c.JSON(cached.Status, cached.Body)
c.Abort()
return
}
acquired, err := m.redis.SetNX(ctx, "idem:"+key, "1", m.ttl).Result()
if err != nil {
c.JSON(500, gin.H{"error": "idempotency check failed"})
c.Abort()
return
}
if !acquired {
for i := 0; i < 30; i++ {
time.Sleep(100 * time.Millisecond)
cached, err = m.getCachedResponse(ctx, key)
if cached != nil {
c.JSON(cached.Status, cached.Body)
c.Abort()
return
}
}
c.JSON(409, gin.H{"error": "duplicate request in progress"})
c.Abort()
return
}
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
c.Next()
m.saveResponse(ctx, key, c.Writer.Status(), blw.body.String())
}
}
并发安全的关键点
两个相同幂等键的请求同时到达时:
- Redis SETNX 只有一个能成功
- 失败者进入轮询等待(100ms × 30次 = 3秒)
- 成功者处理完毕后将结果写入DB
- 失败者从DB读到缓存结果返回
这个「SETNX + 轮询等待」模式是Stripe、支付宝等大厂的标准做法。
支付场景幂等实战
支付接口的完整幂等流程
用户点击支付
│
▼
客户端生成 Idempotency-Key (UUID)
│
▼
POST /api/payments {amount, order_id}
Header: Idempotency-Key: uuid
│
▼
┌─ 幂等检查 ─────────────────────────────┐
│ 1. Redis SETNX │
│ 2. 查幂等记录表 │
│ 3. 查订单状态(已支付?直接返回) │
└─────────────────────────────────────────┘
│ 首次请求
▼
┌─ 业务处理(数据库事务)─────────────────┐
│ BEGIN │
│ INSERT payment (status=pending) │
│ 调用支付渠道API │
│ UPDATE payment (status=success) │
│ UPDATE order (status=paid) │
│ COMMIT │
└─────────────────────────────────────────┘
│
▼
缓存响应到幂等记录表 + Redis
│
▼
返回支付结果给客户端
支付幂等的3个特殊考量
1. 支付渠道也要幂等
调用微信/支付宝时,必须传商户订单号(out_trade_no),同一订单号渠道侧也会去重。
2. 状态机保证
pending → processing → success
→ failed
→ timeout → (人工介入)
已 success 的订单,任何重试都直接返回成功,不会再次扣款。
3. 对账兜底
每日对账任务检查:支付渠道账单 vs 本地订单,发现不一致时告警。幂等不是100%可靠的,对账是最后的安全网。
面试高频考点与踩坑指南
面试常问的6个问题
Q1:GET是幂等的,POST为什么不是?
GET是读操作,不改变服务器状态。POST是写操作,每次调用可能创建新资源。但可以通过幂等键让POST具备幂等性。
Q2:Redis宕机了幂等还有效吗?
Layer 1(Redis)失效后,Layer 2(幂等记录表)和Layer 3(唯一约束)仍然生效。Redis是性能优化层,不是唯一保障。
Q3:幂等键过期了,用户又发了一样的请求怎么办?
过期后视为新请求。但业务层应检查订单状态:如果订单已支付,即使幂等键过期也直接返回已支付结果。幂等键是技术手段,业务状态是最终判断依据。
Q4:分布式锁能实现幂等吗?
可以,但不是最优方案。分布式锁解决的是并发互斥,幂等解决的是重复请求。锁的粒度、超时、死锁问题比幂等键复杂得多。推荐幂等键 + 去重表,而非分布式锁。
Q5:消息队列消费端怎么做幂等?
三种方式:(1) 业务唯一键去重(推荐);(2) 消费端维护已处理消息ID表;(3) 数据库唯一约束。Kafka的幂等生产者是生产者侧保障,消费端仍需自行去重。
Q6:幂等和事务什么关系?
幂等保证「多次执行 = 一次执行」,事务保证「多个操作要么全成功要么全失败」。支付场景两者都需要:事务保证扣款和更新订单状态的原子性,幂等保证重试不会重复扣款。
踩坑清单
| 坑 | 表现 | 解法 |
|---|---|---|
| 幂等键由服务端生成 | 客户端重试时键不同,去重失效 | 必须由客户端生成并复用 |
| 只存成功结果 | 失败重试时返回旧错误 | 成功和失败结果都缓存 |
| 不设过期时间 | Redis/DB数据无限增长 | TTL 24-72h + 定时清理 |
| 并发不加锁 | 两个请求同时通过检查 | SETNX + 轮询等待 |
| 忽略支付渠道幂等 | 本地幂等了但渠道扣了两次 | 商户订单号全局唯一 |
库存扣减场景的幂等设计
支付幂等之外,库存扣减是面试和实战中的第二大高频场景。它与支付的区别在于:支付是「创建新记录」,库存是「更新已有记录」,并发下的竞态更隐蔽。
典型故障:超卖
时间线:
T1: 请求A 读取库存 quantity=1
T2: 请求B 读取库存 quantity=1 (A还未提交)
T3: 请求A 扣减 quantity=0,提交成功
T4: 请求B 扣减 quantity=-1,提交成功 → 超卖!
即使加了幂等键,如果两个请求的幂等键不同(用户两次点击),仍然会超卖。幂等解决重复请求,乐观锁/条件更新解决并发扣减,两者必须组合。
推荐方案:幂等键 + 条件更新
func (s *InventoryService) DeductStock(ctx context.Context, req DeductRequest) error {
key := req.IdempotencyKey
if cached, err := s.idem.GetResult(ctx, key); err == nil && cached != nil {
return cached.Error
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
result, err := tx.ExecContext(ctx, `
UPDATE inventory
SET quantity = quantity - $1, version = version + 1
WHERE product_id = $2 AND quantity >= $1
`, req.Quantity, req.ProductID)
if err != nil {
return err
}
rows, _ := result.RowsAffected()
if rows == 0 {
return ErrInsufficientStock
}
_, err = tx.ExecContext(ctx, `
INSERT INTO idempotency_records (idempotency_key, status, response_body)
VALUES ($1, 'completed', '{"deducted": true}')
ON CONFLICT (idempotency_key) DO NOTHING
`, key)
if err != nil {
return err
}
return tx.Commit()
}
核心要点:WHERE quantity >= $1 是数据库层的原子守卫,即使应用层有漏洞也不会出现负库存。
分布式环境下的幂等挑战
多实例并发:为什么 SETNX 不够 alone
3个API实例同时收到相同幂等键请求时:
| 时序 | 实例1 | 实例2 | 实例3 |
|---|---|---|---|
| T1 | SETNX 成功 | SETNX 失败 | SETNX 失败 |
| T2 | 开始处理(耗时5秒) | 轮询等待 | 轮询等待 |
| T3 | 处理完成写DB | 读到结果返回 | 读到结果返回 |
如果实例1在写DB前崩溃,实例2和3轮询超时后应返回 409 duplicate in progress 而非重新执行——这要求幂等记录表有 processing 状态,超时后可由补偿任务接管。
Redis 集群故障切换
Redis主从切换时,SETNX记录可能丢失(取决于持久化配置)。因此Redis只做性能加速层,数据库唯一约束才是最终保障。面试时这样说能体现架构深度。
时钟偏移与TTL
幂等键TTL不要用本地时钟做复杂逻辑。统一用 expires_at = NOW() + INTERVAL '72 hours' 由数据库生成,避免多实例时钟不一致导致提前过期。
幂等性与消息队列的配合
Kafka 三层幂等体系
生产者幂等(enable.idempotence=true)
→ 保证单分区内无重复消息
Broker 去重(基于 PID + Sequence)
→ 网络重试不产生重复
消费端幂等(业务层)
→ 跨分区、跨消费者组仍需自行保障
很多候选人面试时说「Kafka保证了幂等」——这是常见误区。Kafka生产者幂等只覆盖单个生产者实例向单个分区的重试场景,消费端重复投递仍需 processed_events 表或业务唯一键。
消息ID vs 幂等键
| 字段 | 生成方 | 用途 |
|---|---|---|
| Message ID | Broker/框架 | 消息追踪 |
| Business Key | 业务(order_id) | 消费端去重 |
| Idempotency Key | 客户端 | API层去重 |
消费端推荐用 event_id(Outbox表主键)或 order_id + event_type 组合做去重,不要用 Kafka offset(重放时 offset 会变)。
性能优化:幂等检查不能成为瓶颈
高并发支付场景,每次请求都查DB会成为瓶颈。分层优化策略:
第一层:Bloom Filter 快速预判
// 启动时从DB加载近期幂等键到Bloom Filter
// 查询时:BF说不存在 → 一定不存在,直接处理
// BF说可能存在 → 查Redis/DB确认
Bloom Filter 有误判率(说存在但实际不存在),但不会漏判(说不存在则一定不存在),适合幂等预判。
第二层:异步写回
对于非关键读接口,可以先返回202 Accepted,异步完成幂等记录写入。支付场景不适用——必须同步确认。
第三层:分片幂等表
日订单千万级时,单表 idempotency_records 膨胀。按 hash(idempotency_key) % 64 分64张表,或按日期分区:
CREATE TABLE idempotency_records_202607 PARTITION OF idempotency_records
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
性能基准参考
| 方案 | P99延迟 | 适用QPS |
|---|---|---|
| 纯DB唯一约束 | 15-30ms | < 1000 |
| Redis SETNX + DB | 3-8ms | < 10000 |
| Redis + BF + DB | 1-3ms | < 50000 |
企业落地案例:某支付平台幂等改造实录
某中型支付平台(日交易200万笔)在2025年遭遇重复扣款事故后启动幂等改造:
改造前现状
- POST接口无幂等设计,客户端重试靠「业务状态判断」
- 仅订单表有
status字段,无独立幂等记录 - Redis只做缓存,未参与去重
改造时间线
| 阶段 | 周期 | 动作 | 效果 |
|---|---|---|---|
| Phase 1 | 2周 | 支付/退款接口加 Idempotency-Key Header | 重复扣款归零 |
| Phase 2 | 3周 | 上线幂等中间件,覆盖全部写接口 | 研发不再重复造轮子 |
| Phase 3 | 2周 | 幂等表分区 + 定时清理 | 存储稳定在20GB |
| Phase 4 | 1周 | 对账系统增加幂等键维度 | 可追溯每笔重复请求 |
改造后架构指标
- 重复扣款事故:0(连续运行8个月)
- 幂等检查P99延迟:4.2ms
- 幂等表日增量:约200万行,72小时TTL自动清理
- 客户端接入率:98.7%(剩余为老旧SDK,网关层兜底生成键)
面试高频考点与踩坑指南(进阶篇)
Q7:接口返回201还是200?
首次成功创建返回201,幂等重放返回200并附带原结果。Stripe明确区分:首次201,重复200。统一返回200也可接受,但要在文档中说明。
Q8:幂等键需要全局唯一还是用户维度唯一?
推荐用户维度:
UNIQUE(user_id, idempotency_key)。不同用户可以使用相同格式的键。全局唯一也可以,但索引更大。
Q9:DELETE接口需要幂等键吗?
DELETE天然幂等(删两次结果一样),但建议对「软删除+副作用」(删用户同时清积分)仍用幂等键,因为副作用可能重复触发。
Q10:GraphQL Mutation怎么做幂等?
在HTTP层加
Idempotency-KeyHeader(与REST相同),或在Mutation参数中传idempotencyKey字段。Apollo Gateway可在网关层统一处理。
从零搭建幂等中间件:完整落地清单
如果你要在团队内推广幂等能力,按以下清单逐步落地:
Week 1:规范制定
- 所有
POST/PATCH写接口强制支持Idempotency-KeyHeader - 在API文档(OpenAPI)中标注幂等性等级
- 客户端SDK内置幂等键自动生成与重试复用逻辑
Week 2-3:中间件开发
// 注册到Gin Engine
r.Use(idempotencyMiddleware.Handle())
// 配置白名单:只读接口跳过
idempotencyMiddleware.SkipPaths("/health", "/metrics")
Week 4:灰度与监控
| 监控项 | Prometheus指标名 | 说明 |
|---|---|---|
| 幂等命中率 | idempotency_cache_hit_total |
重复请求被拦截次数 |
| 检查延迟 | idempotency_check_duration_seconds |
P50/P99 |
| 进行中冲突 | idempotency_in_progress_total |
需关注的并发冲突 |
Week 5:全量上线与对账
上线后第一周,每日对账脚本检查:
SELECT DATE(created_at), COUNT(*) AS duplicate_blocked
FROM idempotency_records
WHERE status = 'completed'
AND created_at > (SELECT MIN(created_at) FROM idempotency_records ir2
WHERE ir2.idempotency_key = idempotency_records.idempotency_key
AND ir2.id < idempotency_records.id)
GROUP BY DATE(created_at);
幂等性与其他分布式模式的边界
很多候选人混淆幂等、锁、事务的概念。一张表说清楚:
| 模式 | 解决什么问题 | 粒度 | 典型实现 |
|---|---|---|---|
| 幂等 | 重复请求不产生副作用 | 请求级 | Idempotency Key |
| 分布式锁 | 同一时刻只有一个执行者 | 资源级 | Redis Redlock |
| 事务 | 多个操作原子性 | 操作组级 | DB Transaction |
| 乐观锁 | 并发更新冲突检测 | 行级 | version字段 |
| 去重 | 消息不重复消费 | 消息级 | processed_events表 |
支付场景的标准组合:幂等键(防重复请求)+ 数据库事务(扣款和更新订单原子性)+ 乐观锁(库存并发扣减)。
动手实战:30分钟搭建幂等支付接口
环境准备
docker compose up -d postgres redis
go mod init idempotency-demo
go get github.com/gin-gonic/gin github.com/redis/go-redis/v9 github.com/lib/pq
Step 1:建表(5分钟)
执行前文中的 idempotency_records 和 orders 建表SQL。注意先建幂等表,再建业务表,外键依赖顺序不要错。
Step 2:实现幂等中间件(10分钟)
将前文的 IdempotencyMiddleware 代码放入 middleware/idempotency.go。核心逻辑三步:查缓存 → SETNX → 执行业务 → 存结果。
Step 3:接入支付Handler(5分钟)
r.POST("/api/payments", idemMiddleware.Handle(), paymentHandler.Create)
Handler内不需要写幂等逻辑,中间件已保障。
Step 4:验证(10分钟)
# 首次请求
curl -X POST http://localhost:8080/api/payments \
-H "Idempotency-Key: test-key-001" \
-H "Content-Type: application/json" \
-d '{"order_id":"ORD-001","amount":9900}'
# 重复请求(应返回相同结果,不重复扣款)
curl -X POST http://localhost:8080/api/payments \
-H "Idempotency-Key: test-key-001" \
-H "Content-Type: application/json" \
-d '{"order_id":"ORD-001","amount":9900}'
# 并发测试(应用 autocannon 或 wrk)
autocannon -c 10 -d 5 -H "Idempotency-Key: test-key-002" \
-m POST -b '{"order_id":"ORD-002","amount":100}' \
http://localhost:8080/api/payments
预期结果:10个并发请求只创建1笔支付。如果创建了多笔,检查SETNX和轮询等待逻辑。
常见调试清单
| 现象 | 可能原因 | 检查方法 |
|---|---|---|
| 重复请求仍创建多笔 | SETNX未生效 | Redis连接是否正常 |
| 第二次请求返回500 | 未缓存失败结果 | 检查saveResponse逻辑 |
| 并发仍重复 | 轮询次数不够 | 增加轮询次数或加DB唯一约束 |
深度辨析:5个最容易答错的面试题
辨析1:「所有接口都要做幂等」对吗?
不对。幂等有成本(Redis查询、DB写入、存储增长)。正确做法是按接口风险分级:支付/下单/库存扣减必须做;查询接口天然幂等不需要;低频管理后台接口可降级为仅DB唯一约束。
辨析2:「幂等键用UUID够吗?」
UUID v4够唯一,但要注意:客户端必须在重试时复用同一个UUID,而不是每次重试生成新的。建议在SDK层封装——首次调用生成UUID并缓存到内存,重试时自动复用。
辨析3:「返回相同响应体就是幂等」对吗?
不完全对。幂等的核心是「无副作用」,不仅仅是「返回相同」。如果第一次请求还在处理中(processing),第二次应等待或返回409,而不是返回空响应或错误响应。
辨析4:「微服务网关层做幂等 vs 服务层做幂等」怎么选?
推荐服务层做。网关层可以做第一道拦截(性能优化),但服务层必须有最终保障(因为请求可能绕过网关,如内部服务间调用)。两层都做是最佳实践,只做网关层不够。
辨析5:「幂等和防重(防双击)是一回事吗?」
防双击是幂等的子集。防双击通常指出前端按钮置灰+后端幂等键。但幂等还覆盖网络重试、消息重复等客户端无感知的场景。面试时说「防双击」显得思考不够全面,应说「幂等性设计」。
2026年幂等性技术趋势
趋势1:网关层标准化。 Envoy、Kong、APISIX均已支持 Idempotency-Key 插件,可在网关层做第一道去重,减轻后端压力。
趋势2:数据库原生支持。 PostgreSQL社区在讨论内置幂等性扩展。目前仍推荐应用层实现,但关注PG新版本的进展。
趋势3:与OpenTelemetry集成。 幂等检查链路纳入分布式追踪,可观测每次去重的命中率和延迟,纳入SRE Dashboard。
趋势4:AI Agent工具调用的幂等。 LLM Agent通过Function Calling调用支付API时,模型可能因推理重试而多次调用同一工具。API层幂等是最后一道防线——Agent层无法控制,API层必须保障。
给架构师的3句话总结
- 幂等性是分布式写操作的准入门槛,不是优化项
- 三层防御(Redis + 记录表 + 唯一约束)缺一不可
- 面试时从「为什么需要」讲到「怎么实现」再讲到「踩坑和边界」,层次清晰才能拿高分
推荐学习路线(2周)
第1周:理论+基础实现 — 阅读Stripe文档理解幂等键规范 → 实现Redis SETNX去重 → 实现幂等记录表 → 写单元测试验证重复请求。
第2周:生产化+面试准备 — 接入Gin中间件 → 添加Prometheus监控 → 做并发压测 → 整理6个面试题的标准答案 → 画三层防御架构图(面试白板题)。
最后送一句话:在分布式系统中,重复不是意外,是常态。幂等性是你对常态的正确回应。
本文覆盖了从原理、架构、代码实现、性能优化、企业案例到面试考点的完整知识链。建议收藏后按「学习路线」两周实践一遍,面试前重点复习「深度辨析」和「踩坑清单」两个章节。
总结与延伸阅读
API幂等性是后端开发的基本功,也是面试高频考点。记住核心公式:客户端幂等键 + Redis快速去重 + 数据库唯一约束 = 生产级幂等。
设计要点回顾:
- POST接口默认不幂等,必须显式设计
- 幂等键由客户端生成,重试时复用同一个键
- 3层防线:Redis SETNX → 幂等记录表 → 唯一约束
- 支付场景必须结合状态机 + 对账兜底
- 并发场景用 SETNX + 轮询等待,不要用分布式锁
相关阅读:
- Redis分布式锁实战模式 — 并发控制与幂等的区别
- Go微服务韧性设计 — 重试、熔断与幂等的配合
- Go微服务限流器实现 — 限流与幂等的组合使用
权威参考:
本站提供浏览器本地工具,免注册即可试用 →