Подтвердить что ты не робот

Как подключиться к экземпляру Redis из функции AWS Lambda?

Я пытаюсь создать API для одностраничного веб-приложения, используя AWS Lambda и Сервер без сервера. Я хочу использовать 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, или веб-приложение). Это верно независимо от того, используется ли тег, который я использую для тестирования, в базе данных или нет. Если я затем повторно развернусь, не внося никаких изменений в функцию, она снова будет работать один раз.

На моем локальном компьютере функция сначала регистрирует Connected: true на консоли, затем результаты запроса, затем Connection closed. В AWS он регистрирует Connected: true, затем результаты запроса и его. Во втором запуске он записывает Connection closed. и ничего больше. На третьем и последующих последующих сеансах он ничего не записывает. Ни одна среда не сообщает о каких-либо ошибках.

Кажется довольно очевидным, что проблема связана с подключением к Redis. Если я не закрываю его в обратных вызовах, то последующие попытки вызвать функцию просто тайм-аут. Я также пытался использовать redis.unref вместо redis.quit, но это, похоже, не имело никакого значения.

Любая помощь будет принята с благодарностью.

4b9b3361

Ответ 1

Теперь я решил свою проблему, и надеюсь, что я смогу помочь кому-то, испытывающему эту проблему в будущем.

При подключении к базе данных, как это было в приведенном выше коде, с помощью функции лямбда существует два основных момента:

  • Как только context.succeed(), context.fail() или context.done() вызывается, AWS может заморозить все процессы, которые еще не закончены. Это то, что заставляло AWS регистрировать Connection closed во втором вызове моей конечной точки API - процесс был заморожен как раз до того, как Redis завершил закрытие, затем оттаивал следующий вызов, после чего он продолжал идти туда, где он остановился, сообщая, что соединение было закрыто. Takeaway: если вы хотите закрыть соединение с базой данных, убедитесь, что он полностью закрыт, прежде чем вы вызываете один из этих методов. Вы можете сделать это, поместив обратный вызов в обработчик события, вызванный закрытием соединения (.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
                  })
                }
              })
            }
          })
        }
      }
    })
  })
}

Это работает точно так же, как и должно быть - и оно быстро пылает.