偷天换日术-Web应用中的缓存技术漫谈

广义的来说, 我们将凡是位于速度相差较大的两种(硬件或者服务)之间,用于协调两者数据传输速度差异的[结构或者服务],均可称之为缓存. 缓存属于典型的用空间换时间的方法, 

我们继续上一章的故事, 小白同学受到了我的感召, 开始了共享书吧的创业项目, 并且写出了第一版的程序上线运行了, 因为还没有拿到任何投资, 所以小白同学很苦逼的每月剩下了外出和小伙伴们吃烧烤的200元钱租了XX云的虚拟主机, 开始运营项目. 由于小伙伴们都很捧场, 业务飞速的发展了起来, 很快就有小伙伴来抱怨, 页面打开越来越慢, 有时候点一个按钮要反应好一会儿.

这个时候小白赶紧连上服务器,并且用top命令一看 

top命令查看服务器负载

结果发现其实服务器并没有和我们想象的一样忙成狗, 其实在大多数时候, 我们的程序并没有纯粹的就是在"计算", 因为Web应用大多数时候都没有什么好算的, 大部分的时间其实都浪费在了IO等到上面.

我们看看一个页面的执行一共经历了那些步骤:

web请求的执行过程

当一个页面从请求到在浏览器上看到的时候, 其实就经历了上面的完整步骤, 但是, 如果刷新页面后, 显示的内容没变, 但是其实还是把上面的流程走了一遍, 这个时候就产生了很多冗余的步骤, 那么这个时候我们就需要用缓存来消除重复执行的操作. 缓存的第一个作用: 

减少重复操作的步骤

减少重复操作能带来多大的性能提升呢, 我们来举个例子. 抛开复杂的网络和IO问题, 我们用纯计算的例子, 计算斐波那契数列. 我们用python来做例子, 如下:

递归计算斐波那契数列

这段代码和斐波那契数列的经典公式几乎是等价的, 我们假设 fib_recursion(5) 的话, 计算过程入下图:

fib_recursion(5)时的计算过程

由上图可见, 计算了2次fib(3),3次fib(2),5次fib(1)... 那么当参数更大的时候, 必然会产生更多的冗余计算. 显而易见的, 冗余的计算会增加开销, 降低运算速度.

那么我们改进算法为迭代的方式:

迭代版的菲波那契数列代码

这个时候我们通过变量b来缓存了中间计算结果, 减少了冗余计算.效果我们通过timeit模块来测试, 首先是递归的方式:

timeit.timeit('fib_recursion(30)', setup='from fib import fib_iter, fib_recursively', number=100)

返回的时间是 48.77794599533081's

再来看迭代的方式:

timeit.timeit('fib(30)', setup='from fib import fib_iter, fib_recursively', number=100)

返回的时间是 0.0013899803161621094's

可见在减少冗余操作带来的效率提升能有多大了.

缓存的第二点就是将常用数据放在更快的存储设备中,下面图是不同的存储设备的访问时间量级的表格:

存储设备访问时间量级

L1 L2 L3 Cache这类不是用户级别代码能操作的, 所以Pass. 我们主要讨论的是剩下的几中方式. 由图可知, 数据库和文件访问是比较耗时的, 而内存的访问速度则是千倍于磁盘和数据库. 而由于摩尔定律的影响, 现在的内存价格一降再降, 服务器上的内存也从十几年前的几个G疯涨到了好几百G, 而大部分时候服务器的内存空闲量都是比较大的, 那么为什么不利用内存来加速IO的访问呢. 所以内存缓存就兴起了, 比如memcache, Redis等现在基本成为了网络应用(网站, App后端)的标配了.

Web应用所用缓存的种类

我们从客户端开始到服务端由近及远的来说说我们能用到那些种类的缓存

客户端缓存

离客户最近的就是最快的, 最快的缓存方式莫过于客户端缓存, 因为客户端缓存抛开了所有不必要的步骤, 不经过网络, 不用服务器处理, 直接能返回访问过的内容. 原理很简单, 就是把每个url对应的内容存在本地磁盘上, 下一次访问的时候直接从磁盘读出来显示. 但是这种方式有个弊端, 就是如果服务器上的内容发生了变更的话, 客户端是不会知道的, 所以, 万一你把内容发布出去了后发现写错了, 要修改, 那么修改前访问过的用户看到的还是错的内容. 针对这个情况, HTTP协议有定义相应的应对办法, 就是响应的expire信息, web服务器或者web应用都可以指定返回的内容的expire, 也就是描述内容的过期时间, 可以是几秒, 几分钟, 几小时, 几天,几个月, 几年, 甚至是永久, 客户端收到URL响应的expire就会根据这个来设置缓存内容的过期时间. 

但是这种静态的设置方式对静态内容是适用的, 比如静态的网页, 图片, css, js等, 但是动态响应的内容就不行了, 比如商城系统里看到的货物剩余数量什么的, 客户端缓存因为当内容改变后, 服务端无法通知对应的缓存失效, 而只能采用固定的失效时间, 所以一般只应用到发布的静态内容.

