干货 | 分布式缓存与DB秒级一致设计实践(1)

简介: 干货 | 分布式缓存与DB秒级一致设计实践



01


   前言


爆款项目是2020年携程的一个新项目,目标是将全品类、高性价比的旅行商品统一集合在一个频道供用户选购。出于这样的业务定位,项目有三个特点:


1)高流量

2)部分商品会成为热卖商品

3)承担下单职能


那么在系统设计之初,就必须考虑下面两个点:


1)如何应对高QPS(包括整体高QPS和个别商品的高QPS),高流量,保障C端用户体验?

2)在满足第一点的情况下,如何保障信息的时效性,让用户尽可能看到最新的信息,避免下单时的信息和看到的信息不一致?

 

很显然,要想较好的应对高QPS,高流量的前端请求,需要借助缓存(我们使用了公司推荐的Redis,后文不再做特别说明)。但是怎么使用好缓存解决上面两个问题,这是需要考虑的。我们对比了本文讨论的方案,和另外几个传统方案的优缺点,见下:


考量点/方案 本方案 应用层按需查数据没有时进行缓存 定期全量同步DB数据进入缓存 定期全量同步DB数据至Redis以及通过canal等中间件增量同步DB数据进入缓存
是否可以应对高QPS 可以 可以 可以 可以
是否可以解决热点key的问题 可以 不可以 不可以 不可以
缓存是否可以感知数据库中数据变化而快速更新 可以 不可以 不可以 可以
缓存数据更新的延迟 秒级 取决于缓存过期时间 取决于同步周期,通常是小时/天级别 秒级
缓存过期后能否及时重新写入缓存 可以 可以 不可以 不可以
新增/修改缓存时旧数据是否可能会覆盖新数据 不会 不会
是否支持有选择性的写入缓存,节省缓存集群资源 可以 可以 不可以 不可以
是否会周期性的遍历DB中需要缓存的数据表从而给DB带来额外压力 不会 不会
是否可以与特定业务解耦,从而被其他业务复用 可以 不可以 不能 不能
实现复杂度 较为复杂 简单 简单 中等

 

综上,本方案除了第一次实现有较高的复杂度外,带来的其他优势都是很可观的。目前线上运行下来,数据访问层应用相关的数据如下:


QPS:近万QPS

性能:平均耗时个位数毫秒

缓存数据更新延时:秒级

Redis集群的请求量:进行热点key处理后,减少原本1/4的请求量

Redis集群内存占用:按需进行缓存,内存占用为传统方案的1/10

Redis集群CPU使用情况:整体平稳,未出现局部机器CPU异常


本文将从应用架构、缓存访问组件、缓存更新平台几方面介绍本方案。

   

02


   应用架构

本方案的应用架构大致如下:


其中,浅绿色部分由业务实现,深绿色部分是本方案实现的。后文会详细介绍缓存访问组件和缓存更新平台的设计思路。


   

03


   

缓存访问组件


该组件的存在主要是为了封装缓存的访问。主要做了两件事:


1)按需异步将缓存中需要增、删、改的键值对通过消息传递给缓存更新平台,让其进行实际的缓存更新操作。

2)对热点key进行本地缓存与更新,避免对某个key的大量请求直接打到缓存导致缓存雪崩。

 

1、为什么要异步操作缓存?


这里,可能大家会有一个疑问,为什么要将简单的缓存操作由传统方案中的同步操作变为基于消息机制的异步操作呢?

 

这是由于我们的业务场景要求DB数据与缓存数据能够快速最终一致而决定的。如果采取传统的同步操作,那么极端情况下,可能会出现下面这样的多线程执行时序:


时刻(由近及远) 线程1 线程2 线程3
T1 发现key没有缓存数据,进而读取DB,得到数据v1

T2
更新DB中key对应的数据,由v1变更为v2
T3

发现key没有缓存数据,进而读取DB,得到数据v2
T4

将key<->v2写入缓存
T5 将key<->v1写入缓存

 

可以发现,这样的时序执行完后,缓存中key对应的value是过期的v1而不是数据库中最新的v2,这就会导致严重的用户体验问题,并且这个问题很难被发现。


那么我们是不是可以采用Redis的SETNX命令来解决这个问题呢?其实也是不行的。比如,上面的时序变为线程1先执行完,线程3再执行完,那么实际上缓存中的数据依然会是过期的v1。因为线程3在采用SETNX命令设置缓存时,发现key已经有对应的值了,所以线程3最终的SETNX命令不会执行成功,也就导致了该更新的缓存反而没有更新。


不难看出,这类问题就是由于我们会有大量的并行操作同一个key导致的。所以,这里引入消息机制来异步执行缓存操作就是为了使同一个key的并行操作变为串行操作。

 

