幂等性 是系统服务对外一种承诺,承诺只要调用接口成功,外部多次调用对系统的影响是一致的。
分布式服务接口的幂等性如何设计?
比如不能重复扣款。
这个不是技术问题,没有通用的方法,结合业务来保证幂等性。
考的是你的经验。
解决方案
常见方案有:
- 业务表内唯一索引
- 业务表内状态机
- 基于版本号的更新
- 去重表:基于
mysql、redis/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. 去重表:基于 mysql 、redis/zk 的去重
此方案最为常见。
当业务程序要处理时,先插入表中判断是否处理过:
- 以接口入参作为唯一标识(或者生成唯一
id),来标识这次请求 - 相同
id插入表中,数据库会报错
此方案还行:
- 并发不是特别高,并发请求 1000 以内:用
MySQL去重表没有问题。 - 接口调用很大,一秒请求量达到 几十万:用
Redis
问题:redis 的 key 设置过期如何?