我应该如何从 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?