2、异步操作带来的问题


由于缓存操作由传统方案中的同步操作变为异步操作,那么引入了两个新问题:


1)如果投递消息失败了怎么办?

2)业务希望数据更新成功后缓存务必更新成功,也就是说希望DB数据更新和缓存更新近乎在一个事务里面,这该怎么办?

 

在这个组件中,我们通过引入一张存放于业务的DB的消息记录表来解决上述两个问题。它相当于是一个容灾方案,只要消息进入这张表,缓存更新平台就保证这条消息必然会被消费。 3、关于热点key的处理 

该组件还有一大功能就是对热点key的处理。众所周知,缓存热点key在很多业务中都存在。例如若页面中存在长列表/瀑布流,那么第一屏的产品的访问量肯定比第二屏的产品访问量要高很多;又例如某些商品做活动,那么这类商品肯定要比没做活动的商品访问量高很多。


而爆款业务在可预见的未来,肯定也会出现热点key的问题。若热点key的问题不及时解决,当对单一key的请求量足够大时,可能导致缓存集群中存储该key的机器性能严重下降,从而导致缓存雪崩。所以在系统设计上,我们需要为解决热点key预留可扩展性。

 

目前组件内部热点key的处理流程如下:



通过上述流程可以看到,组件内部解决热点key主要是要解决下面三个问题:

  • 如何判断是热点key?
  • 热点key如何存储?
  • 热点key的内容如何更新?




 

1)如何判断是热点key?

首先我们需要知道哪些key是热点key才能解决热点key的问题,识别热点key采取下面两个方案互补:

a.动态识别热点key:主要针对部分key的访问流量增长相对平稳没那么陡的场景,使应用有能力应对线上一些无法预知的突发情况。

b.预设热点key:主要针对定点开始的活动(比如电商的秒杀),这类流量增长通常会非常陡且高峰很短暂。如果这种场景也采取方案1来主动识别通常就会导致滞后性,其实最终不会起到任何作用。所以我们就需要预设热点key。由于爆款业务处于起步阶段,场景1的问题尚不紧急,所以目前方案1我们计划在未来的迭代实现,这里不做过多讨论。对于方案2,业务目前可以主动将可以判定为热点的key灌给缓存访问组件。组件收到这类key后,当它在从缓存拿到这类key的内容后会主动将内容存入本地内存。后续所有的访问,都会从本地内存读取,从而大幅降低对远端缓存服务器的访问。

2)热点key如何存储?

在前文已经提到,针对热点key,我们选择将其内容存放于应用服务器的内存中,这样做基于下面两个原因:

  • 应用服务器本身一般都是以集群来部署,可以弹性缩扩容;
  • 应用服务器的内存基本上可用空间都在50%以上;



这样做带来的好处是:应用服务器在基于流量变化进行横向缩扩容时,热点key的内存与并发量的支持也跟着一起调整了,避免了多余的维护成本。缓存访问组件在进行本地缓存时,考虑到热点key的访问流量通常是增长快下降也快,而且极端情况下可能出现本地缓存内容和数据库中的内容不一致,所以我们选择在本地进行一个很短时间的缓存,便于其能够应对突发的流量增长的同时也能在极端情况下快速与数据保持一致。

3)热点key的内容如何更新?

前文有提到,我们希望尽可能快的将数据库中最新的数据反映到缓存,热点key的本地缓存也不例外。所以,我们需要建立一个广播机制,让本地缓存能够知晓远端缓存的内容变化了。

这里,我们借助了缓存更新平台。由于所有的缓存更新都是发生在缓存更新平台(见后文),所以其可以将发生变化的缓存key通过消息队列广播给所有缓存访问组件,组件消费到这条消息后,若key是热点key,则进行本地缓存的更新。极端情况下,可能会出现组件消费消息失败从而未更新的问题。针对这种情况,前文有提到,我们采取了很短时间的本地缓存,所以即便出现这个问题,也只会在较短时间有问题,最大程度保障了用户体验。
   


