简单地说,我们现在是Redis

了解更多

Query Caching with Redis



回到博客

当我开始建立网站时,我并不太关注性能。一旦你掌握了HTML和CSS等基础知识,或者你在后端使用的任何编程语言,性能就是你所担心的奢侈品。初学者的目标是建立一个站点,能够从一个页面到另一个页面,并确保它在一堆设备上看起来不错。缓存和性能是我们稍后要解决的问题。

研究缓存解决方案有许多重要的理由。从搜索引擎优化的角度来看,谷歌惩罚慢速网站。如果你的网站有很长的加载时间,你的排名可能会受到影响,这当然会对收入产生严重的影响。谷歌的John Mueller表示,页面加载时间超过两秒钟“影响爬行”,他寻找“少于”2-3秒。“另一个因素是加载缓慢的站点对可用性的影响。当我开始编写代码时,大约8秒钟就可以完全加载一个页面。只有当它花的时间比这个长时,你才会担心人们会放弃这个页面。今天,这段时间大概只有两秒左右,而且可能更少地取决于你所在的行业

底线是:你的网站速度越快,排名就越好,会有更多的访问者留下来与你互动。但是,直到我的客户打电话给我说他们的新网站“太慢了”,我才意识到这个问题的严重性。说句公道话,上面有一些沉重的图像,而且他们使用的是一家糟糕的托管公司。那时候,10美元的主机托管意味着你的网站是在共享服务器上。他们对服务器优化的想法是安装最新版本的CPanel。经过一些深入研究,我构建了一个基本的页面缓存系统,它工作得很好。客户很高兴,我开始学习一项新技能,一切都很好。但很快我意识到,尽管页面级缓存是一种很棒的工具,但它只是一种工具。

当页面缓存不足时

页面缓存的工作原理是这样的:一个请求传入,服务器处理它,然后存储生成的HTML。为了达到这篇博文的目的,让我们用一个Redis的例子。下一次有人请求我们的页面时,它将首先检查缓存。如果页面在那里,系统将使用它而不是在服务器级处理请求。

此方案适用于变化不大的页面,例如条款和条件或隐私政策。这些页面的流量与主应用程序不同,主应用程序可能具有基于用户的动态数据。

让我们想象一下,我们有一个应用程序,它充当用户目录,并允许基于某种活动进行过滤。用户可以查看人员列表并查看其电子邮件地址和电话号码。我们的应用程序使用SQL数据库,因此要获取数据,我们需要一个简单的select查询:
从activity='Basketball'所在的用户中选择用户名、电子邮件、电话号码;

在我们的应用中,假设activity是用户注册时设置的列表。因此,人们可以看到的活动类型因用户而异。我们将把活动列表存储在另一个表中,并且在这个页面上也需要该信息。

从用户id=1的活动列表中选择名称、id;

在这种情况下,我们需要针对当前用户运行一个查询。用户将看到的数据是整个列表的一个子集,该列表对用户来说是唯一的。鉴于此,我们无法缓存页面,因此需要缓存每个单独的查询。

缓存查询,而不是页面

现在我们需要考虑何时缓存查询以及如何缓存查询。一个简单的解决方案是使用Redis散列来存储结果集。我们可以将数据存储为JSON编码的字符串,当我们准备好时,只需将其取出并在我们的代码库中使用。

那么缓存是什么样子的呢?下面是一些伪代码:
1. > HGET用户活动列表缓存
2.如果结果不是nil,则返回结果集,否则转到步骤3
3.运行查询并将DB结果集保存到一个变量
4.如果DB结果大小为> 0
5.将结果集数据转换为JSON字符串
6.> HSET用户活动列表缓存JSON字符串结果集
7.否则结果集<= 0抛出错误

关于上面的伪代码,我应该指出,我们当然忽略了这实际上是一个会话存储模式这一事实。在现实世界中,你不会想要在Redis中永远缓存这些查询,你只会在用户登录并活跃于你的站点时创建并存储它们。我创建了这个通用型用户活动列表仅以Redis为例,演示如何在自己的代码中实现这一点的理论和灵感。

除此之外,我们的密钥名并不是最好的。名字用户活动列表这是一个非常一般和狭窄的例子。对于实际的应用程序,您可能希望以更可预测的方式命名密钥。如果是真的,密钥中会有用户名这都是用户会话的一部分,很容易检索和使用。

