1、写在前面
在企业级应用架构的语境下,流水号具有穿针引线的作用。这里说的企业级有两层含义,一是多应用,多数企业都有一组职能定位各不相同的应用系统提供IT支撑;二是这些应用系统是一个有机整体,而不是相互隔离的。服务的调用和数据的简单传递并不是企业级的充分表达,通过多个职责不同的应用系统配合、协作而完成一系列相关服务场景,这些应用系统才能成为企业级。
银行应用系统是企业级的一种典型应用,其每一个服务场景都需要通过交易渠道、客户信息管理系统、渠道整合、流程审批、业务处理、记账处理、核算处理、客户通知……等一系列应用系统协同,当用户在页面上的一个操作涉及到后台的一系列系统服务集成的时候,就需要一种流水号的设计体系对这些服务进行串接,记录在整个交易场景下各个应用的服务调用状态,以便于时候的查询、统计、分析、补录、基于原服务的二次交易等后续场景的使用。
探讨对象
- 本文讨论的是企业级应用架构体系下、针对多应用系统的的流水号设计方案,不是针对单应用系统的;
- 本文重点在于讨论流水号的应用层面的解决方案,不涉及代码实现;
- 本文重在基于笔者项目经验的探讨和思考,而不是直接应用于项目的指南和结论;
- 本文以笔者所在的金融IT行业切入讨论,但设计思路可应用于一般性企业级架构;
流水号的使用目标
我们简单梳理流水号的应用场景,以作为流水号设计方案的靶向标的:
- 凭证打印:由于金融行业的特殊性,纸质凭证仍然作为非常重要的单据在各个场景中使用,包括业务办理、账实核对、对账等过程。其中一个非常重要的依据便是流水号,凭证上一般有两个流水号,凭证本身的流水号和业务流水号。凭证本身流水号是印刷时就确定的,不在本次讨论范围;业务流水号是系统生成后打印到凭证上的,作为银行整个业务处理周期的唯一索引,是我们的重点讨论对象;
- 业务跟踪:在一个以业务办理、交易处理为主体的应用系统体系中,流水号一旦生成便伴随当前业务的整个生命周期,一直到数据归档为止,后续对于原交易状态和数据的查询、基于原交易的后续业务办理、跨系统的数据整合、交易链路的还原、各系统对服务的响应情况、IT部门对于问题的调查与定位等都是基于该流水号。
2、SOA架构体系下的服务访问模式
场景与服务
SOA架构体系是应用系统专业化、复杂化发展的必然结果。早期银行只有一个综合业务系统,所有的业务都在一个系统里实现,随着业务发展越来越复杂、越来越精细化各类专业业务系统由此诞生,原来一站式服务调用不适用了,要完成一个应用场景需要穿透三四个、乃至七八个应用系统。对于每一个后端应用系统来说,都只提供单一的、独立的、原子的服务,而对于渠道和客户来说提供的是一个场景,这时候就需要使用流水号将对服务的调用和场景关联起来。这有点类似于浏览器的无状态请求通过sessionid进行关联类似,当然不完全相同。
我们看一个典型的SOA服务调用场景。
渠道应用是面向客户服务的,所以其服务的颗粒度是场景级的,比如说:渠道对客户提供的是一个开户服务(图中的系统A),而不会关注客户在哪里建立、卡在哪里建立、账户在哪里建立等。而原子服务层应用便是后台的专业应用系统,就是我们上文说的客户、卡、账户管理的系统(图中的系统C、D),但是这些系统随着专业化、服务化、原子化,其上业务场景的属性逐渐削弱(理想状态下是没有场景);这时候,当当当当……服务整合层强势登场,它提供了一个从原子化的服务到面向客户场景的桥梁,对同类场景进行整合发布,即是面向个性化业务需求的专项业务系统。在联创智融,我们叫他业务整合平台,它提供了可视化的开发平台、完善的高并发、高可用与异常处理机制、成熟的监控运营平台,通过提供完整的服务整合框架让应用开发从技术细节中脱离出来,专注于应用场景的开发,充分利用现有应用系统服务能力实现差异化的业务需求……(不好意思,没忍住,插播一段广告)
流水号的颗粒度
之所以要铺垫这么多,是因为流水号的设计基本思路是对SOA架构体系的落地。分析上图流程,客户直接使用的是渠道类系统A,我们假设S001,代表此次请求;应用系统之间还有多次服务调用,假如A->B是A001,B->C是B001,B->D是B002。很明显,客户的一次服务请求是通过后台的三次服务交互完成的,客户的请求S001和后台的请求A001、B001、B002性质完全不同。客户的请求是基于应用场景的,是一个开户请求、转账请求;后台的服务调用是基于应用服务的,是一个客户核验、记账请求、人行转出请求。为了让整个应用链路都保持S001的服务请求状态,需要将S001向下游每一次服务调用传递,并和A001、B001、B002建立映射关系。
我们将对客户展现的、面向交易场景的流水号S001,叫做全局流水号;将面向服务请求、代表一次服务调用的流水号A001叫做请求流水号。全局流水号有在一个请求链条中保持不变的含义。其中请注意全局流水号和交易场景以及客户服务的区别,详见下图。一次客户服务指的是客户一次业务办理的整个生命周期,可以称为一个会话或者一个session,在这个会话周期内,客户可以办理多个业务(活转定、理财、取现……)而不用重复验证身份,新一代银行可以做到一次支付、一次验密、一次打印等。一个交易场景就是客户具体办理一个业务(存款),其中又可能涉及对后台的多次服务请求,比如客户身份核验就是在正式办理业务之前的一个预处理的步骤,但是也需要柜面和后台交互,这样在一个交易场景内,至少有两次到后台的用户请求。
客户会话级和交易场景级的流水号本次话题并不涉及,因为这两个状态主要在渠道类应用保持和使用,对于整体的应用架构体系并没有要求。
场景化的内容是否适合放到后端业务系统?
作为引申话题我们稍微展开一下:“场景化的业务内容是否适合放到后端业务系统?”。所谓场景化的业务指的是面向客户服务,而不是面向业务处理的业务逻辑,比如说客户校验、代理人信息、授权流程等。在很多银行的业务系统中,这些内容都在核心,这是历史遗留包袱,从上面的简单分析来看,这些内容都应该在场景层(也就是业务整合层)去实现。如果放到后端系统,会有几个问题:1、面向场景的业务处理要重复实现,没有必要且浪费效率;2、在专业化趋势下后端业务系统(主要是金融产品系统)并不需要此类业务信息,和本身的业务处理逻辑无关;3、从技术角度,场景化的内容如果放在后端,与之对应的将是全局流水号,但是后台系统的服务调用都是基于请求流水号,这个矛盾对于该笔流水的后续使用会产生很多不利影响,比如修改、数据整合等等。
3、服务调用类型分析
本章从服务调用类型分析流水号的使用和关注的问题。根据个人经验对服务调用类型总结如下:
- 正向调用;(在用户请求正常提交的情况下服务链路的调用)
- 反向调用;(正向交易失败或者根据业务要求,对原业务做回冲处理)
- 其它后续服务调用; (不是按照正向服务调用链路调用的情况)
- 批量服务;(在一次服务提交中涉及多笔业务明细的情况)
正向调用
正向调用没什么特别需要说明的,就是一个正常的服务场景的调用。但是会涉及服务调用失败的情况下的重试情况,这时候要求下游系统能够辨别出重试交易,所以对于重试类服务,要求上游系统不能生成新的请求流水号,继续使用原请求流水号,如果下游系统上次服务调用时没有成功,则重新完成本次服务;如果已经成功完成,但是由于返回超时等原因,则不重复执行,直接返回交易已成功。
一些基本的规范说明:
- 全局流水号由渠道生成,并在整个服务调用链路上保持不变;
- 在所有场景下,对客户(或者用户)展示的都是全局流水号,请求流水号只会在全局流水号不能区分的时候才使用;
- 请求流水号由上游请求系统调用下游系统时生成,代表唯一一次服务请求;
- 全局流水号和请求流水号都放在报文头中,始终传递;
反向调用
这里的反向调用指的是原服务已经成功处理(也可能没有成功处理)的情况下,根据业务规定或者用户发起对原始服务进行逆操作,一般叫这类服务为冲正,对应的账务处理是抹账。在SOA架构体系下,需要遵循原路来、原路冲的处理原则,否则会因为找不到原始服务调用请求而冲正失败。
从数据完整性上也应该将交易链路中所有节点都做冲正操作,还原业务状态、回写账户、恢复额度等处理。所以从原则上,支持客户冲正的场景,在所有服务请求链路上,都应该提供相应服务的冲正服务,以便基于场景冲正时进行调用。
- 冲正使用全局流水号还是请求流水号?
冲正是一个业务概念,可以是客户发起的,也可能是系统在交易失败后自动发起的。对于客户发起的冲正,其面向的是一个完整的交易请求,也就是全局流水号;对于系统间自动冲正,其面向的是一次服务调用,所以使用请求流水号。也就是说,需要在人机交互切面上(渠道),进行全局流水号和请求流水号的转换,客户在主动发起冲正时,输入的是全局流水号,而渠道需要根据此全局流水号,找到原始的请求流水号,并使用此请求流水号向下游系统发起冲正。下游系统再转换为本系统的请求流水号发起后续冲正服务调用,以此完成整个交易链路的反向冲正。
系统间冲正应使用请求流水号的第二个原因是,部分业务场景可能涉及到对统一系统的多次服务调用,而冲正时可能只冲正部分服务。这时候如果使用全局流水号就无法将前后多次调用的请求区分开,比如某些银行在跨分支行记账时,对记账失败的流水只冲正当前节点,而不是对整个交易请求进行冲正。
也有一些银行系统为了简化系统调用关系,只冲账务性流水,而对中间的业务状态(比如中间业务的状态、支付的状态等)并不关注,为了简单起见,直接到核心冲正站账务流水,这种方式也叫抹账。根据我们上面的分析,这时候如果使用请求流水号则找不到当前业务的交易流水,需要使用全局流水号进行冲正。
总结:原则上,对于冲正业务,客户输入的应该是全局流水号,而系统服务调用应使用请求流水号;但是不能一概而论,需要根据业务场景分析是对整个业务场景的冲正还是一次服务调用的冲正。
其它后续服务调用
基于单笔业务流水的后续操作,比如对原业务流水的查询、补录、修改等业务动作,严格来说冲正也属于这类操作;将冲正与此类业务分开主要是因为此类业务可能与原服务调用链路不一致。参考下图,有可能原始业务出于组装服务场景的需要,经过了B系统,而后续业务因为指向性、针对性较明确,不需要复杂的处理逻辑,这时候可以直接调用C系统的其它服务。
比如说:
- 记账流水查询业务,记账有可能从很多外围的业务系统发起到核心记账,而查询则只需要到核心查询即可;
- 存款代理人信息维护业务,该业务是对存款业务相关的代理人时候维护的,需要到核心登记(历史包袱);存款业务办理有几十个场景,而代理人维护则只需要直接到核心即可;
对于此类业务,除需要传递本业务的全局流水号、请求流水号以外,还需要将原业务的流水号作为服务调用参数传递。
这类服务应使用全局流水号还是请求流水号?
这又是一个没有标准答案的问题。如果后续业务的服务调用路径和原业务调用路径一致,比如说原业务是A->B->C->D,后续业务也是A->B->C->D,那么此时可以使用“原业务请求流水号”作为服务参数;如果调用路径不一致,比如说后续业务是后续业务也是A->B->D,那么使用“原业务请求流水号”作为调用参数则找不到原业务流水,更无从谈起后续业务。参考上图,其实是一个错误的示例,你发现了吗?🙃
批量业务服务
批量业务指的是前台提交一个文件,或者直接在页面上录入多笔,进行提交的场景。与单笔业务不同的是,批量业务中一个业务流水号对应多个业务明细,为了标记每一笔业务明细,需要对每一笔业务明细都生成独立的流水号。这涉及到两种模式:真批量和假批量。
- 真批量:后端业务系统提供的服务接口中,接收的是批量文件或者列表明细数据,在前端到中间系统的整个服务链路中都不对批量文件进行解析,由终端后台应用进行批量到明细的拆解。
- 假批量:与真批量对应,批量文件或者明细任务的拆解有前端应用或者中间应用拆解,并以单笔服务循环调用的方式向后端应用发起。
在此我们不讨论两种模式的优劣,从对流水号的应用来说,批量数据在哪里拆解,就会在哪个系统产生流水号与批量数据明细号的对应关系。如果在前端拆解,则全局流水号和业务明细是一一对应关系,如下表。
业务明细序号 | 客户号 | 其它属性…… | 全局流水号 |
---|---|---|---|
1 | C001 | …… | QJ……001 |
2 | C004 | …… | QJ……002 |
如果在后端拆解,则全局流水号和业务明细是一对多的关系,这时候与业务明细对应的是后台的系统内业务流水号,如下表。
业务明细序号 | 客户号 | 其它属性…… | 全局流水号 | 后台系统流水号 |
---|---|---|---|---|
1 | C001 | …… | QJ……001 | CORE0001 |
2 | C004 | …… | QJ……001 | CORE0002 |
好,该死的问题又来了:应该使用真批量还是假批量?
这一次我们可以做一个有倾向性的回答:请尽可能的使用真批量。从业务角度,批量业务是后台应用本身应提供的一种服务;从技术角度,假批量对性能、网络消耗、客户体验等方面都不佳。从流水号使用的角度,对批量业务的查询应基于全局流水号和批量业务序号两个条件查询。如果是对后台交易流水的查询,则可以通过全局流水号查询出整个批量信息后,再对具体某笔后台流水定位。总之在后台实现批量没有任何业务逻辑上的困难。
TO BE CONTINUED……