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

查看:179
本文介绍了我应该如何从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 function run related),那么我将没有任何问题-可以按照需要从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记录到控制台,然后将查询的结果记录到控制台,然后在Connection closed.上记录在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,Amazon将在内存中尽可能多地缓存这些模块.如果这引起了问题,请尝试将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天全站免登陆