为了提高系统的性能,一般会引入“缓存机制”,将部分热点数据存入缓存中,用空间换取时间,以达到快速响应的目的。
我们对缓存的认知停留在redis,但其实缓存远远不是只有redis的,从客户端发起请求开始,缓存就已经开始了。
- 客户端发起请求
- 经过域名服务器(DNS),这其实也有缓存的,DNS缓存
- 到内容分发服务器(CDN),把前端的代码,页面,图片等信息缓存进去
- 反向代理服务器(Nginx),可以进行缓存部分信息
- 到达分布式系统
- 分布式缓存服务(Redis,memcache),其实细分的话,缓存这个中间件的实现,内部也有自己的缓存的
- 本地缓存,在本地服务构建一张哈希表,进行缓存
- 到达数据库,数据库本身内部就有很多缓存,例如Buffer Pool
- 数据库到服务器,Linux操作系统,操作系统内核态是有page cache的,这其实也是缓存
这就是所谓的多级缓存,其中的缓存策略,算法也各不相同,但是都有共同点,我们学习某一个缓存的策略,其实对每一层级的都是有效的,本质是一样的。
传统缓存
多级缓存总览图
客户端HTTP缓存
当用户通过浏览器请求服务器的时候,会发起 HTTP 请求,如果对每次 HTTP 请求进行缓存,那么可以减少应用服务器的压力。
当第一次请求的时候,浏览器本地缓存库没有缓存数据,会从服务器取数据,并且放到浏览器的缓存库中,下次再进行请求的时候会根据缓存的策略来读取本地或者服务的信息。
**一般信息的传递通过 HTTP 请求头 Header 来传递。**目前比较常见的缓存方式有两种,分别是:
强制缓存
当浏览器本地缓存库保存了缓存信息,在缓存数据未失效的情况下,可以直接使用缓存数据。否则就需要重新获取数据。
在 HTTP 1.1 会使用 Cache-Control 来完成这样的功能,Cache-Control 中有个 max-age 属性,单位是秒,用来表示缓存内容在客户端的过期时间。客户端第一次请求完后,将数据放入本地缓存。那么在 max-age 以内客户端再发送请求,都不会请求应用服务器,而是从本地缓存中直接返回数据。如果两次请求相隔时间超过了 max-age,那么就需要通过服务器获取数据。
对比缓存
需要对比前浏览器第一次请求时,服务器会将缓存标识与数据一起返回,浏览器将二者备份至本地缓存库中。浏览器再次请求时,将备份的缓存标识发送给服务器。服务器根据缓存标识进行判断,如果判断数据没有发生变化,把判断成功的 304 状态码发给浏览器,这时浏览器就可以使用缓存的数据来。服务器返回的就只是 Header,不包含 Body,这样会很大程度上节约了带宽。后两次的缓存标志来判断是否使用缓存。如果标识不一致就直接正式发起请求。
这个数据一致性是很难保证的,就连服务器的redis和MySQL的一致性都很难保证,更别说这个了,所以用的其实很少。
CDN缓存
CDN(Content Delivery Network),即内容分发网络,依靠部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
CDN 主要缓存对象是静态数据。如果在客户端和服务器之间再加上一层 CDN,可以让 CDN 为应用服务器提供缓存,当命中CDN缓存时,就不用再请求应用服务器了。
注: HTTP 缓存提到的两种策略同样可以在 CDN 服务器执行。
Nginx缓存
说完客户端(HTTP)缓存和 CDN 缓存,我们离应用服务越来越近了,在到达应用服务之前,请求还要经过负载均衡器 。
虽说它的主要工作是对应用服务器进行负载均衡,但是它也可以作缓存。可以把一些修改频率不高的数据缓存在这里,例如:用户信息,配置信息。通过服务定期刷新这个缓存就行了。
以 Nginx 为例,Nginx 是一款跨平台的,高性能的 Web 服务器,支持反向代理,负载均衡以及缓存功能。下面,来看看它是如何工作的:
- 用户请求在达到应用服务器之前,会先访问 Nginx 负载均衡器;
- 如果发现有缓存信息,直接返回给用户;
- 如果没有发现缓存信息,Nginx 回源到应用服务器获取信息;
- 另外,可以设置一个缓存更新服务,定期把应用服务器中相对稳定的信息更新到 Nginx 本地缓存中。
相关配置可以搜索讲nginx的文章。
进程内缓存
进程内缓存,是在应用中开辟一块内存空间,数据在运行时被存入这块内存,通过本地内存低延迟、高吞吐的特性提高程序的访问速度。由于其运行在内存中,对数据的响应速度很快,通常我们会把热点数据放在这里。
本地缓存的特点:
- 优点:读取本地内存,没有网络开销,速度更快;
- 缺点:存储容量有限,可靠性低(如重启后丢失),无法在集群中共享;
- 场景:性能要求高,缓存数据量少的地方。
进程外缓存(分布式缓存)
与进程内缓存不同,进程外缓存在应用运行的进程之外,它可以部署到不同的物理节点,并且拥有更大的缓存容量,通常会用分布式缓存的方式实现,如:Redis集群。
分布式缓存是与应用分离的缓存服务,最大的特点是:自身是一个独立的应用/服务,与本地应用隔离,多个应用可直接共享一个或者多个缓存应用/服务。
为了提高缓存的可用性,使部分节点失败或者大部分节点无法通信的情况下集群仍然可用,Redis集群使用了主从复制模型,每个节点都会有 N-1 个复制品(假设:一共有 N 个节点,则每个节点有一个 Master 和 N-1 个 Slave)。当缓存数据写入 Master 节点的时候,会同时同步一份到 Slave 节点。一旦 Master 节点失效,可以通过代理直接切换到 Slave 节点,这时 Slave 节点就变成了 Master 节点,保证缓存的正常工作。
在 Redis 集群中,因为缓存也是分布式部署的,这样就会产生一个问题:数据根据怎样的规律分配到每个缓存应用/服务上?这里介绍一种缓存数据分片的算法:
一致性哈希算法
可以看我写的这个博客。
《一致性哈希算法Golang实现》
以上就是有关于缓存这个话题的内容了。