【面试精讲】如何包管接口的幂等性?常见的实现方案有哪些? ...

打印 上一主题 下一主题

主题 1737|帖子 1737|积分 5211

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
【面试精讲】如何包管接口的幂等性?常见的实现方案有哪些?
目次
一、什么是幂等性 & RPC调用 & HTTP调用
1、幂等性
2、RPC调用
3、HTTP调用
4、幂等性在RPC和HTTP调用中的重要性
二、接口幂等性的原理与方案
1、包管幂等性的原理
2、接口幂等性的实现方案
3、实际应用场景
三、接口幂等性办理方案的代码实现
1、唯一事故ID
2、Token机制(防止重复提交)
3、乐观锁
总结



一、什么是幂等性 & RPC调用 & HTTP调用

1、幂等性

在盘算机科学中,幂等性是一个重要的概念,尤其在分布式系统和网络通信中。幂等性指的是实行某个操纵一次与多次具有同样的结果。这意味着无论对同一个操纵请求进行几次调用,结果都应当相同,不会因为多次实行而产生不同的影响。
例如,在数学中,绝对值函数就是幂等的,因为| |x| | = |x|;而在Web服务中,一个典型的例子是HTTP的GET请求,抱负情况下无论请求多少次,服务器的资源状态都不改变。
幂等性在系统操持过程中极其重要,特别是在处理重复请求、错误恢复和系统状态同步时,可确保系统的一致性和稳定性。在面对网络不稳定、服务调用失败重试等情况时,幂等性的接口可以制止数据的重复修改,使得系统行为更加可预测。
2、RPC调用

RPC(Remote Procedure Call)即长途过程调用,是一种通过网络从长途盘算机程序上请求服务,而不需要了解底层网络技术的协议。RPC使得开辟包括网络分布式多历程程序在内的应用程序更加容易。
在RPC模子中,客户端和服务器之间通常存在一个透明的通信层。客户端只需像当地方法一样调用长途方法,并传递参数,剩余的工作如网络通信、数据编码等则由RPC框架负责。常见的RPC框架有gRPC、Apache Thrift等。
RPC的调用过程概括为以下几个步骤:

  • 客户端调用客户端存根(stub)提供的过程,就像调用当地过程一样。
  • 客户端存根将过程调用打包成消息,然后通过网络发送给服务器。
  • 服务器端存根接收消息,解包并实行所请求的过程。
  • 实行结果被服务器存根打包成消息,并通过网络发送回客户端。
  • 客户端存根接收到结果消息,解包,并返回给客户端调用者。
RPC调用强调的是行为的调用,客户端对于服务提供的行为(即方法)进行长途调用。
3、HTTP调用

HTTP(Hypertext Transfer Protocol)调用是基于HTTP协议的网络请求。它是现代互联网中利用最广泛的协议之一,主要用于Web欣赏器和服务器之间的通信,但也常常用于微服务之间的通讯。HTTP的请求-相应模子非常适合无状态的RESTful API操持。
HTTP调用过程如下:

  • 客户端构建一个HTTP请求,其中包括方法(如GET、POST、PUT、DELETE等)、URL、Header以及可能的消息体(body)。
  • 请求通过网络发送到服务器端。
  • 服务器端解析HTTP请求,并根据请求内容进行处理。
  • 服务器端返回一个HTTP相应,其中包括状态码(如200 OK、404 Not Found等)、相应头和相应体。
与RPC调用不同的是,HTTP调用强调的是资源的状态转换,通过对资源(URI定位)的增删改查(CRUD),达到服务间通信的目的。
4、幂等性在RPC和HTTP调用中的重要性

无论是RPC还是HTTP调用,幂等性都能够防止因网络问题或其他原因导致的重复请求引发数据辩论或状态不一致,这在微服务架构、分布式事故处理、负载均衡等场景中尤其重要。在HTTP中尤其突出,因为HTTP/1.1规范定义了一些方法(如GET、PUT、DELETE)为幂等的,而POST方法通常不是幂等的,因为它代表的是创建新资源的动作。
二、接口幂等性的原理与方案

