目录

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

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


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


一、流量走向示意图

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

  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开销。

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
零零壹118 分钟前
什么是 Redis?为什么你应该关心它?
vue.js·后端·面试
喵手15 分钟前
同事突然考我1000 个线程同时运行,怎么防止不卡?
java·后端·java ee
玄明Hanko17 分钟前
你的 DDD 还在纸上谈兵?是时候落地了!
java·后端·领域驱动设计
一介输生18 分钟前
Spring Boot 实现权限管理(上)
java·后端
喵手19 分钟前
为什么用了 Stream,代码反而越写越丑了?
java·后端·java ee
flzjkl20 分钟前
【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门
java·后端
flzjkl23 分钟前
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
java·后端
讳疾忌医_note23 分钟前
为何 C++ 多态设计总出错?大部份开发者没掌握的虚函数底层逻辑
后端
风铃儿~25 分钟前
Java微服务流量控制与保护技术全解析:负载均衡、线程隔离与三大限流算法
java·分布式·算法·微服务·负载均衡
努力的搬砖人.38 分钟前
搭建一个Spring Boot聚合项目
java·spring boot·后端