6、架构-服务端缓存

news2024/11/14 12:18:59

        为系统引入缓存之前,第一件事情是确认系统是否真的需要缓 存。从开发角度来说,引入缓存会提 高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题;从运维角度来说,缓存会掩盖一些缺 陷,让问题在更久的时间以后,出现在距离发生现场更远的位置上; 从安全角度来说,缓存可能会泄漏某些保密数据,也是容易受到攻击 的薄弱点。冒着上述种种风险,仍能说服你引入缓存的理由,总结起 来无外乎以下两种。

  • 为缓解CPU压力而引入缓存:譬如把方法运行结果存储起来、 把原本要实时计算的内容提前算好、对一些公用的数据进行复用,这 可以节省CPU算力,顺带提升响应性能。
  • 为缓解I/O压力而引入缓存:譬如把原本对网络、磁盘等较慢 介质的读写访问变为对内存等较快介质的访问,将原本对单点部件 (如数据库)的读写访问变为对可扩缩部件(如缓存中间件)的访 问,顺带提升响应性能。

        请注意,缓存虽然是典型以空间换时间来提升性能的手段,但它 的出发点是缓解CPU和I/O资源在峰值流量下的压力,“顺带”而非 “专门”地提升响应性能。这里的言外之意是如果可以通过增强CPU、 I/O本身的性能(譬如扩展服务器的数量)来满足需要的话,那升级硬 件往往是更好的解决方案,即使需要一些额外的投入成本,也通常要 优于引入缓存后可能带来的风险。

缓存属性

        有不少软件系统最初的缓存功能是以HashMap或者 ConcurrentHashMap为起点演进的。当开发人员发现系统中某些资源的 构建成本比较高,而这些资源又有被重复使用的可能时,会很自然地 产生“循环再利用”的想法,将它们放到Map容器中,待下次需要时取 出重用,避免重新构建,这种原始朴素的复用就是最基本的缓存。不 过,一旦我们专门把“缓存”看作一项技术基础设施,一旦它有了通 用、高效、可统计、可管理等方面的需求,其中要考虑的因素就变得 复杂起来。通常,我们设计或者选择缓存至少会考虑以下四个维度的 属性。

  • 吞吐量:缓存的吞吐量使用OPS值(每秒操作数,Operation per Second,ops/s)来衡量,反映了对缓存进行并发读、写操作的效率, 即缓存本身的工作效率高低。
  • 命中率:缓存的命中率即成功从缓存中返回结果次数与总请求 次数的比值,反映了引入缓存的价值高低,命中率越低,引入缓存的 收益越小,价值越低。
  • 扩展功能:即缓存除了基本读写功能外,还提供哪些额外的管 理功能,譬如最大容量、失效时间、失效事件、命中率统计,等等。
  • 分布式缓存:缓存可分为“进程内缓存”和“分布式缓存”两 大类,前者只为节点本身提供服务,无网络访问操作,速度快但缓存 的数据不能在各服务节点中共享,后者则相反。

吞吐量

        缓存的吞吐量只在并发场景中才有统计的意义,因为若不考虑并 发,即使是最原始的、以HashMap实现的缓存,访问效率也已经是常量 时间复杂度(即O(1)),其中涉及碰撞、扩容等场景的处理属于数据 结构基础,这里不再展开。但HashMap并不是线程安全的容器,如果要 让它在多线程并发下正确地工作,就要用 Collections.synchronizedMap进行包装,这相当于给Map接口的所有 访问方法都自动加全局锁;或者改用ConcurrentHashMap来实现,这相 当于给Map的访问分段加锁(从JDK 8起已取消分段加锁,改为 CAS+Synchronized锁单个元素)。无论采用怎样的实现方法,这些线 程安全措施都会带来一定的吞吐量损失。

