Skip to content

快速参考 ​

📋 常用代码片段 ​

1. ASP.NET Core 幂等性中间件注册 ​

csharp
// Program.cs
builder.Services.AddIdempotency(options =>
{
    options.HeaderName = "Idempotency-Key";
    options.DefaultExpiry = TimeSpan.FromHours(24);
});

app.UseIdempotency();

2. 生成幂等键 ​

csharp
// 基于 GUID
var idempotencyKey = Guid.NewGuid().ToString();

// 基于业务数据
var idempotencyKey = $"{userId}_{orderId}_{DateTime.UtcNow:yyyyMMdd}";

// 基于哈希
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
var idempotencyKey = Convert.ToHexString(hash)[..32];

3. PostgreSQL 唯一索引 ​

sql
-- 单字段唯一索引
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- 复合唯一索引
CREATE UNIQUE INDEX idx_orders_user_number 
ON orders(user_id, order_number);

-- 部分唯一索引
CREATE UNIQUE INDEX idx_subscriptions_active 
ON subscriptions(user_id, service_id)
WHERE status = 'active';

4. Redis 原子操作 ​

csharp
// SET NX EX - 原子设置
var acquired = await _redis.StringSetAsync(
    key, 
    value, 
    TimeSpan.FromSeconds(30),
    When.NotExists);

// Lua 脚本 - 原子删除
var luaScript = @"
    if redis.call('get', KEYS[1]) == ARGV[1] then
        return redis.call('del', KEYS[1])
    else
        return 0
    end";

5. 处理唯一约束冲突 ​

csharp
try
{
    await _dbContext.SaveChangesAsync();
}
catch (DbUpdateException ex) when (IsUniqueViolation(ex))
{
    // 处理重复
    return Result.Failure("Record already exists");
}

private bool IsUniqueViolation(DbUpdateException ex)
{
    return ex.InnerException is PostgresException pgEx 
        && pgEx.SqlState == "23505";
}

🔧 常用命令 ​

PostgreSQL ​

sql
-- 查看当前锁等待
SELECT * FROM pg_locks WHERE NOT granted;

-- 查看索引使用情况
SELECT * FROM pg_stat_user_indexes;

-- 清理过期数据
DELETE FROM idempotency_tokens 
WHERE expires_at < NOW() - INTERVAL '7 days';

-- 查看表大小
SELECT pg_size_pretty(pg_total_relation_size('payments'));

Redis ​

bash
# 查看所有键
KEYS idempotency:*

# 查看键的 TTL
TTL idempotency:token:abc123

# 删除键
DEL idempotency:token:abc123

# 查看内存使用
INFO memory

.NET CLI ​

bash
# 添加 NuGet 包
dotnet add package StackExchange.Redis
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

# 运行项目
dotnet run

# 运行测试
dotnet test

📊 HTTP 方法速查 ​

方法安全幂等需要额外设计
GET✅✅❌
HEAD✅✅❌
OPTIONS✅✅❌
POST❌❌✅ 必须
PUT❌✅❌
DELETE❌✅❌
PATCH❌⚠️⚠️ 可能需要

🎯 实现方案选择指南 ​

场景 → 推荐方案 ​

场景推荐方案理由
表单提交防重Token 机制用户体验好
订单创建唯一索引 + Token双重保障
支付扣款唯一索引 + 乐观锁强一致性
消息队列去重全局唯一 ID简单高效
库存扣减乐观锁高性能
分布式系统Redis 分布式锁跨节点同步
高并发场景Redis + 队列削峰填谷

⚡ 性能优化技巧 ​

1. 减少锁粒度 ​

csharp
// ❌ 粗粒度:锁住整个方法
public async Task ProcessOrder(Guid userId)
{
    lock (_lock)
    {
        // 所有用户的请求都串行
    }
}

// ✅ 细粒度:按用户分区
public async Task ProcessOrder(Guid userId)
{
    var userLock = _locks[userId.GetHashCode() % _lockCount];
    await userLock.WaitAsync();
    try
    {
        // 只有同一用户的请求串行
    }
    finally
    {
        userLock.Release();
    }
}

2. 批量操作 ​

csharp
// ❌ 逐条插入
foreach (var item in items)
{
    _dbContext.Items.Add(item);
    await _dbContext.SaveChangesAsync();
}

// ✅ 批量插入
_dbContext.Items.AddRange(items);
await _dbContext.SaveChangesAsync();

3. 异步处理 ​

csharp
// ❌ 同步处理,阻塞响应
public IActionResult CreateOrder(OrderRequest request)
{
    _orderService.Process(request); // 耗时 2s
    return Ok();
}

// ✅ 异步队列,快速响应
public async Task<IActionResult> CreateOrder(OrderRequest request)
{
    await _channel.Writer.WriteAsync(request);
    return Accepted(new { requestId = Guid.NewGuid() });
}

🐛 常见问题排查 ​

问题 1:幂等性不生效 ​

检查清单:

  • [ ] 客户端是否正确传递了 Idempotency-Key
  • [ ] 服务端是否正确读取了请求头
  • [ ] Redis/数据库连接是否正常
  • [ ] 键的格式是否正确

调试代码:

csharp
_logger.LogInformation("Received Idempotency-Key: {Key}", 
    context.Request.Headers["Idempotency-Key"]);

问题 2:性能下降 ​

检查清单:

  • [ ] 是否有数据库锁竞争
  • [ ] Redis 响应时间是否正常
  • [ ] 是否有大量超时请求
  • [ ] 索引是否合理

诊断查询:

