性能优化实战(三):缓存为王-面向缓存的设计

如果你觉得这篇文章对你有帮助,请不要吝惜你的"关注"、"点赞"、"评价"、"收藏",你的支持永远是我前进的动力~~~


本文是我在做网易考拉海购性能优化时的真实实践,希望对你也有帮助!!!


一、流量走向示意图

通常一个请求的流向是这样的:

  1. 客户端:用户通过客户端发起请求。
  2. Nginx:请求首先到达Nginx服务器,这是一个高性能的HTTP和反向代理服务器。
  3. CDN(Content Delivery Network) :一些静态资源会放到CDN上进行加速处理。如果CDN没有的话,CDN会回源向Nginx发请求。
  4. 网关/Web服务:通常会有一个前置网关,统一请求的入口,统一对请求进行鉴权等处理。
  5. App服务:网关或Web服务再将请求分发到负责具体的业务逻辑的应用程序服务(如App1、App2等)
  6. 缓存服务:在应用程序处理过程中,可能会访问缓存服务来获取数据,以提高响应速度和系统性能。
  7. 数据库服务:对于非缓存的数据,应用程序会查询数据库以获取所需信息。

如此会涉及到大量的网络请求和计算环节,这些环节都是可以考虑做优化的切入点。

二、多级缓存、流量漏斗

这张图片展示了面向缓存的设计理念------多级缓存、流量漏斗。具体来说,它描绘了一个倒置的金字塔结构,从上到下依次为:

  1. 客户端缓存:位于最顶层,表示在用户设备(如浏览器)上的数据缓存。
  2. 代理缓存(静态化) :次顶层,通常指CDN(内容分发网络)或反向代理服务器中的缓存机制。
  3. 堆内缓存:应用程序运行时内存中的缓存,用于快速访问频繁使用的对象和数据。
  4. 堆外缓存:与堆内缓存类似,但存储于Java虚拟机之外的直接内存区域,可以避免垃圾回收的影响。
  5. 磁盘缓存:利用硬盘作为缓存介质,速度较慢但容量较大,适合存放不常变化的数据。
  6. 分布式缓存:跨多个服务器的缓存系统,通过集群方式提高性能和可靠性。
  7. DB:数据库层,是数据的最终存储位置,也是缓存的源头。

流量从上至下,层层递减。

这种设计优化了系统的响应时间和吞吐量,减少了对下层(如数据库)的直接查询压力,从而极大的提升了系统的整体性能。

三、缓存设计思路和注意事项

设计思路

  1. 动静分析:分析数据的使用模式,区分哪些数据是静态的(不经常变化)和哪些是动态的(经常变化)。
    1. 动静分离:将静态数据和动态数据分开处理,静态数据可以缓存更长时间,动态数据则需要更频繁的更新。
    2. 动中取静:在动态数据中寻找可以短期静态化的部分,比如将不经常变化的数据从动态数据中分离出来进行缓存。
  1. 防穿透设计:设计缓存策略以防止缓存未命中时直接访问数据库,从而保护数据库不被大量请求压垮。
    1. 并发穿透:处理多个请求同时穿透缓存到达数据库的情况,通常通过锁或队列来控制。
    2. null穿透:当缓存中不存在数据时,避免每次都查询数据库,可以通过缓存空值或特殊标记来处理。
  1. 更新机制:设计一种机制来及时更新缓存中的数据,以保持数据的一致性。
  2. 失效机制:确定缓存数据的失效策略,如过期时间、写入时失效等。
  3. 多级缓存:使用多个缓存层次,如内存缓存、分布式缓存、磁盘缓存等,以提高缓存效率和可靠性。
  4. 计算结果缓存:缓存那些计算成本高昂的结果,以避免重复计算。
  5. 缓存预热:在系统启动或缓存失效后,提前加载热点数据到缓存中,以提升性能。

