第一节 缓存概念及分类

一、什么是数据库缓存 


我们知道常见的数据库,比如oracle、mysql等,数据都是存放在磁盘中。虽然在数据库层也做了对应的缓存,但这种数据库层次的缓存一般针对的是查询内容,而且粒度也太小,一般只有表中数据没有变更的时候,数据库对应的cache才发挥了作用。但这并不能减少业务系统对数据库产生的增、删、查、改的庞大IO压力。所以数据库缓存技术在此诞生,实现热点数据的高速缓存,提高应用的响应速度,极大缓解后端数据库的压力。


以下为memcache数据库缓存为例,以图说明一下什么是数据库缓存:

022f6d6489e6facab3ee148f0150bdc7aa5bfe32

二、数据库缓存的技术特点


性能优越


数据库缓存的第一个技术特点就是提高性能,所以数据库缓存的数据基本上都是存储在内存中,相比io读写的速度,数据访问快速返回。而且在mysql 5.6的版本开始,已经把memcache这种跟数据库缓存直接挂钩的中间件直接集成进去了,已经等不及我们自己去单独部署对应数据库缓存的中间件了。


应用场景


针对数据库的增、删、查、改,数据库缓存技术应用场景绝大部分针对的是“查”的场景。比如,一篇经常访问的帖子/文章/新闻、热门商品的描述信息、好友评论/留言等。因为在常见的应用中,数据库层次的压力有80%的是查询,20%的才是数据的变更操作。所以绝大部分的应用场景的还是“查”缓存。当然,“增、删、改”的场景也是有的。比如,一篇文章访问的次数,不可能每访问一次,我们就去数据库里面加一次吧?这种时候,我们一般“增”场景的缓存就必不可少。否则,一篇文章被访问了十万次,代码层次不会还去做十万次的数据库操作吧。


数据一致性


在很多应用场景中,当一个数据发生变更的时候,很多人在考虑怎么样确保缓存数据和数据库中数据保存一致性,确保从缓存读取的数据是最新的。甚至,有人在对应数据变更的时候,先更新数据库,然后再去更新缓存。我觉得这个考虑不太现实,一方面这会导致代码层次逻辑变得复杂,另外一方面也真想不明白还要缓存干什么了。在绝大多数的应用中,缓存中的数据和数据库中的数据是不一致的。即,我们牺牲了实时性换回了访问速度。比如,一篇经常访问的帖子,可能这篇帖子已经在数据库层次进行了变更。而我们每次访问的时候,读取的都是缓存中的数据(帖子)。既然是缓存,那么必然是对实时性可以有一定的容忍度的数据,容忍度的时间可以是5分钟,也可以是5小时,取决于业务场景的要求。相反,一定要求是实时性的数据库,就不应该从缓存里读取,比如库存,再比如价格。

高可用

自从有了缓存,代码每天快乐的去缓存中愉快的玩耍。为什么说高可用呢,我们知道缓存为数据库抵挡了很多压力,同时也为应用提供了良好的访问速度。但同时有没有想过缓存的感受,如果当数据库缓存“罢工”了,这会出现什么后果?特别在一些高并发的应用中,数据库层肯定是“消化不良“,最终导致应用全面崩溃。所以缓存的高可用显得非常重要。

三、数据库缓存常见开源技术

要说用于数据库缓存场景的开源技术,那必然是memcache和redis这两个中间件。

74ba1c9a1c6cd0a7d59872b5b8e77f366ff6ce50

因为都是专注于内存缓存领域,memcache和redis向来都有争议。比如性能,到底是memcache性能好,还是redis性能更好等。同样都是内存缓存技术,它们都有自己的技术特性。没有更好的技术,只有更合适的技术。个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis。其他简单的key/value存储,选择memcache。所以根据自身业务特性,数据库缓存来选择适合自己的技术。

 

暂不说用不用数据库缓存,见过有人把session存储在数据库中的,也见过把视频/文件转化成二进制存储在数据库的,这种行为无疑是逆天的。合理应用数据库缓存技术,且行且珍惜,切勿走向误区。

静态缓存

上次写了一篇数据库缓存,由于快餐式的风格,遭到了广大读友的吐槽。上篇风格有点 “ 虚 ”,我本身是一个技术控,偏向经验/干货的分享,本文主要描述静态缓存方面的一些心得及分享。作为系列二,有所不足之处,依旧希望大家踊跃“ 亮砖 ”。

说起静态缓存技术,CDN是经典代表之作。静态缓存技术面非常广,涉及的开源技术包含apache、Lighttpd、nginx、varnish、squid等。

静态缓存,一般指 web 类应用中,将图片、js、css、视频、html等静态文件/资源通过磁盘/内存等缓存方式,提高资源响应方式,减少服务器压力/资源开销的一门缓存技术。

