这里总结下,原先公司中支付项目的rpc应用实践,如何设计的。
项目场景
一个2B的sass点餐系统,在商家店铺内,前台就可以看见一台点餐机,上面安装的系统就是我们的sass。当客户点完餐后,会进入支付,商家可用扫码枪扫用户手机二维码,用户支付完成,商家收到支付成功信息,订单完成。
服务处理过程
当商家选择商品,商品选择完成,会生成订单,商家拿起扫码枪扫描客户支付码,扫描完成后,发起调用请求。
注意:上面的商户的一系列操作,是一个完整的过程,只涉及最后的唯一一次请求,只有请求成功了,才会真正产生订单数据。商家所看到的订单只不过是页面上的效果,便于为最后一次请求封装请求参数而已。
整个请求链路如下:
商家请求先经此 “商品service” ,选择商品对应查询商品数据,然后将商家请求中商品除外的参数和“商品service”查询的结果,重新封装成一个rpc请求。此时由“商品service”调用下层的“订单service”。“订单service”完成生成订单的步骤,完成后,带着订单数据和商家请求中的客户支付参数,封装rpc请求,调用“pay service”。“pay service”中会调用第三方支付,如支付宝,微信及各大银行等。拿银行支付通道来说,“pay service”调用银行接口,银行会唤起客户手机支付脚本,如果客户免密支付的话,银行的接口会直接返回“pay service”“支付成功”的结果,否则会返回“支付中”结果,当然也有可能支付失败,这里不考虑。此时整个请求链路完成。
下面阐述一下,具体的细节:
rpc采用dubbo的情况下
从“订单service”调用“pay service”这一过程,存在下层服务处理超时的情况,如果dubbo reference采用failOver的容错模式,即重试机制,那么下层“pay service”超时,上层“订单service”会发起重试,再次调用。这样会有一个问题,会产生多次支付记录,上层调用虽然超时,但下层“pay service”仍然继续请求处理中,支付相关数据仍然产生。如果再次调用,则会再次产生支付数据,显然1份订单数据与多份支付数据不对等,非幂等的写操作。尽管,同一个支付参数,如同一笔订单,同一个用户,“pay service”调用第三方支付接口,支付接口会做重复支付保护,在第二次处理时,会返回“该订单已支付处理过”的结果。但我们的服务会有支付操作的记录,虽然支付安全,但数据错误,不幂等。所以为了避免这个问题,会将上层“订单service”调用“pay service”dubbo reference设置成failFast的模式,这样当下层“pay service”调用超时时,上层“订单service”会直接收到失败的结果,不会再次请求,避免了非幂等的问题。
但这样仍然存在问题,如果说,下层“pay service”已经完成了对第三方支付接口的调用,用户端已经完成了支付扣款,但由于“pay service”处理整个处理时间过长,毕竟调用完第三方支付接口后还有一些业务逻辑,如记录支付状态。那么上层“订单service”会收到failFast返回的失败,认为此笔订单未完成支付,返回商家请求响应中,就是订单未支付成功,那么最终商家就看不到支付成功的结果。但用户确实支付扣款完成了。如何避免这个问题,这里就是借用了第三方支付的处理机制。我们的sass系统上,在订单支付页上提供“支付查询”功能,该功能会发起查询请求,通过订单数据,调用另外一个service,这个service最终调用第三方支付查单接口,查询到最终支付成功的接口,然后返回商家,同时service 还会通过MQ完成一些订单状态改变,支付状态记录等操作,异步完成。(对于第三方回调我们的service,我们的service也是通过MQ异步完成数据状态的改变和记录)
对于“商品service”到“订单service”的过程,因为这个链路的处理,是直接关乎商家的体验,需要保证商家一次性创建订单成功,保证这个交互体验。而下层的“pay service”更多的是付款用户的体验。所以 “商品service”到“订单service” 调用,使用的dubbo failOver的容错模式,如果因网络延迟等问题导致超时,可以重试,再次创建订单。但这样会有问题,虽然交互保证了,但如果真的碰到了下层“订单service”超时,重试就会重复创建订单。实际场景中,点了一次餐,却两个订单,用户可能也实际支付了两次。要解决这个问题,有两种方式:
1、可以对dubbo整合hystrix,下层service增加熔断处理,超时回滚数据即可。
2、业务上增加退单功能。可调用第三方支付退款接口完成退款。
发表评论