使用 Redis 查询缓存

阅读数:104 2019 年 10 月 24 日 16:03

使用Redis查询缓存

当我开始搭建网站时,我并没有太注重性能。一旦你掌握一些基础知识,比如 HTML 和 CSS,或者你在后端使用的任何编程语言,性能就成了你担心的奢侈品。初学者的目标是建一个网站,能够从一个页面跳转到另一个页面,并确保它在服务器上看起来运行地很好。缓存和性能是我们后面要解决的问题。

有很多很好的理由来研究缓存解决方案。从 SEO(搜索引擎优化)的角度来看,谷歌会惩罚加载缓慢的网站。如果你的网站加载需要很长时间,网站排名可能会受到影响,这当然会对收入产生严重的影响。Google 的 John Mueller 说,页面加载时间超过两秒钟会“影响爬取”你的网站数据,他会寻找加载时间少于 2-3 秒的网站。另一个因素是加载缓慢的网站可用性影响。当我开始编码时,完全加载一个页面大约需要 8 秒钟是可接受的。只有花了更长的时间,你才会担心人们会放弃这个页面。如今,这个时间大约是两秒钟,可能会更少,具体取决于你所在的行业。

底线:你的网站越快,排名越好,访问者留下和互动的次数就越多。但是,直到我的客户打电话说他们的新网站“太慢”时,这个问题才引起我的注意。理性地分析,它上面有一些较大的图像,他们正在使用一个糟糕的托管平台。这也回到了 10 美元托管意味着你的网站运行在共享服务器上的一天。他们对服务器优化的想法是安装最新版本的 CPanel(yikes)。经过一番挖掘,我构建了一个基本的页面缓存系统,它运行得很好。客户很高兴,我开始学习一种新的技能,一切都很好。但很快我意识到,虽然页面级缓存是一个很好的工具,但它只是一个工具。

当页面缓存不足时

页面缓存的工作方式如下:一个请求传入,服务器处理它,然后存储结果 HTML。为了说明本篇博客的场景,让我们使用 Redis 的一个例子。下次有人请求我们的页面时,它将首先检查缓存。如果页面在缓存中存在,系统将使用该页面,而不是在服务器级别处理请求。

这种场景适用于不会改变太多的页面,例如条款和条件或隐私政策。这些页面的流量不同于主应用程序,后者可能具有基于用户的动态数据。

假设我们有一个充当用户目录并允许基于某种活动进行过滤的应用程序。用户可以查看人员列表并查看他们的电子邮件地址和电话号码。我们的应用程序使用 SQL 数据库,因此要获取该数据,我们需要一个简单的选择查询:

复制代码
SELECT username,email, phone_number FROM users WHERE activity='baseball';

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

复制代码
SELECT name, id FROMactivity_list WHERE user_id=1;

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

查询缓存,而不是页面缓存

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

那么缓存是什么样子的呢?下面是一些伪代码:

复制代码
> HGETuser-activity-list cache
if the result isnot nil return the result set else go to step 3
run the query andsave the DB result set to a variable
if the DB resultsize is > 0
transform theresult set data to a JSON string
> HSETuser-activity-list cache JSON string result set
else result set<= 0 throw error

关于上面的伪代码,我应该指出,当然我们忽略了这实际上是会话存储模式的事实。在现实世界中,你不会只想在 Redis 中永久缓存这些查询,只有在用户登录并在你的站点上活动时才创建和存储这些查询。我在 Redis 中创建了这个通用的用户活动列表键,仅作为一个示例来演示理论并激发你如何在自己的代码中执行此操作的想法。

除此之外,我们的键名也不是最好的。“用户活动列表”的名称也同样仅适用于这个非常通用和狭窄的示例。对于实际的应用程序,你可能希望以一种更可预测的方式命名你的键名。如果我真的这么做的话,这个键就会在某个地方有用户名,这都是用户会话的一部分,这样就可以很容易地检索和使用。

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

写入时过期

假设此活动列表是用户自己更新的内容。她会去设置页面,摆弄一些东西,并保存更改。她每周只能这样做一次或每月一次。在这种情况下,我们可以保留缓存,直到用户更改某些内容:

1. 用户添加“sportsball”列表并保存
2. 为了响应用户的保存,我们取消了用户活动列表的链接

你可以按照自己喜欢的方式完成第一部分和第二部分。对于这个系统,我假装我们有办法注册 / 推送事件。那部分并不重要,但重要的是我们的系统每次更新时都会删除缓存。我们不需要担心重新缓存数据,因为这是原始函数的工作。

定时过期

在写入时过期可能并不适用于每个场景。在有些情况下,我们只希望缓存一定时间的查询。对于这些,我们可以使用 Redis 来过期我们的键。

让我们拿出第一个缓存解决方案,看看它里面的 Redis 是什么样子的:

1.> HGET 获取用户活动列表缓存
2. 如果结果不是 nil 则返回缓存值,否则转到步骤 3
3. 运行查询并将 DB 结果集保存到变量
4. 如果 DB 结果大小 > 0
5. 将结果集数据转换为 JSON 字符串

HSET 用户活动列表缓存 JSON 字符串结果
EXPIRE 用户活动列表 100

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

结论

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

在我们虚构的应用程序中,如果用户每小时点击这个活动页面 1000 次,而我们每次都必须访问数据库,那就会加起来 (这只用于一个查询)。想象一下,如果这个页面有 2-5 个查询?如果这些查询需要多个表的复杂联接,该怎么办?一个页面上的三个同步查询很容易花费 300 毫秒以上才能从数据库返回数据。在一个我们只有 2-3 秒钟来吸引用户注意力的世界中,为什么要把自己置于不利地位?

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

在 Redis 中缓存查询可以使单个页面的访问时间从 300 毫秒缩小为 6 毫秒。访问整个网站,抓取数据需要花费的时间将从数分钟减少到仅几秒钟。这是一个性能提升,应该会让极大提升客户体验。

本文转载自公众号中间件小哥(ID:huawei_kevin)。

原文链接:

https://mp.weixin.qq.com/s/HSOh1tA3rI5oS_iUOwGQ7g

评论

发布