本文主要通过:浏览器缓存、磁盘缓存、内存缓存、nginx的内存缓存、CDN五个方面围绕静态缓存而展开。

一、浏览器缓存

浏览器缓存,也称为客户端缓存,是静态缓存中最常见最直接的表现形式,很多时候都往往被人忽略掉。

案例1:

我们经常在nginx的配置文件中看到以下缓存配置:

6d000037ed9f276fc6c

案例2:

在经常写 jsp 的时候,html 标签中关于 http 头信息也可以注意到“ expires ”的字样:

6d100037e15c8ded90d

对于案例1和案例2中(nginx设置的expires优先级大于代码中设置的expires优先级),expires是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。如果文件变动较频繁,就不要使用 expires 来缓存。

比如对于常见类web网站来说,css 样式和 js 脚本基本已经定型,所以最适合的方法是 expires 来缓存一些内容到访问者浏览器。

案例3:

通过 chrome 访问服务器端的一张图片,用F12键打开开发者前端调试工具:

6d000037eda08b47417

第一次访问,响应200状态,当第二次及后续访问的时候,变成304状态,客户端已经开始获取浏览器缓存内容,而不需要去服务器端获取对应的请求内容,即 nginx 中 expires 参数设置已经生效。等待客户端缓存时间过期后,会再次请求服务器端内容来更新本地缓存。

6d000037edbc442d9b9

介绍到这里,突然想起一个有意思的需求。比如,访问一张静态文件,不想客户端缓存,需要每次都去服务器端取数据。我们可以用“ last-modified ”参数来实现,即“ last-modified ”是根据文件更新时间来确定是否再次发送加载。

Nginx核心配置如下:

6d000037ed8acbc1f97

我们更改掉服务器传回客户端的“ last-modified ”文件修改时间参数的值,这样导致客户端本地保存的文件时间每次跟服务器端传回来的时间不一致,所以每次客户端“ 误认为 ”服务器端有静态文件更新,每次都会去服务器端取“ 所谓的最新数据 ”。这样我们可以看到,不管在浏览器访问多少次,返回的 http 状态都是200,再也找不到304状态了。

误区:在 nginx 中设置 expires,并不是指把静态内容缓存在 nginx 中,而是设置客户端浏览器缓存的时间,这是很多人的误区所在。

二、磁盘缓存

除了存储在客户端的静态缓存(浏览器静态)技术外,在服务器端的静态缓存技术主要分为磁盘缓存和内存缓存两大类。单纯围绕 nginx 的 squid、varnish 等一类中间件,处理静态数据的性能十分优秀。核心是 nginx 基于 epoll 网络模型,而相比 apache 基于 select 网络模型。所以 apache 的优势在于密计算型,稳定性好。而 nginx 偏向静态处理,反向代理,高并发。比如 apache+php 的稳定性比 nginx+php 要好,而性能是明显 nginx 要优秀许多。

以上仅单纯是对磁盘中静态数据处理的能力,所谓磁盘缓存,指另外的一种缓存静态文件的技术。以 nginx 配置为例:

6d100037e19d4127df6

可以看出 nginx 主要通过 proxy_cache 来实现 web cache,熟悉 nginx 的同学,不难看出,以上配置在 location 这里,不仅可以实现静态文件的缓存,还可以实现动态文件的缓存(这里放在下章节详细介绍)。我们编写个 test.html测试文件,然后并访问。test.html 源码如下:

6d000037edc9ba52346

我们发现服务器的 cache 目录里面,多了两个缓存文件:

6d100037e1746809432

有意思的,这两个文件里面的内容分别为(通过 less 命令查看):

6d100037e1ae69f0753

(b0ad5d3e7f099bfff9e4fc6a159d868c)

6d100037e1e193e6dbb

(53edc39ed253e14415a29412cfc01faf)

所以不难看出,nginx 把 html 内容和图片二进制全部缓存到本地磁盘上了。下次用户再次来访问 test.html 的时候,nginx 直接将缓存在本地磁盘的文件返回给用户。特别是后端如若是部署的 tomcat、iis 等,nginx 强大的静态缓存能力,有效减少了服务器压力。

三、内存缓存

紧接上面描述的磁盘缓存,内存缓存顾名思义,就是把静态文件缓存在服务器端的内存中。所以这种缓存,如若命中缓存的话,取内存中的缓存数据返回比取磁盘中的缓存数据返回,性能要高很多。以 varnish 为例,varnish 核心配置如下:

启动命令:

6d100037e18e22f534b

参数简介:

6d000037ee413494231

default.vcl核心配置如下:

6d000037ee6dd062ae8

Varnish对.gif、.jpg、.jpeg、.png等结尾的 URL 缓存时间设置1小时。varnish设置完毕后,我们用命令行方式,通过查看网页头来查看命中情况:

6d000037ee881153360

6d100037e203342923b

最后,我们可以通过 varnishadm 命令来清理缓存,也可以通过 varnishstat 命令来查看 varnish 系统缓存状态。

四、Nginx 的内存缓存

以上主要以 Varnish 为例,介绍了内存缓存静态资源的方法。其实 nginx 也有内存缓存,相比 squid、varnish 而言,nginx 的内存缓存需要通过编码实现。如下配置:

6d000037ee7f70a3812

memcached_pass 指定服务器地址,使用变量 $memcache_key 为 key 查询值,去 memcache 查询对应 value 值。

如我们访问:http://***.***.***.***/image/test.jpg ,则 nginx 去 memcache 中查询key 为“ test.jpg ”的 value 值并返回。如果没有相应的值,则返回 error_page 404。介绍到这里,关键在于存储在 memcache 中的静态文件,需要通过代码写入 memcache 中。怎么样通过 php/java 等代码把静态资源的数据写入 memcache 中,关于这块的示例就不再过多介绍了。

Nginx的内存缓存因为需要通过编码实现,所以灵活性特别高。这块可以结合自身业务系统的特点,让静态缓存的灵活性和效率都能得到保障。可能唯一的缺陷就是,通过编码实现的方式,给我们维护管理带来了负担。在之前我曾参与的一个电商系统,就是把客户的订单照片通过 php 代码写入 memcache,客户访问取图的时候,从 memcache 中获取,速度效率特别高。Nginx 作为一款在七层无所不能且轻量级高性能的中间件,能够直接去 memcache 中取数据,来实现静态缓存的效果,这块相应的功能是其他软件无法相媲美的。

五、CDN

说起 CDN,大家都不陌生,它是静态缓存加速最典型的代表。CDN技术并不是一门新的技术,它是基于传统 nginx、squid、varnish 等 web 缓存技术,结合 DNS 智能解析的静态缓存加速技术。值得注意的是,他对动态链接访问并没有加速效果。架构原理图如下:

6d100037e2349afb0db

所以CDN的静态缓存技术核心主要在于两点:

节点缓存:对需要加速的网站应用,相应的静态资源通过内存缓存+磁盘缓存的方式缓存在服务器端。

精准调度:对访问的用户 ip 进行智能解析调度,实现就近缓存节点访问。比如以上图例中,北京用户访问 www.a.com。通过 dns 解析的时候,分析用户 ip,发现是北京用户。则 dns 返回对应北京缓存节点的 ip 地址给到用户,则用户 www.a.com 默认访问北京服务器上面的缓存数据,实现就近访问的策略,大大提升了访问速度。

动态内容缓存

1 缓存与速度

    这里所说的动态内容缓存是自行实现的缓存机制,包括整页缓存、局部缓存、数据缓存等。

    缓存的目的是把花费昂贵开销的计算结果保存起来,以后需要的时候直接取出,避免重复的计算,一切缓存的本质都是如此。

    CPU缓存是位于CPU和内存之间的临时寄存器,它的容量不大,但交换速度高于内存,CPU把频繁交换的数据放在缓存中,以后需要的时候直接从缓存中读出,从而避免访问速度较慢的内存。

    缓冲(Buffer)的目的在于改善各部件速度不匹配的问题。例如:用户态空间的数据写入磁盘时,显然内存的速度要快于磁盘的速度,这时加入磁盘缓冲区,让数据源源不断地写入磁盘缓冲区,再由磁盘缓冲区再写入磁盘,这样内存就不用等磁盘而慢下来。视频缓冲就是这种类型。

    缓冲和缓存都需要一块存储区,本质都是与速度不一致有关,但缓存更注重的是策略,也就是说缓存命中率,凡是使用缓存都需要注意命中率问题。

