深入缓存核心技术:大型网站多级缓存的分层架构

在互联网高速发展的今天,缓存技术被广泛地应用。无论业内还是业外,只要是提到性能问题,大家都会脱口而出“用缓存解决”。

这种说法带有片面性,甚至是一知半解,但是作为专业的我们,需要对缓存有更深、更广的了解。

缓存技术存在于应用场景的方方面面。从浏览器请求,到反向代理服务器,从进程内缓存到分布式缓存。其中缓存策略,算法也是层出不穷,今天就带大家走进缓存。

正文

缓存对于每个开发者来说是相当熟悉了,为了提高程序的性能我们会去加缓存,但是在什么地方加缓存,如何加缓存呢?

假设一个网站,需要提高性能,缓存可以放在浏览器,可以放在反向代理服务器,还可以放在应用程序进程内,同时可以放在分布式缓存系统中。

从用户请求数据到数据返回,数据经过了浏览器,CDN,代理服务器,应用服务器,以及数据库各个环节。每个环节都可以运用缓存技术。

从浏览器/客户端开始请求数据,通过 HTTP 配合 CDN 获取数据的变更情况,到达代理服务器(Nginx)可以通过反向代理获取静态资源。

再往下来到应用服务器可以通过进程内(堆内)缓存,分布式缓存等递进的方式获取数据。如果以上所有缓存都没有命中数据,才会回源到数据库。

缓存的请求顺序是:用户请求 → HTTP 缓存 → CDN 缓存 → 代理服务器缓存 → 进程内缓存 → 分布式缓存 → 数据库。

看来在技术的架构每个环节都可以加入缓存,看看每个环节是如何应用缓存技术的。

1. HTTP缓存

当用户通过浏览器请求服务器的时候,会发起 HTTP 请求,如果对每次 HTTP 请求进行缓存,那么可以减少应用服务器的压力。

当第一次请求的时候,浏览器本地缓存库没有缓存数据,会从服务器取数据,并且放到浏览器的缓存库中,下次再进行请求的时候会根据缓存的策略来读取本地或者服务的信息。

一般信息的传递通过 HTTP 请求头 Header 来传递。目前比较常见的缓存方式有两种,分别是:

  • 强制缓存
  • 对比缓存

1.1. 强制缓存

当浏览器本地缓存库保存了缓存信息,在缓存数据未失效的情况下,可以直接使用缓存数据。否则就需要重新获取数据。

这种缓存机制看上去比较直接,那么如何判断缓存数据是否失效呢?这里需要关注 HTTP Header 中的两个字段 Expires 和 Cache-Control。

Expires 为服务端返回的过期时间,客户端第一次请求服务器,服务器会返回资源的过期时间。如果客户端再次请求服务器,会把请求时间与过期时间做比较。

如果请求时间小于过期时间,那么说明缓存没有过期,则可以直接使用本地缓存库的信息。

反之,说明数据已经过期,必须从服务器重新获取信息,获取完毕又会更新最新的过期时间。

这种方式在 HTTP 1.0 用的比较多,到了 HTTP 1.1 会使用 Cache-Control 替代。

Cache-Control 中有个 max-age 属性,单位是秒,用来表示缓存内容在客户端的过期时间。

例如:max-age 是 60 秒,当前缓存没有数据,客户端第一次请求完后,将数据放入本地缓存。

那么在 60 秒以内客户端再发送请求,都不会请求应用服务器,而是从本地缓存中直接返回数据。如果两次请求相隔时间超过了 60 秒,那么就需要通过服务器获取数据。

1.2. 对比缓存

需要对比前后两次的缓存标志来判断是否使用缓存。浏览器第一次请求时,服务器会将缓存标识与数据一起返回,浏览器将二者备份至本地缓存库中。浏览器再次请求时,将备份的缓存标识发送给服务器。

服务器根据缓存标识进行判断,如果判断数据没有发生变化,把判断成功的 304 状态码发给浏览器。

这时浏览器就可以使用缓存的数据来。服务器返回的就只是 Header,不包含 Body。

下面介绍两种标识规则:

1.2.1. Last-Modified/If-Modified-Since 规则