命中率与淘汰策略

        有限的物理存储决定了任何缓存的容量都不可能是无限的,所以 缓存需要在消耗空间与节约时间之间取得平衡,这要求缓存必须能够 自动或者人工淘汰掉缓存中的低价值数据。考虑到由人工管理的缓存 淘汰主要取决于开发者如何编码,不能一概而论,这里只讨论由缓存 自动进行淘汰的情况。笔者所说的“缓存如何自动地实现淘汰低价值 目标”,现在被称为缓存的淘汰策略。

        在了解缓存如何实现自动淘汰低价值数据之前,首先要定义怎样 的数据才算是“低价值”。由于缓存的通用性,这个问题的答案必须 是与具体业务逻辑无关的,只能从缓存工作过程收集到的统计结果来 确定数据是否有价值,通用的统计结果包括但不限于数据何时进入缓 存、被使用过多少次、最近什么时候被使用,等等。一旦确定选择何 种统计数据,就决定了如何通用地、自动地判定缓存中每个数据的价 值高低,也相当于决定了缓存的淘汰策略是如何实现的。目前,最基 础的淘汰策略实现方案有以下三种。

  • FIFO(First In First Out):优先淘汰最早进入被缓存的数据。 FIFO的实现十分简单,但一般来说它并不是优秀的淘汰策略,越是频 繁被用到的数据,往往会越早存入缓存之中。如果采用这种淘汰策 略,很可能会大幅降低缓存的命中率。
  • LRU(Least Recent Used):优先淘汰最久未被访问过的数据。 LRU通常会采用HashMap加LinkedList的双重结构(如LinkedHashMap) 来实现,以HashMap来提供访问接口,保证常量时间复杂度的读取性 能,以LinkedList的链表元素顺序来表示数据的时间顺序,每次缓存命 中时把返回对象调整到LinkedList开头,每次缓存淘汰时从链表末端开 始清理数据。对大多数的缓存场景来说,LRU明显要比FIFO策略合 理,尤其适合用来处理短时间内频繁访问的热点对象。但是如果一些 热点数据在系统中被频繁访问,只是最近一段时间因为某种原因未被 访问过,那么这些热点数据此时就会有被LRU淘汰的风险,换句话 说,LRU依然可能错误淘汰价值更高的数据。
  • LFU(Least Frequently Used):优先淘汰最不经常使用的数据。 LFU会给每个数据添加一个访问计数器,每访问一次就加1,需要淘汰 时就清理计数器数值最小的那批数据。LFU可以解决上面LRU中热点数 据间隔一段时间不访问就被淘汰的问题,但同时它又引入了两个新的 问题。第一个问题是需要对每个缓存的数据专门维护一个计数器,每 次访问都要更新,但这样做会带来高昂的维护开销;另一个问题是不 便于处理随时间变化的热度变化,譬如某个曾经频繁访问的数据现在 不需要了,但很难自动将它清理出缓存。

        缓存的淘汰策略直接影响缓存的命中率,没有一种策略是完美 的、能够满足系统全部需求的。不过,随着淘汰算法的不断发展,近 年来的确出现了许多相对性能更好、也更复杂的新算法。以LFU为例, 针对它存在的两个问题,近年来提出的TinyLFU和W-TinyLFU算法就会 有更好的效果。

分布式缓存

        相比缓存数据在进程内存中读写的速度,一旦涉及网络访问,由 网络传输、数据复制、序列化和反序列化等操作所导致的延迟要比内 存访问高得多,所以对分布式缓存来说,处理与网络相关的操作是对 吞吐量影响更大的因素,往往也是比淘汰策略、扩展功能更重要的关 注点,这也决定了尽管有Ehcache、Infinispan这类能同时支持分布式 部署和进程内部署的缓存方案,但通常进程内缓存和分布式缓存选型 时会有完全不同的候选对象及考察点。在我们决定使用哪种分布式缓 存前,首先必须确定自己的需求是什么。

        尽管多级缓存结合了进程内缓存和分布式缓存的优点,但它的代 码侵入性较大,需要由开发者承担多次查询、多次回填的工作,也不 便于管理,如超时、刷新等策略都要设置多遍,数据更新更是麻烦, 很容易出现各个节点的一级缓存以及二级缓存中数据不一致的问题。 所以,必须“透明”地解决以上问题,才能使多级缓存具有实用的价 值。一种常见的设计原则是变更以分布式缓存中的数据为准,访问以 进程内缓存的数据优先。大致做法是当数据发生变动时,在集群内发 送推送通知(简单点的话可采用Redis的PUB/SUB,求严谨的话可引入 ZooKeeper或etcd来处理),让各个节点的一级缓存中的相应数据自动 失效。当访问缓存时,提供统一封装好的一、二级缓存联合查询接 口,接口外部是只查询一次,接口内部自动实现优先查询一级缓存, 未获取到数据再自动查询二级缓存的逻辑。 

缓存风险

