简体   繁体   English

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

[英]How should I connect to a Redis instance from an AWS Lambda function?

I'm trying to build an API for a single-page web app using AWS Lambda and the Serverless Framework . 我正在尝试使用AWS Lambda无服务器框架为单页面Web应用程序构建API。 I want to use Redis Cloud for storage, mostly for its combination of speed and data persistence. 我想使用Redis Cloud进行存储,主要是因为它结合了速度和数据持久性。 I may use more Redis Cloud features in the future, so I'd prefer to avoid using ElastiCache for this. 我将来可能会使用更多的Redis Cloud功能,所以我宁愿避免使用ElastiCache。 My Redis Cloud instance is running in the same AWS region as my function. 我的Redis Cloud实例与我的函数在同一AWS区域中运行。

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. 我有一个名为related的函数,它从GET请求中获取一个hashtag到API端点,并检查数据库中是否有一个条目。 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. 如果没有,它应该查询RiteTag ,将结果写入Redis,然后将结果返回给用户。

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

Here's the ../lib/related.js file: 这是../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)
                }
              })
            }
          })
        }
      }
    })
  })
}

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. 如果我在本地运行该函数(使用sls function run related ),我没有任何问题 - 标记从它们应该读取和写入Redis数据库。 However, when I deploy it (using sls dash deploy ), it works the first time it's run after deployment , and then stops working. 但是,当我部署它时(使用sls dash deploy ),它在部署后第一次运行时工作,然后停止工作。 All subsequent attempts to run it simply return null to the browser (or Postman, or curl, or the web app). 所有后续尝试运行它只会返回null到浏览器(或邮差,或卷曲,或Web应用程序)。 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. 如果我然后重新部署,不对函数本身进行任何更改,它再次工作一次。

On my local machine, the function first logs Connected: true to the console, then the results of the query, then Connection closed. 在我的本地计算机上,该函数首先记录Connected: true到控制台,然后查询结果,然后Connection closed. On AWS, it logs Connected: true , then the results of the query, and that's it. 在AWS上,它记录Connected: true ,然后记录查询的结果,就是这样。 On the second run, it logs Connection closed. 在第二次运行时,它会记录Connection closed. and nothing else. 没有别的。 On the third and all subsequent runs, it logs nothing at all. 在第三次和所有后续运行中,它根本不记录任何内容。 Neither environment ever reports any errors. 这两种环境都没有报告任何错误。

It seems pretty clear that the problem is with the connection to Redis. 似乎很清楚,问题在于与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. 我也尝试使用redis.unref而不是redis.quit ,但这似乎没有任何区别。

Any help would be greatly appreciated. 任何帮助将不胜感激。

I've now solved my own problem, and I hope I can be of help to someone experiencing this problem in the future. 我现在已经解决了自己的问题,希望将来可以帮助遇到这个问题的人。

There are two major considerations when connecting to a database like I did in the code above from a Lambda function: 连接到数据库时有两个主要注意事项,就像我在上面的代码中从Lambda函数中所做的那样:

  1. Once context.succeed() , context.fail() , or context.done() is called, AWS may freeze any processes that haven't finished yet. 一旦调用了context.succeed()context.fail()context.done() ,AWS可能会冻结尚未完成的任何进程。 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. 这是导致AWS在第二次调用我的API端点时Connection closed - 该过程在Redis完成关闭之前被冻结,然后在下一次调用时解冻,此时它继续向右停止,报告连接已关闭。 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). 你可以通过在一个由连接关闭( .on('end')触发的事件处理程序中.on('end')一个回调来实现这一点)。
  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. 如果您将代码拆分为单独的文件并在每个文件的顶部require它们,就像我一样,亚马逊将尽可能多地在内存中缓存这些模块。 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. 如果这会导致问题,请尝试在函数内部而不是在文件顶部移动require()调用,然后导出该函数。 Those modules will then be re-imported whenever the function is run. 无论何时运行该函数,都将重新导入这些模块。

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. 请注意,我还将我的Redis配置放在一个单独的文件中,因此我可以将其导入其他Lambda函数而无需复制代码。

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

Redis Configuration 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))
}

The Function 功能

'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. 这完全按照应有的方式工作 - 而且速度也很快。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM