如果你觉得这篇文章对你有帮助,请不要吝惜你的"关注"、"点赞"、"评价"、"收藏",你的支持永远是我前进的动力~~~
本文是我在做网易考拉海购性能优化时的真实实践,希望对你也有帮助!!!
一、流量走向示意图
通常一个请求的流向是这样的:
- 客户端:用户通过客户端发起请求。
- Nginx:请求首先到达Nginx服务器,这是一个高性能的HTTP和反向代理服务器。
- CDN(Content Delivery Network) :一些静态资源会放到CDN上进行加速处理。如果CDN没有的话,CDN会回源向Nginx发请求。
- 网关/Web服务:通常会有一个前置网关,统一请求的入口,统一对请求进行鉴权等处理。
- App服务:网关或Web服务再将请求分发到负责具体的业务逻辑的应用程序服务(如App1、App2等)
- 缓存服务:在应用程序处理过程中,可能会访问缓存服务来获取数据,以提高响应速度和系统性能。
- 数据库服务:对于非缓存的数据,应用程序会查询数据库以获取所需信息。
如此会涉及到大量的网络请求和计算环节,这些环节都是可以考虑做优化的切入点。
二、多级缓存、流量漏斗
这张图片展示了面向缓存的设计理念------多级缓存、流量漏斗。具体来说,它描绘了一个倒置的金字塔结构,从上到下依次为:
- 客户端缓存:位于最顶层,表示在用户设备(如浏览器)上的数据缓存。
- 代理缓存(静态化) :次顶层,通常指CDN(内容分发网络)或反向代理服务器中的缓存机制。
- 堆内缓存:应用程序运行时内存中的缓存,用于快速访问频繁使用的对象和数据。
- 堆外缓存:与堆内缓存类似,但存储于Java虚拟机之外的直接内存区域,可以避免垃圾回收的影响。
- 磁盘缓存:利用硬盘作为缓存介质,速度较慢但容量较大,适合存放不常变化的数据。
- 分布式缓存:跨多个服务器的缓存系统,通过集群方式提高性能和可靠性。
- DB:数据库层,是数据的最终存储位置,也是缓存的源头。
流量从上至下,层层递减。
这种设计优化了系统的响应时间和吞吐量,减少了对下层(如数据库)的直接查询压力,从而极大的提升了系统的整体性能。
三、缓存设计思路和注意事项
设计思路
- 动静分析:分析数据的使用模式,区分哪些数据是静态的(不经常变化)和哪些是动态的(经常变化)。
-
- 动静分离:将静态数据和动态数据分开处理,静态数据可以缓存更长时间,动态数据则需要更频繁的更新。
- 动中取静:在动态数据中寻找可以短期静态化的部分,比如将不经常变化的数据从动态数据中分离出来进行缓存。
- 防穿透设计:设计缓存策略以防止缓存未命中时直接访问数据库,从而保护数据库不被大量请求压垮。
-
- 并发穿透:处理多个请求同时穿透缓存到达数据库的情况,通常通过锁或队列来控制。
- null穿透:当缓存中不存在数据时,避免每次都查询数据库,可以通过缓存空值或特殊标记来处理。
- 更新机制:设计一种机制来及时更新缓存中的数据,以保持数据的一致性。
- 失效机制:确定缓存数据的失效策略,如过期时间、写入时失效等。
- 多级缓存:使用多个缓存层次,如内存缓存、分布式缓存、磁盘缓存等,以提高缓存效率和可靠性。
- 计算结果缓存:缓存那些计算成本高昂的结果,以避免重复计算。
- 缓存预热:在系统启动或缓存失效后,提前加载热点数据到缓存中,以提升性能。
注意事项
- 防止流量倾斜:确保缓存节点之间的负载均衡,避免某些节点过载。
- 避免热点key:设计缓存策略以避免某些key成为热点,导致缓存服务器压力过大。
- 避免大value:避免缓存过大的数据值,因为这会占用更多内存并可能导致性能问题。
- 命中率:关注缓存的命中率,确保缓存策略能够有效减少数据库的访问。
- 避免缓存集中失效:避免缓存数据在同一时间大量失效,这可能导致数据库压力骤增。
- 缓存雪崩:采取措施防止缓存雪崩,即大量缓存同时失效导致系统负载急剧上升。
- 防止缓存污染:确保缓存数据的一致性和准确性,避免缓存了错误或过时的数据。
- 缓存一致性:在多个缓存节点之间保持数据的一致性,特别是在数据更新时。
- 序列化、反序列化的CPU开销:注意缓存数据的序列化和反序列化对CPU的影响,选择合适的序列化方法。
- 容量评估好,防止频繁GC:合理评估缓存容量,避免内存不足导致频繁的垃圾回收。
- 缓存不要当DB使用:缓存不是数据库的替代品,它主要用于提高读取性能,不应承担数据库的数据持久化职责。
四、促销缓存设计演进之路
我对促销系统的缓存设计做了多次改进,以应对不断丰富的业务场景和并发压力
第一版:延时合并刷新缓存
促销服务直接依赖分布式缓存服务,会定时刷新缓存。同时,下单也会实时查询数据库刷新缓存
但大促期间,会有大量的热点商品,这些热点商品就会频繁的查库刷缓存,对DB造成较大的压力。
所以我们做了优化,对同一商品,延时合并刷新,降低了热点商品的缓存刷新次数,进而减少了DB的访问次数。
第二版:多级缓存+计算结果缓存
随着业务访问量的猛增,尤其是大促前全链路压测定的不合理的目标,缓存的压力也变的很大。
因此这一版我们主要引入了多级本地缓存,形成三级缓存结构:分布式缓存、堆内缓存、计算结果缓存
为什么要引入计算结果缓存呢?因为业务上增加了不同用户群体不同定价,每次请求都需要根据用户群体身份从基础数据进行计算,会耗费大量的计算资源。因此我们把计算结果也缓存在了本地,缓存有效期10秒。
优化的结果,就是进一步降低了DB,尤其是远程缓存的访问次数,缓解了分布式缓存服务的压力。
第三版:动中取静,对象拆分
可不可以进一步降低数据库的访问次数呢,毕竟数据库才是一个系统中最脆弱的组成部分?
我们可以进一步利用动静分析的思路,对ActivityGoodsDTO对象进行分析,根据分析结果进一步拆分。
ActivityGoodsDTO 对象中包含活动信息、赠品信息和售卖数量信息,而只有售卖数量信息是变更频繁的,因此我们这个对象拆分成两个对象ActivityGoodsDTO、ActivityGoodsDTO:
- ActivityGoodsDTO:包含了活动信息和赠品信息。
- 更新时机包括活动发布和全量定时任务。
- 本地缓存有效期较长,静默期可以更长。
- ActivityGoodsStoreDTO:包含了售卖数量信息。
- 更新时机包括活动发布、全量定时任务以及活动库存变更通知(从binlog中的数据获取)。
- 本地缓存有效期较短。
拆分后使得数据库(DB)基本没有流量,远程缓存调用虽然有些微增长,但数据包变得很小,从而降低了响应时间和反序列化的CPU开销。