1.缓存穿透

        缓存的目的是缓解CPU或者I/O的压力,譬如对数据库做缓存,大 部分流量都从缓存中直接返回,只有缓存未能命中的数据请求才会流 到数据库中,这样数据库压力自然就减小了。但是如果查询的数据在 数据库中根本不存在,缓存里自然也不会有,这类请求的流量每次都 不会命中,且每次都会触及末端的数据库,缓存就起不到缓解压力的 作用了,这种查询不存在的数据的现象被称为缓存穿透

        缓存穿透有可能是业务逻辑本身就存在的固有问题,也有可能是 恶意攻击所导致。为了解决缓存穿透问题,通常会采取下面两种办 法。

  • 对于业务逻辑本身不能避免的缓存穿透,可以约定在一定时间 内对返回为空的Key值进行缓存(注意是正常返回但是结果为空,不 应把抛异常的也当作空值来缓存),使得在一段时间内缓存最多被穿 透一次。如果后续业务在数据库中对该Key值插入了新记录,那应当 在插入之后主动清理掉缓存的Key值。如果业务时效性允许的话,也 可以对缓存设置一个较短的超时时间来自动处理。
  • 对于恶意攻击导致的缓存穿透,通常会在缓存之前设置一个布 隆过滤器来解决。所谓恶意攻击是指请求者刻意构造数据库中肯定不 存在的Key值,然后发送大量请求进行查询。布隆过滤器是用最小的 代价来判断某个元素是否存在于某个集合的办法。如果布隆过滤器给 出的判定结果是请求的数据不存在,直接返回即可,连缓存都不必去 查。虽然维护布隆过滤器本身需要一定的成本,但比起攻击造成的资 源损耗仍然是值得的。

2.缓存击穿

        我们都知道缓存的基本工作原理是首次从真实数据源加载数据, 完成加载后回填入缓存,以后其他相同的请求就从缓存中获取数据, 以缓解数据源的压力。如果缓存中某些热点数据忽然因某种原因失效 了,譬如由于超期而失效,此时又有多个针对该数据的请求同时发送 过来,这些请求将全部未能命中缓存,到达真实数据源中,导致其压 力剧增,这种现象被称为缓存击穿。要避免缓存击穿问题,通常会采 取下面两种办法。

  • 加锁同步,以请求该数据的Key值为锁,使得只有第一个请求 可以流入真实的数据源中,对其他线程则采取阻塞或重试策略。如果 是进程内缓存出现问题,施加普通互斥锁即可,如果是分布式缓存中 出现问题,就施加分布式锁,这样数据源就不会同时收到大量针对同 一个数据的请求了。
  • 热点数据由代码来手动管理。缓存击穿是仅针对热点数据自动 失效才引发的问题,对于这类数据,可以直接由开发者通过代码来有 计划地完成更新、失效,避免由缓存的策略自动管理。

3.缓存雪崩

        缓存击穿是针对单个热点数据失效,由大量请求击穿缓存而给真 实数据源带来压力。还有一种可能更普遍的情况,即不是针对单个热 点数据的大量请求,而是由于大批不同的数据在短时间内一起失效, 导致这些数据的请求都击穿缓存到达数据源,同样令数据源在短时间 内压力剧增。 出现这种情况,往往是因为系统有专门的缓存预热功能,或者大 量公共数据是由某一次冷操作加载的,使得由此载入缓存的大批数据 具有相同的过期时间,在同一时刻一起失效;也可能是因为缓存服务 由于某些原因崩溃后重启,造成大量数据同时失效。这种现象被称为 缓存雪崩。要避免缓存雪崩问题,通常会采取下面三种办法。

  1. 提升缓存系统可用性,建设分布式缓存的集群
  2. 启用透明多级缓存,这样各个服务节点一级缓存中的数据通常 会具有不一样的加载时间,也就分散了它们的过期时间。
  3. 将缓存的生存期从固定时间改为一个时间段内的随机时间,譬 如原本是1h过期,在缓存不同数据时,可以设置生存期为55min到 65min之间的某个随机时间。

4.缓存污染

缓存污染是指缓存中的数据与真实数据源中的数据不一致的现象。这种情况通常是由于开发者在更新缓存时操作不规范造成的,可能导致数据的不一致性。虽然缓存通常不追求强一致性,但最终一致性仍然是必须的。

造成缓存污染的常见原因

  1. 更新缓存不规范:开发者从缓存中获得某个对象并更新其属性,但由于某些原因(如业务异常回滚),最终未能成功写入数据库,导致缓存中的数据是新的,但数据库中的数据是旧的。
  2. 操作顺序错误:如果更新操作的顺序不正确,例如先失效缓存后写数据源,可能会导致缓存和数据源的不一致。

提高缓存一致性的设计模式

