文件集成的事故发生在边界
一句话总结
文件对接不是过时的方式,而是大批量、结算、对账的标准方式。安全接收文件的关键有三点——只取走写完的文件(完成标志),确认取回的内容与发送的一致(字节数、校验和、尾记录),运行两次结果也相同(远端标记、按文件的入库记录)。而且最后要与我们的账本核对(对账)。
为什么需要它
保险理赔数万笔、信用卡收单凭证、工资转账明细、日终结算——这些都不会逐笔调用 API。而是把一天的数据做成一个文件,在规定的时刻交出去,接收的一方一次性入库。对方机构不开放 API 的情况也很多,而最重要的是,文件能让当天的边界变得清晰。“9 月 23 日的理赔”就是一个文件,核对笔数和合计,如果对不上,就整个文件重新拉取。
然而,文件对接的事故几乎全都发生在边界上。取走了对方还在写的文件,只入库了一半。把传输中损坏的文件在不知情的情况下放入。重新运行批处理,又把昨天的文件放进去,理赔被记了两次。入库了,但与我们的受理账本有三笔不同,要到一个月后的结账才知道。本模块的各个步骤与这四种事故一一对应。
工作原理
SFTP。SFTP 是运行在 SSH 连接之上的文件传输协议。在生产环境中用 sftp user@host 连接对方服务器,此时 SSH 会校验对方服务器的主机密钥(对第一次见到的密钥不询问就直接接受的设置,会给中间人攻击留下机会)。自动化批处理无法交互式地一问一答,所以要用 -b <배치파일>(占位符为批处理文件)传入命令清单,在批处理模式下,只要有一条命令失败就会在那里停止(sftp(1))。
本实验在一个 Pod 内运行。我们不是把合作方的 SFTP 服务器连同 SSH 服务器(sshd)单独搭建起来,而是使用 sftp 的 -D 选项——因为本实验要学的不是 SSH 服务器的运维,而是收发文件的规则。手册把 -D 说明为“不经过 ssh,直接连接本地的 sftp 服务器”。SFTP 协议(列目录、下载、重命名)与生产环境相同,省去的是 SSH 连接、认证和主机密钥校验。使用生产批处理时,别忘了这三者必须加上。
完成标志。是在文件全部上传之后再上传的一个小标记文件。接收的一方只取走有标志的文件。如果在标志里写上字节数和 sha256,连传输中的损坏也能抓到。SHA-256 是一种哈希,输入只要差一个比特,就会得出完全不同的值,所以能把大小相同、内容却损坏的情况筛选出来。
原子重命名。如果把接收的文件直接写成最终名称,接收途中别的批处理(入库)就可能把那个文件拿走。要先用临时名称(.part)接收完毕并校验,再改成最终名称。POSIX 的 rename() 在同一个文件系统内,会以原子方式改变目标名称——其他进程看到的只是旧状态或新状态,看不到中间状态(rename)。移到其他文件系统的 mv 是复制后删除,没有这种性质。所以临时文件要放在与最终目录相同的位置。
重复运行也一样。批处理一定会被重新运行(失败之后、故障恢复之后、有人失误)。取回的文件要在远端的名称末尾加上 .fetched 来标记,入库则以文件名为单位记录(load_log),避免同一个文件放进去两次。入库时一个文件就是一个事务——要么全部进去,要么一条也不进去。中途如果出现理赔编号重复的行,就回退到前面的行。只入库了一半的文件,即使重新运行也无法修复。
头记录和尾记录。文件内部也有边界。H(头记录)写明这是什么文件(日期、收发机构),T(尾记录)写明共几笔、合计多少。接收的一方要自己数 D 行并相加,与尾记录核对。没有尾记录,说明文件没有完整到达。行长度要按字节来量——含有韩文名字的行,如果以 UTF-8 重新保存,字看上去一样,行却变长了(与第 1 模块相同的陷阱)。
对账(reconciliation)。入库并不是终点。用理赔编号把合作方文件和我们的受理账本对起来,分为双方都有且金额相同的(一致)、只有合作方有的、只有我们有的,以及金额不同的。不一致的通常是业务事件——要么是我们漏了受理,要么是合作方没有扣除取消的部分。对账结果是供人查看的报告,所以按理赔编号排序,金额要把双方都展示出来。
在现场相遇的样子
最常见的事故,是只相信“20 点之前上传”这个约定,在 20 点就去取的批处理。某一天合作方服务器变慢,20 点 01 分才写完,而我们入库了一个只有一半的文件。没有完成标志、只靠时刻来同步,一定会有一次这样。第二种是重新运行事故。故障恢复负责人“以防万一”又运行了一次批处理,由于没有入库记录,同一个文件被放进去两次。第三种是不做对账。对接以“没有错误地结束”来判断成功,而业务以“数字对得上”来判断。最后,在生产 SFTP 批处理中,有人图方便加上了关闭主机密钥校验的设置(StrictHostKeyChecking=no),这意味着在不确认对方服务器的情况下收发理赔数据。
下一项实验要做什么
用合作方夹具创建远端目录,并通过 SFTP 批处理查看列表。然后逐步扩展接收脚本 fetch.sh——只取有标志的文件、校验和比对与隔离、临时名称与远端标记。接着制作头尾记录校验器 check.py、按文件入库的 load.py 和账本对账 recon.py,并用今天的文件输出对账报告。