对接第三方系统的难点与注意事项
Overview
需要对接第三方的场景
第一类
:自己需要做一件事情,但是纯靠自己要动用很多资源(产品、设计、开发、测试等),还要花较长的时间。如果恰好市面上有成熟的解决方案,我们完全可以把专业的活交给专业的人,利用他们的能力来解决自己的问题,同时让对方获得他们想要的,达到双赢。
举个例子,新启动一个电商平台,为了方便用户支付。一般会对接微信和支付宝,还要支持各家的银行卡做支付。这多家银行,一家家的对接、一家家的谈费率、一家家的联调、一家家的结算,想想都可怕。
如果此时正好有一家这样的公司,他们已经和这些银行最好了对接,并且提供标准化的接口,也支持后台去查流水记录。那完全就可以直接和他们合作,对接这家的系统。
第二类
:自己不具备第三方的能力或者需要为第三方提供服务时。
举个例子,信贷类风控需要一些第三方的数据扩展风控的维度;或者为第三方提供营销获客时,需要对接第三方的API接口。
业务关键问题
考虑需要需要哪些接口
- 我需要提供什么: 我们需要和对方约定好我们提供哪些内容,对方才可以有效的处理并返回给我们需要的结果。比如我想查询物流轨迹,那只需要我们传给对方运单号即可(有的公司还需要快递公司)。
- 需要别人返回给我什么内容: 比如,我希望反馈给我物流轨迹,每一个时间点,从哪里发到了哪里;还需要当前运单的状态,是已揽件还是拒签。
- 异常情况如何处理: 比如支付成功还是失败,失败有哪些原因等,拿到不同的结果我们应该怎么办?
- 我方提供哪些接口,对方提供哪些接口,接口大致的数量
- 业务流程定义
重复做同样的对接工作
大部分人都不愿意做重复劳动,当发现业务朝着要重复对接的方向走的时候,是时候可以考虑做标准化接口了,将自己的能力标准化出去,类似于开放平台,让别人按照你的标准来做。
备注:如果自己公司还没牛逼到这个地步,那还是乖乖对接对方的接口吧
如何计费
1.是按单次收费还是按用户收费
单次收费,用一次给一次钱;按用户收费,一个用户给一次钱,下次同一个用户就不收费。类似于广告计费的CPC和CPA,按次的单价和按用户的单价大部分情况不一样,就根据自己的业务情况去推算是按次还是按用户划算。
2.是定期统计结算还是对方开个账户实时扣款?
定期统计结算,双方要存在一个对账过程,对账OK财务给对方结款。开账户实时扣款,系统化一些,但要注意余额不足的时候要及时充钱。
3.对方是开套餐包还是无限量供应?
套餐包,给你多少次服务,收你多少钱,你每用一次,套餐就减少一点,跟移动流量包的意思差不多。
技术关键问题
通讯标准
- 确定协议 确认接口对接的网络协议:https/http 端口号 或 tcp 端口号 Webservice
- 确定请求格式 数据传参+响应格式为:application/json,数据访问方式 POST请求
- 考虑是否需要安全考虑,比如内网,外网一定要有认证机制
- 幂等校验方面,确保本公司接口和三方公司接口都有唯一校验功能,防止重复提交
- 重试机制方面,一定要确认是否需要接口调用失败后的重试机制,保证数据传输的最终一致性。
异常处理
一般情况下,异常结构应该区分为三大类:
- RpcException:调用异常
- BusinessException:业务异常
- Exception:其他程序异常
对于不同的第三方服务,什么情况下对应什么异常,可能会有不同的划分标准,一般情况下有以下规则:
- Http StatusCode == 500 时为RpcException
- 业务响应码 != 统一成功响应码 时为BusinessException
- 其他异常不作归类
一致性
对于涉及到支付、退款等有下单概念的接口或涉及到状态问题时,则需要考虑到一致性的问题。一般情况下有以下要求(第三方服务也叫上游)由于第三方服务一般不受控,这里说的一致性往往只能是最终一致性。
- 游有的数据,本地一定要有
- 上游状态与本地每条数据的状态相同 (或者状态可以相对来说一一映射)
事务发起
-
确定接口当中的唯一标识是哪个字段,通常是requestNo,这个字段的值将上游数据和本地数据进行一一对应,上游存在的requestNo,本地必须存在
-
划分状态
1PENDING:本地数据已创建,未发起接口请求 2UNCONFIRMED:本地数据已创建,不知道接口请求发起了没有,等待回查 3PROCESSING:接口请求已发起,并且上游已响应,等待回查确认最终状态 4SUCCESS:终态,业务已成功 5FAIL:终态,业务已失败 6DEAD:终态,本地数据已创建,接口死活请求不了,上游也查不到对应数据,不要了,根据实际情况也可以归类为FAIL
这里的流程可以概述为:
11)发起上游接口前,生成一个全局唯一的requestNo,该条数据的状态为PENDING,并且入库提交。 22)请求上游接口没有异常的情况下: 3 如果允许的话,同步处理状态,更新状态入库。 4 否则,直接更新为PROCESSING,表示请求上游已成功,等待进一步确认状态。 53)请求上游接口遇到RpcException,更新为PROCESSING,表示请求上游已成功,等待进一步确认状态。 64)请求上游接口遇到BusinessException,更新为FAIL,表示请求上游已成功,等待进一步确认状态。(这里可能根据不同的业务返回码,处理为PROCESSING,等待进一步确认) 75)处理过程中遇到其他Exception,更新为UNCONFIRMED,不能确定是否已请求上游,等待进一步确认状态,这里可以概述为本地事务处理失败,即保存到本地数据库时失败。
如果你对上游的信任度较低,可以直接将PROCESSING状态也合并为UNCONFIRMED通一由事务回查处理
下面用一段伪代码来描述接口调用的流程:
1// 开启本地事务 2startTrans(); 3Order order = new Order(); 4// 唯一请求号 5String requestNo = UUID(); 6order.setRequestNo(requestNo); 7order.setState(OrderState.PENDING); 8order.save(); 9 // 提交本地事务 10commit(); 11 12try { 13 startTrans(); 14 RpcResponse rpcRes = rpcService.requestToRpcCreateOrder(...); 15 order.setState(OrderState.PROCESSING); 16 17 // 如果你的接口可以同步返回业务状态 18 if(rpcRes.getState() == 'SUCCESS') { 19 order.setState(OrderState.SUCCESS); 20 } 21 if(rpcRes.getState() == 'FAIL') { 22 order.setState(OrderState.FAIL); 23 } 24 25 order.save(); 26 commit(); 27} catch(RpcException e) { 28 startTrans(); 29 // 认为是处理中,等待后续回查 30 order.setState(OrderState.PROCESSING); 31 order.save(); 32 commit(); 33} catch(BusinessException e) { 34 startTrans(); 35 // 认为是失败 36 order.setState(OrderState.FAIL); 37 // 失败时,建议记录rpc响应参数 38 order.setRpcResponseCode(e.getCode()); 39 order.setRpcResponseMsg(e.getMsg()); 40 order.save(); 41 commit(); 42} catch(Exception e) { 43 startTrans(); 44 // 认为是待确认,等待后续回查 45 order.setState(OrderState.UNCONFIRMED); 46 order.save(); 47 commit(); 48}
事务回查(重试)
经过上面的流程,数据会剩下UNCONFIRMED 和 PROCESSING 两种状态,因此对这两种状态进行进一步确认,保证数据到达终态。
事务回查有几种实现方式:
- 利用定时器扫描数据库状态为UNCONFIRMED或PROCESSING的数据
- 保证数据库有索引
- 如果requestNo字段也有索引,则可利用覆盖索引机制缩短查询时间,查询上游数据状态一般只需要requestNo
- 把UNCONFIRMED或PROCESSING数据的requestNo存入Redis,再利用定时器处理
- 利用队列,将UNCONFIRMED 和 PROCESSING塞在回查队列中
实际上,假如你的rpc请求不需同步返回出去,推荐使用具有事务机制的消息队列,否则利用队列方案需要考虑复杂度的上升程度
那么UNCONFIRMED 和 PROCESSING分别怎么处理呢
-
对应PROCESSING,处理思路很简单,因为这种状态上游肯定能够返回对应的状态(实际上有的上游并不一定),只要查询到对应状态更新为SUCCESS或FAIL即可
-
对应UNCONFIRMED需要区分上游数据不存在的情况,也就是说上面的事务发起流程当中,上游没有收到我们的请求,那么我们需要根据业务情况进行处理:
- 重新发起这个请求(要确保上游接口是否幂等,否则要自己处理)
- 更新为DEAD,抛弃这个请求
如果上游存在该记录,则视为PROCESSING情况处理即可
如果你的上游提供异步处理通知,则可按照同样的思路完成事务回查这个阶段
总结
本文简单总结了一下对接第三方服务接口时需要考虑的几个问题:通讯标准、异常处理、一致性,实际处理时通常会分为RPC层
与Service层
来处理,RPC层
封装通讯标准、异常处理的问题,Service层
处理一致性问题。
关于分布式应用下的幂等与重试
业务幂等需要从几个方面去控制:
- 接口层,服务提供方需要引入订单号与订单状态机机制,保证重复提交的请求服务端不会重复交易。
- 数据库层,引入联合索引机制,防止数据重复。
- 高并发场景下,还需要引入分布式锁机制(或者数据库本身的机制)来防止重复交易。
参考
[幂等实例](