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

Posted

技术标签:

【中文标题】我应该如何从 AWS Lambda 函数连接到 Redis 实例?【英文标题】:How should I connect to a Redis instance from an AWS Lambda function? 【发布时间】:2016-09-02 20:15:52 【问题描述】:

我正在尝试使用 AWS Lambda 和 Serverless 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,但这似乎没有任何区别。

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

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

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

    一旦调用context.succeed()context.fail()context.done(),AWS 可能会冻结任何尚未完成的进程。这就是导致 AWS 在第二次调用我的 API 端点时记录 Connection closed 的原因——该进程在 Redis 完成关闭之前被冻结,然后在下一次调用时解冻,此时它从停止的地方继续,报告说连接已关闭。要点:如果您想关闭数据库连接,请确保在调用其中一种方法之前 完全关闭它。您可以通过将回调放入由连接关闭触发的事件处理程序(在我的情况下为.on('end'))中来做到这一点。 如果您像我一样将代码拆分为单独的文件并在每个文件的顶部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
                  )
                
              )
            
          )
        
      
    )
  )

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

【讨论】:

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

以上是关于我应该如何从 AWS Lambda 函数连接到 Redis 实例?的主要内容,如果未能解决你的问题,请参考以下文章