我应该如何从 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 实例?的主要内容,如果未能解决你的问题,请参考以下文章

如何将特定 AWS API Gateway 阶段连接到特定 AWS lambda 别名

使用 Node.js 从 AWS Lambda 函数连接到 MySql 数据库,没有连接回调

如何从 AWS Lambda .Net Core 应用程序 API 连接到 AWS RDS SQL Server?

如何从 AWS SAM 本地 docker 实例连接到主机 MySQL?

无法从 lambda 中的 python 连接到 aws redshift

ReactJS - NodeJS - 在 AWS Lambda 上连接到 Graphql 的问题