为了尽可能地提高使用缓存时的一致性,以下几种常见的设计模式可以帮助管理缓存更新:

  1. Cache Aside

    • 读数据:先读缓存,如果缓存中没有,再读数据源,然后将数据放入缓存,再响应请求。
    • 写数据:先写数据源,然后失效缓存。

    这种模式的关键在于:

    • 先后顺序:必须先写数据源后失效缓存。如果先失效缓存后写数据源,可能在缓存失效和数据源更新之间存在时间窗口,此时新的查询请求会读到旧的数据并重新填充到缓存中,导致数据不一致。
    • 失效缓存:而不是尝试更新缓存,因为更新过程中数据源可能会被其他请求再次修改,处理多次赋值的时序问题非常复杂。失效缓存可以避免这种情况,确保下次读取时自动回填最新的数据。
  2. Read/Write Through

    • 读数据:先读缓存,如果没有,再读数据源,并将数据写入缓存。
    • 写数据:先写缓存,再写数据源。

    这种模式保证了缓存和数据源的一致性,但实现起来比较复杂,可能会带来性能开销。

  3. Write Behind Caching

    • 读数据:先读缓存,如果没有,再读数据源,并将数据写入缓存。
    • 写数据:先写缓存,然后异步地将数据写入数据源。

    这种模式通过异步更新数据源,提高了写操作的性能,但在数据源更新的延迟期间,可能会导致数据不一致。

Cache Aside 模式的局限性

Cache Aside 模式虽然简洁且成本低,但依然不能完全保证一致性。例如,当某个从未被缓存的数据请求直接流到数据源,如果数据源的写操作发生在查询请求之后但回填到缓存之前,缓存中回填的内容可能与数据库的实际数据不一致。然而,这种情况的发生概率较低,Cache Aside 模式仍然是一个低成本且相对可靠的解决方案。

通过以上设计模式,可以在一定程度上减少缓存污染,确保缓存与数据源之间的一致性,但完全避免是不现实的,特别是在高并发和复杂业务场景下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1794934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx企业级负载均衡:技术详解系列(18)—— 作为上传服务器

你好,我是赵兴晨,97年文科程序员。 在上一期的技术分享中,我们探讨了如何高效搭建Nginx下载服务器,并讨论了长连接优化策略。那么今天,咱们进一步了解Nginx的另一面——作为上传服务器的配置技巧。 作为上传服务器&a…

RPC 框架

RPC 全称 Remote Procedure Call——远程过程调用。 RPC技术简单说就是为了解决远程调用服务的一种技术,使得调用者像调用本地服务一样方便透明。RPC是一种通过网络从远程计算机程序上请求服务,不需要了解底层网络技术的协议。 集群和分布式 集群&…

【matlab】绘图插入并放大/缩小子图

参考链接 代码分为两个:绘图代码与magnify.m 绘图代码就是普通的绘图代码,以下为例 %https://zhuanlan.zhihu.com/p/655767542 clc clear close all x 0:pi/100:2*pi; y1 sin(x); plot(x,y1,r-o); hold on y2sin(x)-0.05; y3sin(x)0.05; xlim([0 2*…

企业在现代市场中的战略:通过数据可视化提升财务决策

新时代,财务规划团队不仅仅是企业内部的一个部门,更是帮助企业做出明智决策和设定战略目标的中坚力量。在当今瞬息万变的商业环境中,财务专业人士需要具备应对挑战并引导企业走向成功的角色职能。企业领导者时常面临着数据压力,需…

混剪素材哪里找?分享几个热门混剪素材下载网站

在短视频和新媒体的世界里,高质量的混剪素材是吸引观众的关键。今天,我将为大家详细介绍几个优秀的素材网站,它们不仅资源丰富,而且完全满足新媒体创作者的需求。这篇文章将帮助你理解如何有效利用这些平台提升你的视频创作。 蛙…

小型企业网络组网与配置仿真实验

实验要求如下: 我这里以学号46为例 一、IP 地址规划表 (一)主类网络 (二)子网划分 需要自己计算有效ip范围 在C类主网络192.168.46.0/24中,我们需要先了解这个网络的子网掩码为255.255.255.0,其二进制…

DDMA信号处理以及数据处理的流程---DDMA原理介绍

Hello,大家好,我是Xiaojie,好久不见,欢迎大家能够和Xiaojie一起学习毫米波雷达知识,Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程,本系列文章将从目标生成、信号仿真、测距、测速、cfar…

Vitis HLS 学习笔记--接口聚合与解聚-AXI主接口

目录 1. 简介 2. 用法及语法 3. 详细解读 4. 总结 1. 简介 在使用 Vitis HLS 工具进行硬件设计时,如果你在接口上使用了结构体,工具会自动把结构体里的所有元素组合成一个整体。就像把一堆零件组装成一个玩具一样。这样做的好处是,数据可…

【System Verilog and UVM基础入门4】程序和接口

