请选择 进入手机版 | 继续访问电脑版

EDA方案说明 - 以还款流程举例

[复制链接]
丁翼 发表于 2020-12-31 18:13:26 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
目次





笔者认为,EDA(事件驱动架构)不应该简单地被明确为使用异步或同步的消息来串联流程,它是一种架构设计的思想。
按照这种思想来设计系统架构所带来的的长处是巨大的。它能资助我们界说服务边界,完成服务自治,更好地实现服务间的松耦合;它能更好地应对业务需求的变更,充实低沉业务流程调解带来的资本与风险。
在架构设计中应用EDA思想的前提,是确保所有到场搭建或重构的设计与开辟人员充实明确该思想,但这往往不那么容易,这篇文章提到了以下两点原因:


  • 事件驱动大概是客观世界的运作方式,但不是人的自然思考问题的方式
  • 事件驱动架构会增加额外的复杂性,比如调试的困难性,又比如并不直观的最终一致性
为了资助明确,通过某产品还款流程的设计与实现,来举例说明事件驱动架构在实际业务场景中的应用方案。该实践并不能称之为EDA应用的最佳实践,只是笔者在重构过程中向EDA迈出的一小步。
还款流程概述

还款业务的正向流程如下(省略了一些非核心的逻辑与异常分支):

业务流程介绍

还款流水初始化

同步操作。
订单中心在一个事务中执行以下两个操作:


  • 带状态修改原乞贷订单还款状态为还款中,更新条数不为1将会触发事务回滚
  • 入库一条还款流水,还款流水中未来自上游的订单号作为唯一索引,插入失败将会触发事务回滚
这样可以包管:重复的还款请求不会触发两次还款逻辑,同一订单差别的还款请求强制串行。
代扣

同步操作,支付平台对接第三方支付渠道从用户银行卡中扣款至指定银行账户。
外部核销

将还款行为对应的信息流同步至资金方,根据资金方接口性质决定是同步或异步操作。
内部核销

外部核销完成后,内部系统相应订单流水状态与数据的变更,以及还款筹划与用户账单的变更。
后续处理惩罚

还款设计到的订单数据处理惩罚完成后,进行的别的操作,如通知用户、回调上游调用方、账务系统记账等操作。
重构前的设计

被动的异步设计——能接纳同步流程即接纳同步流程,当某个流程遇到外部的异步依赖时才进行异步操作。
由于外部核销流程中,部分资金方接口设计为异步操作——同步接口会返回中间状态,需要等候处理惩罚完成后的回调或主动查询处理惩罚结果来异步获取核销结果——所以还款流程整体被分为两个步调:

  • 还款流水初始化 >> 代扣 >> 提交外部核销
  • 外部核销结果回调 >> 内部核销 >> 后续处理惩罚
此中第一步调交互如下:

存在的问题

逻辑节点过多,接口耗时与可靠性难以预估,无法包管用户体验

在用户提交还款请求后,进行了太多的同步操作,这大大增加了接口执行过程中的不确定性,任何一个步调的耗时或异常都会影响整个请求。我们能够包管的仅是在局域网中的交互可靠性,比方数据库的读写,内部服务间的RPC调用,但当引入了外部系统时不确定性将大大增加,比方支付渠道的代扣接口发生颠簸时,甚至不可用时,将直接影响还款接口的可用性。
包管数据一致性的资本高,逻辑重

在较长的同步流程中,一个流程发生异常时,为了包管数据一致性,必须进行一些操作来处理惩罚已经完成的操作。而在涉及多个服务的分布式系统中,这样的代价往往是很大的。
发生异常时难以自动规复,无法实现服务自治

流程完全由上游系统触发,当发生异常时当次流程作废,只能通过上游甚至客户本人重新发起请求来触发执行。
流程不易维护,变更资本和风险大

当外部情况发生变化,导致需要调解现有业务流程时,难以评估本次变更带来的影响,尤其是对于尚不熟悉完整业务流程的开辟人员和测试人员。以至于任何一个微小的改动都需要进行全流程的测试,极大地增加了迭代资本。
非关联流程间存在不应该存在的耦合

比方还款的后续处理惩罚中,账务系统记账、短信通知用户、回调上游调用方这三个操作,组织在一个同步流程中,一定存在执行的先后,但客观上这三个操作并没有顺序性的要求。非关联操作耦合在一起,此中某一项操作失败后会影响别的操作的执行,失败后的重试也不便于处理惩罚,部分操作须要性高,必须包管至少执行一次(比方记账),部分操作须要性低,失败可以不重试(比方短信通知和回调上游)。
重构后的设计

将流程按符合的粒度拆分为子流程,引入事件来驱动子流程的执行,将业务流程进一步解耦。
最常见的流程解耦方式即为同步转异步,接纳主动的异步设计,简单地通过异步消息来串联流程即可办理几乎所有已知的问题,但EDA的应用应该远不止此。

