前瞻性思考###
缘由
新系统重构中,收获了一个重要的设计教训。
事情是这样滴,如下图所示:
有一个 Hbase 表 oe_item 存放订单商品相关的交易信息,rowkey 设计为 “订单号_oldItemID” ,是通过 storm 同步任务处理 old_item 表的binlog订阅写入该表的。oe_item 的量级很大。
在老的实现方式中,由于应用在访问这个表之前无法取到订单的oldItemID, 因此,需要拿到订单号去 scan 这个表。显然这个开销是很大的。当需要访问大量订单的 oe_item 数据时,并发访问这个表会导致超时,线程被hang住,进而影响系统整体稳定性。若有可能,应该干掉 scan oe_item 这个威胁系统稳定性的耗时操作。
系统重构后,通过详情API接口,能够获取到新订单及newItemID, 能够获取到老订单及oldItemId。显然,对于老订单来说,可以用{order_no}_{oldItemID}拼成 rowkey 来 batchGet oe_item 表; 然而,对于新订单,由于无法获取到oldItemID, 这使得要获取新订单 oe_item 表的信息,依然要 scan 这个表,而不能替换为 batchGet, —— 众所周知, batchGet 操作通常比 scan 操作的性能要更好,获取大数据量时稳定性更优 , —— 因此,错过了优化系统稳定性的一个关键环节。是不是很蛋疼 ?
教训
- 要敏锐地主动发现旧设计的一些过时之处。即使当时不能解决,也应记录下来,当机会来临时进行重构优化。
- 在新系统重构中,及时用新设计来取代和兼容老设计。在这个例子中,如果在系统重构中,将 rowkey 设计为 新订单 -> "{order_no}{newItemID}",老订单 -> "{order_no}{oldItemID}",就可以用 batchGet 取代 scan ,大幅提升系统在获取大数据量时的稳定性。
根本性解决方案###
在没有对整体设计足够清晰之前,不要急于着手去解决一个问题。 仓促地解决一个问题,会导致解决不足,继续受到该问题的困扰,之前的方案甚至会造成束缚。
敏捷设计并不是不做充分的思考和设计,而是强调不要“过犹不及”; 完成当前需要的,并为扩展预留空间。 这实际上需要更充分的设计考量。
比如做订单导出配置化时,我是采用小步优化逼近的方式来实现配置能力的。这固然也能解决问题,但是当面对新问题时,时而有某个地方考虑不周到,需要再修改代码和发布。 这即是因为在整体设计上没有思考的足够清晰,没有足够的融会贯通, 以致于总是纰漏百出。
因此,解决问题,要从整体设计上尽量考量得足够清晰,然后再下手去做。
不要兼容老方案###
这里说的是,当建立新模型时,不要去兼容老方案,否则永远都没法摆脱历史包袱。
比如做新退款信息获取的时候,当时贪急求快(也考虑到退款金额不对导致订单状态不对,会诱导商家误发货), 就沿用老方案的存储,将新退款的数据也存储在老的退款 HBase 表里, 结果新老退款都需要去 scan 这个表,性能开销甚大,而且对稳定性有影响。
正确的做法应该是, 针对新模型,建立新方案的存储,然后在应用中聚合两种方案,分流,冷却老方案的存储和获取,一段时间后,就会自动切换到新方案上,摆脱老方案的历史包袱。
建立良好约定###
约定胜于配置。 良好的约定,可以使得系统的交互和整合更加简洁清晰, 而不需要考虑过多的情况,导致复杂度上升。
比如导出扩展字段时,约定扩展字段在下单表里的存储形式,然后导出按照这种约定去获取相应数据。 除非下单存储有误,否则导出是不会有问题的,省了很多事。
首尾呼应###
在设计存储的时候,就要想到如何去使用。
Think Deeper, Design Better.