作者:京东零售 李孟冬
VOP作为京东企业业务对外的API对接采购供应链解决方案平台,一直致力于从企业采购数字化领域出发,发挥京东数智化供应链能力,通过产业链上下游耦合与链接,有效助力企业客户的成本优化与资产效能提升。本文将介绍VOP如何通过亿级消息仓库系统来保障上千家企业KA客户与京东的数据交互。
消息(仓库)作为电商业务场景必不可少的核心功能,自VOP上线以来,就开始了建设和演进迭代之路。截止目前,VOP消息仓库已接入200+内部消息端,对外提供80+消息,服务3000+企业客户,覆盖商品、地址、发票、订单、售后、物流等VOP所有业务场景。
消息系统中,一般有两种消费模式:服务端推送和客户端拉取。本文除了对于消息仓库的技术架构演进做对应叙述,重点介绍当前客户端拉取的消息仓库建设实践经验。
以商品消息为例,京东企业业务目前大约有5600W+商品,这些商品涉及基本信息、价格、库存等的变更,客户侧会通过消息API主动获取商品变更消息,并通过查询实时商品信息接口来获取对应信息,同步本地商品库,业务处理完毕后,删除这一批商品类消息,定时循环。其他类消息同理,不多加描述。
和我们所了解的系统一样,随着业务发展和企业客户规模的增多,消息仓库整体架构和底层存储系统都逐渐出现瓶颈。特别在于数据库方面,毕竟在高并发读写的场景下很大一部分工作是围绕数据库展开的,所以前期两次的升级迭代主要需要解决的问题也是如何提升数据库容量。
虽然最初我们也通过读写分离等手段来有效降低数据库的负载,提升系统容量和稳定性,但是其缺点也是极其明显:主从延迟、从库数据量有限、TPS高 等问题无法妥善解决。
并且,随着618、1111等各种活动的开展,且VOP侧客户的不断增加,消息激增成为我们不得不尽快面对的问题,限流、缓存等手段随能保证系统的高可用及并发能力。但是消息大量积压、消费水平有限、消息同步不及时等问题越发严重,随之带来的就是对业务有损,所以我们在评估后,对系统进行升级,通过分析最掣肘我们的核心原因还是在于数据库。(此时消息表行数亿行,容量超过10G)
因此在读写分离无法不能满足我们的业务需要时(已经历过数据归档),分库分表的模式也就需要登上舞台了。具体如何分库分表,注意事项等我就不多加赘述了,感兴趣推荐翻阅菜鸟积分系统的分库分表实践https://mp.weixin.qq.com/s/uFgSe59XP7RoXLTmm3u0KQ
由于是多master的架构,分库分表除了包含读写分离模式的所有优点外,还可以解决读写分离架构中无法解决的 TPS 过高的问题,同时分库分表理论上是可以无限横向扩展的,也解决了读写分离架构下从库数量有限的问题。
当然在实际的工程实践中一般需要提前预估好容量,因为数据库是有状态的,如果发现容量不足再扩容是非常麻烦的,应该尽量避免。
在分库分表的模式下可以通过不启用查询从库的方式来避免主从延迟的问题,也就是说读写都在主库,因为在分库后,每个 master 上的流量只占总流量的 1/N,大部分情况下能扛住业务的流量,从库只作为 master 的备份,在主库宕机时执行主从切换顶替 master 提供服务使用。
优化后的前期情况是美好的,无论从客户角度还是从内部消费水平都得到了大幅提升,其跳点和峰值消息下高TPS影响CPU等问题都得到了解决,整个消息仓库性能和稳定性趋于稳定。
为什么说前期情况好,相信大家都有所预料了,虽然分库大幅提升了系统整体的吞吐能力和稳定性,但是由于前期的容量评估问题(业务增长加剧)及本身现有架构的局限性(单体应用),在仓库稳定运行一年左右,又出现了一些显而易见的痛点问题:
不破不立,为避免消息问题长期以来的频繁影响及其他系统雷同的消息需求,我们急需打造一套可复用可扩展的企业消息中心,在满足业务的同时,还需综合考虑可用性、低成本、高吞吐和强扩展性,并且在迁移过程中保证消息不丢失和客户无感知。
经过多方调研和排查之后,初步选取了2种存储方案:MySQL+es和MongoDB。
我们在存储成本、开发运维成本、性能对比三个方面进行评估Mysql+es和MongoDB的方案。(仅供参考,具体仍需根据自身业务评估)
没有完美的架构,只有刚好的架构,没有满足一切的架构,只有满足目标的架构
综上分析,MongoDB不仅完全满足业务需求,同时在其他方面也优于其他方案,因此最终选用MongoDB分片集群作为了最底层的数据存储方式,并对系统架构重新梳理,分为四个阶段:消息接收阶段,消息中转阶段,消息写入阶段 ,消息可视化阶段,主要职责如下:
补充:MongoDB分片集群无单点故障的原因——当 MongoDB 被部署为一个分片集群时,应用程序通过驱动,访问路由节点, 也就是 Mongos 节点 Mongos 节点会根据读写操作中的片键值,把读写操作分发的特定的分片执行,然后把分片的执行结果合并,返回给应用程序。那集群中的数据是如何分布的呢?这些元数据记录在 Config Server 中,这也是一个高可用的复制集。每个分片管理集群中整体数据的一部分,也是一个高可用复制集。此外,路由节点,也就是 Mongos 节点在生产环境通常部署多个。这样,整个分片集群没有任何单点故障。
消息仓库V3.0给我们带来的成果也是十分显著,高标准达到了预期的目标:
之前我们一直铆足劲的往前追赶,现在系统稳定,为实现未来客户和商品的增量对消息仓库无影响&稳定运行3年+的目标,我们决定在限制资源有限性的情况下,转换角度思考问题和优化目标。随即我们针对消息数据开展了几个专题的治理,核心围绕流量治理、系统稳定性建设、降低成本三个方面出发。
锁定目标定后,剩下的只是迈步朝它慢慢走下去。
1)优化业务场景,从源头减少调用量,梳理系统流程,优化无效数据源的接入,历史空跑逻辑等。
2)a、无效客户管控(LoadingCache),由于其他端外界客户接入VOP,存在部分不消费消息的无效客户,需进行主动屏蔽,以此解决无效客户消息中转存储的问题。b、缓存,减少耗时操作等等。
3)消息过滤器(jimdb),通过防重控制+时间窗口对客户未消费且重复sku进行去重,以此解决客户消息消费延迟,客户消息量大,重复消息多,客户系统重启后消息量巨大的问题,并大幅减少我侧MongoDB存储数据量。
这里补充一个小插曲,在流量治理过程中,我们也在数据中发现了一些问题,并作为指导我们产品优化的数据支撑,通过技术手段进行优化和处理。**如:通过数据分析,我们在整个消费过程中,部分客户(如:联通)消费较慢或者无效消费导致信息同步不及时的问题,因此从技术角度出发与客户技术侧沟通,通过建立自动补推功能,来提升客户与京东的同步率,即通过自助补推功能,来辅助客户同步异常情况下二次同步,以价格变更为例,通过客户下单价格不一致,来自助补推价格变更消息,以此挽回由于客户同步异常导致异常的订单,提升客户成单率, 进一步提升整体GMV产出。
这里也给我带来思考,无论引入还是自研,无论架构还是工具,落到实处,真实解决业务中的问题,在降本增效中带来价值,不论大小,均为创新。
1)提高资源利用率:优化部分代码结构,如:通过list.contains()转化为set.contains()将其时间复杂度由O(n)降至O(1)、比较耗时或者不必放在主流程中执行的任务异步处理、单个写转化为批量写、减少传统重量级锁使用操作系统互斥量带来的性能损耗等等,以此解决大流量下,机器 cpu飙升影响整体性能的情况。
2)a、主动降级队列:前面有提到MongoDB设置租户id的分片规则,所以在单客户频繁进行大量商品池操作时,会发出该客户的大量商品出入池消息,由于当前整个系统吞吐性能极佳,所以在写入MongoDB时,会造成单分片的热点写问题,所以设定主动降级队列。具体实现为 在消息仓库多租户场景下,不影响整体客户的情况下,配置化(某客户+配置详消息类型)的进行异常客户的过载流量隔离,来保证底层存储介质的服务质量,即异常流量超过阈值则进入降级队列。 b、JMQ消费线程调优等
serverless自动扩缩:采用秒级消息接收量阈值和机器CPU阈值来触发自动扩缩策略,通过调优后非大促期间消息仓库整体资源成本下降52%。
目前的消息仓库从正式服役到通过不断的迭代和更新已踏入V3.0+版本,成功经历了四次大促,系统各项性能指标稳定。以最近的大促为例,22年双十一开门红,消息相关接口性能稳定,MongoDB整体写入QPS 2w ,查询QPS 4.3w。 并且通过评估能完全应对接下来独立场切换带来的消息增长情况。
在消息仓库整体架构演进升级的过程中,虽然基础中间件给我们提供了各种高可用的能力,但可用性最终还是要回归我们业务架构本身。业务系统需要根据各平台业务特性尽可能选择最优的可用性方案,并在系统架构中遵循一些原则,如最大限度减少关键依赖;消除扩容瓶颈;预防和缓解流量峰值;过载时做好优雅降级等等。而且更重要的一点是,我们需要时刻思考架构如何支撑业务的长期增长。
后续有时间也可以给大家同步一下我们另一个数据推送平台。(一键三连催更)
本次就写到这,零零散散,很多细节点(如:如何线程调优提升吞如,大流量消息下的数据埋点及分析等等)无法完全描绘,如有问题,欢迎交流。希望文章中的消息仓库的演进经验,给大家带来一些收获,或者说,大家不妨思考一下你们会采用何种技术方案和手段来解决演进中遇到的问题。