在客户端第一次请求的时候,服务器会返回资源最后的修改时间,记作 Last-Modified。客户端将这个字段连同资源缓存起来。

Last-Modified 被保存以后,在下次请求时会以 Last-Modified-Since 字段被发送。

当客户端再次请求服务器时,会把 Last-Modified 连同请求的资源一起发给服务器,这时 Last-Modified 会被命名为 If-Modified-Since,存放的内容都是一样的。

服务器收到请求,会把 If-Modified-Since 字段与服务器上保存的 Last-Modified 字段作比较:

  • 若服务器上的 Last-Modified 最后修改时间大于请求的 If-Modified-Since,说明资源被改动过,就会把资源(包括 Header+Body)重新返回给浏览器,同时返回状态码 200。
  • 若资源的最后修改时间小于或等于 If-Modified-Since,说明资源没有改动过,只会返回 Header,并且返回状态码 304。浏览器接受到这个消息就可以使用本地缓存库的数据。

注意:Last-Modified 和 If-Modified-Since 指的是同一个值,只是在客户端和服务器端的叫法不同。

1.2.2. ETag / If-None-Match 规则

客户端第一次请求的时候,服务器会给每个资源生成一个 ETag 标记。这个 ETag 是根据每个资源生成的唯一 Hash 串,资源如何发生变化 ETag 随之更改,之后将这个 ETag 返回给客户端,客户端把请求的资源和 ETag 都缓存到本地。

ETag 被保存以后,在下次请求时会当作 If-None-Match 字段被发送出去。

在浏览器第二次请求服务器相同资源时,会把资源对应的 ETag 一并发送给服务器。在请求时 ETag 转化成 If-None-Match,但其内容不变。

服务器收到请求后,会把 If-None-Match 与服务器上资源的 ETag 进行比较:

  • 如果不一致,说明资源被改动过,则返回资源(Header+Body),返回状态码 200。
  • 如果一致,说明资源没有被改过,则返回 Header,返回状态码 304。浏览器接受到这个消息就可以使用本地缓存库的数据。

注意:ETag 和 If-None-Match 指的是同一个值,只是在客户端和服务器端的叫法不同。

2. CDN 缓存

HTTP 缓存主要是对静态数据进行缓存,把从服务器拿到的数据缓存到客户端/浏览器。

如果在客户端和服务器之间再加上一层 CDN,可以让 CDN 为应用服务器提供缓存,如果在 CDN 上缓存,就不用再请求应用服务器了。并且 HTTP 缓存提到的两种策略同样可以在 CDN 服务器执行。

CDN 的全称是 Content Delivery Network,即内容分发网络。

让我们来看看它是如何工作的吧:

  • 客户端发送 URL 给 DNS 服务器。
  • DNS 通过域名解析,把请求指向 CDN 网络中的 DNS 负载均衡器。
  • DNS 负载均衡器将最近 CDN 节点的 IP 告诉 DNS,DNS 告之客户端最新 CDN 节点的 IP。
  • 客户端请求最近的 CDN 节点。
  • CDN 节点从应用服务器获取资源返回给客户端,同时将静态信息缓存。注意:客户端下次互动的对象就是 CDN 缓存了,CDN 可以和应用服务器同步缓存信息。

CDN 接受客户端的请求,它就是离客户端最近的服务器,它后面会链接多台服务器,起到了缓存和负载均衡的作用。

3. 负载均衡缓存

说完客户端(HTTP)缓存和 CDN 缓存,我们离应用服务越来越近了,在到达应用服务之前,请求还要经过负载均衡器。

虽说它的主要工作是对应用服务器进行负载均衡,但是它也可以作缓存。可以把一些修改频率不高的数据缓存在这里,例如:用户信息,配置信息。通过服务定期刷新这个缓存就行了。

以 Nginx 为例,我们看看它是如何工作的:

  • 用户请求在达到应用服务器之前,会先访问 Nginx 负载均衡器,如果发现有缓存信息,直接返回给用户。
  • 如果没有发现缓存信息,Nginx 回源到应用服务器获取信息。
  • 另外,有一个缓存更新服务,定期把应用服务器中相对稳定的信息更新到 Nginx 本地缓存中。