子流程

子流程1:还款提交


子流程2 & 子流程3:代扣提交 & 代扣乐成


子流程4:外部核销


子流程5:内部核销


后续处理惩罚


如何办理问题

通过异步消息解耦流程

用户提交还款请求时,同步处理惩罚逻辑只做须要性的校验与防并发操作,校验通事后视为还款提交乐成,发送还款提交乐成事件消息后立即返回(消息投递乐成后由消息中间件包管至少消费一次),后续操作由还款提交乐成事件驱动,这样该接口只包罗一次查库、一次写库、一次与消息中间件的交互与若干内存运算,接口可靠性得以包管。
消费逻辑可重入的前提下,使用消息重试机制

笔者使用的消息中间件是RocketMQ,消费失败后会进入retry队列,按照所设置的指定时间隔断重复投递。基于这个特性,设置公道的时间隔断,能极大的节省异常处理惩罚的资本,实现流程级的自治。比方,上游重复提交,下游服务调用超时等问题,根据详细场景编写相应的异常处理惩罚逻辑后,均能通过重复消费的流程来驱动,能极大地节省生产问题处理惩罚的人力资本。
以代扣接口超时举例:
  1. // 代扣提交事件消费逻辑伪代码public void consumeDeductSubmitEvent(String deductFlowNo) {    // 更新代扣流水状态:初始化-->代扣处理惩罚中    int updateCount = deductFlowRepo.updateStatus(deductFlowNo, "INIT", "PROCESSING");    if(updateCount != 1) {        // 查询代扣流水状态        String status = deductFlowRepo.selectStatus(deductFlowNo);        // 通过数据库状态防止重复消费:打印warn日志,状态异常时直接丢弃消息        if(!StringUtils.equalsAny(status, "INIT", "PROCESSING")) {                        log.warn("DeductFlow[{}] status do not support to deduct.", deductFlowNo);                return;        }        // 预期之外的错误,打印error日志,抛出异常,需要人工查察原因,问题修复后挂起的单会通过重试完成        if(StringUtils.equalsAny("INIT")) {                        log.error("DeductFlow[{}] status update error.", deductFlowNo);                throw new RuntimeException();        }    }        // 发送代扣请求    DeductResponse deductResponse = paymentClient.deduct(deductRequest);    // 查抄异常是否为重复的流水号(当代扣接口不幂等时),如果是则查询指定代扣流水号的代扣结果    if(deductResponse.isSuccess() ||       deductResponse.isDuplicateFlowNo() && paymentClient.getDeductResult(deductFlowNo).isSuccess()) {                // do nothing    } else {        // 代扣明确失败,流程竣事        handleDeductFail(reason);        return;    }        // 更新代扣流水状态:代扣处理惩罚中-->代扣乐成        // 发送代扣乐成事件消息}
复制代码
上述消费逻辑中每一个逻辑点都设计为可重入的,那么整体的消费逻辑就是可重入的,重复消费不会带来一致性问题。如果下游系统接口设计为幂等,上游处理惩罚代扣结果的逻辑将更加统一:
  1. // 发送代扣请求DeductResponse deductResponse = paymentClient.deduct(deductRequest);if(deductResponse.isSuccess()) {    // do nothing} else {    // 代扣失败,流程竣事    handleDeductFail(reason);    return;}
复制代码
流程变更简单

当需要新增或下线子流程时,只需要调解相关子流程和所监听和发送的事件消息,不需要关注未发生变更的子流程逻辑。各个独立的子流程逻辑一目了然,维护简单。
不相关操作彻底解耦

通过注册独立的监听器来分别实现账务系统记账、短信通知用户、回调上游调用方等操作。
事件消息设计

RocketMQ消息发布和订阅是基于topic/tag的,同时其管理后台支持根据消息key过滤,这样,我们可以将一系列事件消息按topic组织起来,通过tag来标识,消息key指定为该事件的唯一标识,这样就可以很方便的根据key查找已生存的事件消息,并能方便地手动重发。
以还款场景举例,界说一个topic:topic_event_repay,界说一系列tag:submit/success,消息key即为还款流水号。
为制止消息泛滥,事件消息的消息体也存在相关的设计规范,但与本文相关性不大,这里不再赘述。
总结

如序中所言,本方案仅仅是将一个流程分解为各个子流程,并使用消息中间件通过异步化解耦,并不能称之为EDA应用的最佳实践,但这次重构带来的代价是毋庸置疑的。EDA思想以及DDD(范畴驱动设计)能为我们的架构设计提供指导,我们在学习它们的同时,也需要时刻思考我们面对的问题与它们的关联,我们学习和应用的出发点始终是为了办理已面对的问题,制止陷入“为了应用而应用”的误区。

来源:https://blog.csdn.net/wyy51y/article/details/111990779
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )