正文
第一版的性能瓶颈在统计结果持久化上。为了确保数据的准确性,把所有的统计指标持久化放在一个数据库事务里。一笔订单状态更新后,会在一个事务里有两类操作:
为此做了数据库事务的瘦身:
-
去除历史状态的 mysql 持久化,而是通过单条 binlog 消息的前后状态对比,决定统计逻辑,这样就做到了统计逻辑上的无状态。但又产生了新问题,如何保证消息有且只有处理一次,为此引入了一个 redis 用于保存最近 24 小时内已成功处理的消息 binlog 偏移量,而 Storm 的消息分发机制又可以保证相同消息总是能分配到一个 bolt,避免线程安全问题。
-
统计业务拆分,先是线上业务和公司内部业务分离,随后又把线上业务按不同产品拆分。这个不仅仅是 bolt 级别的拆分,而是在 spout 就完全分开。
-
随着统计应用拆分,在 canal 和 Storm 应用之间加上消息队列。canal 不支持多消费者,而实时统计业务也不用关系数据库底层迁移、主从切换等维护工作,加上消息队列能把底层数据的维护和性能优化交给更专业的团队来做。
-
热点数据在 mysql 里做了分桶。比如,通常一个店铺天级别的统计指标在 mysql 里是一行数据。如果这个店铺有突发的大量订单,会出现多个 bolt 同时去 update 这行数据,出现数据热点,mysql 里该行数据的锁竞争异常激烈。我们把这样的热点数据做了分桶,实验证明在特定场景下可以有一个数量级吞吐量提升。
最终,第二版的订单实时统计结构如下,主要变化在于引入了 MQ,并使用 redis 作为消息状态的存储。而且由最初的一个应用,被拆成了多个应用。
经过第二版的优化,实时统计的吞吐量已经不成问题,但还是遇到了做大数据最重要的准确性的问题:
-
统计口径是会变化的,同样是 GMV,一年前和现在的算法可能有变化。例如一笔货到付款订单,是买家下单算成交,还是卖家发货成交,在不同的时期可能使用不同的算法。
-
实时统计只能按照当时的算法来做计算。有可能出现一段时间周期内的 GMV,前一段是按旧算法来计算,后一段按新算法来计算,提供的数据就不准确了。
-
实时统计难免会出现 bug,有不准确的结果,修复错误数据是个难题。