开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
问题
扩展关系数据库是一个大问题。由于joins的要求,很难跨节点分区数据和运行数据库服务器集群,因此,我们在某种意义上被迫在单个节点上运行关系数据库服务器。今天的大多数 Web 应用程序都由 Postgres 或 MySQL 提供支持,它们本身就是出色的数据库,并且有可能支持大量并发请求。然而,这些数据库有一个限制,它们无法扩展以处理非常大的负载——比如 Facebook 或谷歌的负载。
为了提高数据库性能,可以在查询级别进行一些优化。例如,可以以尽可能最好的方式构建查询,以避免不必要的查找和连接,这往往会加载数据库。这对扩展应用程序没有真正帮助。但是,这肯定会提高性能,这意味着应用程序可以支持更多的用户(我将其称为性能改进而不是扩展)。
有一个重要的概念位于计算机科学的核心——缓存。缓存一直有助于提高性能。缓存几乎无处不在
- RAM 在某种意义上是硬盘/SSD 的缓存
- L3缓存是RAM的缓存
- 您的浏览器缓存您访问的网页
- DNS 服务器缓存 DNS 条目以加快解析速度
- Web 服务器缓存它们提供的网页
- 动态规划本质上就是缓存
还有很多例子表明缓存是提高性能的好方法(请注意,我们谈论的是提高性能而不是扩展)。我们可以对 Web 应用程序做同样的事情。
来自原始文档——Redis 是一种开源(BSD 许可)内存数据结构存储,用作数据库、缓存和消息代理。这里我们就把redis作为缓存说一下。
以前 memcached 相当普遍(甚至 Quora 也使用 memcached)。但是现在,大量的 web 应用程序使用 Redis 作为缓存,stackoverflow 社区建议使用 Redis来做任何新的事情
如何使用它?
下载和启动 Redis 服务器在官方文档中有很好的描述。Redis 默认在端口号 6379 上运行。
Web 应用程序具有缓存配置设置,可以将其更改为使用 Redis 作为缓存。以下是在 Django 中如何执行此操作(在 settings.py 文件中)
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
一旦设置了缓存,使用 Redis 缓存数据项就相当简单了。Redis 是一个键值存储。为了存储一个值,我们将它与一个键相关联并将其存储在 Redis 中。
例如,如果我们有一个名为 TestModel 的模型,并且我们想要缓存一个特定的查询,我们可以按如下方式进行(当然在某些视图中):
from django.core.cache import cache..
if cache.get('all') is not None:
objects = cache.get('all')
else:
objects = TestModel.objects.all()
cache.set('all', objects)
我们尝试首先在缓存中找到名为 all 的键(缓存与 Redis 相关联)。如果我们找到它,我们返回它,否则我们查询数据库。
当缓存命中率良好时,这有助于显着提高服务器性能。
上述情况有一个捷径:
objects = cache.get_or_set('all', TestModel.objects.all())
正如它所建议的那样,get_or_set 方法尝试在缓存中查找名为“all”的键,如果未找到,它将存储值为“TestModel.objects.all()”的键。
Redis 更强大,可以存储列表和集合,这让生活变得非常轻松。可以在此处找到 Redis 支持的数据类型的详细列表。
问题
发出写入时会出现问题。例如,假设我们在 TestModel 表中插入一个新条目。Postgres 数据库(在硬盘/SSD 中)将包含 1 个额外的行(新插入的条目),但是,Redis 缓存(在 RAM 中)不会使用这个新行进行更新,因此,我们现在有一个陈旧的缓存。请注意,我们的数据库位于 Postgres 或 MySQL 中。我们只是将 Redis 用作缓存。
解决方案
解决方案相当简单。每当发出写入时,与该特定模型(表)关联的所有缓存都会失效(标记为陈旧)。对于我们的案例,以下简单的解决方案适用于 Django:
[@receiver](http://twitter.com/receiver) (post_save, sender=TestModel, dispatch_uid="Write issued")
[@receiver](http://twitter.com/receiver) (post_delete, sender=TestModel, dispatch_uid="Write issued")
def invalidate_cache(sender, instance, **kwargs):
cache.delete('all ')
上面的代码是非常特定于 Django 的,因此需要一些解释。我们基本上是使用 Django 信号来实现我们的目标——在模型上发出写入时使所有缓存失效。因此,当在模型实例上调用 save(更新表的一行或添加新行)或在模型实例上调用 delete(删除表的一行)时,信号发出并调用 invalidate_cache 方法导致键 all 失效。这意味着键“all”已从缓存中删除,因此,进一步查找将导致缓存未命中(有效地导致查询被发送到 Postgres)。
数字
我用 Redis 进行了一些实验以获得一些数字。在 Redis 服务器旁边设置了 Postgres 服务器。在 Postgres 中创建了一个包含 100000 行的表。然后使用Loadtest向服务器发送多个查询,这有效地导致了全表扫描。这些数字令人印象深刻,Redis 确实通过一个很好的因素帮助提高了性能:桌子扫描。这些数字令人印象深刻,Redis 确实通过一个很好的因素帮助提高了性能:
没有 Redis:0.98 秒
使用 Redis:0.005 秒
结论
除了查询优化之外,Redis 缓存可能是减少数据库服务器负载并提高其性能的好方法。