幂等性问题小结

202 阅读2分钟

幂等性 是系统服务对外一种承诺,承诺只要调用接口成功,外部多次调用对系统的影响是一致的。

分布式服务接口的幂等性如何设计?

比如不能重复扣款。

这个不是技术问题,没有通用的方法,结合业务来保证幂等性。

考的是你的经验。

解决方案

常见方案有:

  1. 业务表内唯一索引
  2. 业务表内状态机
  3. 基于版本号的更新
  4. 去重表:基于 mysqlredis/zk 的去重

1. 业务表唯一索引(唯一主键)

此机制利用数据库的唯一约束。

业务场景:电商系统创建销售出库单的接口。

  • 此接口需要保证幂等性。
  • 网络超时、重复调用时候,需要保证一个订单只能对应一个销售出库单。

例如:销售出库表中针对 order_id 创建一个唯一索引。

CREATE TABLE IF NOT EXISTS `sale` (
  `id` VARCHAR(64) NOT NULL COMMENT '主键 id',
  `order_id` VARCHAR(64) NOT NULL COMMENT '订单 id'
  `create_time` DATETIME NOT NULL COMMENT '创建时间',
  `modify_time` DATETIME NOT NULL COMMENT '更新时间',
  PRIMARY KEY (id),
  UNIQUE uni_order_id order_id
  ) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT '销售出库表表';

Tips:在分表分库场景下,路由规则需保证在相同 key(唯一约束),落在同一个数据库和同一表中。


2. 业务表内状态机

举个栗子:修改订单状态,将订单状态修改为待发货。

例如:将订单待付款(status=5) 改为 待发货(status=10)

UPDATE order
SET status = 10
WHERE order_id = 1 AND status = 5;

sql 修改成功会返回行数变化; 若行数变化为 0,则表示修改不成功。


3. 乐观锁机制(版本号更新)

直接来看 sql

  • 当版本号为 1时,才允许更新。
UPDATE goods
SET count = count - 1, version = version + 1 
WHERE id = 1 AND version = 1;

4. 去重表:基于 mysqlredis/zk 的去重

此方案最为常见。

当业务程序要处理时,先插入表中判断是否处理过:

  • 以接口入参作为唯一标识(或者生成唯一 id),来标识这次请求
  • 相同 id 插入表中,数据库会报错

此方案还行:

  • 并发不是特别高,并发请求 1000 以内:用 MySQL 去重表没有问题。
  • 接口调用很大,一秒请求量达到 几十万:用 Redis

问题:rediskey 设置过期如何?