从这个问题开始,面试官就已经进入了实际的生产问题的面试了。
- 一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?
- 你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了 5
台机器上,接着有个接口就是付款接口。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。 - 或者是订单系统调用支付系统进行支付,结果不小心因为网络超时了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。。。
- 所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。
这个不是技术问题,这个没有通用的一个方法,这个应该结合业务来保证幂等性。
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款、不能多插入一条数据、不能将统计值多加了 1。这就是幂等性。
其实保证幂等性主要是三点:
- 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
- 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql
中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。 - **每次接收请求需要进行判断,判断之前是否处理过。**比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId
已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
实际运作过程中,你要结合自己的业务来,比如说利用 Redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。
例如下面的一个过程:
- 首先判断redis中是否有订单记录,有则返回已支付完成[
get order_id
] - 查询数据库判断没有订单,先插入订单,支付中【单号是唯一索引】
- 有订单, (1)判断是支付中,进入下一步 (2)如果是完成
set order_id payd
,则返回支付完成 - 开启事务,加悲观锁:
- 再次判断mysql状态是支付中(防并发):是则下一步,不然返回支付完成
- 支付完成,修改mysql状态为支付完成
- commit提交
- redis:设置为已支付
set order_id payd
考虑一个问题,如果把给redis设置payd放在数据库操作的前面会有什么问题?
考虑到服务器当掉的情况,会出现redis设置了payd,但是订单没有支付的情况。
那么数据库操作在前面完成了,这个时候挂掉了,问题不大,重新请求其他的服务器,发现访问到数据库中的状态是支付成功,再将状态设置到redis中就好了。