保证接口幂等性的主流方法(java面试接口保证幂等性)
1、什么是接口幂等性
接口幂等性简单来说,就是需要确保一个操作无论执行多少次,其结果和对系统状态的影响都与执行一次相同。换个维度,也可以理解为对于相同的请求,无论请求多少次,都应该返回相同的结果。(即重复请求获得相同的响应,而不应该产生额外的副作用)
注:幂等性的概念并不局限于分布式系统,它同样适用于单机架构。幂等性是一种设计原则,确保了操作的一致性和可预测性。
HTTP方法的幂等性是指在多次执行相同请求的情况下,服务器的状态不会改变。以下是以表格形式展示GET、POST、PUT、DELETE方法是否满足幂等性:
方法 | 幂等性 | 描述 |
GET | 是 | GET请求用于从服务器检索数据。无论请求多少次,GET请求都不应改变服务器的状态,因此是幂等的。 |
POST | 否 | POST请求用于向服务器提交新的数据。每次POST请求都可能创建新的资源或执行某些操作,因此不是幂等的。 |
PUT | 可能是 | PUT请求用于更新服务器上的现有资源。如果PUT操作指定了资源的完整状态,并且每次请求都是相同的,那么它可以是幂等的。但是,如果PUT请求只更新部分状态,那么它不一定是幂等的。 |
DELETE | 可能是 | DELETE请求用于删除服务器上的资源。如果资源不存在,再次执行DELETE请求应该不会产生错误,也不应改变服务器状态,这使得DELETE在某些情况下是幂等的。但是,如果DELETE操作依赖于资源的特定状态,那么它可能不是幂等的。 |
2、接口幂等性和接口防抖的区别
很多朋友分不清接口幂等性和接口防抖,接口防抖简单来说,就是函数或事件在同一时间内多次被触发,只执行最后一次。接口防抖的核心是减少在一定时间内频繁触发的操作,主要用于处理mousedown、mousemove、keyup、keydown等频繁发生的事件。
接口幂等性和接口防抖的核心区别如下:
特性 | 接口幂等性 | 防抖(Debouncing) |
定义 | 确保一个操作无论执行多少次,其结果和对系统状态的影响都与执行一次相同。 | 减少在一定时间内频繁触发的操作,仅在最后一次操作后执行。 |
目的 | 保证操作的一致性和系统状态的稳定性。 | 减少不必要的操作和计算,提高响应速度和系统性能。 |
应用场景 | 网络请求、数据库更新、支付系统等。 | 用户输入、滚动事件、按钮点击等。 |
实现方式 | 1.唯一事务ID;2.乐观锁;3. 悲观锁;4.检查数据状态;5.使用缓存;6.数据库事务;7.重试机制;8.状态机;9.消息队列;10.服务端确认; | 1.延迟执行;2.取消前一个操作;3.定时器;4.节流(Throttling)结合使用;5、防抖函数封装;6、异步防抖; |
影响 | 系统状态不变,数据一致性。 | 减少操作次数,避免频繁触发。 |
适用操作 | 更新操作、创建操作、删除操作等。 | 事件触发、用户输入等。 |
用户感知 | 用户不会感知到操作被重复执行。 | 用户可能会感知到操作响应的延迟。 |
技术挑战 | 需要确保操作的原子性,处理并发和网络问题。 | 需要合理设置延迟时间,避免影响用户体验。 |
3、接口幂等性的应用场景
接口幂等性的意义在于保证数据的一致性、提高系统稳定性、简化系统设计。接口幂等性的主要应用场景如下:
A、网络请求处理:
确保客户端重复发送相同的请求不会对服务器状态产生影响,如重复的GET请求。
B、数据库操作:
在执行更新、删除等操作时,确保重复执行不会对数据库状态产生额外的影响。
C、支付系统:
保证支付操作的安全性和一致性,避免重复支付。
D、分布式系统:
在分布式系统中,确保不同节点间的操作一致性,防止数据不一致。
E、缓存机制:
在缓存数据时,确保缓存更新操作的一致性。
F、消息队列:
处理消息时,确保消息被正确且仅一次处理。
G、API网关:
在API网关中,确保请求被正确路由和处理,避免重复处理。
H、微服务架构:
在微服务架构中,确保服务间的调用一致性和状态同步。
I、任务调度:
在任务调度系统中,确保任务的执行不会被重复调度。
4、接口幂等性主要实现方案
接口幂等性的主流实现方案包括:唯一约束、锁机制、token机制、状态机幂、抽取防重表等。
注:很多老的架构场景基本都应用到了token机制,但token机制有一个缺陷,它需要维护一个状态,消耗额外的内存,并且面临状态丢失的风险,通常配合redis一起使用。
抽取防重表也是一个主要实现方案,但这种方案压力放到了数据库上,而数据库往往是很多项目的性能瓶颈。
主流且可落地的接口幂等性实现方案如下:
5、接口幂等性代码实现思路
使用唯一事务ID(伪)代码如下:
import java.util.concurrent.ConcurrentHashMap;
public class IdempotentService {
private final ConcurrentHashMap<String, Boolean> requestMap = new ConcurrentHashMap<>();
public boolean processRequest(String requestId) {
if (requestMap.containsKey(requestId)) {
System.out.println("请求已处理");
return false;
}
try {
System.out.println("正在处理请求: " + requestId);
// 执行业务逻辑
boolean success = true; // 假设业务逻辑执行成功
requestMap.put(requestId, success);
return success;
} catch (Exception e) {
System.out.println("请求处理出错: " + requestId);
return false;
}
}
}
使用乐观锁(伪)代码如下:
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;
import javax.transaction.Transactional;
@Service
public class UserService {
private final EntityManager entityManager;
public UserService(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
public boolean updateUserName(Long userId, String newName, int version) {
User user = entityManager.find(User.class, userId, LockModeType.OPTIMISTIC);
if (user == null) {
return false;
}
if (user.getVersion() != version) {
throw new OptimisticLockException("检测到陈旧数据");
}
user.setName(newName);
user.setVersion(user.getVersion() + 1);
return true;
}
}