找回密码
 会员注册
查看: 20|回复: 0

分布式多级缓存系统设计与实战

[复制链接]

2

主题

0

回帖

7

积分

新手上路

积分
7
发表于 2024-10-13 00:07:13 | 显示全部楼层 |阅读模式
分布式多级缓存系统设计与实战 320 1. 缓存系统概述如上图,是一次最基本的网络请求。用户请求从界面(浏览器或 App 界面)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容。随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,数据库每秒能接受的请求次数也是有限的。如何能够有效利用有限的资源来提供尽可能大的吞吐量?是每个开发同学绕不开的课题。一个有效的办法就是引入缓存,打破标准流程,如下图1到4每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少计算量,有效提升响应速度,让有限的资源服务更多的用户。image.png缓存可以应用上图1到4个的各个环节中,且不同环节缓存策略略有不同。本文将主要从3和4点讲解缓存的使用。2. 缓存架构演变2.1. 无缓存架构如上图,是一次最基本的网络请求。请求从网络层直接请求到 DB。此时请求耗时最大卡点在数据库的磁盘 IO 上。2.2. 引入分布式缓存数据库针对2.1无缓存架构的数据库磁盘IO耗时,可添加了一道缓存数据库例如 redis。借助缓存中间件,可消除数据库的 IO 瓶颈。快速返回数据。如下图:通过缓存数据库1、可防止流量直接打到数据库层,减缓数据库压力。2、缓存快速返回,可提高请求查询速率。2.2.1 为什么选择redis?1、纯内存操作,无磁盘 IO 耗时2、key-value 数据库,时间复杂度 O(1),相比数据库的 O(Log n),访问速度更快3、IO 多路复用线程模型,IO 阶段无阻塞此时系统卡点在缓存数据库的网络通信上。即使缓存数据库读取数据很快,但是和应用服务间仍然隔着一层网络通信。2.3. 引入 JVM 本地缓存针对2.2缓存数据库架构,访问缓存数据库的网络通信问题,可在 JVM 应用层添加本地缓存,解决网络 IO 问题。如下图在应用内部新增本地缓存,使流量在应用层直接返回。避免进一步访问到 redis。本架构虽然可大大提高数据读取速率,但其成本也是更高的。1、需要在多台 JVM 机器上冗余缓存,对内存要求高。2、缓存在多台 JVM 实例,数据一致性维护成本高。建议根据自身业务场景,从以下3方面考量是否才有本地缓存。1、业务访问量 QPS2、硬件资源内存是否充足3、变更场景是否频繁常用本地缓存JDK MAPguavaCacheCaffeine Cache2.3.1 数据读取流程image.png按优先级依次从本地、redis、DB 中读取数据。实现了本地(一级缓存)、缓存数据库(二级缓存)和 DB 的多级缓存架构。3. 痛点和优化3.1 数据一致性问题存在多级缓存,虽然大大提高了数据的读取速率。但是数据散落在各个不同的区域,数据一致性就是一个绕不过去的问题。特别是针对本地缓存,同时散落在多个多台 JVM 实例中。数据变更时,必须同步修改redis、本地缓存和DB。以下是基于canal + 广播消息实现的一致性异步处理方案。1、DB 修改数据2、通过监听 canal 消息,触发缓存的更新3、针对 redis 缓存中,因为集群中只共享一份,直接同步缓存即可4、针对本地缓存,因为集群中存在多分,且分散在不同的 JVM 实例中。故再借助广播 MQ 机制,通知到各个业务实例。同步本地缓存3.1.1 同步缓存机制直接删除缓存,查询时直接加载优点:操作简单缺点:未命中缓存时,取重新加载。此次查询请求慢。重新加载缓存优点:提前设置缓存,查询效率高**注意:**此方案同步缓存,为先 DB 操作、后异步同步缓存。会存在短暂 DB 和缓存不一致场景。需根据自身业务场景考量,如有必要,可前置删除缓存,再 DB 操作。3.2. 热点 key 监控以上架构,系统缓存只能被动加载。只有 key 被访问后,系统才能触发加载。在高并发的情况下,如一直出现缓存穿透,大量流量请求到数据库,对数据库还是很大的考验。所以优秀的缓存系统,应该能自动识别出热点 key。前置将数据缓存下来。3.2.1 热点 key 探测引入缓存中间调度服务:热点 key 探测中间服务器概念1、业务实例汇总 key 访问情况并将上报到“热点 key 探测中间服务器”。2、“热点 key 探测中间服务器”根据各业务实例上报的信息,识别该 key 是否为热点。3、“热点 key 探测中间服务器”将识别结果通知到各业务实例。如若为热点 key:业务实例自动预热缓存,等待流量访问。如若非热点 key:业务实例释放该热点 key,释放内存占用。详情见:参考4. 缓存注意事项4.1 key 设计1、长度短:redis key 越短,占用内存越小2、高命中率:命中率不高,缓存意义不大4.1.1 value 设计1、尽可能小,避免出现 big keyredis 是单线程机制,big key 会阻塞后续请求。仅缓存必要的字段,不必要字段,及时瘦身2、改少读多变更频繁的数据不建议缓存,频繁的数据变更会导致缓存实现和一致性同步问题,反而会损耗系统性能3、计算逻辑复杂的结果4.1.2 缓存穿透访问一个不存在的 key。由于实际上并不存在,所以每次都会访 DB解决方案缓存空值或默认对象(依据业务场景)布隆过滤器4.1.3 缓存击穿某个 key 瞬间访问量过大,但突然过期,导致大部分流量打到了 DB解决方案histrix 保护,对 DB 的访问限流只有获得锁的线程才能去 DB 读取数据,并填充到缓存中1.使用互斥锁2.永不过期3.资源保护4.1.4 缓存雪崩由于大部分 key 设置了相同的失效时间,某一时间大量缓存同时失效,导致大部分流量瞬间打到 DB,导致 DB 压力过大。解决方法key 使用不同的过期时间,或者加一个随机时间5. 实战经验1、评估预计占用的缓存大小,避免占满 redis 集群和 JVM 内存2、评估预计 QPS,如2.2架构。大量从 redis 中获取对象,会涉及平凡的对象反序列化操作,此处存在耗 CPU 操作。3、严格禁止 bigKey。redis的单线程模型,出现 bigKey 会严重降低 redis 服务吞吐量。4、必须设置过期时间6. 踩坑记录6.1. 本地缓存被污染由于缓存在 JVM 内部,且保存在老年代。业务方拿去使用的时候,直接修改了缓存的数据,导致缓存数据不正确。解决1、取对象时,直接 copy 一份。(复制对象耗 CPU,不推荐)2、将缓存对象设置成不可编辑。(推荐)6.2. 缓存计算结果,而不是响应结果缓存的 value 是 Response 对象,首次请求失败,导致缓存的数据为response.success=false。后续所有命中均操作失败。解决将缓存结果由 Response,调整为实际的计算结果6.3. 本地内存彪高,触发频繁 full GC初次引入本地缓存(之前是 redis )。将大量数据缓存在本地,导致 JVM 内存彪高。解决1、引入本地缓存前考虑预计内存,进而考虑是否值得接入本地缓存。2、仅缓存热点 key,非热点 key 不缓存在本地6.4. 降级到 redis 缓存,CPU 彪高为优化 JVM 内存,将本地缓存降级到 redis。QPS 高场景,触发大量序列化和young GC,导致系统 CPU 彪高。解决1、评估 QPS,考虑是否可降级2、仅缓存热点 key,非热点 key 不缓存在本地7. 总结在计算机世界里,缓存无处不在。但不管缓存系统如何设计,其本质都是空间换时间。也就是提升数据的获取速率。缓存系统的设计各有千秋、各有优劣。没有最优秀的架构,只有最适合的架构。应该根据自身实际业务情况考虑缓存架构的设计。并从缓存命中率、数据库压力、数据一致性、系统吞吐量等综合评估设计的合理性。,包含前端、后端、测试、UED 等,Base 在风景如画的杭州,一个富有激情、创造力和执行力的团队。团队现有500多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 11:25 , Processed in 0.345604 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表