订单不是状态,是一串事件
一句话总结
订单的最终状态不是存储在某个地方的字段,而是通过按顺序折叠多个事件而形成的结果,如果没有设定折叠规则和折叠顺序,在同一日志中会产生不同的答案。
为什么需要这个?
进入证券公司支持系统,几乎没有例外,都会以这句话开始:“我收到了结算通知,但屏幕上显示为取消。”
第一次听到这句话时,会认为画面出了问题或者DB更新晚了。所以orders打开桌子status看体温。那里有cancelled上面写着。如果不知道接下来要做什么,调查就到此为止。
停止的原因是相信订单状态是一个值。实际上是这样的。
09:31:04.118 new 수량 300, 지정가 71,500
09:31:04.180 ack 거래소가 접수를 확인
09:44:12.007 partial_fill 150주 체결, 누적 150
09:47:55.310 cancel_req 잔량 취소 요청
09:47:55.402 fill 150주 체결, 누적 300
09:47:55.408 cancelled 잔량 취소 확정
在这六行中,必须找出“这个命令是什么”。只看最后一个活动,就是取消。只看结算数量,就是全部结算。两者都是这个日志中的事实,哪一个叫状态是我们规定的规则。没有明确规定的规则的系统,代码很多地方都写着不同的规则,所以屏幕、通知邮件和结算布局会给出不同的答案。
怎么行动
处理事件的函数大致是这样的。这里重要的是不是代码,而是每一行包含什么样的判断。
new 주문 수량이 정해진다. 상태는 '접수'
ack 거래소가 받았다. 상태는 '유효'
reject 거절. 여기서 끝난다
replace 주문 수량이 바뀐다. 상태는 그대로
cancel_req 아무것도 바꾸지 않는다 ← 여기가 첫 번째 함정
cancelled 상태는 '취소'
fill 계열 누적 체결에 더하고, 주문 수량에 닿으면 '체결'
**cancel_req不改变状态是核心。**请求取消和取消是不同的事实。请求是我们发送的,确认是交易所给的。请求时状态cancelled实际上,将改为换成这种实现方式是很常见的,这样一来,在请求之后附有结算的订单就会被取消。顾客收到了股票,但我们的账簿上却没有。
第二个陷阱是部分结算会多次发生。300周订单以150+100+50分成三次结算,这不是奇怪的事情,而是基本情况。每次结算只包含自己份额的数量,累积我们需要计算。交易所cum_qty虽然也会一起给,但如果那个值和我们测到的值不一样,那本身就是购买信号。
第三个陷阱是相同的签约会两次到来。如果会话中断或重新连接或请求重新发送,已经收到的签约会再次到来。所以每次签约都是唯一的exec_id附着着,用它过滤的是接收方的责任。如果不过滤的话,累积数量会膨胀,超过订单数量的是明显的,但没有超过的是没有任何警报的情况下留下错误。
在现场相遇的样子
一家证券公司每天凌晨都失败地安排夜间结算。日志中有六条消息说“成交数量超过订单数量”,负责人在每天早上用手把六条消息改过来。已经六个月了。
原因是没有经过重新发送的结算。而且真正的问题不是这六件。由于同样的原因,还有二十几件订单数量膨胀,但它们没有超过订单数量,所以没有留下任何信息。每天早上纠正的六件是冰山一角,下面六个月没有人计数。
另一个经常遇到的画面是状态字段和事件日志分开数年的样子。状态字段由实时处理器更新,事件日志保持原样堆积,如果处理器错过一次事件,该订单的状态字段将永远错误。事件不会再次流出,通常没有重新计算状态字段的步骤。因此,调查时不要相信状态字段,而是从事件中重新创建是第一步。
接下来在实习中要做的事情
首先在下面的理论中观察时钟和顺序,然后进行实践。
亲自制作900件订单的活动日志,只用那个日志重新构建每个订单的最终状态。然后与OMS持有的状态字段进行对比,寻找不一致的订单。只比较状态字符串时和比较成交数量时产生的数字都不一样,这个差异是这次事故的本体。