凤凰架构4——透明多级分流系统

本文详细探讨了HTTP缓存的机制,包括强制缓存与协商缓存,以及相关的Header字段如Cache-Control、Expires、Etag和If-None-Match等。此外,介绍了域名解析的步骤,强调了DNS缓存和连接数优化的重要性,特别是TCP的Keep-Alive机制和HTTP/2的多路复用技术。内容还涵盖了负载均衡的层次和策略,如轮询、最少连接数和一致性哈希。最后,讨论了服务端缓存的管理和风险,如缓存穿透、击穿和雪崩问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

透明多级分流系统

客户端缓存

HTTP 协议的无状态性决定了它必须依靠客户端缓存来解决网络传输效率上的缺陷。

无状态简化了服务器设计,但是会导致携带重复数据,导致性能下降。HTTP通过客户端缓存解决此问题,形成了“状态缓存”(客户端直接根据缓存信息对目标网站的状态判断)、“强制缓存”和“协商缓存”的 HTTP 缓存机制

强制缓存

在指定时间之前,除非用户手动刷新页面,不然强制缓存在页面跳转等都可生效,Http有两类Header实现强制缓存

Expires

Expires: Wed, 8 Apr 2020 07:28:00 GMT 服务器承诺在此时间之前资源不会变动,浏览器可以直接缓存信息,不需重新请求
缺点

  1. 客户端时间修改会影响缓存
  2. 用户私有信息可能被未登录用户获取
  3. 不能保证某些资源不进行缓存
Cache-Control

如果 Cache-Control 和 Expires 同时存在,并且语义存在冲突,以Cache-Controller为准

  • max-age和s-maxage:表示资源在请求时间多少秒以内有效(不收客户端时钟影响)
  • public和private: public代表可以被CDN等缓存,private只能被当前客户端缓存
  • no-cache和no-store:no-cache 指明该资源不应该被缓存,令强制缓存完全失效,no-store 不强制会话中相同 URL 资源的重复获取,但禁止浏览器、CDN 等以任何形式保存该资源。
  • no-transform:禁止资源被修改。
  • min-fresh(s)和only-if-cached:min-fresh 建议服务器能返回一个不少于该时间的缓存资源,only-if-cached 表示客户端要求不必给它发送资源的具体内容利用缓存进行相应,如果不能命中就503
  • must-revalidate和proxy-revalidate:must-revalidate 表示资源过期从服务器获取,proxy-revalidate 用于提示CDN等资源过期重新获取

协商缓存

基于检测的缓存机制,被称为 协商缓存。强制缓存存在,从强制缓存取;强制缓存过期,从协商缓存取。
协商缓存有两种检查机制:1.根据资源的修改时间, 2.根据资源的唯一标识是否修改进行检查

  • Last-Modified 和 If-Modified-Since:服务期的响应头,告诉客户端资源的最后修改时间。存在这个Header的资源,客户端在次请求,通过 If-Modified-Since 把之前收到的资源最后修改时间发送回服务端。

    • 在这时间后没有修改,就直接返回304/Not Modified
    • 修改 就返回完整响应体 200/OK
  • Etag 和 If-None-Match:Etag是服务器返回客户端的唯一标识。当带有这个Header,客户端在此请求会在 If-None-Match 带上唯一标识

    • 如果发现资源唯一标识一致,代表资源没有被修改 直接返回304/Not Modified
    • 如果发现唯一标识不同,返回完整信息 200/OK

Etag和Last-Modified的纠结

  • Last-Modified 对文件在一秒内修改多次的情况无法标注准确时间,如果文件定期生成但内容没有变化Last-Modified修改,导致缓存失效了,只能通过Etag解决
  • etag每次请求都要对资源进行哈希运算,比获取修改时间 开销大很多
  • 服务器优先验证etag,etag一致的情况下对比Last-Modified(防止浏览器未将修改时间纳入哈希范围)

但是对于一个URL请求多个资源的场景,HTTP提供了内容协商机制,设计了请求头 Accept*,响应头 Content-*
Vary Header可以明确返回给用户什么类型的资源,

协商缓存不仅在浏览器的地址输入、页面链接跳转、新开窗口、前进、后退中生效,而且在用户主动刷新页面(F5)时也同样是生效的,只有用户强制刷新(Ctrl+F5)或者明确禁用缓存(譬如在 DevTools 中设定)时才会失效

域名解析