sql
-- 查看慢查询
SELECT query, mean_time, calls 
FROM pg_stat_statements 
ORDER BY mean_time DESC 
LIMIT 10;

问题 3:死锁 ​

症状:

  • 请求长时间无响应
  • 数据库日志中出现 deadlock detected

解决方案:

  1. 确保事务尽可能短
  2. 避免嵌套锁
  3. 设置合理的超时时间
  4. 使用相同的顺序获取锁

📝 最佳实践速查 ​

设计原则 ​

✅ DO:

  • 为所有 POST 请求设计幂等性
  • 使用 UUID 作为幂等键
  • 设置合理的过期时间
  • 做好监控和告警
  • 记录详细的日志

❌ DON'T:

  • 不要忽略幂等性
  • 不要使用可预测的幂等键
  • 不要忘记清理过期数据
  • 不要在锁内执行耗时操作
  • 不要假设客户端一定会重试

安全建议 ​

  1. HTTPS 传输:防止幂等键被窃听
  2. 一次性使用:Token 使用后立即失效
  3. 绑定用户:幂等键与用户会话关联
  4. 限流保护:防止恶意请求
  5. 审计日志:记录所有关键操作

🔍 监控指标 ​

关键指标 ​

csharp
// 1. 请求总量
metrics.Counter("idempotency.requests.total");

// 2. 缓存命中率
var hitRate = cacheHits / (cacheHits + cacheMisses);

// 3. 平均响应时间
metrics.Histogram("idempotency.response.time");

// 4. 错误率
var errorRate = errors / totalRequests;

// 5. 锁等待时间
metrics.Histogram("idempotency.lock.wait.time");

告警规则 ​

yaml
# Prometheus 告警规则示例
groups:
  - name: idempotency
    rules:
      - alert: HighErrorRate
        expr: rate(idempotency_errors_total[5m]) > 0.05
        for: 5m
        annotations:
          summary: "High error rate on idempotency"
          
      - alert: LowCacheHitRate
        expr: idempotency_cache_hit_rate < 0.5
        for: 10m
        annotations:
          summary: "Low cache hit rate"
          
      - alert: HighLockWaitTime
        expr: histogram_quantile(0.99, idempotency_lock_wait_time) > 1000
        for: 5m
        annotations:
          summary: "High lock wait time"

📚 扩展阅读 ​

RFC 规范 ​

官方文档 ​

设计模式 ​


🎓 面试常见问题 ​

Q1: 什么是幂等性? ​

A: 幂等性是指同一个操作执行一次和执行多次产生的结果是一致的。在 API 设计中,意味着相同的请求无论发送多少次,服务器资源和返回结果都保持一致。

Q2: 如何实现 POST 请求的幂等性? ​

A:

  1. 客户端生成唯一的幂等键(Idempotency-Key)
  2. 服务端接收请求时检查该键
  3. 如果键已存在,返回缓存的结果
  4. 如果键不存在,处理请求并存储结果

Q3: Token 机制和唯一索引有什么区别? ​

A:

  • Token 机制:应用层实现,灵活性高,支持复杂场景
  • 唯一索引:数据库层实现,简单可靠,性能更好

通常两者结合使用,形成多层防护。

Q4: 如何处理高并发下的锁竞争? ​

A:

  1. 使用 Redis 代替数据库锁
  2. 实现分区策略,分散热点
  3. 使用异步队列削峰
  4. 优化索引,减少锁持有时间

💼 实战模板 ​

订单创建模板 ​

csharp
[HttpPost]
public async Task<ActionResult<Order>> CreateOrder(
    [FromBody] CreateOrderRequest request,
    [FromHeader(Name = "Idempotency-Key")] string idempotencyKey)
{
    // 1. 验证幂等键
    if (string.IsNullOrWhiteSpace(idempotencyKey))
    {
        return BadRequest("Idempotency-Key is required");
    }
    
    // 2. 检查是否已处理
    var cached = await _cache.GetAsync<Order>($"order:{idempotencyKey}");
    if (cached != null)
    {
        return Ok(cached);
    }
    
    // 3. 创建订单
    var order = await _orderService.CreateOrderAsync(request, idempotencyKey);
    
    // 4. 缓存结果
    await _cache.SetAsync($"order:{idempotencyKey}", order, TimeSpan.FromHours(24));
    
    return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}

支付处理模板 ​

csharp
[HttpPost("{id}/pay")]
public async Task<IActionResult> PayOrder(
    Guid id,
    [FromBody] PaymentRequest request)
{
    await using var transaction = await _dbContext.Database.BeginTransactionAsync();
    
    try
    {
        // 1. 获取订单(带锁)
        var order = await _dbContext.Orders
            .FirstOrDefaultAsync(o => o.Id == id && o.Status == "pending");
        
        if (order == null)
        {
            return NotFound("Order not found or already paid");
        }
        
        // 2. 调用支付网关
        var paymentResult = await _paymentGateway.ChargeAsync(new PaymentChargeRequest
        {
            Amount = order.TotalAmount,
            IdempotencyKey = $"order_{id}" // 传递幂等键
        });
        
        // 3. 更新订单状态
        if (paymentResult.Success)
        {
            order.Status = "paid";
            order.PaidAt = DateTime.UtcNow;
        }
        
        await _dbContext.SaveChangesAsync();
        await transaction.CommitAsync();
        
        return Ok(paymentResult);
    }
    catch (Exception ex)
    {
        await transaction.RollbackAsync();
        _logger.LogError(ex, "Payment failed for order: {OrderId}", id);
        return StatusCode(500, "Payment processing failed");
    }
}

祝开发顺利! 🚀

Released under the MIT License.