对接一个第三方服务接口要考虑什么?(一)

通讯标准

  1. 确定协议 Http/Https
  2. 确定参数传输格式 json/x-www-form-urlencoded/…
  3. 具体接口参数的数据类型(弱类型对接强类型语言时容易踩坑)
  4. 确定身份校验方式
    • token
    • 参数签名:单一Key、交换公钥
    • 证书校验
  5. 确定响应结构(统一成功响应码等等)

异常处理

一般情况下,异常结构应该区分为三大类:

  1. RpcException:调用异常
  2. BusinessException:业务异常
  3. Exception:其他程序异常

对于不同的第三方服务,什么情况下对应什么异常,可能会有不同的划分标准,一般情况下有以下规则:

  1. Http StatusCode == 500 时为RpcException
  2. 业务响应码 != 统一成功响应码 时为BusinessException
  3. 其他异常不作归类

一致性

对于涉及到支付、退款等有下单概念的接口或涉及到状态问题时,则需要考虑到一致性的问题。一般情况下有以下要求(第三方服务也叫上游):

  1. 上游有的数据,本地一定要有
  2. 上游状态与本地每条数据的状态相同 (或者状态可以相对来说一一映射)

由于第三方服务一般不受控,这里说的一致性往往只能是最终一致性

事务发起

  1. 确定接口当中的唯一标识是哪个字段,通常是requestNo,这个字段的值将上游数据和本地数据进行一一对应,上游存在的requestNo,本地必须存在
  2. 划分状态:
  • PENDING:本地数据已创建,未发起接口请求
  • UNCONFIRMED:本地数据已创建,不知道接口请求发起了没有,等待回查
  • PROCESSING:接口请求已发起,并且上游已响应,等待回查确认最终状态
  • SUCCESS:终态,业务已成功
  • FAIL:终态,业务已失败
  • DEAD:终态,本地数据已创建,接口死活请求不了,上游也查不到对应数据,不要了,根据实际情况也可以归类为FAIL

这里的流程可以概述为:

  1. 发起上游接口前,生成一个全局唯一的requestNo,该条数据的状态为PENDING,并且入库提交。
  2. 请求上游接口没有异常的情况下:
    1. 如果允许的话,同步处理状态,更新状态入库。
    2. 否则,直接更新为PROCESSING,表示请求上游已成功,等待进一步确认状态。
  3. 请求上游接口遇到RpcException,更新为PROCESSING,表示请求上游已成功,等待进一步确认状态。
  4. 请求上游接口遇到BusinessException,更新为FAIL,表示请求上游已成功,等待进一步确认状态。(这里可能根据不同的业务返回码,处理为PROCESSING,等待进一步确认)
  5. 处理过程中遇到其他Exception,更新为UNCONFIRMED,不能确定是否已请求上游,等待进一步确认状态,这里可以概述为本地事务处理失败,即保存到本地数据库时失败。

如果你对上游的信任度较低,可以直接将PROCESSING状态也合并为UNCONFIRMED通一由事务回查处理

下面用一段伪代码来描述接口调用的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
复制代码// 开启本地事务
startTrans();
Order order = new Order();
// 唯一请求号
String requestNo = UUID();
order.setRequestNo(requestNo);
order.setState(OrderState.PENDING);
order.save();
// 提交本地事务
commit();

try {
startTrans();
RpcResponse rpcRes = rpcService.requestToRpcCreateOrder(...);
order.setState(OrderState.PROCESSING);

// 如果你的接口可以同步返回业务状态
if(rpcRes.getState() == 'SUCCESS') {
order.setState(OrderState.SUCCESS);
}
if(rpcRes.getState() == 'FAIL') {
order.setState(OrderState.FAIL);
}

order.save();
commit();
} catch(RpcException e) {
startTrans();
// 认为是处理中,等待后续回查
order.setState(OrderState.PROCESSING);
order.save();
commit();
} catch(BusinessException e) {
startTrans();
// 认为是失败
order.setState(OrderState.FAIL);
// 失败时,建议记录rpc响应参数
order.setRpcResponseCode(e.getCode());
order.setRpcResponseMsg(e.getMsg());
order.save();
commit();
} catch(Exception e) {
startTrans();
// 认为是待确认,等待后续回查
order.setState(OrderState.UNCONFIRMED);
order.save();
commit();
}

事务回查(重试)

经过上面的流程,数据会剩下UNCONFIRMED 和 PROCESSING 两种状态,因此对这两种状态进行进一步确认,保证数据到达终态。

事务回查有几种实现方式:

  1. 利用定时器扫描数据库状态为UNCONFIRMED或PROCESSING的数据
    • 保证数据库有索引
    • 如果requestNo字段也有索引,则可利用覆盖索引机制缩短查询时间,查询上游数据状态一般只需要requestNo
  2. 把UNCONFIRMED或PROCESSING数据的requestNo存入Redis,再利用定时器处理
  3. 利用队列,将UNCONFIRMED 和 PROCESSING塞在回查队列中

实际上,假如你的rpc请求不需同步返回出去,推荐使用具有事务机制的消息队列,否则利用队列方案需要考虑复杂度的上升程度

那么UNCONFIRMED 和 PROCESSING分别怎么处理呢

  • 对应PROCESSING,处理思路很简单,因为这种状态上游肯定能够返回对应的状态(实际上有的上游并不一定),只要查询到对应状态更新为SUCCESS或FAIL即可
  • 对应UNCONFIRMED需要区分上游数据不存在的情况,也就是说上面的事务发起流程当中,上游没有收到我们的请求,那么我们需要根据业务情况进行处理:
+ 重新发起这个请求(要确保上游接口是否幂等,否则要自己处理)
+ 更新为DEAD,抛弃这个请求如果上游存在该记录,则视为PROCESSING情况处理即可

如果你的上游提供异步处理通知,则可按照同样的思路完成事务回查这个阶段

总结与思考

本文简单总结了一下对接第三方服务接口时需要考虑的几个问题:通讯标准、异常处理、一致性,实际处理时通常会分为RPC层与Service层来处理,RPC封装通讯标准、异常处理的问题,Service层处理一致性问题。最后留下了一个问题还未进行讨论,在入库前、重新发起请求前、异步通知时都需要考虑幂等的问题,下一篇文章针对幂等再来分享几种处理方案吧。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%