DNS解析步骤

  1. 客户端查询本地DNS缓存(以TTL衡量缓存是否有效),如果域名改变了IP,但是域名并没有通知其他的更新缓存,只能依靠TTL过期来保持一致性
  2. 客户端将地址发送给本地的DNS
  3. 本地DNS会按照是否有www.icyfenix.com.cn的权威服务器——》icyfenix.com.cn——》com.cn——》cn 依次查询地址,如果没有查询到会查到 点号的 根服务器为止
    • 权威域名服务器(Authoritative DNS):负责翻译特定域名的 DNS 服务器
    • 根域名服务器(Root DNS):是指固定的、无需查询的顶级域名(Top-Level Domain)服务器
  4. 查到根域名服务器后会得到 cn的权威服务器地址 —》最后得到www.icyfenix.com.cn的权威服务器地址
  5. 通过www.icyfenix.com.cn的权威服务器,查询www.icyfenix.com.cn的地址记录(不一定就是指Ip地址)

传输链路

连接数优化

TCP需要三次握手才开始数据传输,启动慢。减少请求书,增加客户端与服务端的链接

连接复用技术(Keep-Alive 机制)

  • 客户端对同一个域名长期持有一个或多个不会用完即断的 TCP 连接
  • 客户端维护一个 FIFO 队列,减少TCP链接成本
  • FIFO也会引起“队首阻塞”(Head-of-Line Blocking)问题,服务端不能哪个请求完成就先返回哪个,因为如果顺序乱了,客户端很难分清资源的归属问题

“HTTP 管道”(HTTP Pipelining)复用技术
在服务器建立FIFO队列,但是也避免不了队首阻塞问题,但是服务端能够较为准确地评估资源消耗情况,可以提升提升链路传输的效率,但是这个推广的不怎么成功

HTTP/2 多路复用(HTTP/2 Multiplexing)技术
HTTP/1.x中,HTTP请求是最小的信息单位 ;HTTP/2中,帧(Frame)是最小粒度的信息单位,每个帧都附带一个流 ID 以标识这个帧属于哪个流,客户端可以将不同流中的数据重组出不同 HTTP 请求和响应报文来
在这里插入图片描述
通过多路复用,对每个域名只需要一个TCP连接即可,也无需可以合并HTTP请求了。

传输压缩

  • GZip可以对文本资源进行很好的压缩,对于不需要压缩的资源通过MIME判断是否进行压缩
  • 服务器对请求输出时进行“即时压缩”(On-The-Fly Compression),整个压缩在内存内完成,不必等完成在响应,但是服务器无法给出Content-Length
  • 在这种情况下,TCP连接无法知道他什么时候请求完毕,所以在HTTP/1.0中 持久连接和“即时压缩”只能进行二选一
  • 在HTTP/1.1 中,增加了另一种“分块传输编码”(Chunked Transfer Encoding)的资源结束判断机制,在响应 Header 中加入“Transfer-Encoding: chunked”之后,就代表这个响应报文将采用分块编码,最后以一个长度为0的分块代表资源结束。

HTTP/1.1 通过分块传输解决了即时压缩与持久连接并存的问题,到了 HTTP/2,由于多路复用和单域名单连接的设计,已经无须再刻意去提持久链接机制了,但数据压缩仍然有节约传输带宽的重要价值。

快速 UDP 网络连接(Quick UDP Internet Connections,QUIC)

  • QUIC是基于UDP实现的,QUIC的可靠传输是靠自己实现的。能对每个流单独控制,一个流发生错误协议栈仍然可以给其他流提供服务。
  • QUIC在移动端提供了友好的支持,无需依靠IP地址,通过连接标识符唯一标识客户端与服务器之间的连接,即使IP地址发生变化,原始链接任然有效。

内容分发网络

路由解析

在这里插入图片描述
在这里插入图片描述

内容分发

CDN缓存节点中必须有用户想要请求的资源副本,才可能代替源站来响应用户请求。这里面又包括了两个子问题:“如何获取源站资源”和“如何管理(更新)资源”。

获取源站资源

  • 主动分发(Push): 分发由源站主动发起,将内容从源站或者其他资源库推送到用户边缘的各个 CDN 缓存节点上。(用户端透明)
  • **被动回源(Pull):**用户访问CDN节点发现没有缓存该资源,会实时从源站获取,首次访问相对较慢(双向透明)