接下来,我们应该解决几个问题。首先,我们得到了用户活动列表,但是在运行此查询一次之后,我们可能应该使查询过期,这样就不会冒险向用户显示一些相当陈旧的数据。有几种不同的方法可以解决这个问题。

写时过期

假设此活动列表是用户自己更新的。她会进入设置页面,摆弄一些东西,然后保存更改。她可能每周或每月只做一次。在这种情况下,我们可以保留缓存,直到用户更改某些内容:
1.用户在列表中添加“Sportspall”并保存
2.为了响应用户保存,我们取消了用户活动列表的链接

你可以用你喜欢的方式来做第一部分和第二部分。对于这个系统,我假设我们有一种注册/推送事件的方法。这部分并不重要,但真正重要的是,每次我们更新时,系统都会删除缓存。我们不需要担心重新缓存数据,因为这是原始函数的工作。

按时到期

写时过期可能并不适用于所有场景。在某些情况下,我们只希望缓存查询一段时间。对于这些,我们可以使用Redis来过期我们的密钥。

让我们来看看我们的第一个缓存解决方案,看看它是什么样子的Redis EXPIRE:
1. > HGET用户活动列表缓存
2.如果结果不是nil,返回缓存的值,否则转到步骤3
3.运行查询并将DB结果集保存为变量
4.如果DB结果大小为> 0
5.将结果集数据转换为JSON字符串
HSET用户活动列表缓存JSON字符串结果
届满用户活动列表100

这里的诀窍在第五步。设置密钥后,我们使用Redis设置该密钥的过期时间(以秒为单位)。Redis将为我们删除密钥,因此我们不必担心在代码中管理该密钥。

结论

在页面级缓存不起作用或无效的情况下,缓存数据库查询是一个很好的选择。我们可以使用Redis设置并获取一个包含保存的查询值的散列值,并返回这些值,而不是访问数据库。这将大大加快我们的网站速度。想想看:从数据库访问和返回信息给用户的标准时间是100毫秒,而从Redis访问和返回信息的平均时间只有两毫秒。这是一个巨大的性能提升。

每小时网页浏览量 每页的数据库查询 每次查询的时间 数据库查询的# 每小时对性能的影响
1,000 2-5 100毫秒 2000 - 5000 3.33-8.33分钟

在我们虚构的应用程序中,如果用户每小时点击这个活动页面1000次,而我们每次都要点击数据库,那么这就加起来了(这只针对一个查询)。想象一下,如果这个页面有2-5个查询?如果这些查询需要多个表的复杂联接,该怎么办?一个页面上的三个同步查询很容易就需要300毫秒才能从数据库返回数据。在一个我们只有2-3秒的时间来吸引用户注意力的世界里,为什么要让自己处于这种劣势呢?

现在,假设我们可以90%的时间从缓存中检索数据。

每小时网页浏览量 每页的数据库查询 每次查询的时间 每个缓存的时间命中 缓存命中 数据库查询的# #的Redis调用 每小时对性能的影响
1,000 2-5 100毫秒 2ms 90% 200-500 1,800-4,500 23.5-59秒

在Redis中缓存查询可以在单个页面上将300毫秒变成6毫秒。在整个网站上,这将抓取数据的时间从一小时内的几分钟减少到几秒钟。这是一个性能提升,应该会让客户非常满意。

在我们虚构的应用程序中,如果用户每小时点击这个活动页面1000次,而我们每次都要点击数据库,那么这就加起来了(这只针对一个查询)。想象一下,如果这个页面有2-5个查询?如果这些查询需要多个表的复杂联接,该怎么办?一个页面上的三个同步查询很容易就需要300毫秒才能从数据库返回数据。在一个我们只有2-3秒的时间来吸引用户注意力的世界里,为什么要让自己处于这种劣势呢?

现在,假设我们可以90%的时间从缓存中检索数据。

在Redis中缓存查询可以在单个页面上将300毫秒变成6毫秒。在整个网站上,这将抓取数据的时间从一小时内的几分钟减少到几秒钟。这是一个性能提升,应该会让客户非常满意。

Baidu