在系统间通信过程中,尤其是在分布式系统中,接口的幂等性是维护数据一致性和系统稳定性的关键。本部分将首先介绍包管幂等性的原理,随后展开详细的办理方案。
1、包管幂等性的原理

幂等性的焦点是确保一个或多个操纵的重复实行不会对系统的状态产生额外影响。在分布式系统大概任何需要通过网络通信的系统架构中,由于网络延迟、系统故障、用户重复操纵等因素,相同的请求可能会被发送多次。如果系统对每个独立的请求都按照一次新操纵来处理,则可能会导致数据不一致或状态错误。
实现幂等性的根本原理包括:

  • 辨认请求:系统需要能够辨认出重复的请求。这通常通过在请求中包含一个唯一标识符(如请求ID或事故ID)来实现。
  • 存储操纵状态:系统需记录操纵的实行状态(例如已处理、正在处理、未处理),以及关键步骤的结果,确保即使在服务重启或失败后也能恢复和维持操纵状态。
  • 处理重复请求:当系统接收到一个已知的重复请求时,它可以采取得当的行动,如直接返回先前操纵的结果,大概忽略重复的请求。
2、接口幂等性的实现方案

实现接口幂等性可以采取多种策略,以下是一些常见的方法:

  • 利用唯一事故ID:为每个操纵请求分配一个唯一的事故ID。服务端查抄该ID,如果之前已经处理过,就直接返回原来的处理结果,否则进行处理并存储该ID与结果的关联。
  • 乐观锁:利用乐观锁可以在更新数据时查抄数据版本。每次数据修改时,版本号增长。如果请求中的版本号与服务器上的不匹配,则意味着有其他操纵已经修改了数据,当前操纵将被拒绝。
  • Token机制:在操纵开始前,客户端请求一个操纵令牌(token),并在随后的操纵中利用该token。服务器对每个token只允许一次有效的操纵,从而防止重复处理。
  • 指纹机制:根据请求的特性(如参数、时间戳等)生成请求指纹。服务端查抄指纹是否已存在,存在则认为是重复请求。
  • 幂等框架支持:一些现代框架和组件(如消息队列、数据库等)提供了内建的支持来处理幂等性,例如Kafka的Exactly-once语义,大概数据库的unique constraint。
每种方案都有其实用场景和限定。例如,事故ID和Token机制实用于需要严格幂等性包管的场景,而乐观锁和指纹机制可能更适合并发度不是特别高的环境。
3、实际应用场景



  • 电子商务:在订单支付环节,包管支付操纵的幂等性可以防止因网络延迟或用户重复点击“支付”按钮导致的多次扣款。
  • 银行系统:转账操纵需要幂等性保护,确保同一笔转账指令不会因为重复处理而导致资金异常。
  • 微服务架构:在微服务之间调用时,尤其是在服务自动重试机制存在的情况
三、接口幂等性办理方案的代码实现

在接口幂等性的策略中,我们通常要结合详细的业务场景选择得当的实现方式。这一部分将提供几种常见的幂等性包管方法的代码示例。
1、唯一事故ID

创建一个TransactionalApiController类,有一个process方法,用于处理带有唯一事故ID的请求。
系统通过查抄一个全局的ConcurrentHashMap来辨认重复的请求,并返回之前存储的相应结果。 
  1. @RestController
  2. @RequestMapping("/api")
  3. public class TransactionalApiController {
  4.     private ConcurrentHashMap<String, Object> requestCache = new ConcurrentHashMap<>();
  5.     @PostMapping("/process")
  6.     public ResponseEntity<Object> process(@RequestBody RequestData requestData) {
  7.         String transactionId = requestData.getTransactionId();
  8.         
  9.         // 检查是否为重复请求
  10.         if (requestCache.containsKey(transactionId)) {
  11.             return ResponseEntity.ok(requestCache.get(transactionId));
  12.         }
  13.         // 处理业务逻辑
  14.         Object result = performBusinessLogic(requestData);
  15.         // 将结果存储和返回,以便后续重复请求可以使用
  16.         requestCache.put(transactionId, result);
  17.         
  18.         return ResponseEntity.ok(result);
  19.     }
  20.    
  21.     private Object performBusinessLogic(RequestData requestData) {
  22.         // 实际业务处理逻辑...
  23.         return new Object(); // 返回处理结果
  24.     }
  25.    
  26.     static class RequestData {
  27.         private String transactionId;
  28.         // 其他业务数据
  29.         
  30.         // 省略getter、setter和构造器
  31.     }
  32. }