管理资源

  • CDN 缓存的管理不存在通用的准则
  • 常见做法是超时被动失效与手工主动失效相结合
    • 超时失效是 给缓存设置有效期
    • 主动失效是通过调用CDN缓存失效接口,网站更新自动调用接口

CDN 应用

  1. 加速静态资源
  2. 安全防御:源站只为CDN提供服务(可以防御Ddos攻击,但是源站IP泄露还是不安全)
  3. 协议升级:例如可以实现源站是 HTTP 协议,对外开放的网站是基于 HTTPS的
  4. 状态缓存:源站的 301/302 转向就可以缓存起来让客户端直接跳转、还可以通过 CDN 开启HSTS、可以通过 CDN 进行OCSP 装订加速 SSL 证书访问
  5. 修改资源:可以在返回资源的时候修改内容
  6. 访问控制:实现 IP 黑/白名单功能,根据 IP 的访问流量来实现 QoS 控制、根据 HTTP 的 Referer 来实现防盗链
  7. 注入功能:CDN 可以在不修改源站代码的前提下,为源站注入各种功能

负载均衡

调度后方的多台机器,以统一的接口对外提供服务。
四层负载均衡的优势是性能高,七层负载均衡的优势是功能强。
做多级混合负载均衡,通常应是低层的负载均衡在前,高层的负载均衡在后。

在这里插入图片描述

  • 四层负载均衡主要指的是维持着同一个 TCP 连接,而不是说它只工作在第四层。
  • 主要是工作在二层(数据链路层,改写 MAC 地址)和三层(网络层,改写 IP 地址)上,单纯只处理第四层(传输层,可以改写 TCP、UDP 等协议的内容和端口)的数据无法做到负载均衡的转发

数据链路层负载均衡

在这里插入图片描述

  • 修改数据帧的MAC目标地址,让原本到负载均衡器的请求转发到服务器集群中的对应机器。
  • 只修改了目标地址,对于上层看来数据都是没有被修改过的
  • 需要把真实物理服务器集群所有机器的虚拟 IP 地址(Virtual IP Address,VIP)配置成与负载均衡器的虚拟 IP 一样,所以响应结果可以直接返回给客户端
    在这里插入图片描述
    这种负载均衡模式也常被很形象地称为“三角传输模式”(Direct Server Return,DSR),也有叫“单臂模式”(Single Legged Mode)或者“直接路由”(Direct Routing)。
  • 数据链路层复杂均衡效率很高,无法感知应用层协议
  • 负载均衡器需要和服务器在同一个子网,无法跨VLAN

网络层负载均衡

在这里插入图片描述
源和目标 IP 地址代表了数据是从分组交换网络中哪台机器发送到哪台机器的,可以通过改变地址实现数据包的转发。

  • 第一种方法:把原来数据包的 Headers 和 Payload 整体作为另一个新的数据包的 Payload,新数据包的 Headers 中写入真实服务器的 IP 作为目标地址,在真实服务器接受到后,把外层的Header拆掉,拿原来的数据包使用。套娃氏的被称为“IP 隧道”(IP Tunnel)

    • 因为要封装新的数据包,所以效率有所下降,但是没有修改原有数据包信息,返回仍然无须经过均衡器。由于工作在网络层,所以可以跨越VLAN
    • 在这里插入图片描述
    • 需要服务器支持IP隧道协议(不过linux几乎都支持)
    • 这种模式需要专门配置,保证服务器与均衡器有相同的虚拟IP地址(回复该数据包时,需要这个虚拟IP作为源地址,客户端收到数据包才能解析)
    • 当好几个服务共用一个物理服务器时,得考虑第二种方案
  • 第二种方案——改变目标数据包,直接修改数据包Header中的目标地址(不能直接响应给客户端,需要让应答流量继续回到负载均衡,由负载均衡把应答包的源 IP 改回自己的 IP,再发给客户端)

    • 在这里插入图片描述
    • 这种模式被称为“网络地址转换”(Network Address Translation,NAT) NAT 模式。
  • 还有一种更加彻底的 NAT 模式:负载均衡器在转发时,把源IP和目标IP都修改了,真实服务器无须配置网关就能够让应答流量经过正常的三层路由回到负载均衡器上,但是这样真实服务器就无法拿到客户端的IP

应用层负载均衡

在这里插入图片描述

代理分为三种,正向代理(客户端设置,对服务器透明)、反向代理(服务端设置,对客户端透明)、透明代理(双方透明,网络中间设备架设)