4. 进程内缓存

通过了客户端,CDN,负载均衡器,我们终于来到了应用服务器。应用服务器上部署着一个个应用,这些应用以进程的方式运行着,那么在进程中的缓存是怎样的呢?

进程内缓存又叫托管堆缓存,以APC为例,同时会受到托管堆回收算法的影响。

由于其运行在内存中,对数据的响应速度很快,通常我们会把热点数据放在这里。

在进程内缓存没有命中的时候,我们会去搜索进程外的缓存或者分布式缓存。这种缓存的好处是没有序列化和反序列化,是最快的缓存。缺点是缓存的空间不能太大,对垃圾回收器的性能有影响。

这里我们需要关注几个缓存的回收策略,具体的实现架构的回收策略会有所不同,但大致的思路都是一致的:

  • FIFO(First In First Out):先进先出算法,最先放入缓存的数据最先被移除。
  • LRU(Least Recently Used):最近最少使用算法,把最久没有使用过的数据移除缓存。
  • LFU(Least Frequently Used):最不常用算法,在一段时间内使用频率最小的数据被移除缓存。

在分布式架构的今天,多应用中如果采用进程内缓存会存在数据一致性的问题。

这里推荐两个方案:

  • 消息队列修改方案
  • Timer 修改方案

4.1. 消息队列修改方案

应用在修改完自身缓存数据和数据库数据之后,给消息队列发送数据变化通知,其他应用订阅了消息通知,在收到通知的时候修改缓存数据。

4.2. Timer 修改方案

为了避免耦合,降低复杂性,对“实时一致性”不敏感的情况下。每个应用都会启动一个 Timer,定时从数据库拉取最新的数据,更新缓存。

不过在有的应用更新数据库后,其他节点通过 Timer 获取数据之间,会读到脏数据。这里需要控制好 Timer 的频率,以及应用与对实时性要求不高的场景。

进程内缓存有哪些使用场景呢?

  • 场景一:只读数据,可以考虑在进程启动时加载到内存。当然,把数据加载到类似 Redis 这样的进程外缓存服务也能解决这类问题。
  • 场景二:高并发,可以考虑使用进程内缓存,例如:秒杀。

5. 分布式缓存

说完进程内缓存,自然就过度到进程外缓存了。与进程内缓存不同,进程外缓存在应用运行的进程之外,它拥有更大的缓存容量,并且可以部署到不同的物理节点,通常会用分布式缓存的方式实现。

分布式缓存是与应用分离的缓存服务,最大的特点是,自身是一个独立的应用/服务,与本地应用隔离,多个应用可直接共享一个或者多个缓存应用/服务。

既然是分布式缓存,缓存的数据会分布到不同的缓存节点上,每个缓存节点缓存的数据大小通常也是有限制的。

数据被缓存到不同的节点,为了能方便的访问这些节点,需要引入缓存代理,类似 Twemproxy。他会帮助请求找到对应的缓存节点。

同时如果缓存节点增加了,这个代理也会只能识别并且把新的缓存数据分片到新的节点,做横向的扩展。

为了提高缓存的可用性,会在原有的缓存节点上加入 Master/Slave 的设计。当缓存数据写入 Master 节点的时候,会同时同步一份到 Slave 节点。

一旦 Master 节点失效,可以通过代理直接切换到 Slave 节点,这时 Slave 节点就变成了 Master 节点,保证缓存的正常工作。

每个缓存节点还会提供缓存过期的机制,并且会把缓存内容定期以快照的方式保存到文件上,方便缓存崩溃之后启动预热加载。

5.1. 高性能

当缓存做成分布式的时候,数据会根据一定的规律分配到每个缓存应用/服务上。

如果我们把这些缓存应用/服务叫做缓存节点,每个节点一般都可以缓存一定容量的数据,例如:Redis 一个节点可以缓存 2G 的数据。

如果需要缓存的数据量比较大就需要扩展多个缓存节点来实现,这么多的缓存节点,客户端的请求不知道访问哪个节点怎么办?缓存的数据又如何放到这些节点上?

缓存代理服务已经帮我们解决这些问题了,例如:Twemproxy 不但可以帮助缓存路由,同时可以管理缓存节点。

