Better

Ethan的博客,欢迎访问交流

使用Redis缓存MongoDB数据库

如今,在开发 Web 服务时,性能是非常重要的。当于数据库交互时,我们可以使用 cache 来提速。

背景

缓存旨在解决存储问题,存储越大,就会越慢,反之亦然。在计算机上,你可以使用很大的硬盘,但是相对就会慢一些。你可以使用 RAM,快但是存储能力较小。你还是可以使用CPU寄存器,非常快但是非常小。

缓存存储最近访问的数据在一个较快的存储系统中。每次请求数据的时候,它可能从内存中取出。缓存数据背后的基本假设是数据最近被读取过,并且有很高的概率再次被读取,因此他们应该存储在一个更快的内存,因此下次阅读会更快。

为了更好的理解这个概念,举例如下,一群人在一个图书馆,图书馆本身是一个巨大的存储系统,但是很难去找到某一本书。我们可以想象,图书馆是一个巨大的但是缓慢的存储系统,假设有一些人,当他们找到某一本书后,阅读它但是不会放回原位,而是放在他们的书桌上,他们有这个表现是因为他们确定他们会再次阅读它。在这个例子中,图书馆是主要存储系统,而书桌是我们的缓存。

下面我们使用 MongoDB 作为主要存储系统,使用 Redis 作为缓存。使用 express 构建 web 服务。关闭 express 和 MongoDB,这里我就多介绍了,主要介绍如何在 Node 中使用 Redis。

安装

Linux 环境下安装 Redis 步骤就不详说了,主要说明一下 Redis 如何后台启动,具体步骤如下

# 修改 redis.conf 配置文件下如下参数
daemonize yes
# 使用配置文件进行启动
redis-server ./redis.conf  默认端口6379

顺便讲一下 redis 密码问题,如果不设置密码的话,那么只要别人知道你的IP和端口,就可以任意的访问你的缓存数据,所以通常我们都会设置密码

# 关闭服务,因为修改配置文件需要重启才能生效,进入src目录执行
./redis-cli -p 6379 shutdowm
# 修改 redis.conf 文件,开启如下
requirepass yourPwd
# 开启服务
redis-server ./redis.conf

缓存

同样引入图书管的例子,如果我需要去找特定标题的书,我会怎么做呢:

  1. 看书桌上是不是已经有了,如果有,缓存命中,贼棒
  2. 如果没有,缓存未命中,就去图书馆找,找到之后会放到书桌上,也就是加入缓存

开始步入主题:

  1. 安装 redis 并键入命令 redis-server 启动
  2. 项目安装 redis 客户端,npm install redis --save
  3. 初始化 redis 客户端
    var redisClient = require('redis').createClient;
    var redis = redisClient(6379, 'localhost');
    
  4. 在需要缓存的地方加入cache,例子如下
    module.exports.findBookByTitleCached = function (db, redis, title, callback) {
     redis.get(title, function (err, reply) {
         if (err) callback(null);
         else if (reply) //Book exists in cache
         callback(JSON.parse(reply));
         else {
             //Book doesn't exist in cache - we need to query the main database
             db.collection('text').findOne({
                 title: title
             }, function (err, doc) {
                 if (err || !doc) callback(null);
                 else {
                     //Book found in database, save to cache and return to client
                     redis.set(title, JSON.stringify(doc), function () {
                         callback(doc);
                     });
                 }
             });
         }
     });
    };
    

缓存策略

上面我们简单的实现了缓存,但是这里有个问题,它保存每个结果到 Redis 中,这样的话 Redis 可能会慢慢过载计算机的 RAM,直到填满。

由于内存的限制,我们必须删除缓存中的一些项,只保留部分。理想情况下,我们要保持这些的几率最高的再次阅读。选择我们要删除的项目,我们必须建立一种缓存策略。删除随机项可能是一个有效的政策,但它显然不会是非常有效的。我们将使用一个最受欢迎的政策:LRU(最近最少使用)。

幸运的是,Redis 内置 LRU 机制,因此我们不需要在应用层那么麻烦。为此,我们要做的是配置一下使用LRU的方式删除项目即可。实现这个目的,我们只需要在启动命令增加两个参数。第一个参数是限制最大内存,第二个参数是设置使用 LRU 策略。

# 这个比较难记,同样可以直接在redis.conf下配置,使用配置文件的方式启动,当然记得去掉前面的注释
redis-server --maxmemory 10mb --maxmemory-policy allkeys-lru

缓存更新

这里有一个问题是,当数据更新时,保持缓存同步更新。

因此,当我们更新数据时,如果数据项也在缓存中,我们就需要更新缓存。

// 那修改完的新数据,调用加入redis方法即可
redis.set(title, JSON.stringify(doc), function (err) {
    if (err) callback(err);
    else callback(null);
});

另一种情况值得注意如下:针对同一个数据库,你有多个缓存,可能发生,多台机器上运行应用程序时,为每个机器上使用缓存是很棒的,因为它可以阻止巨大的与数据库交互的流量,在这种情况下,我们需要一个机制确保来自一台机器的更新会影响到所有的缓存。不幸的是,这个场景的深化是超出了本教程的范围,但是我们可能会提出一些巧妙的解决方案来解决这个问题,我在接下来的文章之一。

总结

在本教程中,我展示了如何通过缓存加速 web引用。尽管本教程使用 Redis 缓存,您可以使用其他键值存储。另一个流行的数据库是 Memcached。我选择了 Redis 主要是因为它的流行,其详细的文档和易用性。

当然,缓存是一个巨大的性能推进器,它不是适合每一个应用程序,这里有一些值得思考的注意事项,当你思考加入缓存时

  • 数据库读取对你的性能影响真的很大么,你需要做一些测试,验证他是不是你真正的问题
  • 你会使用许多不同的键查询吗,在数据库中,查询参数可以有很多,在缓存中,只能有一个key(一个参数或参数集合)用来查询,缓存所有可能的 keys 可能是有害的,思考那个查询是最常用的,以及需要被缓存。
  • 你的应用需要执行大量的更新么,虽然缓存加速读取,也减慢写入。
  • 你在尝试缓存复杂的查询么,复杂查询会很难,同时降低缓存效率

Premature optimization is the source of all evil.(过早的优化是万恶之源。)

这应该提醒你,优化有自己合适的时间和地点。

Refer



留言