AWS Lambdaとgolang + nodejs + nginxを使用して画像のサムネイルを生成する別のオプション

こんにちは、Habrのユーザーの皆様!



私の名前はニキータです。現在、モバイルアプリケーションのスタートアップでバックエンド開発者として働いています。 最後に、私は本当に自明ではない非常に興味深いタスクを手に入れました。その解決策をあなたと共有したいと思います。



会話の実際の内容は? 開発されたモバイルアプリケーションには、画像の処理があります。 簡単に推測できるように、写真がある場所にはプレビューが表示される可能性があります。 もう1つの条件は、私にとって設定されたほぼ最初の一般的なタスクです。すべてをAmazonのクラウドで機能させ、拡張することです。 少し叙情的な場合:ハンズフリーモードでビジネスパートナーの友人と電話で会話しました。そこでは、サーバーシンキングから逃げるという主なアイデアが簡単に聞こえる貴重な指示をたくさん受け取りました。 さてさて、出発します。



画像の生成は、リソースの点でかなり高価な操作です。 このバックエンドのセクションは、このような種類の「負荷テスト」については予想通りに不十分でした。実際にデフォルトのLAMP設定を使用して非常に死んだVDS-keで、少なくとも追加のチューニングなしで、最適化されていない場所がすべてすぐに出て保証されます。 このため、PHPバックエンドからこのタスクを削除することにしました。 多かれ少なかれ均一な負荷を与えるもの、つまりデータベースクエリ、アプリケーションロジック、JSON応答など、興味のないAPIルーチンを彼にやらせてください。 アマゾンに詳しい人は言うだろう:問題は何ですか? 自動モードでEC2インスタンスのスケーリングを設定し、このタスクをPHPに残すことができないのはなぜですか? 答えは「そうマイクロサービス」です。 しかし、真剣に-バックエンドアーキテクチャのコンテキストには、この記事の範囲を超える多くのニュアンスがあるため、この質問には答えません。 それが発生した場合、誰もが自分のアーキテクチャのコンテキストでそれに答えます。 私は解決策を提供したいだけで、猫を歓迎します。



入門:画像は条件付きのs3 bucket.mydomainに保存されます。以降、すべての場所をバケットと呼びます。 バケットのコンテンツは静的でパブリックと見なされますが、リストは禁止されているため、各オブジェクトにはパブリック読み取りACLがあり、バケット自体は非パブリック読み取りですが、バケット内のファイル構造はfolder / subfolder / filename.extのように見えます。



この記事では、バックエンドのファイル操作のアーキテクチャを完全には説明していません。ここでは、写真のプレビューを使用した例を使用して、一部(簡略化)のみを説明します。 ファイルシステムを使用したバックエンドの残りの作業は、この記事の範囲外です。



適切なサイズの画像が事前に生成され、ファイルシステムから単純に転送される場合、私は決定の支持者です。 透かしを動的に適用した経験がありましたが(つまり、画像は常に新しい方法で生成されました)、非常に良い結果を示しました(判明したよりも多くの負荷が予想されました)。 動的にそれらを動的に作成することを恐れないでください、このアプローチでは私には生命に対する権利もありますが、一般的には、あるイベントのためにプレビューが1回生成され、ファイルシステムがある場合はそれが与えられる場合が最適だと思いますそうでない場合は、再生成が試行されます。 これにより、かなり優れた制御性が得られ、プレビューサイズの要件が突然変更された場合に役立ちます。 このアプローチは現在のタスクで実装されました。 ただし、重要な点が1つあります。uri-schemeで(おそらく自分自身と)同意する必要があります。 私の場合(もう一度、簡単に言うと)、次のようになります。





