node.js - ¿Cómo debo conectarme a una instancia de Redis desde una función de AWS Lambda?
amazon-web-services aws-lambda (1)
Ahora he resuelto mi propio problema, y espero poder ayudar a alguien que experimente este problema en el futuro.
Hay dos consideraciones principales cuando se conecta a una base de datos como hice en el código anterior de una función Lambda:
- Una vez que se llama a
context.succeed()
,context.fail()
ocontext.done()
, AWS puede congelar cualquier proceso que aún no haya finalizado. Esto fue lo que causó que AWS registrara laConnection closed
en la segunda llamada a mi punto final de API: el proceso se congeló justo antes de que Redis terminara de cerrarse y luego se descongelara en la siguiente llamada, momento en el que continuó justo donde se había detenido, informando que La conexión estaba cerrada. Para llevar: si desea cerrar la conexión de su base de datos, asegúrese de que esté completamente cerrada antes de llamar a uno de esos métodos. Puede hacer esto colocando una devolución de llamada en un controlador de eventos que se desencadena por un cierre de conexión (.on(''end'')
, en mi caso). - Si divide su código en archivos separados y los
require
en la parte superior de cada archivo, como hice yo, Amazon almacenará en memoria caché tantos módulos como sea posible en la memoria. Si eso causa problemas, intente mover las llamadasrequire()
dentro de una función en lugar de en la parte superior del archivo, luego exporte esa función. Esos módulos se volverán a importar cuando se ejecute la función.
Aquí está mi código actualizado. Tenga en cuenta que también he puesto mi configuración de Redis en un archivo separado, por lo que puedo importarlo en otras funciones de Lambda sin duplicar el código.
El controlador de eventos
''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)
}
})
}
Configuración de 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))
}
La función
''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
})
}
})
}
})
}
}
})
})
}
Esto funciona exactamente como debería, y también es muy rápido.
Estoy intentando crear una API para una aplicación web de una sola página utilizando AWS Lambda y Serverless Framework . Quiero usar Redis Cloud para almacenamiento, principalmente por su combinación de velocidad y persistencia de datos. Es posible que use más funciones de Redis Cloud en el futuro, por lo que prefiero evitar usar ElastiCache para esto. Mi instancia de Redis Cloud se ejecuta en la misma región de AWS que mi función.
Tengo una función llamada related
que toma un hashtag de una solicitud GET a un punto final de API, y comprueba si hay una entrada para ella en la base de datos. Si está allí, debería devolver los resultados inmediatamente. Si no, debería consultar RiteTag , escribir los resultados a Redis y luego devolver los resultados al usuario.
Soy bastante nuevo en esto, así que probablemente estoy haciendo algo adorablemente ingenuo. Aquí está el controlador de eventos:
''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)
}
})
}
Aquí está el archivo ../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)
}
})
}
})
}
}
})
})
}
Todo esto funciona como se esperaba, hasta cierto punto. Si ejecuto la función localmente (utilizando la sls function run related
), no tengo ningún problema: las etiquetas se leen y se escriben en la base de datos de Redis como debería ser. Sin embargo, cuando lo implemento (usando sls dash deploy
), funciona la primera vez que se ejecuta después de la implementación y luego deja de funcionar. Todos los intentos posteriores de ejecutarlo simplemente devuelven el null
al navegador (o Postman, o curl, o la aplicación web). Esto es cierto independientemente de si la etiqueta que uso para la prueba ya está en la base de datos o no. Si luego vuelvo a desplegar, sin hacer cambios a la función en sí, funciona de nuevo, una vez.
En mi máquina local, la función primero registra Connected: true
a la consola, luego los resultados de la consulta, luego Connection closed.
En AWS, registra Connected: true
, luego los resultados de la consulta, y eso es todo. En la segunda ejecución, registra la Connection closed.
y nada más. En la tercera y todas las ejecuciones posteriores, no registra nada en absoluto. Ninguno de los entornos informa de errores.
Parece bastante claro que el problema está en la conexión con Redis. Si no lo cierro en las devoluciones de llamada, los intentos subsiguientes de llamar a la función solo expiran. También he intentado usar redis.unref
lugar de redis.quit
, pero eso no parece hacer ninguna diferencia.
Cualquier ayuda sería muy apreciada.