前言
在程序开发的过程中是否遇到如下的问题:
- 同一件商品手速很快多点击了几次,在后盾生成了两笔订单。
- 同一笔订单点了因为网络卡顿,点了两次领取,后果发现反复领取了。
- 微服务架构下利用间通过 RPC 调用失败,进入重试机制,导致一个申请提交屡次。
- 黑客利用充值抓包到的数据,进行屡次调用充值、评论、拜访,造成数据的异样。
这些问题均能够通过接口幂等性设计来解决。幂等性意味着同一个申请无论被反复执行多少次,都能产生雷同的后果,不会导致反复的操作或不统一的数据状态。
在古代分布式系统中,接口的幂等性设计和实现至关重要。本文将深入探讨接口幂等的重要性、实现办法以及可能面临的挑战,并提供测试接口幂等性的无效策略。
什么是接口幂等性
接口幂等性指的是一个接口或操作在雷同的申请参数下,无论被执行多少次,其后果都是统一的且不会产生副作用。换句话说,如果一个申请曾经胜利执行,再次执行雷同的申请应该不会对系统状态产生任何额定的影响。例如,一个获取用户信息的接口就是幂等的,因为屡次获取同一个用户的信息不会扭转零碎的状态。
相同,非幂等接口可能会导致反复的操作和潜在的问题。以领取操作为例,如果没有实现幂等性,反复领取可能会给用户和商家带来不必要的麻烦和损失。
为什么须要接口幂等性
- 避免反复操作 :幂等性能够确保零碎不会因为反复的申请而产生反复的操作,从而防止数据谬误和不统一。
- 进步系统可靠性 :在网络不稳固或其余异常情况下,反复的申请是很常见的。幂等性能够帮忙零碎解决这些反复申请,而不会导致系统出错或不稳固。
- 加强用户体验 :用户不须要放心因为不小心反复操作而导致的问题,从而进步了用户的应用体验和满意度。
- 简化错误处理 :因为幂等接口能够平安地解决反复申请,因而在处理错误和复原时更加容易,缩小了简单的谬误复原逻辑。
如何设计接口幂等性
- 应用惟一标识 :为每个申请调配一个惟一的标识,例如申请 ID 或流水号。通过在申请中传递这个惟一标识,零碎能够判断是否曾经解决过该申请。
- 设计幂等的操作 :确保操作自身是幂等的。例如,更新数据时能够采纳 ” 更新或插入 ” 的策略,而不是间接批改已有记录。
- 应用事务 :在波及多个数据库操作的状况下,应用事务来确保整个操作的原子性和幂等性。
- 利用缓存 :将申请的后果缓存起来,当接管到雷同的申请时,间接返回缓存中的后果,防止反复执行操作。
如何实现接口幂等性
以下实现形式是基于 demo 实现,用于阐明幂等性的设计和实现。
- 惟一标识 :能够通过生成全局惟一的 ID(如 UUID)来标识每个申请。在申请的参数中蕴含这个 ID,服务器在解决申请时能够依据 ID 来判断是否曾经解决过该申请。
服务端生成 requestId 之后将 requestId 放到 redis 中,当然须要给 ID 设置一个生效工夫,超时的 ID 也会被删除。
public class RequestIdGenerator {public static String generateRequestId() {Stirng uuid = UUID.randomUUID().toString();
putCacheIfAbsent(uuid);
return uuid;
}
}
在接口中,将生成的申请 ID 与申请参数一起传递给服务器。
// 生成申请 ID
String requestId = RequestIdGenerator.generateRequestId();
// 构建申请参数
Map<String, String> requestParams = new HashMap<>();
requestParams.put("requestId", requestId);
requestParams.put("otherParam", "value");
// 发送申请
httpClient.sendRequest(requestParams);
服务器在接管到申请后,能够依据申请 requestId 来判断是否曾经解决过该申请,并进行相应的解决。
当后端接管到订单提交的申请的时候,会先判断 requestId 在缓存中是否存在,第一次申请的时候,requestId 肯定存在,也会失常返回后果,然而第二次携带同一个 requestId 的时候被回绝了。
- 幂等的操作 :以订单状态更新为例,如果订单曾经处于最终状态(如已领取或已发货),再次更新订单状态不会扭转其理论状态,因而是幂等的。
public class OrderService {public void updateOrderStatus(String orderId, OrderStatus status) {
// 依据 orderId 获取订单
Order order = orderIdToOrderMapper orderIdToOrder(orderId);
// 判断订单是否处于最终状态
if (order.isFinalStatus()) {
// 订单已处于最终状态,不须要进行理论的更新操作
return;
}
// 更新订单状态
order.setStatus(status);
orderRepository.save(order);
}
}
- 事务 :在数据库操作中,能够应用事务来保障操作的原子性和幂等性。如果某个操作失败,事务能够回滚到之前的状态,防止不统一的数据。
@Transactional
public void performTransactionalOperation() {
// 开启事务
Transaction transaction = transactionManager.beginTransaction();
transaction.setIsolationLevel(IsolationLevel.READ_COMMITTED);
transaction.setPropagationBehavior(Propagation.REQUIRED);
// 数据库操作 1
//...
// 数据库操作 2
//...
// 提交事务
transactionManager.commit();}
开启事务是一种乐观锁实现的形式,一开始更新数据就把数据加锁了,具备强烈的独占和排他个性。
- 缓存 :通过将申请的后果缓存起来,能够防止反复执行雷同的操作。当接管到雷同的申请时,间接从缓存中获取后果返回。
public class CacheService {private Map<String, Object> cache = new ConcurrentHashMap<>();
public Object getCachedResult(String key) {
// 从缓存中获取后果
if (cache.containsKey(key)) {return cache.get(key);
}
// 执行理论的操作并获取后果
Object result = performExpensiveOperation(key);
// 将后果缓存起来
cache.put(key, result);
// 返回后果
return result;
}
}
应用幂等性接口带来了什么后果
- 并发申请解决 :在高并发环境下,可能会同时接管到多个雷同的申请。为了解决这种状况,能够应用分布式锁或其余并发管制机制来确保只有一个申请执行理论的操作。目前的分布式锁个别基于 zookeeper 或者 redis 实现。
- 失败申请的解决 :如果申请在执行过程中失败,须要确保幂等性依然失去保护。能够通过记录申请的状态或应用重试机制来解决失败的申请。
- 与现有零碎的集成 :在将幂等性引入现有零碎时,可能须要对现有零碎进行一些批改和适配。这可能波及到与其余组件或服务的协调和集成。
- 测试的复杂性 :因为幂等性的测试须要模仿反复申请和各种边界状况,测试的复杂性可能会减少。须要设计全面的测试用例来笼罩各种可能的状况。
怎么验证接口是否具备幂等性
- 模仿反复申请 :应用测试工具或手动模仿发送雷同的申请屡次,查看后果是否统一。
- 验证数据一致性 :查看相干的数据是否在反复申请后保持一致,没有呈现反复操作或数据不统一的状况。
- 压力测试 :在高并发状况下测试接口的幂等性,确保在大量申请同时达到时零碎依然能正确处理。
- 异常情况测试 :模仿各种异常情况,如网络中断、服务器故障等,查看接口在这些状况下是否依然放弃幂等性。
幂等性接口的总结
实现接口的幂等性对于构建牢靠和高效的零碎至关重要。通过应用惟一标识、幂等操作、事务和缓存等技术,能够无效地设计和实现幂等接口。
同时,要留神解决可能面临的挑战,并通过全面的测试来确保接口的正确性和稳定性。在理论我的项目中,踊跃利用这些办法将有助于进步零碎的可靠性、安全性和用户体验。
参考
- 后任开发在代码里下毒了,领取下单竟然没加幂等 https://juejin.cn/post/7324186292297482290
对于作者
来自一线全栈程序员 nine 的八年摸索与实际,继续迭代中。欢送关注“雨林寻北”或增加集体卫星 codetrend(备注技术)。