复制代码
2、Token机制(防止重复提交)

客户端首先调用getToken方法获取一个唯一的Token。在提交信息时,客户端需要在HTTP头部携带这个Token,服务器端会验证Token是否有效,如果有效则实行业务逻辑并使该Token失效。 
  1. @RestController
  2. @RequestMapping("/api")
  3. public class TokenApiController {
  4.     private final TokenStore tokenStore;
  5.     public TokenApiController(TokenStore tokenStore) {
  6.         this.tokenStore = tokenStore;
  7.     }
  8.     @GetMapping("/token")
  9.     public ResponseEntity<String> getToken() {
  10.         // 生成并返回一个新的Token
  11.         String token = UUID.randomUUID().toString();
  12.         tokenStore.storeToken(token);
  13.         return ResponseEntity.ok(token);
  14.     }
  15.     @PostMapping("/submit")
  16.     public ResponseEntity<Object> submit(@RequestHeader("X-Request-Token") String token, @RequestBody Data data) {
  17.         if (!tokenStore.checkAndInvalidateToken(token)) {
  18.             throw new DuplicateRequestException("Token has already been used or does not exist.");
  19.         }
  20.         // 执行业务逻辑
  21.         Object result = businessLogic(data);
  22.         return ResponseEntity.ok(result);
  23.     }
  24.     // 在这里实现TokenStore,确保线程安全性和过期管理
  25. }
复制代码
3、乐观锁

在EntityWithVersion实体中,我们利用了@Version注解标志了一个版本字段。在实行更新时,如果检测到版本不一致,则意味着另一个并发操纵已经修改了数据,当前操纵将抛出异常。
  1. @Entity
  2. public class EntityWithVersion {
  3.     @Id
  4.     private Long id;
  5.    
  6.     @Version
  7.     private Integer version;
  8.     // 其他字段和方法
  9. }
  10. @Repository
  11. public interface EntityWithVersionRepository extends JpaRepository<EntityWithVersion, Long> {
  12. }
  13. @Service
  14. public class OptimisticLockService {
  15.     private final EntityWithVersionRepository repository;
  16.     public OptimisticLockService(EntityWithVersionRepository repository) {
  17.         this.repository = repository;
  18.     }
  19.     public EntityWithVersion updateEntity(Long id, SomeData newData) {
  20.         EntityWithVersion entity = repository.findById(id)
  21.                 .orElseThrow(() -> new EntityNotFoundException(id));
  22.         // 更新实体数据
  23.         entity.setSomeField(newData.getSomeField());
  24.         // 如果在这个更新操作的过程中,实体的版本号被其他线程修改,则JPA会抛出OptimisticLockingFailureException
  25.         return repository.save(entity);
  26.     }
  27. }
复制代码
总结

以上代码示例只是展示了如何在Java中实现幂等性控制的根本思路。实际项目中可能需要根据业务需求与系统架构进行相应的调解和优化。此外,还需思量存储介质的性能、异常处理、日记记录等因素,以确保系统的稳定运行和良好的用户体验。

欢迎评论区留言讨论,如果本文对你有帮助 欢迎关注 、点赞 、收藏 、评论!!!
 博主v:XiaoMing_Java

     
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

千千梦丶琪

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表