这里有介绍三种缓存数据分片的算法,有了这些算法缓存代理就可以方便的找到分片的数据了。

5.1.1. 哈希算法

Hash 表是最常见的数据结构,实现方式是,对数据记录的关键值进行 Hash,然后再对需要分片的缓存节点个数进行取模得到的余数进行数据分配。

例如:有三条记录数据分别是 R1,R2,R3。他们的 ID 分别是 01,02,03,假设对这三个记录的 ID 作为关键值进行 Hash 算法之后的结果依旧是 01,02,03。

我们想把这三条数据放到三个缓存节点中,可以把这个结果分别对 3 这个数字取模得到余数,这个余数就是这三条记录分别放置的缓存节点。

Hash 算法是某种程度上的平均放置,策略比较简单,如果要增加缓存节点,对已经存在的数据会有较大的变动。

5.1.2. 一致性哈希算法

一致性 Hash 是将数据按照特征值映射到一个首尾相接的 Hash 环上,同时也将缓存节点映射到这个环上。

如果要缓存数据,通过数据的关键值(Key)在环上找到自己存放的位置。这些数据按照自身的 ID 取 Hash 之后得到的值按照顺序在环上排列。

如果这个时候要插入一条新的数据其 ID 是 115,那么就应该插入到如下图的位置。

同理如果要增加一个缓存节点 N4 150,也可以放到如下图的位置。

这种算法对于增加缓存数据,和缓存节点的开销相对比较小。

5.1.3. Range Based 算法

这种方式是按照关键值(例如 ID)将数据划分成不同的区间,每个缓存节点负责一个或者多个区间。跟一致性哈希有点像。

例如:存在三个缓存节点分别是 N1,N2,N3。他们用来存放数据的区间分别是,N1(0, 100], N2(100, 200], N3(300, 400]。

那么数据根据自己 ID 作为关键字做 Hash 以后的结果就会分别对应放到这几个区域里面了。

原文地址:https://blog.51cto.com/14230003/2445244

时间: 2024-10-07 04:25:56

深入缓存核心技术:大型网站多级缓存的分层架构的相关文章

大型网站架构系列:电商网站架构案例(2)

电网网站架构案例系列的第二篇文章.主要讲解网站架构分析,网站架构优化,业务拆分,应用集群架构,多级缓存,分布式Session. 五.网站架构分析 根据以上预估,有几个问题: 需要部署大量的服务器,高峰期计算,可能要部署30台Web服务器.并且这三十台服务器,只有秒杀,活动时才会用到,存在大量的浪费. 所有的应用部署在同一台服务器,应用之间耦合严重.需要进行垂直切分和水平切分. 大量应用存在冗余代码 服务器SESSION同步耗费大量内存和网络带宽 数据需要频繁访问数据库,数据库访问压力巨大. 大型

大型网站架构演变史(含技术栈与价值观)

这篇文章是参考李智慧的<大型网站技术架构:核心原理与案例分析>和现蘑菇街CTO曽宪杰的<大型网站系统与Java中间件实践>写的一篇读书笔记. 前言 何谓大型网站?大型网站的特点是什么?大型网站架构发生演变的源动力是什么?大型网站的架构演变经历了哪些阶段?在演变的某个具体阶段使用到常用技术有哪些,为什么要使用这些技术,同时这些技术又解决了什么问题?笔者在初次接触大型网站时思考了以上几个问题,本着缘木求鱼的方式,我打算详细的扒一扒大型网站的演变史.如果对以上的几个问题都理解透彻了,那么

大型网站架构模式

一.前言 为了解决大型网站面临的高并发访问.海量数据处理.高可靠运行等一系列问题与挑战,大型互联网公司在时间中提出了许多解决方案,以实现网站高性能.高可用.易伸缩性.可扩展.安全等各种技术架构目标. 二.分层 最常见的架构模式,将系统在横向维度上切分成几个部分,每个部分单一职责.然后通过上层对下层的依赖和调用组成一个完成的系统.网站一般分为三个层次:应用层.服务层和数据层,其具体结构如下图所示: 通过分层,一个庞大系统切分成不同部分,便于分工合作和维护.各层之间具有一定的独立性,只要维持调用接口

