23

我正在尝试使用AWS LambdaServerless Framework为单页 Web 应用程序构建 API 。我想使用Redis Cloud进行存储,主要是因为它结合了速度和数据持久性。将来我可能会使用更多 Redis Cloud 功能,因此我宁愿避免使用 ElastiCache。我的 Redis Cloud 实例与我的函数在同一 AWS 区域中运行。

我有一个名为的函数related,它从 GET 请求到 API 端点获取标签,并检查数据库中是否有它的条目。如果它在那里,它应该立即返回结果。如果没有,它应该查询RiteTag,将结果写入 Redis,然后将结果返回给用户。

我对此很陌生,所以我可能正在做一些非常天真的事情。这是事件处理程序:

'use strict'

const lib = require('../lib/related')

module.exports.handler = function (event, context) {
  lib.respond(event, (err, res) => {
    if (err) {
      return context.fail(err)
    } else {
      return context.succeed(res)
    }
  })
}

这是../lib/related.js文件:

var redis = require('redis')
var jsonify = require('redis-jsonify')
var rt = require('./ritetag')
var redisOptions = {
  host: process.env.REDIS_URL,
  port: process.env.REDIS_PORT,
  password: process.env.REDIS_PASS
}
var client = jsonify(redis.createClient(redisOptions))

module.exports.respond = function (event, callback) {
  var tag = event.hashtag.replace(/^#/, '')
  var key = 'related:' + tag

  client.on('connect', () => {
    console.log('Connected:', client.connected)
  })

  client.on('end', () => {
    console.log('Connection closed.')
  })

  client.on('ready', function () {
    client.get(key, (err, res) => {
      if (err) {
        client.quit()
        callback(err)
      } else {
        if (res) {
          // Tag is found in Redis, so send results directly.
          client.quit()
          callback(null, res)
        } else {
          // Tag is not yet in Redis, so query Ritetag.
          rt.hashtagDirectory(tag, (err, res) => {
            if (err) {
              client.quit()
              callback(err)
            } else {
              client.set(key, res, (err) => {
                if (err) {
                  callback(err)
                } else {
                  client.quit()
                  callback(null, res)
                }
              })
            }
          })
        }
      }
    })
  })
}

在某种程度上,所有这些都按预期工作。如果我在本地运行该函数(使用sls function run related),我没有任何问题——标签按应有的方式从 Redis 数据库读取和写入。但是,当我部署它(使用sls dash deploy)时,它在部署后第一次运行时工作,然后停止工作。所有后续运行它的尝试都只是返回null到浏览器(或 Postman、curl 或 Web 应用程序)。无论我用于测试的标签是否已经在数据库中,这都是正确的。如果我随后重新部署,不对函数本身进行任何更改,它就会再次工作——一次。

在我的本地机器上,该函数首先记录Connected: true到控制台,然后是查询结果,然后Connection closed.在 AWS 上,它记录Connected: true,然后是查询结果,仅此而已。在第二次运行时,它记录Connection closed.并没有其他内容。在第三次和所有后续运行中,它根本没有记录任何内容。两种环境都不会报告任何错误。

很明显,问题出在与 Redis 的连接上。如果我不在回调中关闭它,那么随后调用该函数的尝试就会超时。我也尝试过使用redis.unref而不是redis.quit,但这似乎没有任何区别。

任何帮助将不胜感激。

4

1 回答 1

37

我现在已经解决了我自己的问题,我希望我能对将来遇到这个问题的人有所帮助。

像我在上面的代码中从 Lambda 函数那样连接到数据库时,有两个主要考虑因素:

  1. 一旦调用context.succeed()context.fail()context.done(),AWS 可能会冻结任何尚未完成的进程。这就是导致 AWS 记录Connection closed对我的 API 端点的第二次调用的原因——该进程在 Redis 完成关闭之前被冻结,然后在下一次调用时解冻,此时它从停止的地方继续,报告连接是关闭。要点:如果要关闭数据库连接,请在调用其中一种方法之前确保它已完全关闭。您可以通过将回调放入由连接关闭(.on('end')在我的情况下为 )触发的事件处理程序中来做到这一点。
  2. 如果您像我一样将代码拆分为单独的文件并将require它们放在每个文件的顶部,Amazon 将在内存中缓存尽可能多的这些模块。如果这会导致问题,请尝试将require()调用移动到函数内部而不是文件顶部,然后导出该函数。每当函数运行时,这些模块将被重新导入。

这是我更新的代码。请注意,我还将 Redis 配置放入单独的文件中,因此我可以将其导入其他 Lambda 函数而无需重复代码。

事件处理程序

'use strict'

const lib = require('../lib/related')

module.exports.handler = function (event, context) {
  lib.respond(event, (err, res) => {
    if (err) {
      return context.fail(err)
    } else {
      return context.succeed(res)
    }
  })
}

Redis 配置

module.exports = () => {
  const redis = require('redis')
  const jsonify = require('redis-jsonify')
  const redisOptions = {
    host: process.env.REDIS_URL,
    port: process.env.REDIS_PORT,
    password: process.env.REDIS_PASS
  }

  return jsonify(redis.createClient(redisOptions))
}

功能

'use strict'

const rt = require('./ritetag')

module.exports.respond = function (event, callback) {
  const redis = require('./redis')()

  const tag = event.hashtag.replace(/^#/, '')
  const key = 'related:' + tag
  let error, response

  redis.on('end', () => {
    callback(error, response)
  })

  redis.on('ready', function () {
    redis.get(key, (err, res) => {
      if (err) {
        redis.quit(() => {
          error = err
        })
      } else {
        if (res) {
          // Tag is found in Redis, so send results directly.
          redis.quit(() => {
            response = res
          })
        } else {
          // Tag is not yet in Redis, so query Ritetag.
          rt.hashtagDirectory(tag, (err, res) => {
            if (err) {
              redis.quit(() => {
                error = err
              })
            } else {
              redis.set(key, res, (err) => {
                if (err) {
                  redis.quit(() => {
                    error = err
                  })
                } else {
                  redis.quit(() => {
                    response = res
                  })
                }
              })
            }
          })
        }
      }
    })
  })
}

这完全可以正常工作-而且速度也非常快。

于 2016-05-08T05:18:11.677 回答