相关文章
|
4月前
|
人工智能 安全 Java
分布式 Multi Agent 安全高可用探索与实践
在人工智能加速发展的今天,AI Agent 正在成为推动“人工智能+”战略落地的核心引擎。无论是技术趋势还是政策导向,都预示着一场深刻的变革正在发生。如果你也在探索 Agent 的应用场景,欢迎关注 AgentScope 项目,或尝试使用阿里云 MSE + Higress + Nacos 构建属于你的 AI 原生应用。一起,走进智能体的新世界。
1089 69
|
4月前
|
关系型数据库 Apache 微服务
《聊聊分布式》分布式系统基石:深入理解CAP理论及其工程实践
CAP理论指出分布式系统中一致性、可用性、分区容错性三者不可兼得,必须根据业务需求进行权衡。实际应用中,不同场景选择不同策略:金融系统重一致(CP),社交应用重可用(AP),内网系统可选CA。现代架构更趋向动态调整与混合策略,灵活应对复杂需求。
|
6月前
|
数据采集 消息中间件 监控
单机与分布式:社交媒体热点采集的实践经验
在舆情监控与数据分析中,单机脚本适合小规模采集如微博热榜,而小红书等大规模、高时效性需求则需分布式架构。通过Redis队列、代理IP与多节点协作,可提升采集效率与稳定性,适应数据规模与变化速度。架构选择应根据实际需求,兼顾扩展性与维护成本。
184 2
|
9月前
|
人工智能 安全 应用服务中间件
阿里巴巴 MCP 分布式落地实践:快速转换 HSF 到 MCP server
本文分享了阿里巴巴内部将大规模HSF服务快速转换为MCP Server的实践经验,通过Higress网关实现MCP协议卸载,无需修改代码即可接入MCP生态。文章分析了MCP生态面临的挑战,如协议快速迭代和SDK不稳定性,并详细介绍了操作步骤及组件功能。强调MCP虽非终极解决方案,但作为AI业务工程化的起点具有重要意义。最后总结指出,MCP只是AI原生应用发展的第一步,未来还有更多可能性值得探索。
1396 48
|
5月前
|
消息中间件 缓存 监控
中间件架构设计与实践:构建高性能分布式系统的核心基石
摘要 本文系统探讨了中间件技术及其在分布式系统中的核心价值。作者首先定义了中间件作为连接系统组件的&quot;神经网络&quot;,强调其在数据传输、系统稳定性和扩展性中的关键作用。随后详细分类了中间件体系,包括通信中间件(如RabbitMQ/Kafka)、数据中间件(如Redis/MyCAT)等类型。文章重点剖析了消息中间件的实现机制,通过Spring Boot代码示例展示了消息生产者的完整实现,涵盖消息ID生成、持久化、批量发送及重试机制等关键技术点。最后,作者指出中间件架构设计对系统性能的决定性影响,
|
9月前
|
监控 Linux 应用服务中间件
Linux多节点多硬盘部署MinIO:分布式MinIO集群部署指南搭建高可用架构实践
通过以上步骤,已成功基于已有的 MinIO 服务,扩展为一个 MinIO 集群。该集群具有高可用性和容错性,适合生产环境使用。如果有任何问题,请检查日志或参考MinIO 官方文档。作者联系方式vx:2743642415。
3188 57
|
9月前
|
安全 JavaScript 前端开发
HarmonyOS NEXT~HarmonyOS 语言仓颉:下一代分布式开发语言的技术解析与应用实践
HarmonyOS语言仓颉是华为专为HarmonyOS生态系统设计的新型编程语言,旨在解决分布式环境下的开发挑战。它以“编码创造”为理念,具备分布式原生、高性能与高效率、安全可靠三大核心特性。仓颉语言通过内置分布式能力简化跨设备开发,提供统一的编程模型和开发体验。文章从语言基础、关键特性、开发实践及未来展望四个方面剖析其技术优势,助力开发者掌握这一新兴工具,构建全场景分布式应用。
891 35
|
9月前
|
缓存 NoSQL Java
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
245 5
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
|
10月前
|
存储 负载均衡 测试技术
ACK Gateway with Inference Extension:优化多机分布式大模型推理服务实践
本文介绍了如何利用阿里云容器服务ACK推出的ACK Gateway with Inference Extension组件,在Kubernetes环境中为多机分布式部署的LLM推理服务提供智能路由和负载均衡能力。文章以部署和优化QwQ-32B模型为例,详细展示了从环境准备到性能测试的完整实践过程。
|
11月前
|
并行计算 PyTorch 算法框架/工具
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
本文探讨了如何通过技术手段混合使用AMD与NVIDIA GPU集群以支持PyTorch分布式训练。面对CUDA与ROCm框架互操作性不足的问题,文章提出利用UCC和UCX等统一通信框架实现高效数据传输,并在异构Kubernetes集群中部署任务。通过解决轻度与强度异构环境下的挑战,如计算能力不平衡、内存容量差异及通信性能优化,文章展示了如何无需重构代码即可充分利用异构硬件资源。尽管存在RDMA验证不足、通信性能次优等局限性,但该方案为最大化GPU资源利用率、降低供应商锁定提供了可行路径。源代码已公开,供读者参考实践。
1024 3
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践