接口多次调用结果不一致?如何保证接口的幂等性

什么是幂等性?

幂等性最早是数学里面的一个概念,后来被用于计算机领域。

接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。

什么情况下需要幂等性?

在微服务架构下或者调用外部接口时,我们在完成一个订单流程时经常遇到下面的场景:

  1. 一个订单创建接口,第一次调用超时了,然后调用方重试了一次
  2. 在订单创建时,我们需要去扣减库存,这时接口发生了超时,调用方重试了一次
  3. 当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,调用方重试了一次
  4. 一个订单状态更新接口,调用方连续发送了两个消息,一个是已创建,一个是已付款。但是你先接收到已付款,然后又接收到了已创建
  5. 在支付完成订单之后,需要发送一条短信,当一台机器接收到短信发送的消息之后,处理较慢。消息中间件又把消息投递给另外一台机器处理

为了解决以上问题,就需要保证接口的幂等性。

总的来说在增删改查4个操作中,尤为注意就是增加或者修改

查询操作

​ 查询对于结果是不会有改变的,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作

删除操作

​ 删除一次和多次删除都是把数据删除。(多次操作,结果一样,具备幂等性。注意删除的数据不存在时,可能返回结果不一样)

更新操作

​ 修改在大多场景下结果一样,但是如果是增量修改是需要保证幂等性的,如下例子:

​ 把表中 id 为 XXX 的记录的A字段值设置为1,这种操作不管执行多少次都是幂等的

​ 把表中 id 为 XXX 的记录的A字段值增加1,这种操作就不是幂等的

新增操作

​ 增加在重复提交的场景下会出现幂等性问题,如以上的支付问题

如何保证幂等性

token机制

1、服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。

2、然后调用业务接口请求时,把token携带过去,一般放在请求头部。

3、服务器判断token是否存在redis中,存在表示第一次请求,然后删除token,继续执行业务。

4、如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

关键点 先删除token,还是后删除token。

后删除token:如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。这个问题其实是数据库和缓存redis数据不一致问题。

先删除token:如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

先删除token可以保证不会因为重复请求,业务数据出现问题。出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了。

token机制缺点

业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。

唯一索引

创建唯一性索引的表来实现幂等性,在每次执行业务之前,先执行插入操作,因为唯一字段就是主键ID,因此如果重复插入的话会触发唯一约束而导致插入失败,在这种情况下(插入失败我们就可以判断他为重复提交的请求)

如果是分库分表场景下,路由规则要保证相同请求下,落在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键是不相关的。

唯一ID
调用接口时,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过。

通过悲观锁来实现幂等性

首先来判断数据的状态:

select status from table_name where id='xxx';

然后再进行操作:

insert into table_name(id)  values('xxx');
update table_name set status = 'xxx';

这种情况是非原子操作,所以在高并发环境下可能会造成一个业务被执行两次的问题。

当一个程序在执行中时,而另一个程序也开始状态判断的操作。

所以需要使用事务

开启事务

select status from table_name where id='xxx';
insert into table_name(id)  values('xxx');
update table_name set status = 'xxx';

提交事务

注意:MySQL数据库必须选用 innodb 存储引擎,且 id 字段一定要是主键或者是唯一索引,不然会锁表,影响其他业务执行

分布式锁实现

分布式锁实现幂等性的逻辑是,在每次执行方法之前判断,是否可以获取到分布式锁,如果可以,则表示为第一次执行方法,否则直接舍弃请求即可。

需要注意的是分布式锁的key必须为业务的唯一标识,通常适用redis或者zookeeper来实现分布式锁。

参考链接
参考链接
参考链接
参考链接

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中调用HTTPS接口并忽略证书,可以通过使用自定义的信任管理器来实现。证书验证是HTTPS通信的一部分,目的是确保通信双方的身份和保障通信的安全性。然而,有时我们需要在测试环境或特殊情况下绕过证书验证。 首先,我们需要创建一个自定义的信任管理器,该管理器将忽略证书验证。以下是一个简单的示例: ```java import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.*; public class SSLUtil { public static void ignoreSSL() throws NoSuchAlgorithmException, KeyManagementException { TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } } ``` 接下来,在实际调用HTTPS接口之前,我们需要在代码中调用`ignoreSSL()`方法: ```java public class Main { public static void main(String[] args) { try { SSLUtil.ignoreSSL(); // 在这里进行HTTPS接口调用,忽略证书验证 // ... } catch (Exception e) { e.printStackTrace(); } } } ``` 通过使用上述代码,我们可以成功忽略证书验证,并在Java中调用HTTPS接口。请注意,在生产环境中,强烈建议不要忽略证书验证,以确保通信的安全性。仅在特殊情况下,例如调试或开发环境下使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值