枢纽用路由表决定报文发往何处
一句话总结
枢纽只看报文头中的交易码和接收机构来确定目的地。这个判断不放在代码里,而放在数据(路由表)里,把规则重叠时谁胜出写进文档定死,而对哪里都发不出去的报文,不要吞掉,而是立即以错误响应返回。
为什么需要它
正如第 1 模块所见,系统之间直接相连的点对点连接,会随着系统数量增加而使连线按平方增长。枢纽辐射(hub-and-spoke)结构把所有连线汇集到一个枢纽上,作为代价,要让枢纽新承担一项工作——这个报文该交给谁。Hohpe 与 Woolf 的《Enterprise Integration Patterns》把这个角色称为 Content-Based Router。它是一个根据消息中的数据(有没有某个字段、某个字段的值是什么)来选择发送通道的组件。我们的枢纽只看其中报文头的两个字段。
同一篇文章还有一个警告。路由器容易成为目的地每次变化都要修改的频繁维护点,如果规则复杂,可以选择通过可配置的规则集来计算目的地的形式。在银行枢纽中,这个警告就是现实。交易码每个月都会新增,系统一迁移,几十个交易码的目的地会同时改变。如果把路由写成 if tx_code == "BKTR0001" 这样的代码,每增加一种交易都得重新部署枢纽,而枢纽的部署会让该银行的所有交易都短暂地受到冲击。所以要把路由规则抽成表(数据),枢纽代码只负责“读表并按优先级挑选”。表经常变化,而解释规则的方式很少变化——这就是把常变的与少变的分开。
工作原理
源头是业务部门的接口清单。哪种交易发往哪个系统,往往不是由开发人员,而是由业务负责人用 Excel 来管理的。这份清单里混有已下线的接口、开发中的接口,以及同一种交易的旧版本。枢纽使用的路由表,是对它整理之后的成果。只载入状态为运行的行,如果同一个交易码有多条,就只保留版本最大的那一条。这里常见的陷阱是版本比较。从 Excel 导出的 CSV 中,版本是文字,所以如果按字符串比较,"9" 会比 "10" 大。必须转换成整数再比较。
表中写的不只是“去哪里”,还有“怎么去”。路由表的一行中,除了目的地,还有同步、异步的区分和超时。超时因交易而异是有理由的。余额查询必须在几秒内答复才有意义,而保险理赔文件的提交,花几十秒才是正常的。如果把所有交易统一成一个值,设短了,理赔全都会显示为失败;设长了,枢纽的连接会为了等待停住的账户系统而堆积。超时是交易的性质,所以要与交易码放在同一行。
规则有四条,并且有顺序。本课程的虚构标准(ROUTING.md)是这样规定的。
| 顺序 | 规则 | 条件 | 为什么排在这个位置 |
|---|---|---|---|
| 1 | ORG | 接收机构不是本行 → FEP | 发往其他机构的报文,必须只通过汇集了对外专线和各机构格式的那一个出口发出 |
| 2 | EXACT | 交易码完全一致 | 例外要用最具体的规则来写 |
| 3 | PREFIX | 最长前缀一致 | 用 CD* 把整个业务群归在一起,但像 CDLN* 这样的下级分组胜出 |
| 4 | NONE | 什么都不匹配 → E101 | 不要猜测着发送 |
“具体的胜出”这个想法与 IP 路由相同。RFC 1812 写道,路由器在选择路径时,先选特定主机的路径,然后按前缀由长到短的顺序选择网络前缀。如果 CD* 和 CDLN* 都匹配,更长的一方就是了解更多信息的规则。
优先级不应该是表的行顺序。如果按“从上往下第一个匹配的行”来实现,行顺序就成了隐藏的规则。只要有人把 Excel 按交易码排序后重新导出,CD* 就会排到 CDLN* 的上面,卡贷报文就会悄悄地发往银行卡系统。没有任何错误提示。所以优先级要由代码显式地计算——先找完全一致的,前缀按长度排序。
无法路由的,要以响应返回。如果枢纽收到表中没有的交易码,只留日志就把报文丢掉,发送的渠道就要一直等待响应,直到超时才知道失败。在这期间客户的画面一直在转圈,如果渠道还重发,同样的白跑一趟会发生两次。枢纽会把收到的请求翻转过来,立刻生成带有响应码 E101 的响应报文(与第 1 模块的生成响应完全一样——保持交易码和 GUID,互换机构)。而对于格式错误的报文(E102),就不能生成这样的响应。报文头本身不可信,GUID 也就不可信。
每一次决定都要留下审计日志。要回答“这笔交易为什么发往了信息系统”,不仅需要目的地,还需要哪条规则匹配了(EXACT、PREFIX、ORG、NONE)和 GUID。日志只追加。会被覆盖的审计日志,就不是审计日志。
在现场相遇的样子
最常见的事故是过宽的前缀吞掉新交易。在有一条发往银行卡系统的 CD* 规则的状态下,出现了应当发往账户系统的新交易 CDLN0010,如果忘了在表里加入完全一致的行,这笔交易就会在没有错误的情况下发往银行卡系统。银行卡系统不认识这笔交易而拒绝,故障会议上先怀疑的就是银行卡系统。如果审计日志中打印着 PREFIX,原因一行就能看出来。本来期望完全一致的交易却以前缀方式发出,这件事本身就是信号。
第二种是已下线的接口留在表里。迁移系统之后如果不删除旧的行,有一段时间没有人向那个交易码发送,所以很安静。某一天有人重新使用了那个交易码,流量就会发往已下线的系统。这就是要设置状态列,并机械地坚持“只载入运行中的”的原因。第三种是像本模块的版本比较陷阱那样,整理清单的脚本中的细小失误。制作出路由表之后,要运行一次与原始清单核对的测试。
下一项实验要做什么
整理业务部门凌乱的接口清单,制作路由表 routes.csv,并一步步扩展路由器 router.py——完全一致、前缀(长的胜出)、基于机构的规则、按报文头路由、E101 响应、审计日志。评分器每次都会用新的交易码和目的地名称生成随机表来运行路由器,所以如果把规则写死在代码里,是无法通过的。最后分派收件箱中的 30 笔。