我应该如何从 AWS Lambda 函数连接到 Redis 实例? [英] How should I connect to a Redis instance from an AWS Lambda function?

查看:28
本文介绍了我应该如何从 AWS Lambda 函数连接到 Redis 实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to build an API for a single-page web app using AWS Lambda and the Serverless Framework. I want to use Redis Cloud for storage, mostly for its combination of speed and data persistence. I may use more Redis Cloud features in the future, so I'd prefer to avoid using ElastiCache for this. My Redis Cloud instance is running in the same AWS region as my function.

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

I have a function called related that takes a hashtag from a GET request to an API endpoint, and checks to see if there's an entry for it in the database. If it's there, it should return the results immediately. If not, it should query RiteTag, write the results to Redis, and then return the results to the user.

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

I'm pretty new to this, so I'm probably doing something adorably naive. Here's the event handler:

'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 函数运行相关),我不会有任何问题 - 标签会按照应有的方式从 Redis 数据库读取和写入.但是,当我部署它时(使用sls dash deploy),它会在部署后第一次运行时起作用,然后停止工作.所有后续运行它的尝试都将 null 返回到浏览器(或 Postman、curl 或 Web 应用程序).无论我用于测试的标签是否已经在数据库中,这都是正确的.如果我然后重新部署,不对函数本身做任何更改,它会再次运行 - 一次.

All of this works as expected, to a point. If I run the function locally (using sls function run related), I have no problems whatsoever—tags are read from and written to the Redis database as they should be. However, when I deploy it (using sls dash deploy), it works the first time it's run after deployment, and then stops working. All subsequent attempts to run it simply return null to the browser (or Postman, or curl, or the web app). This is true regardless of whether the tag I use for testing is already in the database or not. If I then re-deploy, making no changes to the function itself, it works again—once.

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

On my local machine, the function first logs Connected: true to the console, then the results of the query, then Connection closed. On AWS, it logs Connected: true, then the results of the query, and that's it. On the second run, it logs Connection closed. and nothing else. On the third and all subsequent runs, it logs nothing at all. Neither environment ever reports any errors.

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

It seems pretty clear that the problem is with the connection to Redis. If I don't close it in the callbacks, then subsequent attempts to call the function just time out. I've also tried using redis.unref instead of redis.quit, but that didn't seem to make any difference.

任何帮助将不胜感激.

推荐答案

我现在已经解决了我自己的问题,希望对以后遇到这个问题的人有所帮助.

I've now solved my own problem, and I hope I can be of help to someone experiencing this problem in the future.

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

There are two major considerations when connecting to a database like I did in the code above from a Lambda function:

  1. 一旦 context.succeed()context.fail()context.done() 被调用,AWS 可能会冻结任何尚未完成的过程.这就是导致 AWS 在第二次调用我的 API 端点时记录 Connection closed 的原因——该过程在 Redis 完成关闭之前被冻结,然后在下一次调用时解冻,此时它继续在它停止了,报告连接已关闭.要点:如果要关闭数据库连接,请确保在调用这些方法之一之前 完全关闭它.您可以通过在连接关闭触发的事件处理程序中放置一个回调来实现这一点(.on('end'),在我的例子中).
  2. 如果您将代码拆分为单独的文件,并在每个文件的顶部 require 它们,就像我所做的那样,亚马逊将在内存中缓存尽可能多的这些模块.如果这导致了问题,请尝试将 require() 调用移动到函数内部而不是文件顶部,然后导出该函数.每当函数运行时,这些模块就会被重新导入.
  1. Once context.succeed(), context.fail(), or context.done() is called, AWS may freeze any processes that haven't finished yet. This is what was causing AWS to log Connection closed on the second call to my API endpoint—the process was frozen just before Redis finished closing, then thawed on the next call, at which point it continued right where it left off, reporting that the connection was closed. Takeaway: if you want to close your database connection, make sure it's fully closed before you call one of those methods. You can do this by putting a callback in an event handler that's triggered by a connection close (.on('end'), in my case).
  2. If you split your code into separate files and require them at the top of each file, like I did, Amazon will cache as many of those modules as possible in memory. If that's causing problems, try moving the require() calls inside a function instead of at the top of the file, then exporting that function. Those modules will then be re-imported whenever the function is run.

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

Here's my updated code. Note that I've also put my Redis configuration into a separate file, so I can import it into other Lambda functions without duplicating code.

'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
                  })
                }
              })
            }
          })
        }
      }
    })
  })
}

这完全符合预期——而且速度也非常快.

This works exactly as it should—and it's blazing fast, too.

这篇关于我应该如何从 AWS Lambda 函数连接到 Redis 实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