注意事项

  1. 防止流量倾斜:确保缓存节点之间的负载均衡,避免某些节点过载。
  2. 避免热点key:设计缓存策略以避免某些key成为热点,导致缓存服务器压力过大。
  3. 避免大value:避免缓存过大的数据值,因为这会占用更多内存并可能导致性能问题。
  4. 命中率:关注缓存的命中率,确保缓存策略能够有效减少数据库的访问。
  5. 避免缓存集中失效:避免缓存数据在同一时间大量失效,这可能导致数据库压力骤增。
  6. 缓存雪崩:采取措施防止缓存雪崩,即大量缓存同时失效导致系统负载急剧上升。
  7. 防止缓存污染:确保缓存数据的一致性和准确性,避免缓存了错误或过时的数据。
  8. 缓存一致性:在多个缓存节点之间保持数据的一致性,特别是在数据更新时。
  9. 序列化、反序列化的CPU开销:注意缓存数据的序列化和反序列化对CPU的影响,选择合适的序列化方法。
  10. 容量评估好,防止频繁GC:合理评估缓存容量,避免内存不足导致频繁的垃圾回收。
  11. 缓存不要当DB使用:缓存不是数据库的替代品,它主要用于提高读取性能,不应承担数据库的数据持久化职责。

四、促销缓存设计演进之路

我对促销系统的缓存设计做了多次改进,以应对不断丰富的业务场景和并发压力

第一版:延时合并刷新缓存

促销服务直接依赖分布式缓存服务,会定时刷新缓存。同时,下单也会实时查询数据库刷新缓存

但大促期间,会有大量的热点商品,这些热点商品就会频繁的查库刷缓存,对DB造成较大的压力。

所以我们做了优化,对同一商品,延时合并刷新,降低了热点商品的缓存刷新次数,进而减少了DB的访问次数。

第二版:多级缓存+计算结果缓存

随着业务访问量的猛增,尤其是大促前全链路压测定的不合理的目标,缓存的压力也变的很大。

因此这一版我们主要引入了多级本地缓存,形成三级缓存结构:分布式缓存、堆内缓存、计算结果缓存

为什么要引入计算结果缓存呢?因为业务上增加了不同用户群体不同定价,每次请求都需要根据用户群体身份从基础数据进行计算,会耗费大量的计算资源。因此我们把计算结果也缓存在了本地,缓存有效期10秒。

优化的结果,就是进一步降低了DB,尤其是远程缓存的访问次数,缓解了分布式缓存服务的压力。

第三版:动中取静,对象拆分

可不可以进一步降低数据库的访问次数呢,毕竟数据库才是一个系统中最脆弱的组成部分?

我们可以进一步利用动静分析的思路,对ActivityGoodsDTO对象进行分析,根据分析结果进一步拆分。

ActivityGoodsDTO 对象中包含活动信息、赠品信息和售卖数量信息,而只有售卖数量信息是变更频繁的,因此我们这个对象拆分成两个对象ActivityGoodsDTO、ActivityGoodsDTO:

  1. ActivityGoodsDTO:包含了活动信息和赠品信息。
  • 更新时机包括活动发布和全量定时任务。
  • 本地缓存有效期较长,静默期可以更长。
  1. ActivityGoodsStoreDTO:包含了售卖数量信息。
  • 更新时机包括活动发布、全量定时任务以及活动库存变更通知(从binlog中的数据获取)。
  • 本地缓存有效期较短。

拆分后使得数据库(DB)基本没有流量,远程缓存调用虽然有些微增长,但数据包变得很小,从而降低了响应时间和反序列化的CPU开销。

相关推荐
这孩子叫逆4 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
coderWangbuer1 小时前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
攸攸太上1 小时前
JMeter学习
java·后端·学习·jmeter·微服务
Kenny.志1 小时前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba2 小时前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
feng_xiaoshi2 小时前
【云原生】云原生架构的反模式
云原生·架构
妍妍的宝贝2 小时前
k8s 中微服务之 MetailLB 搭配 ingress-nginx 实现七层负载
nginx·微服务·kubernetes
千里码aicood3 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455663 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端