大型网站架构系列:电商网站架构案例

大型网站架构是一个系列文档,欢迎大家关注.本次分享主题:电商网站架构案例.从电商网站的需求,到单机架构,逐步演变为常用的,可供参考的分布式架构的原型.除具备功能需求外,还具备一定的高性能,高可用,可伸缩,可扩展等非功能质量需求(架构目标). 根据实际需要,进行改造,扩展,支持千万PV,是没问题的. 本次分享大纲 电商案例的原因 电商网站需求 网站初级架构 系统容量估算 网站架构分析 网站架构优化 架构总结 电商网站案例,一共有三篇本篇主要说明网站的需求,网站初始架构,系统容量估算方法. 一.电商

大型网站技术架构介绍--squid

一.大型网站技术架构介绍 1.pv高  ip高 并发量 2.大型网站架构重点 1. 高性能:响应时间,TPS,系统性能计数器.缓存,消息队列等. 高可用性High Availability   99.99% 7*24 2.衡量标准:假设环境中一台或者多台服务器宕机,服务是否依然可用.解决关键办法:冗余.资源定位,健康检查.负载均衡,关键服务器冗余:web DB ,及时有效的监控和报警 3.高伸缩性[高可维护性] 是否可以用多台服务器构建集群,是否容易向集群添加新的服务器,新服务是否可提供相同的服

课堂作业04——读大型网站技术架构后感

结合XXX项目 读大型网站技术架构:核心原理与案例分析 --5,6,7章后感 在阅读大型网站技术架构之前我们学到:可以用六个质量属性衡量系统架构的好坏,所谓上梁不正下梁歪,架构也是获取许多质量属性的基础. 讲到可用性,就像永远没有不变的需求,一个系统的完全可用性永远无法达到的,但可用性是一个系统最重要的属性,不可用的系统是没有意义的.国内的QQ服务99.99%可用,也就是每年只有53分钟的时间是不可用的,而Twitter网站的可用性不足99%,相比较而言,国内在社交软件的可用性要求上还是比较高的

大型网站技术架构(二)--架构模式

大型网站技术架构(一)--大型网站架构演化 每一个模式描述了一个在我们周围不断重复发生的问题及该问题解决方案的核心.这样,你就能一次又一次地使用该方案而不必做重复工作. 所谓网站架构模式即为了解决大型网站面临的高并发访问.海量数据.高可靠运行灯一系列问题与挑战.为此,在实践中提出了许多解决方案,以实现网站高性能.高可靠性.易伸缩.可扩展.安全等各种技术架构目标. 1.分层 分词是企业应用系统中最常见的一种架构牧师,将系统在横向维度上切分成几个部分,每个部分负责一部分相对简单并比较单一的职责,然后

大型网站技术架构(2):架构要素和高性能架构

上一篇我们把整个架构演变过程大致说了一下,这次我们来说说从哪方面进行考虑设计 为了使网站的能够应对高并发访问,海量数据处理,高可靠运行等一系列问题,我们可以选择横向或纵向两个方向来入手 基本思路 首先可以对整个架构进行分层,一般可以分为 应用层,服务层,数据层:实践中,大的分层结构中还可以继续分层,比如 应用层 还可以继续分为 视图层 和 业务逻辑层,服务层也可以继续细分为 数据接口层 逻辑处理层 等 通过分层,我们把一个庞大的系统切分为不同的部分,便于分工开发和维护:各层之间相互有一定的独立性

大型网站技术

大型网站开发 网站架构 缓存和数据一致性 分布式事务 负载均衡和高可用 微服务 消息队列 秒杀系统 大型网站特点 海量数据 高并用 高可用 需求多 容量的估算 常见容量估算:数据量 并发量 带宽 CPU|MEM|DISK 容量评估步骤 1.评估总访问量 2.评估平均访问量 QPS 3.评估高峰 QPS 4.评估系统 单机极限 5.计算容量 常见性能测试方案 ab JMeter LoadRunner 系统负载 System Load :系统CPU繁忙程度的度量 有多少进程在等待被CPU调用 (进程