目录 方法task和函数function 接口 [System Verilog特性] 方法task和函数function 首先要明白一个事情!Task任务,是消耗时间的,函数function是不消耗时间的! 这样写看着是不是很高大上呢?此外,如果我们想修改时钟周期怎么办呢?这时我们可以在task clk_gen(int period…

从报名到领证:软考高级【系统分析师】报名考试全攻略

本文共计13156字,预计阅读39分钟。包括七个篇章:报名、准考证打印、备考、考试、成绩查询、证书领取及常见问题。 不想看全文的可以点击目录,找到自己想看的篇章进行阅读。 一、报名篇 报名条件要求: 1.凡遵守中华人民共和国宪…

盛夏之约,即将启程,2024中国北京消防展将于6月26举行

盛夏之约,即将启程,2024中国北京消防展将于6月26举行 盛夏之约,即将启程!备受瞩目的2024中国(北京)消防技术与设备展览会将于6月26-28 日在北京.首钢会展中心盛大召开。作为消防安全和应急救援的年度盛会&…

Camtasia Studio2024永久免费版及最新版本功能讲解

在当前数字化时代,视频内容的制作与编辑变得愈发重要。无论是企业宣传、在线教育还是个人Vlog制作,一款功能强大且易于上手的视频编辑软件成为了刚需。Camtasia Studio作为市场上备受欢迎的视频编辑与屏幕录像工具,凭借其强大的功能与用户友好…

Golang——gRPC与ProtoBuf介绍

一. 安装 1.1 gRPC简介 gRPC由google开发,是一款语言中立,平台中立,开源的远程过程调用系统。gRPC客户端和服务器可以在多种环境中运行和交互,例如用java写一个服务器端,可以用go语言写客户端调用。 1.2 gRPC与Protob…

android睡眠分期图

一、效果图 做医疗类项目&#xff0c;经常会遇到做各种图表&#xff0c;本文做的睡眠分期图。 二、代码 引入用到的库 api joda-time:joda-time:2.10.1 调用代码 /*** 睡眠* 分期*/private SleepChartAdapter mAdapter;private SleepChartAttrs mAttrs;private List<SleepI…

day26-单元测试

1. 单元测试Junit 1.1 什么是单元测试&#xff1f;&#xff08;掌握&#xff09; 1.2 Junit的特点&#xff1f;&#xff08;掌握&#xff09; 1.3 基本用法&#xff1a;&#xff08;掌握&#xff09; 实际开发中单元测试的使用方式&#xff08;掌握&#xff09; public class …

安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑

安徽京准NTP时钟系统&#xff1a;GPS北斗卫星授时下的生活重塑 安徽京准NTP时钟系统&#xff1a;GPS北斗卫星授时下的生活重塑 时间的流逝自古以来时钟都是人类生活与活动的基础。然而&#xff0c;随着科技的进步&#xff0c;我们对时间管理和测量的方法已经发生了翻天覆地的变…

【UML用户指南】-09-对基本结构建模-类图

目录 1、概述 2、引入 3、过程 4、常用建模技术 4.1、对简单协作建模 4.2、对逻辑数据库模式建模 4.3、正向工程 1、概述 类图是面向对象系统建模中最常见的图。 类图显示一组类、接口、协作以及它们之间的关系 类图用于对系统静态设计视图建模。其大多数涉及到对系统的…

完整指南:远程管理 Linux 服务器的 Xshell6 和 Xftp6 使用方法(Xshell无法启动:要继续使用此程序........,的解决方法)

&#x1f600;前言 在当今软件开发领域&#xff0c;远程管理 Linux 服务器已成为日常工作的重要组成部分。随着团队成员分布在不同的地理位置&#xff0c;远程登录工具的使用变得至关重要&#xff0c;它们为开发人员提供了访问和管理服务器的便捷方式。本文将介绍两款功能强大的…

深度学习框架-----Tensorflow2基础

一、基础概念 1、深度学习框架基础概念 深度学习框架的出现降低了入的槛。我们不在需要丛从复杂的神经网络和反向传播算法开始编代码&#xff0c;可以依据需要&#xff0c;使用已有的模型配置参数&#xff0c;而模型的参数自动训练得到。我们也可以在已有模型的基础上增加自定…

[word] word怎样转换成pdf #职场发展#经验分享#职场发展

word怎样转换成pdf word怎样转换成pdf&#xff1f;word格式是办公中常会用到的格式&#xff0c;word格式编辑好了要想转换成pdf格式再来传输的话需要怎么操作呢&#xff1f;小编这就给大家分享下操作方法&#xff0c;一起来学习下吧&#xff01; 1、安装得力PDF转换器&#x…