声明:本文中的细节来源于 Shopee 研发团队于 2024 年深圳 ArchSummit 上的分享。所有的技术细节都要归功于 Shopee 研发团队。原始内容和其他参考资料的链接在文章末尾的参考资料部分。本文试图分析细节并提供我们的意见。如果您发现任何不准确或遗漏,请留下评论,我们将尽力修复它们。
Shopee 做为东南亚领航的电商平台,业务涵盖全球十余个国家与市场。商品系统做为电商平台的最核心的业务支撑系统,面临着数据体量大、并发流量大、且下游依赖众多等挑战。Shopee 因为面向全球十余个国家与市场,不仅有着庞大的用户群,还需要支持更多元化的商品,以及更多样的本土化营销与模式。这使得 Shopee 的商品系统不仅面临着百亿级数据的体量与百万级并发请求,其商品模型更是需要承载上千个字段。
研发团队针对海量商品治理时面临着诸多挑战,其中围绕数据存储方面的挑战主要有以下几点,围绕着以下几点挑战导致其数据库服务器成本非常高昂:
- 百亿级商品数据规模下,数据库存储体量巨大,并且数据量持续增长;
- 为应对流量穿透缓存对数据库的压力,导致数据库从库众多,并且流量持续增长;
- 为应对峰值流量需要预留足够的缓冲空间;
- 需要考虑其他数据场景而引起的存储扩散比例大;
为应对以上挑战的同时降低数据库服务器成本,Shopee 研发团队做了哪些升级改造呢?我们接下来透过 Shopee 团队在 2024 年深圳举办的 ArchSummit 中的分享,来一窥究竟。
提升缓存命中率降低数据库负载
理想情况下,我们希望通过缓存能够承载系统 99% 的查询流量,从而降低数据库的负载,减少从库数量。但实际情况往往不尽如人意,缓存能承载的流量通常在 50% - 99% 之间,而数据库依然要承载近 50% 的访问流量,那么就需要为这部分负载,考虑增设从库的部署。由此则带来了数据库服务器成本的增加。所以,Shopee 研发团队首先通过降低从库的服务器数量着手,通过增加缓存命中率来降低缓存穿透为数据库带来的负载。
通过优化 TTL 策略提升缓存命中率
缓存的存储容量是有限的,通常我们在系统设计时很少会让数据常驻缓存,至少不会是全量数据常驻缓存,毕竟这是一个性价比不高的实现。所以通常我们会为缓存数据配置合适的过期时间,如何选择最优 TTL 策略则是复杂的问题。Shopee 研发团队采用多项决策因素制定一系列 TTL 参数策略,并通过对线上流量的采集,经过仿真环境的流量回放,不断调优 TTL 参数,实现最优 TTL 策略的选择。
对于中小研发团队来讲,如果不具备如 Shopee 一样的基础设施能力想做到类似的调优,可以尝试在应用服务中添加异步的统计与采集功能,将 TTL 调整到一个合适的策略,只不过执行周期可能会长一些。在考虑实际业务体量与收益的情况下,该优化策略的排序通常可以靠后一些。
改善缓存容量利用率提升缓存命中率
另一方面通过减少存储数据体积,使得单位容量的节点存储更多的数据。减少存储数据体积主要从两个维度着手,一个是减少冗余,另一个是数据压缩。
减少冗余的手段包含去除重复字段,以及过滤无使用场景的字段属性两个方面展开,是一个相对成本低并且收益确定性高的改造。对于 Shopee 的低成本是依赖于整个研发团队的工程质量为前提,如果我们所在的研发团队工程质量稍显逊色的话,需要重新评估这项改造的成本。我们需要基于服务下游业务场景,梳理每个服务接口所提供数据属性的用途,确认可移除的字段属性,最后得出缓存数据模型并实施改造。如果研发团队工程质量稍显逊色的话,需要一定人力成本与时间执行梳理工作。
在数据压缩方面,Shopee 研发团队基于数据体积减少百分比、操作单位耗时以及 CPU 开销三个维度对比了 zlib 与 zstd(即 Zstandard ,开源自 Meta 的一种快速压缩算法)两种压缩算法库。最终采取了 zstd 做为数据压缩方案并获得了 50% 以上的数据体积收益。
多级缓存压缩缓存成本
通过对热点数据做服务本地缓存的方式,一方面进一步提升性能与吞吐,另一方面降低系统性风险。对于性能的提升与吞吐想必大家比较容易理解,关于降低系统性风险方面,主要是指避免热点数据高访问流量造成缓存节点宕机,影响整体可用性。这也进一步收缩了对于缓存服务的成本,否则为应对热点数据流量需要增加相应节点资源。
因为增加了应用服务本地缓存,其挑战也会随之增加。比如如何识别热点数据,以及是否需要选择合适通知机制与数据一致性问题等。但是结合实际业务场景,Shopee 研发团队并未选择实现成本高昂的方案,而是通过时间窗口内数据访问阈值以及短 TTL 的方案。关于应用服务本地缓存的数据一致性问题,如果大家有兴趣可以在评论区留言,我们会在后续文章中给出可落地的设计方案。
实现数据冷热分离降低存储成本
通过对缓存使用方面的升级改造,除了降低数据库服务器的数量。除此之外,存储成本的降低最直接的方式是采用更廉价的资源。在保证系统服务质量的前提下,则需要我们对数据做冷热分离。
面对冷热数据分离最关键的部分在于如何定义与区分冷热数据。Shopee 研发团队主要将其划分为活跃高频访问数据、被在线系统低频访问的数据以及不会被在线系统访问的数据。前两类数据可通过近一段时间访问比来划分,如近 n 月访问占比超过 x% 。同时可通过数据上的时间属性进行划分,如以当前时间 n 年为临界时间。最后一类则包含软删除数据、无用日志数据、长期无活动商家商品数据等。
基于上述数据划分基础,分别为三类数据部署相应规格的存储系统与合适的服务器,从而实现存储成本的降低。但是,与之对应的会为系统设计带来挑战。
首先是热数据到低频访问数据所面临的问题,主要集中在以下两方面:
- 应用服务以及相关系统需要对数据库的物理删除有所感知;
- 服务需要对数据做查询降级。
其次面对不被在线系统访问的数据,需要应对如下问题:
- 确保在线应用用户体验问题;
- 确保在线业务流程正常,以及各区域市场业务合规;
- 避免缓存穿透、全局 ID 不能被复用,以及系统异常。
虽然这部分数据不会被在线系统访问,但数据其实并未在系统中消失,所以在不能将其视为删除状态。对于在线系统而言更像是一种不可见的数据。当商品系统这一支撑系统所提供的服务,存在数据不存在的情况下,需要下游服务做好对应的适配。同时需要针对失效数据做全流程的验证,确保业务环节无卡点、无异常。同时针对部分数据访问不存在的场景给出相对友好的提示。
关于避免缓存穿透的部分,我们认为 Shoppe 研发团队可能通过缓存 ID 的形式对此类数据进行检索,避免穿透缓存后造成对数据库的访问。基于目前已知的内容,结合 Shopee 研发团队对于这类数据的定义范围,可能访问此类数据的流量本身并不大,对数据库的访问负载也很小。所以避免缓存穿透的投入产出比可能是一个值得商榷的部分。
总结
通过上述改造 Shopee 研发团队将商品数据的数据库存储成本,主库成本仅占改造前成本的 50% 以下,从库成本仅占改造前成本的 25% 以下。
技术改造项 | 数据库-主库 | 数据库-从库 |
通过优化 TTL 策略提升缓存命中率 | - | 成本减少 15% 以上 |
改善缓存容量利用率提升缓存命中率 | - | 成本减少 10% 以上 |
多级缓存压缩缓存成本 | - | - |
实现数据冷热分离 | 成本减少 50% 以上 | 成本减少 50% 以上 |
基于上述整理,我们为 Shopee 的商品系统的绘制的存储架构如下图所示。此次 Shopee 的分享对于中小研发团队同样有着非常值得借鉴参考的部分。虽然整体方案上是相对成熟的方案,但是要想落地的话,更多的在于工程细节与执行层面。对于中小研发团队而言,可以基于上述技术决策与内容,结合自身实际情况,重新排列优先级来实现对自身系统的改造。
如果你对 Shopee 研发团队的分享感兴趣,可以通过下方的 Reference 了解更多,如果你对缓存相关系统设计方案感兴趣可阅读本人的缓存与主副本数据一致性系统设计方案(上篇)与缓存与主副本数据一致性系统设计方案(下篇)两篇文章。
Reference
你好,我是 HAibiiin,一名探索独立开发的 Product Engineer,你可以关注【公众号:单核生悟】与我交流,一起探索技术之外的更多可能。
本文为 大厂案例 系列文章。排除一部分公关属性的内容,国内科技公司对于系统架构、技术经验等内容的公开信息并不算多,集中在一线城市的技术大会与高昂的票价,以及整体相对闭塞的环境。对于中小研发团队及个人学习者难有机会参与,导致可借鉴的 “干货” 更是稀缺资源。在大厂案例系列文章中,我将整理这个科技公司公开技术分享,并结合个人经验分析整理,让更广泛的开发者能够获取到有参考价值的内容,希望我的系列文章能给你帮助。如果你喜欢这个系列可以留言告诉我,如果你有感兴趣的方向也可以留言告诉我。