1. 幂等性的概念
接口幂等性是指在软件工程和Web服务领域中,一个接口(通常是HTTP API)无论被调用一次还是多次,其对系统产生的副作用应该是相同的,即结果保持一致,不会因为多次请求而有所不同。换句话说,多次执行同一操作应当产生与执行一次相同的效果,不会额外改变系统的状态。
2. 幂等性的核心价值
确保数据一致性:在存在网络延迟或不稳定的情况下,请求可能被重复发送。幂等性确保即使请求多次到达服务器,也不会导致多次创建资源、多次扣除账户余额等错误,从而维护了数据的一致性和完整性。
简化错误处理:客户端可以更简单地处理请求失败的情况,只需重试即可,无需复杂的逻辑来判断是否需要重试或如何处理潜在的副作用。
提升用户体验:用户操作(如点击支付按钮)因网络问题或误操作导致的重复提交,不会引起多次扣款等不良后果,增强了用户对系统的信任感。
3. 幂等性与HTTP方法
请求类型 | 安全性与幂等性 | 描述 |
GET | 安全且幂等 | 用于获取资源,多次请求应返回相同的结果 |
PUT | 不安全但幂等 | 用于替换整个资源。无论PUT操作执行多少次,资源最终状态都是一样的,即被最后的请求体内容所替换 |
POST | 不安全且不幂等 | 用于创建新的资源,多次调用可能会创建多个资源 |
DELETE | 不安全但幂等 | 重复提交,删除一个已经不存在的资源,不会改变系统状态 |
其中POST是非幂等的,因为它用于创建新的资源,多次调用可能会创建多个不同的资源。但在某些设计中,如果POST请求设计为总是创建相同的结果(基于请求数据和当前系统状态),也可以实现幂等性。
4. 幂等性的应用场景
幂等性对于保证系统的一致性和可靠性至关重要,尤其是在分布式系统和网络不稳定环境下,它有助于处理如下几种常见场景:
- 网络重传:在网络不稳定的情况下,请求可能会被重传,幂等性确保重传不会导致多次执行操作,如多次扣款。
- 用户误操作:用户可能因页面刷新、误点击等原因重复提交请求,幂等性保护用户免受因此导致的数据不一致影响。
- 用户恶意刷单:比如在抢兑换码、投票这种互动中,可能会有用户会进行恶意的重复发送请求,恶意刷单,使结果不实。
- 异步消息重复处理:在使用消息队列或事件驱动架构时,消息可能因故障恢复机制被重新投递,幂等性处理可以避免业务逻辑被重复执行
5. 解决方案
保证接口幂等性有多种方法,当前提供数据库方案,如下:
5.1. 数据库唯一索引
这可以算是最简单的方法了,直接在数据库表中的某个关键字段设置唯一索引,下面以订单(order)数据库表为例写一段SQL:
alter table `order` add UNIQUE KEY `t_xxx` (`某个字段`);
加了唯一索引之后,第一次请求数据可以插入成功。但后面的相同请求,插入数据时会报异常,表示唯一索引有冲突:
Duplicate entry '002' for key 'order.t_code
具体流程图如下:
5.2. 建立防重表
有同学可能就问了,上面的方案不太合理,因为有时候我们的表中需要一些重复数据,只有一些特殊场景才不需要重复数据,唯一索引方案可能就不合适了。
这个时候可以试试防重表,简单来说就是我们再单独建立一张表,只需要含有id和想要唯一的索引字段。
具体流程图如下:
5.3. 悲观锁机制
悲观锁机制可以作为一种手段来帮助解决接口幂等性问题,尤其是在处理高并发场景下对数据的并发修改需求。悲观锁的基本假设是数据在并发访问期间很可能发生冲突,因此在事务开始时就立即锁定所需的数据资源,防止其他事务对其进行修改,直到当前事务结束。
以下是利用MySQL悲观锁机制解决接口幂等性的基本方案:
1. 使用SELECT ... FOR UPDATE
在执行更新操作之前,先使用SELECT ... FOR UPDATE语句锁定要修改的行。这会在读取数据的同时加上排他锁(X锁),确保其他事务无法修改这些数据,直到当前事务结束(提交或回滚)。
START TRANSACTION;
SELECT * FROM your_table WHERE id = ? FOR UPDATE;
-- 根据查询结果判断是否需要更新
IF (需要更新) THEN
UPDATE your_table SET column=value WHERE id = ?;
END IF;
COMMIT;
2. 结合接口幂等性设计
结合上述SQL操作,可以设计一个幂等性接口处理流程:
大缺点:悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。
5.4. 乐观锁机制
悲观锁会增加锁争用的可能性,可能导致事务等待和阻塞,影响系统性能,特别是在高并发场景下。因此,应谨慎使用,乐观锁某些情况下就是个很好的替代方案。
MySQL乐观锁机制是一种非阻塞的并发控制策略,它假定多线程同时修改数据的概率较低,因此不会在事务一开始就对数据加锁,而是在更新数据时检查数据是否被其他事务修改过。这对于高并发场景下解决接口幂等性问题特别有效,因为它减少了锁的竞争,提高了系统的吞吐量。以下是利用MySQL乐观锁机制解决接口幂等性的方案:
1. 版本字段实现乐观锁
在数据库表中添加一个额外的字段作为版本控制,比如version字段,初始值为1。
CREATE TABLE your_table (
id INT PRIMARY KEY,
data VARCHAR(100),
version INT DEFAULT 1
);
2. 接口调用流程
① 客户端请求:客户端发出修改数据的请求,请求中包含数据的原始版本号(如果有的话)。
② 读取数据并附带版本信息:服务端首先查询需要更新的数据,并获取其当前的版本号。
SELECT * FROM your_table WHERE id = ?;
③ 幂等性检查与更新:在更新数据时,同时在UPDATE语句中加入版本号的条件检查,确保只有当版本号与查询时一致时才执行更新。
UPDATE your_table
SET data = ?, version = version + 1
WHERE id = ? AND version = ?;
---(data = ? 是要更新的新数据值。
---version = version + 1 表示更新成功后,版本号递增。
---WHERE id = ? AND version = ? 确保只有当记录的版本号与预期一致时才执行更新。)
④ 结果判断:根据UPDATE语句影响的行数判断是否更新成功。
如果影响行数为1,说明更新成功,且数据没有被其他事务修改过。
如果影响行数为0,说明数据在执行更新前已经被其他事务修改,此时可以根据业务逻辑选择重试或返回错误信息。
具体流程图如下:
今天写到这里,明天再来写token方案哈。