我应该如何从AWS Lambda函数连接到Redis实例?

Nic*_*las 16 amazon-web-services redis node.js aws-lambda serverless-framework

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

我有一个函数调用related,它从GET请求到API端点获取一个hashtag,并检查数据库中是否有一个条目.如果它在那里,它应该立即返回结果.如果没有,它应该查询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)
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

这是../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)
                }
              })
            }
          })
        }
      }
    })
  })
}
Run Code Online (Sandbox Code Playgroud)

所有这些都按预期工作到了一定程度.如果我在本地(使用sls function run related)运行该函数,我没有任何问题 - 标记从Redis数据库读取和写入应该是.但是,当我部署它(使用sls dash deploy)时,它在部署后第一次运行时工作,然后停止工作.所有后续尝试运行它只是返回null浏览器(或邮递员,或卷曲,或Web应用程序).无论我用于测试的标签是否已经存在于数据库中,都是如此.如果我然后重新部署,不对函数本身进行任何更改,它再次工作一次.

在我的本地计算机上,该函数首先登录Connected: true到控制台,然后是查询结果,然后Connection closed.在AWS上,它会记录Connected: true,然后是查询结果,就是这样.在第二次运行时,它记录Connection closed.,没有别的.在第三次和所有后续运行中,它根本不记录任何内容.这两种环境都没有报告任何错误.

似乎很清楚,问题在于与Redis的连接.如果我没有在回调中关闭它,那么后续尝试调用该函数只是超时.我也试过用redis.unref而不是redis.quit,但这似乎没有任何区别.

任何帮助将不胜感激.

Nic*_*las 23

我现在已经解决了自己的问题,希望将来可以帮助遇到这个问题的人.

连接到数据库时有两个主要注意事项,就像我在上面的代码中从Lambda函数中所做的那样:

  1. 一旦context.succeed(),context.fail()或被context.done()调用,AWS可能会冻结尚未完成的任何进程.这是导致AWS登录Connection closed到我的API端点的第二次调用的原因 - 该过程在Redis完成关闭之前被冻结,然后在下一次调用时解冻,此时它继续向右停止,报告连接是关闭.要点:如果要关闭数据库连接,请确保调用其中一种方法之前将其完全关闭.您可以通过在由连接关闭(.on('end')在我的情况下)触发的事件处理程序中放置回调来完成此操作.
  2. 如果您将代码拆分为单独的文件,require并将它们放在每个文件的顶部,就像我一样,亚马逊会尽可能多地在内存中缓存这些模块.如果这导致问题,请尝试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)
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

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))
}
Run Code Online (Sandbox Code Playgroud)

功能

'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
                  })
                }
              })
            }
          })
        }
      }
    })
  })
}
Run Code Online (Sandbox Code Playgroud)

这完全按照应有的方式工作 - 而且速度也很快.

  • 这是很好的信息.我只是想澄清AWS Lambda中对`require`的一个误解:不是AWS缓存那些需求,这就是Node.js中核心模块导入器的工作方式.正如您所说,导出函数是处理此问题的最安全方法. (6认同)
  • @ C.Lee需要在节点中工作的方式是它将在运行时获取和缓存任何需求.如果所需的代码需要被调用者的上下文来提供正确的结果,那么它将无法工作,因为缓存的第一次运行是节点环境的操作.正确执行此操作的一种方法是要求将作为IIFE运行的函数,并在由代码编译时返回"新鲜"执行. (5认同)
  • @JonathanKempf 这与 ES6 中的导入相同吗?谢谢 (3认同)