2 页面缓存

    对动态网页来说,缓存的内容实际是动态网页输出的HTML,称为页面缓存。

    对于其它动态内容比如动态图片或动态XML数据,也是将它们的输出结果整体进行缓存,实现机制跟动态内容一样。

    可以将动态内容缓存存储在磁盘上,磁盘空间大、廉价,可以存储大量的缓存文件,这是种比较容易部署的方法。

    一个动态网页根据URL参数不同,会产生多种不同的结果,而每种结果都必须生成对应的缓存文件。如果缓存文件多的话,那么Cache目录下会拥挤大量的文件,这样CPU花费在遍历目录的时间就非同异常,如果写缓存比较频繁,那么CPU很容易100%,这时可以采用缓存目录分级来解决这个问题。可以将每个目录下的子目录或文件数量控制在有限的范围内,但同时会增加点目录切换的次数。

    动态内容的目的在于提供变化的内容,所以它的缓存不可以长期有效,否则就失去了动态内容的意义。所以动态内容的缓存机制必须能够判断缓存何时失效,何时重新生成新的缓存。

    为每个缓存标记一个过期时间,动态内容每次对缓存进行过期检查,这是种常见的缓存过期检查策略,当然方法有很多。

    动态内容在创建缓存文件时会留下两个时间标记,一个是缓存文件的创建时间,一个是缓存失效时间,它有两种过期检查方法:

    每次检查时,根据缓存创建的时间、缓存有效期长度及当前时间来判断是否过期。即当前时间离缓存创建时间的长度超过了缓存有效期的时间长度则认为它过期了,这是一种相对比较。

    每次检查时,根据缓存失效时间和当前时间来比较来判断是否过期,这是一种绝对比较。

    两者的区别在:应用第2种方法时,在缓存过期前,如果修改了缓存有效期长度,是不会影响上一次缓存的过期时间。而第1种方法,修改缓存有效期长度会影响每一次的过期检查。如果缓存有效期长度不变的话,那么两者是没啥区别的。

    缓存过期检查是存在一定开销的。

    不只是缓存文件的加载存在磁盘IO开销,脚本文件和页面模板也一样,不过有脚本加速器可以对脚本文件进行缓存和优化。

    在输出缓存之前,不要加载不必要的东西。

    为避免加载缓存文件时的磁盘IO和磁盘本身的负载,可以将缓存内容存放在本机内存中,这样加载缓存文件就没有磁盘IO的开销。

    还可以将HTML缓存存储在单独的缓存服务器上,利用memcached可以通过TCP将缓存存储到其它服务器中。而memcached就是利用内存空间来保存缓存数据,减少了不必要的磁盘IO,另一方面,memcached在存储区中维护着对每个key的过期检查,一旦过期,memcached将自动删除过期的key,这种过期检查很方便。不过放在缓存服务器上还是存在TCP Socket开销。

    将缓存保存在内存中虽然可以减少磁盘IO开销,但内存空间毕竟有限,这样会导致空间不够用时,使得缓存命中率降低,吞吐率也会相应地降低。这时就要想到如何扩展缓存空间,显然,利用memcached来实现分布式缓存扩展成为可能。

    缓存有效期的取值不能太长,虽然这样缓存命中率高了,但动态内容的更新却不能及时实现 。如果缓存有效期的取值太短,虽然动态内容的更新内容可以及时实现,但这样频繁创建缓存不如不使用缓存。

    缓存机制提供了一个有效的缓存控制途径,那就是可以在任何时候强制清除缓存,这在动态内容更新频率较低的时候适合使用。

3 局部无缓存

    在流行的模板框架中,在整页缓存的基础上,都提供了局部无缓存的支持,它允许在页面中指定一块包含动态数据的HTML代码段,每次这些动态数据进行实时计算,然后和其余的缓存合成最终网页。

    要注意的是要评估局部动态数据的影响力,如果一个动态网页中占主要开销的数据计算置于无缓存状态,那么这时缓存就失去了意义,这时可以考虑使用其它的缓存方式或页面组织结构,比如用数据层缓存。

4 静态化内容

    实际情况中,吞吐率往往受限于Web服务器的出口带宽。

    静态化网页的性能要大大高于动态缓存的性能,静态网页在请求的时候不涉及内容计算,但不代表它不需要计算,它需要动态程序来创建和更新。

    一般使用CMS来管理静态内容,同时CMS可以在必要的时候更新静态化内容。

    静态内容更新策略有2种,一是在需要更新的时候重新生成静态内容,二是定时重新生成静态化内容。

    静态网页可以不必整页更新,可以使用SSI(服务器端包含)技术实现各个局部页面的独立更新,这节省了整页的计算开销和磁盘IO开销,甚至网络IO,但任何节省都存在一定的开销 。SSI技术可以在任何一款主流的Web服务器中找到相应的模块。一旦网页支持SSI,那么每次请求的时候服务器必须通读网页内容,查找include标签,这需要大量的CPU开销。

    请求的文件越大,花在传输上的时间就越多,单位时间的数据传输量也越大,同时花在处理Socket和打开文件等时间比例就越小。

    使用SSI在管理静态内容时可以重用页面,可提高可维护性。在站点负载不大或带宽有限制的情况下,完全可以使用必要的include来管理静态化内容。



你可能感兴趣的:(缓存课堂,缓存分类,nginx缓存配置,动态缓存,静态缓存)