新しい単語プリセットが登場しました 、それは何ですか? 実装の過程で、私は考えました、そしてあなたが幅/高さのために2番目のuriセグメントを解析するなら、それはあなた自身のために穴を掘ることができることがわかります。 そして、賢明な人が2番目のuriセグメントの値を1から9000以上にしたい場合はどうなりますか? これについて、私は開発プロセスの他の参加者と、どのサイズのプレビューが必要かというトピックについて同意しました。 名前がuriの2番目のセグメントとして渡されるサイズの異なるいくつかの「プリセット」が判明しました。 繰り返しますが、管理性の問題に戻ります。何らかの理由でプレビューのサイズを変更する必要がある場合は、prewmanagerで環境変数を修正するだけで十分です。これについては後で説明し、ファイルシステムから無関係なファイルを削除します。



一般的に、操作スキームは図のようになります。



画像



ここで何が起こっていますか:

リクエスト1で、which / photo / nginxがリクエストをs3にプロキシします。 原則として、ファイル2とプレビューは1つのバケットに保存されるため、リクエスト2でも同じことを行います。 AWSの公式ドキュメントに従って、バケット内のオブジェクトの数は無制限です。 しかし、図に示されている場合、1つの違いがあります。 彼は、s3からの403/404応答の処理方法の変更に取り組んでいます。 ところで、403の答えについて。 問題は、資格情報なしでリポジトリにアクセスすると(私の場合)、つまり 実際、公開読み取りオブジェクトにのみアクセスできるため、リスティング権がないため(Amazonは404の代わりに403を付与します。これにより、configのエントリが決定されます。error_page 403 404 = 404 /404.jpg; :



location / { set $s3_bucket 'bucket.s3.amazonaws.com'; set $req_proxy_str $s3_bucket$1; error_page 403 404 =404 /404.jpg; if ($request_uri ~* /prew/(.*)){ error_page 403 404 = @prewmanager; } proxy_http_version 1.1; proxy_set_header Authorization ''; proxy_set_header Host $s3_bucket; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_hide_header x-amz-id-2; proxy_hide_header x-amz-request-id; proxy_hide_header Set-Cookie; proxy_ignore_headers "Set-Cookie"; proxy_buffering off; proxy_intercept_errors on; proxy_pass http://$req_proxy_str; } location /404.jpg { root /var/www/error/; internal; } location @prewmanager { proxy_pass http://prewnamager_host:8180; proxy_redirect http://prewnamager_host:8180 /; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; access_log off ; }
      
      





お気づきかもしれませんが、prewmanagerは何らかのネットワークサービスにプロキシされています。 ここに、この記事のすべての塩があります。 nodejsで記述されたこのサービスは、goで記述されたaws lambdaを実行し、ラムダ関数が完了するまで処理されたuriへの呼び出しを「ブロック」し、待機中の全員にaws lambdaの結果を提供します。 残念ながら、prewmanagerのコード全体を提供することはできません。そのため、スクリプトの最初の完全に機能するバージョンを別々のセクションで説明します(すみません)。 制作にはもっと美しいバージョンがありますが、悲しいかな。 しかし、それにもかかわらず、私の意見では、このコードは「作業の論理を理解する」のに非常に適しており、スケッチとして使用することは可能です。



 //   requre, process.env.*  .. const lambda = new AWS.Lambda({...}); const rc = redis.createClient(...); const getAsync = promisify(rc.get).bind(rc); function make404Response(response) { //          404  --   } function makeErrorResponse(response) { //       } // AWS Lambda   base64    content-type function makeResultResponse(response, response_payload) { let buff = new Buffer(response_payload.data, 'base64'); response.statusCode = 200; response.setHeader('Content-Type', response_payload.content_type); response.end(buff); return; } http.createServer(async function(request, response) { //    uri,       .. //  redis,    (null)     AWS lambda //               //    --     let reply = false; try { reply = await getAsync(redis_key); } catch (err) { } if(reply === null) { //    30  rc.set(redis_key, 'blocked', 'EX', 30); //      // ,     -- 404 switch (preset) { case "preset_name_1": var request_payload = { src_key: "photo/" + aws_ob_key, src_bucket: src_bucket, dst_bucket: dst_bucket, root_folder: dst_root, preset_name: preset, rewrite_part: "photo", width: 1440 }; var params = { FunctionName: "my_lambda_function_name", InvocationType: "RequestResponse", LogType: "Tail", Payload: JSON.stringify(request_payload), }; lambda.invoke(params, function(err, data) { if (err) { makeErrorResponse(response); } else { rc.set(redis_key, data.Payload, 'EX', 30); let response_payload = JSON.parse(data.Payload); if(response_payload.status == true) { makeResultResponse(response, response_payload); } else { console.log(response_payload.error); makeErrorResponse(response); } } }); break; ... default: make404Response(response); } } else if (reply === false) { //      makeErrorResponse(response); } else { //      2  //     -- blocked //    , ..   if(reply == 'blocked') { let res; let i = 0; const intervalId = setInterval(async function() { try { res = await getAsync(redis_key); } catch (err) { } if (res != null && res != 'blocked') { let response_payload = JSON.parse(res); if(response_payload.status == true) { makeResultResponse(response, response_payload); } else { console.log(response_payload.error); makeErrorResponse(response); } clearInterval(intervalId); } else { i++; //      if(i > 100) { makeErrorResponse(response); clearInterval(intervalId); } } }, 500); } } }).listen(port);
      
      





大根はどこから来たのですか? このタスクでは、私はこのように推論しました:私たちは大根を持つインスタンスが一方では好きなだけスケーリングできるクラウドにあり、他方では同じパラメータを持つ関数への繰り返しの呼び出しをブロックすることについての質問が発生したときすでにプロジェクトで使用されていますか? 地元で心に留め、nakoleochny「ガベージコレクター」を書く? なぜ、いつこのデータ(または大根のロックフラグ)を特定の存続期間で配置できるのか、そしてこのすばらしいツールがすべてを処理してくれるのです。 まあ、それは論理的です。



最後に、Goで記述されたAWS Lambdaの関数コード全体を提供します。 「ハローワールド」に続く3番目のビナリウムであり、そこに私が書いて編集した小さなものがあるので、キックしないようにお願いします。 投稿されているgithubへのリンクを次に示します。何か問題がある場合は、プルリクエストをお願いします。 しかし、一般に、すべてが機能しますが、彼らが言うように、完璧に制限はありません。 関数が機能するには、JSONペイロードが必要です。リクエストを受信した場合、githubで関数をテストする方法、JSONペイロードの例などを追加します。



AWS Lambdaの設定に関するいくつかの言葉:すべてが簡単です。 関数を作成し、環境を登録し、最大時間とメモリを割り当てます。 アーカイブを注ぎ、使用します。 しかし、この記事の範囲を超えたニュアンスがあります:IAMは彼の名前です。 ユーザー、ロール、権利も設定する必要がありますが、これがなければ何も機能しないのではないかと心配しています。



結論として、このシステムは既に実稼働環境でテストされていると言いたいのですが、高負荷の負荷は自慢できませんが、一般に問題はありませんでした。 現在の政治状況の文脈では、はい、私たちはアマゾンのブロックに陥った最初の人の1人でした。 文字通り、初日に。 しかし、彼らは騒ぎを起こさず、弁護士の仕事の邪魔をしませんでしたが、ロシアのホスティングにnginxを設置しました。 一般に、Amazon s3は非常に便利で、十分に文書化され、維持されているリポジトリであり、ミームやその他の外科医以外の外科医のはげたブラザーズアドバイザーのため、少なくとも拒否しないでください。 そして、サブドメインのすべての統計情報が配置されているため、上記のnginxの構成は次のようになります。変更が最小限の行のほとんどがロシア連邦のサーバーに転送され、1日の作業中に誰もが忘れていました。



ご清聴ありがとうございました。



All Articles