web-dev-qa-db-ja.com

AWS Lambda関数からRedisインスタンスに接続するにはどうすればよいですか?

AWS Lambda および Serverless Framework を使用して、単一ページのWebアプリ用のAPIを構築しようとしています。ストレージに Redis Cloud を使用したいのですが、主に速度とデータの永続性の組み合わせに使用します。将来的にはより多くのRedis Cloud機能を使用する可能性があるため、このためにElastiCacheの使用を避けたいと思います。私のRedis Cloudインスタンスは、私の機能と同じAWSリージョンで実行されています。

GETリクエストからAPIエンドポイントへのハッシュタグを取得し、データベースにエントリがあるかどうかを確認するrelatedという関数があります。存在する場合は、すぐに結果が返されます。そうでない場合は、 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を使用して)デプロイすると、デプロイ後に初めて動作し、動作しなくなります。それを実行しようとするそれ以降のすべての試みは、ブラウザに(またはPostman、curl、またはWebアプリに)nullを返すだけです。これは、テストに使用するタグが既にデータベースにあるかどうかに関係なく当てはまります。その後、機能自体に変更を加えずに再デプロイすると、再び機能します。

私のローカルマシンでは、関数はまずConnected: trueをコンソールに記録し、次にクエリの結果を記録し、次にConnection closed. AWSでConnected: trueを記録し、次にクエリの結果を記録します。それでおしまい。 2回目の実行では、Connection closed.のみをログに記録します。 3回目以降のすべての実行では、何も記録されません。どちらの環境もエラーを報告しません。

問題はRedisへの接続にあることは明らかです。コールバックで閉じない場合、その後の関数呼び出しの試行はタイムアウトになります。 redis.unrefの代わりにredis.quitを使用してみましたが、それは何の違いももたらさないようです。

どんな助けも大歓迎です。

16
Nicholas

私は今、自分の問題を解決しました。将来、この問題を経験している人の助けになることを願っています。

上記のコードでLambda関数から行ったように、データベースに接続するときの2つの主要な考慮事項があります。

  1. context.succeed()context.fail()、またはcontext.done()が呼び出されると、AWSはまだ終了していないプロセスをフリーズする場合があります。これが、AWSがAPIエンドポイントへの2回目の呼び出しで_Connection closed_をログに記録した原因です。Redisが終了する直前にプロセスがフリーズし、次の呼び出しで解凍されました。接続が閉じられたことを報告します。テイクアウト:データベース接続を閉じたい場合は、完全に閉じていることを確認してくださいbeforeこれらのメソッドの1つを呼び出します。これを行うには、接続のクローズ(私の場合は.on('end'))によってトリガーされるイベントハンドラーにコールバックを配置します。
  2. コードを個別のファイルに分割し、各ファイルの先頭で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
                  })
                }
              })
            }
          })
        }
      }
    })
  })
}
_

これは本来どおりに機能します。また、非常に高速です。

23
Nicholas