说起缓存相关技术,老多了, memcache、redis、squid、varnish、web cache、 CDN等等。缓存技术五花八门,但这些技术间有什么共性的地方,又有什么不同的地方呢?答案肯定是有的,这次为大家分享及整理一下缓存方面的技术,主要分为三个系列展开:
缓存随谈系列之一:数据库缓存
缓存随谈系列之二:静态缓存
缓存随谈系列之三:动态缓存
一、什么是数据库缓存
我们知道常见的数据库,比如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的配置文件中看到以下缓存配置:
案例2:
在经常写 jsp 的时候,html 标签中关于 http 头信息也可以注意到“ expires ”的字样:
对于案例1和案例2中(nginx设置的expires优先级大于代码中设置的expires优先级),expires是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。如果文件变动较频繁,就不要使用 expires 来缓存。
比如对于常见类web网站来说,css 样式和 js 脚本基本已经定型,所以最适合的方法是 expires 来缓存一些内容到访问者浏览器。
案例3:
通过 chrome 访问服务器端的一张图片,用F12键打开开发者前端调试工具:
第一次访问,响应200状态,当第二次及后续访问的时候,变成304状态,客户端已经开始获取浏览器缓存内容,而不需要去服务器端获取对应的请求内容,即 nginx 中 expires 参数设置已经生效。等待客户端缓存时间过期后,会再次请求服务器端内容来更新本地缓存。
介绍到这里,突然想起一个有意思的需求。比如,访问一张静态文件,不想客户端缓存,需要每次都去服务器端取数据。我们可以用“ last-modified ”参数来实现,即“ last-modified ”是根据文件更新时间来确定是否再次发送加载。
Nginx核心配置如下:
我们更改掉服务器传回客户端的“ last-modified ”文件修改时间参数的值,这样导致客户端本地保存的文件时间每次跟服务器端传回来的时间不一致,所以每次客户端“ 误认为 ”服务器端有静态文件更新,每次都会去服务器端取“ 所谓的最新数据 ”。这样我们可以看到,不管在浏览器访问多少次,返回的 http 状态都是200,再也找不到304状态了。
误区:在 nginx 中设置 expires,并不是指把静态内容缓存在 nginx 中,而是设置客户端浏览器缓存的时间,这是很多人的误区所在。
二、磁盘缓存
除了存储在客户端的静态缓存(浏览器静态)技术外,在服务器端的静态缓存技术主要分为磁盘缓存和内存缓存两大类。单纯围绕 nginx 的 squid、varnish 等一类中间件,处理静态数据的性能十分优秀。核心是 nginx 基于 epoll 网络模型,而相比 apache 基于 select 网络模型。所以 apache 的优势在于密计算型,稳定性好。而 nginx 偏向静态处理,反向代理,高并发。比如 apache+php 的稳定性比 nginx+php 要好,而性能是明显 nginx 要优秀许多。
以上仅单纯是对磁盘中静态数据处理的能力,所谓磁盘缓存,指另外的一种缓存静态文件的技术。以 nginx 配置为例:
可以看出 nginx 主要通过 proxy_cache 来实现 web cache,熟悉 nginx 的同学,不难看出,以上配置在 location 这里,不仅可以实现静态文件的缓存,还可以实现动态文件的缓存(这里放在下章节详细介绍)。我们编写个 test.html测试文件,然后并访问。test.html 源码如下:
我们发现服务器的 cache 目录里面,多了两个缓存文件:
有意思的,这两个文件里面的内容分别为(通过 less 命令查看):
(b0ad5d3e7f099bfff9e4fc6a159d868c)
(53edc39ed253e14415a29412cfc01faf)
所以不难看出,nginx 把 html 内容和图片二进制全部缓存到本地磁盘上了。下次用户再次来访问 test.html 的时候,nginx 直接将缓存在本地磁盘的文件返回给用户。特别是后端如若是部署的 tomcat、iis 等,nginx 强大的静态缓存能力,有效减少了服务器压力。
三、内存缓存
紧接上面描述的磁盘缓存,内存缓存顾名思义,就是把静态文件缓存在服务器端的内存中。所以这种缓存,如若命中缓存的话,取内存中的缓存数据返回比取磁盘中的缓存数据返回,性能要高很多。以 varnish 为例,varnish 核心配置如下:
启动命令:
参数简介:
default.vcl核心配置如下:
Varnish对.gif、.jpg、.jpeg、.png等结尾的 URL 缓存时间设置1小时。varnish设置完毕后,我们用命令行方式,通过查看网页头来查看命中情况:
最后,我们可以通过 varnishadm 命令来清理缓存,也可以通过 varnishstat 命令来查看 varnish 系统缓存状态。
四、Nginx 的内存缓存
以上主要以 Varnish 为例,介绍了内存缓存静态资源的方法。其实 nginx 也有内存缓存,相比 squid、varnish 而言,nginx 的内存缓存需要通过编码实现。如下配置:
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 智能解析的静态缓存加速技术。值得注意的是,他对动态链接访问并没有加速效果。架构原理图如下:
所以CDN的静态缓存技术核心主要在于两点:
节点缓存:对需要加速的网站应用,相应的静态资源通过内存缓存+磁盘缓存的方式缓存在服务器端。
精准调度:对访问的用户 ip 进行智能解析调度,实现就近缓存节点访问。比如以上图例中,北京用户访问 www.a.com。通过 dns 解析的时候,分析用户 ip,发现是北京用户。则 dns 返回对应北京缓存节点的 ip 地址给到用户,则用户 www.a.com 默认访问北京服务器上面的缓存数据,实现就近访问的策略,大大提升了访问速度。