CDN

CDN的全称是内容分发网络(content delivery network), 作用是把内容从你的服务器搬运到里用户最近的服务器, 当你访问内容的时候, CDN会自动的把请求发送到离你最近的缓存服务器. 原理如下图:

CDN原理

由于网络的复杂性(国内国外互通的带宽和延迟问题, 国内南北互通的问题), 我们没法保证服务器到任何地方的网络都能一样的快, 所以, CDN将缓存服务器分布到很多地方的数据中心, 并且通过高效的调度协议, 在一个资源在一个地方被访问后就会缓存在最近最快的缓存服务器上, 以后里这个服务器最进最快的用户客户端在请求这个资源的时候都会直接从这个服务器返回, 访问的用户越多, 缓存命中的机率就会越大, 加速的效果也会越明显.

CDN明显不适合小规模的应用, 因为第一, CDN服务是收费的, 按流量计费, 很贵. 第二, 因为小规模应用访问的人少, 所以命中率不高(失效期端短的时候).

早期的CDN大多用来缓存静态内容,  比如视频, 图片, CSS, JS等, 但是最近技术发展后新的CDN技术也可用来缓存动态内容, 比如提供了接口可以让服务端通知CDN某个资源过期.

HTTP服务器缓存

不管是Nginx, 还是IIS亦或者是Apache都提供了缓存, 工作的方式基本上都是类似的, 访问过的资源直接返回, 本着又去有用屁话少的原则这里就不赘述了.

应用页面缓存

在应用服务中的控制器输出的时候, 部分Web框架会提供页面缓存机制, 这个级别的缓存是用户代码可以控制的, 所以对于动态应用来说, 可以精确的在内容更新后立即让缓存失效, 从而不会出现内容更新了而客户端获取的内容不更新的情况.

应用缓存

在应用程序的执行逻辑中通过缓存API去访问的缓存服务, 有可能是Web框架提供的, 也有可能是诸如memcache或者Redis这样子的外部服务, 通过对应的API访问, 甚至可以是你自己实现的.

建议直接使用成熟的memcache或者Redis, 因为如果直接用进程内的数据结构直接缓存数据, 虽然访问速度是最快的, 但是如果应用程序在多进程环境下执行的话就会存在数据同步的问题, 并且还会在多个进程中存在缓存数据的多份COPY. 如果是访问本地的磁盘来实现缓存的话, 因为磁盘的效率和通过网络访问内存缓存服务的效率大致相仿, 但是还是存在不能跨服务器的问题, 所以在将来扩容的时候还是会扯到蛋, 不如直接一次性到位了.

关于应用缓存的应用多半是在读取数据库的时候, 使用上也是有非常多的注意事项的, 特别是涉及到事务, 详细的可以参加下文: 酷壳:缓存更新的套路

如果上文的套路太深的不好实施的话, 有个比较小白的办法就是, 在涉及到更新的操作, 不要使用缓存的对象, 重新从数据库读一个出来会比较靠谱. 

数据库缓存

数据库缓存是现代关系型数据库的标准配置了, 除了运维修改数据库设置能够影响数据库缓存的话, 用户代码基本上不会碰到这部分, 不过我们在实际应用的时候还是需要考虑到的数据库缓存对访问速度的影响. 比如MySQL对缓存的敏感度就很高, 缓存越大, 跑得越快. 并且大多数数据库针对查询, 排序, 结构维护都有独立的缓存, 如果缓存设置和实际情况不符的话, 不但不能加速, 反而会适得其反.

关于数据库缓存的部分已经不属于小白需要掌握的部分了, 如果小白想要深入成为老白或者老鸟的话, 请找找相关的资料, 比入:

MySQL · 特性分析 · 执行计划缓存设计与实现 传送门

MySQL数据库性能优化之缓存参数设置 传送门

更多的请自行搜索...

在项目中应用缓存

在掌握了上述一堆缓存技术后, 小白就开始着手将他们应用在系统中. 

首先是客户端缓存的设置, 针对所有的静态页面, 比如关于, 登录, 注册, 首页, 都设置较短时间, 比如1小时或者1天左右的缓存.

CDN可以采用又拍云或者七牛云的服务, 这两家都有免费额度, 可以把图片和CSS, JS都放上面, 这样所有的静态内容都从自己的服务上剥离出去了.

在系统内部加入Redis或者memcache作为应用缓存在各个服务之间作为数据管道和缓存中间计算结果.

所以修改后系统结构就入下图:

加入缓存后的系统结构


然后小白的网站又重新快了起来, 而且没有增加一分钱的开销(因为用了七牛或者又拍的免费额度). 然后用户量又能蹭蹭的涨起来了.

然而天有不测风云, 某日风和日丽, 但是网上暗潮汹涌, 服务器到豆瓣的网络连接突然断掉了, 这个时候很多用户又在通过ISBN查书籍信息, 然后小白的服务也跟着挂掉了.

所以下一章我们来聊聊 后台服务与异步任务

to be continue...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容