应用层负载均衡性能比较差,会有带宽问题,但是他可以感知具体的通信内容,发挥更大的作用

可以实现的功能

  1. 实现CDN缓存方面的功能(静态资源缓存、协议升级、安全防护、访问控制,等等)
  2. 更智能化的路由(根据 Session、URL、用户身份登 路由)
  3. 某些安全攻击可以由七层均衡器来抵御(过滤特定报文,SQL注入,Ddos等)
  4. 很多微服务架构的系统中,链路治理措施都需要在七层中进行,譬如服务降级、熔断、异常注入,等等

均衡策略与实现

  • 轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器。此种均衡算法适合于集群中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。
  • 权重轮循均衡(Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。
  • 随机均衡(Random):把来自客户端的请求随机分配给内部中的多个服务器,在数据足够大的场景下能达到相对均衡的分布。
    权重随机均衡(Weighted Random):此种均衡算法类似于权重轮循算法,不过在分配处理请求时是个随机选择的过程。
  • 一致性哈希均衡(Consistency Hash):一致性的意思是保证当服务集群某个真实服务器出现故障,只影响该服务器的哈希,而不会导致整个服务集群的哈希键值重新分布。
  • 响应速度均衡(Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如 Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。
  • 最少连接数均衡(Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不平衡,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡策略适合长时处理的请求服务,如 FTP 传输。

服务端缓存

引入缓存的理由有下面两种

  • 为缓解 CPU 压力而做缓存:把一些公用的数据进行复用,这可以节省 CPU 算力,顺带提升响应性能。
  • 为缓解 I/O 压力而做缓存:譬如把原本对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,将原本对单点部件(如数据库)的读写访问变为到可扩缩部件(如缓存中间件)的访问,顺带提升响应性能。

缓存属性

缓存四大考虑维度

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

并发读写的时候吞吐量受多方面影响。怎样设计数据结构以尽可能避免数据竞争,存在竞争风险时怎样处理同步(主要有使用锁实现的悲观同步和使用CAS实现的乐观同步)、如何避免伪共享现象(CPU缓存行在提供免费加载好处的同时,存在不同变量存在相互干扰的可能)

对读写操作而来的状态维护,有两种可选择的处理思路

  • 以 Guava Cache 为代表的同步处理机制,即在访问数据时一并完成缓存淘汰、统计、失效等状态变更操作,通过分段加锁等优化手段来尽量减少竞争。
  • 以 Caffeine 为代表的异步日志提交机制,将对数据的读、写过程看作是日志(即对数据的操作指令)的提交过程。尽管日志也涉及到写入操作,有并发的数据变更就必然面临锁竞争,但异步提交的日志已经将原本在 Map 内的锁转移到日志的追加写操作上,日志里腾挪优化的余地就比在 Map 中要大得多。
以 Caffeine 为例

在 Caffeine 的实现中,设有专门的环形缓存区(Ring Buffer,也常称作 Circular Buffer)来记录由于数据读取而产生的状态变动日志。为进一步减少竞争,Caffeine 给每条线程(对线程取 Hash,哈希值相同的使用同一个缓冲区)都设置一个专用的环形缓冲。

在这里插入图片描述
从 Caffeine 读取数据时,数据本身会在其内部的 ConcurrentHashMap 中直接返回,而数据的状态信息变更就存入环形缓冲中,由后台线程异步处理。如果异步处理的速度跟不上状态变更的速度,导致缓冲区满了,那此后接收的状态的变更信息就会直接被丢弃掉,直至缓冲区重新富余。通过环形缓冲和容忍有损失的状态变更,Caffeine 大幅降低了由于数据读取而导致的垃圾收集和锁竞争,因此 Caffeine 的读取性能几乎能与 ConcurrentHashMap 的读取性能相同。

向 Caffeine 写入数据时,将使用传统的有界队列(ArrayQueue)来存放状态变更信息,写入带来的状态变更是无损的,不允许丢失任何状态,这是考虑到许多状态的默认值必须通过写入操作来完成初始化,因此写入会有一定的性能损失。

命中率与淘汰策略

基础的淘汰策略实现方案:

  • FIFO(First In First Out): 优先淘汰最早进入被缓存的数据(很可能会大幅降低缓存的命中率)。
  • LRU(Least Recent Used): 优先淘汰最久未被使用访问过的数据。适合用来处理短时间内频繁访问的热点对象,但是热点数据最近未被访问,可能错误淘汰价值更高的数据
  • LFU(Least Frequently Used): 优先淘汰最不经常使用的数据。避免LRU热点数据暂时未被访问的问题,但是需要维护计数器(影响吞吐量),但是如果是频繁访问的数据不需要了很难被清除

针对LFU出现的问题,出现了两个新的 TinyLFU 和 W-TinyLFU 算法

  • **TinyLFU(Tiny Least Frequently Used):**是LRF的改进版本

    • 针对计数器带来的性能问题:采用 Sketch 进行分析,损失一定准确性,用少量样本来估计全体数据特征。使用Count–Min Sketch算法,用记录频率和空间来近似地找出缓存中的低价值数据
    • 针对清楚不需要的热点数据:滑动时间窗,每隔一段时间数值减半
    • TinyLFU 在实现减少计数器维护频率的同时,也带来了无法很好地应对稀疏突发访问的问题(TinyLFU 在实现减少计数器维护频率的同时,也带来了无法很好地应对稀疏突发访问的问题)
  • W-TinyLFU(Windows-TinyLFU): 是TinyLFU的改进版本

    • 结合了 LRU 和 LFU 两者的优点,从整体上看是它是 LFU 策略,从局部实现上看又是 LRU 策略。
    • 将新记录暂时放入一个名为 Window Cache 的前端 LRU 缓存里面,让这些对象可以在 Window Cache 中累积热度,如果能通过 TinyLFU 的过滤器,再进入名为 Main Cache 的主缓存中存储,主缓存根据数据的访问频繁程度分为不同的段(LFU 策略,实际上 W-TinyLFU 只分了两段),但单独某一段局部来看又是基于 LRU 策略去实现的(称为 Segmented LRU)。每当前一段缓存满了之后,会将低价值数据淘汰到后一段中去存储,直至最后一段也满了之后,该数据就彻底清理出缓存
扩展功能
  • **加载器:**主动通过加载器去加载指定 Key 值的数据
  • 淘汰策略
  • 失效策略
  • 事件通知
  • 并发级别
  • 容量控制
  • 引用方式
  • 统计信息: 提供诸如缓存命中率、平均加载时间、自动回收计数等统计。
  • 持久化
    在这里插入图片描述
    在这里插入图片描述
  • 以分布式缓存中的数据为准,访问以进程内缓存的数据优先。
  • 大致做法是当数据发生变动时,在集群内发送推送通知,让各个节点的一级缓存自动失效掉相应数据。当访问缓存时,提供统一封装好的一、二级缓存联合查询接口,接口外部是只查询一次,接口内部自动实现优先查询一级缓存,未获取到数据再自动查询二级缓存的逻辑。

缓存风险

缓存穿透——查询数据库没有的数据

业务逻辑不能避免的穿透

  • 返回值为空的key缓存,后续如果该key存在了对应数据,则插入时主动删除key;如果允许一定延迟的话,可以设置一个较短的超时时间。

恶意攻击

  • 布隆过滤器
缓存击穿——热点数据忽然失效
  1. 加锁同步,让只一个第一个请求可以进入,其他采取阻塞或重试。
  2. 热点数据由代码来手动管理,开发者有计划的进行更新
缓存雪崩——大批量数据短时间内失效
  1. 提升缓存系统可用性,建设分布式缓存的集群。
  2. 启用透明多级缓存,各个服务节点一级缓存中的数据通常会具有不一样的加载时间,也就分散了它们的过期时间。
  3. 将缓存的生存期从固定时间改为一个时间段内的随机时间
缓存污染——缓存数据与数据库数据不一

写数据的注意点:
一是先后顺序是先数据源后缓存:避免脏数据回填
二是应当失效缓存,而不是去尝试更新缓存

Cache Aside(低成本,比较可靠)

  • 读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求。
  • 写数据时,先写数据源,然后失效(而不是更新)掉缓存。
  • 还是会有点问题,读缓存失效的情况下,并发出现一个写缓存(概率很低)

Read/Write Through

将cache作为主数据源,从cache种读取、写入数据,cache负责写入db

  • 读(Read Through):

    • 从 cache 中读取数据,读取到就直接返回 。
    • 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
  • 写(Write Through):

    • 先查 cache,cache 中不存在,直接更新 DB。
    • cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache 和 DB)。

Write Behind Caching

  • 在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。
  • write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。
  • 数据不是强一致性的,而且可能会丢失
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值