Category: Amazon ElastiCache*


Amazon ElastiCache for Redis を使ったChatアプリの開発

Sam Dengler は、アマゾン ウェブ サービスのソリューションアーキテクトです。

このブログ記事では、チャットアプリケーションに関連する概念とアーキテクチャのパターンについて説明します。また、チャットクライアントとサーバーの実装の詳細、サンプルのチャットアプリケーションを AWS アカウントに展開する方法についても説明します。

背景情報

チャットアプリケーションを構築するには、クライアントがチャットルームの他の参加者に再配信されるメッセージを送信できる通信チャネルが必要となります。この通信は、一般に publish-subscribe パターン (PubSub) を使用して実装されます。このパターンでは、メッセージが中央トピックチャネルに送信されます。関係者は、このチャンネルをサブスクライブして更新の通知を受けることができます。このパターンでは、発行者の知識なしに受信者のグループを拡大または縮小できるように、発行者と受信者を切り離しています。

PubSubは、クライアントが WebSockets を使用して通信するバックエンドサーバーに実装されます。WebSockets は、クライアントとサーバー間で双方向にストリーミングされるデータのチャネルを提供する永続的な TCP 接続です。単一サーバアーキテクチャでは、1 つの PubSub アプリケーションが発行者と受信者の状態を管理し、WebSocket を介してクライアントにメッセージを再配布することもできます。次の図は、単一サーバー PubSub アーキテクチャ上の 2 つのクライアント間でメッセージが WebSocket を通過するパスを示しています。

単一サーバーアーキテクチャは、通信フローを説明するのに役立ちます。しかし、ほとんどのソリューションビルダーはマルチサーバーアーキテクチャで設計したいと考えています。マルチサーバーアーキテクチャは、信頼性を高め、伸縮性を高め、クライアントの数が増えるにつれてアプリケーションを水平的に拡大するのに役立ちます。

マルチサーバーアーキテクチャでは、クライアントはサーバープールにトラフィックを転送するロードバランサーに対して WebSocket 接続を行います。これらのサーバーは、WebSocket 接続とそれを経由してストリーミングされるデータを管理します。WebSocket 接続が PubSub アプリケーションサーバーとの間で確立されると、その接続は永続化され、データは両方向のアプリケーションにストリームされます。ロードバランサーは、WebSocket 接続のリクエストを健全なサーバーに配信します。つまり、2 つのクライアントが異なるアプリケーションサーバーに WebSocket 接続を確立できます。

 

複数のアプリケーションがクライアントの WebSocket 接続を管理するため、アプリケーションはメッセージを再配布するためにそれらの間で通信する必要があります。この通信が必要なのは、メッセージが WebSocket を介して 1 つのアプリケーションサーバーにストリームアップされ、別のアプリケーションサーバーに接続されたクライアントにストリームダウンされる必要があるためです。クライアント接続を管理しているアプリケーションから PubSub ソリューションを外部に出すことで、アプリケーション間の共有通信の要件を満たすことができます。

 

次の図は、マルチサーバー PubSub アーキテクチャ上の 2 つのクライアント間でメッセージが WebSocket を通過するパスを示しています。永続的な接続は、各クライアントと WebSocket サーバー間のロードバランサーを通じて確立されます。また、永続的な接続は、WebSocket サーバーと PubSub サーバー間で、すべてのクライアント間で共有されるサブスクリプショントピックごとに確立されます。

 

 

カスタムの PubSub ソリューションも可能ですが、既存のソフトウェアアプリケーションを使用してこの機能を提供することもできます。Redis は、高速なオープンソースのインメモリ型データストアおよびキャッシュで、PubSub をサポートしています。Amazon ElastiCache for Redis は、Redis 対応のインメモリサービスです。使いやすく、Redis の性能を利用でき、もっとも要求の厳しいアプリケーションに対応できる可用性、信頼性、パフォーマンスを提供します。

Using ElastiCache for Redis and the WebSocket support found in the that is part of Elastic Load Balancing の一部である Application Load Balancer にある ElastiCache for Redis と WebSocket サポートを使用して、サンプルのチャットアプリケーションを構築する方法を説明します。アプリケーションには、Node.js と AWS Elastic Beanstalk に基づくバックエンドと、Vue.js ウェブクライアントがあります。サンプルアプリケーションのすべてのコードは、すべて elasticache-redis-chatapp GitHub リポジトリにあります。

アーキテクチャ

次の図は、Redis、Application Load Balancer、Node.js Elastic Beanstalk アプリケーション、および Vue.js ウェブクライアント用の ElastiCache を使用した AWS の最終的なアーキテクチャを示しています。

チャットアプリケーションの実装を高いレベルで見てみましょう。

実装の概要

サンプルチャットアプリケーションは、以下のスクリーンショットに示す共有チャットルームで通信するメンバーとメッセージで構成されています。

このサンプルアプリケーションでは、メンバー登録、プロフィール管理、ログインを拒否しています。代わりに、ブラウザでチャットアプリケーションを開くと、ユーザーの代わりにランダムなユーザー名とアバターでメンバーが生成されます。この名前とアバターは、左側のメンバーリストに表示されます。他のメンバーがブラウザでアプリケーションを開いて参加したり離したりすると、その名前がウェブアプリケーションに表示されます。メンバーは、他のウェブクライアントに再配信されメインのチャットウィンドウに表示されるメッセージを送信できます。

次に、Vue.js ウェブクライアントの詳細を調べ、Node.js バックエンドアプリケーションを確認します。

Vue.js ウェブクライアント

ウェブクライアントは、ビューレイヤを管理する Vue.js、UI 用の Bootstrap、および WebSocket 通信用の Socket.io を使用して実装されています。初心者向けに複雑さを軽減するために、JavaScript バンドルは使用していません。ただし、本稼働アプリケーションでは webpack または類似のソフトウェアを検討する必要があります。Vue.js は、基礎となるデータモデルの更新に基づいて UI の変更をレンダリングします。これらのフレームワークとライブラリを、最新の単一ページの代表的なウェブアプリケーションとして選択しました。ただし、コミュニティには多くの類似した選択肢があり、毎日多くのものが出現しています。

次に、Vue.js アプリケーションコンポーネントを設定する HTML マークアップのコードスニペットと、メンバーを表示するイテレーターを示します。機能に焦点を当てるために、いくつかの中間的なマークアップと CSS スタイルを削除しています。。完全なサンプルは GitHub リポジトリにあります。

<html>
<body>
    <div id=”app”>
        <li v-for="(value, key) in members">
            <img v-bind:src="value.avatar">
            <small>{{ value.username }}</small>
        </li>
    </div>

v-for パラメータは、イテレーターを定義するために使用されます。この場合は、この後で説明するメンバーオブジェクトデータモデルのキー値タプルです。反復されるループ内で Mustache テンプレートを使用して、各メンバーオブジェクトにアクセスし、ユーザー名を表示します。Mustache テンプレートは HTML 属性内では機能しないため、メンバーのアバター画像 URL を解決するためには v-bind 引数を使用する必要があります。

Vue.js は、スマート DOM の差の計算に基づいて UI の変更を最小限にレンダリングします。このアプローチにより、基になる Vue.js データモデルの状態変更に専念できます。サンプルアプリケーションでは、HTML 内で JavaScript コードをインライン展開しています。しかし、本番稼働用システムでは、外部の .vue ファイルを使用して UI コンポーネントをモジュール化し、ビルド時に webpack を使用して変換する可能性があります。Socket.io ライブラリも初期化されており、多少カバーされています。

<script src="js/vue/2.1.10/vue.min.js"></script>
<script src="js/socket.io/1.7.2/socket.io.min.js"></script>
<script>
    var socket = io();

    new Vue({
        el: '#app',
        data: {
            message: '',
            messages: [],
            members: {}
        }

ここでは、Vue.js アプリケーションを宣言し、アプリケーション ID で HTML div 要素にバインドしました。また、次の 3 つのデータモデルを宣言しました。

  • message: フォームに入力されたメッセージテキスト
  • messages: メッセージのリスト。メッセージを追加するだけなので、配列が使用されます。
  • messages: メンバーのリスト。メンバーがチャットルームを離れたときにメンバーを見つけて削除できるように、オブジェクトが使用されています。

マークアップおよび Vue.js アプリケーション宣言に加えて、ウェブクライアントは WebSocket 接続を確立し、サブスクライブするトピックを宣言し、それらのトピックに発行されたメッセージが前に宣言されたデータモデルをどのように変更するかを確立します。このアプリケーションでは、コミュニケーションのための 5 つのトピックを確立します。それぞれのトピックが、トリガーイベントと対応するアクションとともに示されます。

[メッセージ]

トリガー: メッセージがチャットルームに送信される。

アクション: メッセージテキストおよびメンバーメタデータを使用して、メッセージのリストを更新します。

[member_add]

トリガー: メンバーがチャットルームに参加する。

アクション: メンバーのユーザー名とパスワードをメンバーのリストに追加します。

[member_delete]

トリガー: メンバーがチャットルームを離れる。

アクション: メンバーの一覧からメンバーを削除します。

[message_history]

トリガー: クライアントがメッセージのリストを初期化。

アクション: メッセージのリストを、最近の履歴メッセージの切り詰められたリストとして設定します。

[member_history]

トリガー: クライアントがメンバーのリストを初期化。

アクション: メンバーリストをチャットルームに参加しているメンバーのリストとして設定します。

 

以下に、これらのメソッドを実装するための JavaScript コードを示します。以前の Vue.js コードをリファレンスポイントとして維持しています。

new Vue({
    el: '#app',
    data: {
        message: '',
        messages: [],
        members: {}
    },
    methods: {
        send: function() {
            socket.emit('send', this.message);
            this.message = '';
        },
    mounted: function() {
        socket.on('messages', function(message) {
            this.messages.push(message);
        }.bind(this));

        socket.on('member_add', function(member) {
            Vue.set(this.members, member.socket, member);
        }.bind(this));

        socket.on('member_delete', function(socket_id) {
            Vue.delete(this.members, socket_id);
        }.bind(this));

        socket.on('message_history', function(messages) {
            this.messages = messages;
        }.bind(this));

        socket.on('member_history', function(members) {
                    this.members = members;
        }.bind(this));
    }

以前のコードスニペットで宣言された Socket.io オブジェクトは、前述のトピックごとに 1 つずつ、socket.on を使用してデータトピックにサブスクライブします。メッセージがトピックに発行されると、コールバック関数が実行されます。データモデルは、アクション (セット、追加、削除) とターゲットデータモデル (配列、オブジェクト) に従って更新されます。バインド (this) 文が追加され、Vue.js データモデルがコールバック関数スコープに挿入されます (詳細については、Function.prototype.bind を参照してください)。

最後は、メッセージフォームの送信を処理する Vue.jsメソッドです。Vue.js は、フォーム提出をメソッドにバインドする便利なメソッドを提供します。このメソッドは、WebSocket 上にメッセージテキストを発行し、メッセージを空の文字列に設定します。この文字列は、Vue.js バインディングを使用して UI を更新します。

Node.js バックエンドアプリケーション

ここでは、ウェブクライアントの基本について説明しました。次に、Node.js バックエンドアプリケーションについて見ていきましょう。PubSub を使ってデータを保持し、WebSocket メッセージを再配布するために Redis がどのように使用されるかを見ていきます。

Redis と WebSockets の設定

ウェブクライアントがブラウザで開かれると、PubSub アプリケーションで WebSocket が確立されます。接続すると、アプリケーションは既存のメンバーとメッセージを新しいクライアントに発行するために、いくつかのデータをアセンブルする必要があります。また、新しいチャットルーム参加者について他のクライアントを更新する必要があります。次に、HTTP アプリケーションと WebSocket の宣言を示すコードスニペットを示します。

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;

WebSocket リスナーの作成に加えて、アプリケーションは Redis クラスタへの複数の接続も確立する必要があります。 Redis データモデルを更新してトピックにメッセージを発行するには、1 つの接続が必要です。 トピックのサブスクリプションごとに追加の接続が必要になります。

var Redis = require('ioredis');
var redis_address = process.env.REDIS_ADDRESS || 'redis://127.0.0.1:6379';

var redis = new Redis(redis_address);
var redis_subscribers = {};

function add_redis_subscriber(subscriber_key) {
    var client = new Redis(redis_address);

    client.subscribe(subscriber_key);
    client.on('message', function(channel, message) {
        io.emit(subscriber_key, JSON.parse(message));
    });

    redis_subscribers[subscriber_key] = client;
}
add_redis_subscriber('messages');
add_redis_subscriber('member_add');
add_redis_subscriber('member_delete');

このコードスニペットでは、
Redis コマンドチャネルは ioredis JavaScript クライアントを使用して確立されています。
また関数は、新しいトピックのチャンネルを初期化するためにも定義され、
チャンルのトピックについてキー入力されたすべての受信者のハッシュに追加されます。
各サブスクリプションチャネルは同じように機能します。

  • Redis サブスクリプションチャンネルで JSON 文字列メッセージを受信します。
  • JSON 文字列を JavaScript オブジェクトに解析します。
  • Redis PubSub と同じトピックを使用して、JavaScript オブジェクトを WebSocket 接続に発行します。

この後で説明するように、JavaScript オブジェクトは、Redis データ型の値として保存され、PubSub トピックに発行されるときに JSON 文字列にシリアル化されることが重要です。WebSocket を介して発行する前に、JSON 文字列を JavaScript オブジェクトに逆シリアル化して戻す必要があります。Socket.io ライブラリがクライアントと通信するときには、オブジェクトをシリアル化したり逆シリアル化したりするため、この逆シリアル化が生じる必要があります。

ウェブクライアントがブラウザのチャットルームに参加すると、クライアントは WebSocket への新しい接続を確立します。次のような関数を定義することによって、この接続が確立されたときにアクションを実行できます。

io.on('connection', function(socket) {

... application business logic ... 

}

ソケットは、クライアントへの各 WebSocket 接続を識別するために使用される ID プロパティを含むオブジェクトです。socket.id 値を使用して、メンバーを識別します。この識別により、Redis データモデルからメンバーを見つけて削除することができます。また、member_delete トピックを使用しているすべてのチャットルームクライアントにメンバーの削除を伝えることもできます。以下で説明する他の関数は、このコールバック関数のコンテキストにあります。

次のセクションでは、新しいクライアントが WebSocket を介して Node.js バックアップアプリケーションに接続すると何が起こるかを見ていきます。

新しいクライアント接続の初期化

新しいクライアントがチャットルームに参加すると、いくつかのことが起こります。

現在のメンバーリストが取得されます。
これがクライアントの再接続でない限り、ランダムなユーザー名とアバター URL で新しいメンバーが作成され、Redis Hash に保存されます。
最近の履歴メッセージの切り詰められたリストが検索されます。
これらのタスクを達成するためのコードを見てみましょう。まず、次のコードがあります。

var get_members = redis.hgetall('members').then(function(redis_members) {
    var members = {};
    for (var key in redis_members) {
        members[key] = JSON.parse(redis_members[key]);
    }
    return members;
});

ioredis JavaScript クライアントは、非同期実行処理で Promises を使用します。HGETALL (‘members’) 呼び出しは、キー ‘members’に格納されているハッシュのすべてのキーと値を返します。Redis はハッシュデータ型をサポートしていますが、1 レベルの深さしかありません。ハッシュの値は文字列でなければなりません。コールバック関数は、次のチェーンで逆シリアル化されたハッシュのキーと値のペアを反復して、メンバーを初期化します。

var initialize_member = get_members.then(function(members) {
    if (members[socket.id]) {
        return members[socket.id];
    }

    var username = faker.fake("{{name.firstName}} {{name.lastName}}");
    var member = {
        socket: socket.id,
        username: username,
        avatar: "//api.adorable.io/avatars/30/" + username + '.png'
    };
    
    return redis.hset('members', socket.id, JSON.stringify(member)).then(function() {
        return member;
    });
});

initialize_member Promise 関数は、メンバーが再接続ソケットであるかどうかをまずチェックします。再接続ソケットでない場合は、Faker を使用してランダムなユーザー名で新しいメンバーが生成されますこのユーザー名から、Adorable Avatars サービスを使用してランダムなアバター URL が生成されます。

クライアント初期化の最後のステップは、過去の最近のメッセージの切り捨てられたリストを取得することです。これを行うには、ソート対象セットと呼ばれる別の Redis データ型を利用することができます。このタイプは Redis セットに似ていますが、セット内の各要素のランクを含みます。ソート対象セットは、リーダーボードの一般的なデータ型です。タイムスタンプがランクとして使用されている場合は、時間順に並べられた要素のコレクションを格納するためにも使用できます。

var get_messages = redis.zrange('messages', -1 * channel_history_max, -1).then(function(result) {
    return result.map(function(x) {
       return JSON.parse(x);
    });
});

We use a Redis method on the Sorted Set, called ZRANGE というソート対象セットでは、ランクに基づいて要素のリストを返す Redis メソッドを使用します。要素は最低スコアから最高スコアまで並べられます。したがって、初期化時に取得するメッセージの最大数 (-1 * channel_history_max) まで最後の要素 (-1) を取得する必要があります。繰り返しになりますが、各要素は JSON 文字列としてシリアル化されていますので、要素を JavaScript オブジェクトに対して逆シリアル化する必要があります。

要約すると、新しいクライアントがチャットルームに参加すると、いくつかのことが起こります。

  1. 現在のメンバーリストが取得されます。
  2. これがクライアントの再接続でない限り、ランダムなユーザー名とアバター URL で新しいメンバーが作成され、Redis Hash に保存されます。
  3. 最近の履歴メッセージの切り詰められたリストが検索されます。

これらの手順をそれぞれを確認しました。ここで、初期化とクライアントへのストリームデータの完了方法を見ていきましょう。ioredis は Promise を使用するため、非同期の実行を連鎖させて、すべてが完了するのを待ってから Promise.all を使用して結果を処理できます。

Promise.all([get_members, initialize_member, get_messages])
    .then(function(values) {
        var members = values[0];
        var member = values[1];
        var messages = values[2];

...

)};

これですべての必要なデータが得られたので、WebSocket 接続を使用してデータをストリーミングして新しいクライアントを初期化し、新しいメンバーがチャットルームに参加したことをすべてのメンバーに伝える必要があります。

io.emit('member_history', members);
io.emit('message_history', messages);
redis.publish('member_add', JSON.stringify(member));

Socket.io の emit メソッドを使用して、初期化中のクライアントにメッセージとメンバーのリストをストリーミングします。1 つの WebSocket を使用して複数のメッセージを送信できます。ここでは、トピック (member_history, message_history) は先ほどのクライアントコードでレビューしたトピックリスナーに対応しています。新しいメンバーはすべての参加者に伝えなければなりません。これを行うには、Redis コマンドチャネルを使用して、シリアル化された JSON 文字列を member_add トピックに発行します。すでに行っているように、WebSocket を使用して同じトピックをリッスンしているクライアントにメッセージを再配布するため、3 つの Redis トピックを設定しました。

次のセクションでは、チャットルームで送信されたメッセージを処理するハンドラを設定する方法を見ていきましょう。

メッセージの処理

新しいクライアントが初期 WebSocket 接続を完了すると、新しいクライアントによって送信されるメッセージ用のメッセージハンドラも定義する必要があります。メッセージは、メッセージテキスト、メンバーのユーザー名、メンバーのアバター、メッセージの作成タイムスタンプで構成されます。

socket.on('send', function(message_text) {
    var date = moment.now();
    var message = JSON.stringify({
        date: date,
        username: member['username'],
        avatar: member['avatar'],
        message: message_text
    });

    redis.zadd('messages', date, message);
    redis.publish('messages', message);
});

ZADD コマンドとメッセージ作成タイムスタンプをランクとして使用して、ソート対象セットに格納されたメッセージ履歴にメッセージを追加します。最後に、Redis コマンドチャネルを使用してメッセージトピックを発行しました。Redis/WebSockets の再配布は以前に定義しています。

クライアントを初期化し、チャットルームで送信されたメッセージを処理する方法について説明しました。最後に、クライアントがチャットルームを離れるときの対処方法を見てみましょう。

切断処理

Socket.io は、クライアントがサーバーに接続するときに確立された WebSocket のハートビートを作成します。ハートビートが失敗すると、クライアントで切断イベントが発生します。

socket.on('disconnect', function() {
    redis.hdel('members', socket.id);
    redis.publish('member_delete', JSON.stringify(socket.id));
});

クライアントが切断すると、メンバーの初期化中に実行されたアクションが取り消されます。まず、Redis HDEL メソッドを使用して、クライアントの WebSocket ソケット ID を使用するメンバーハッシュ Redis データ型からクライアントを削除します。同じメソッドをクライアントをハッシュに追加するのに使用します。すべての参加者に対してチャットルームに加わった新しいメンバーを通知するように、メンバーがチャットルームを離れることをすべての参加者に通知する必要があります。これは、member_delete Redis トピックを使用して行います。このトピックは、WebSocket を使用して残りのクライアントに再配布されます。

これでコードの確認は完了です。次に、AWS CloudFormation を使用してアプリケーションスタックを AWS にデプロイする方法を確認します

AWS CloudFormation を使用したアプリケーションスタックのデプロイ

CloudFormation は、開発者やシステム管理者は、関連する AWS リソースのコレクションを簡単に作成および管理する方法を提供します。CloudFormation は、整った予測可能な方法でリソースを提供し、更新します。チャットアプリケーションの CloudFormation スタックを起動するには、次のボタンをクリックします。

CloudFormation スクリプトは、Elastic Beanstalk 環境、アプリケーション、および構成テンプレートを作成します。Redis の ElastiCache クラスタと、ロードバランサ、アプリケーションサーバー、Redis クラスタの Amazon EC2 セキュリティグループも作成します。このようにして、アーキテクチャレイヤ間の最小権限セキュリティ構成にベストプラクティスを使用します。

ElisiCache for Redis の設定スニペットに関する 1 つの注意点AWS::EC2::SecurityGroup では進入セキュリティルールのインライン指定が可能ですが、そうすることで、CacheCluster と SecurityGroup の間に循環参照が作成されてしまいます。次のスニペットに示すように、進入ルールを別の AWS::EC2::SecurityGroupIngress に分割して循環参照を破棄する必要があります。

リソース:

RedisCluster:
    Type: AWS::ElastiCache::CacheCluster
    Properties:
      CacheNodeType:
        Ref: ClusterNodeType
      VpcSecurityGroupIds:
        - !GetAtt CacheSecurityGroup.GroupId
      Engine: redis
      NumCacheNodes: 1
  CacheSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Cache security group
  CacheSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt CacheSecurityGroup.GroupId
      IpProtocol: tcp
      FromPort: !GetAtt RedisCluster.RedisEndpoint.Port
      ToPort: !GetAtt RedisCluster.RedisEndpoint.Port
      SourceSecurityGroupId: !GetAtt ApplicationSecurityGroup.GroupId

次に、WebSocket をサポートするための Elastic Beanstalk Nginx プロキシ設定の設定を変更する方法を確認してみましょう。

WebSocket サポートのための AWS Elastic Beanstalk の Nginx 設定

AWS Elastic Beanstalk は、Java、.NET、PHP、Node.js、Python、Ruby、Go および Docker を使用して開発されたウェブアプリケーションやサービスを、Apache、Nginx、Passenger、IIS など使い慣れたサーバーでデプロイおよびスケーリングするための、使いやすいサービスです。

Elastic Beanstalk は、Elastic Load Balancer (ELB) と Application Load Balancer (ALB) の両方をサポートします。弊社のクライアントとサーバーは WebSocket を使用して通信するので、WebSockets サポートのための ALB を構成しますサンプルの Node.js バックエンドアプリケーションでは、Node.js ベースの Elastic Beanstalk の事前構成済みアプリケーションスタックを選択します。アプリケーションコードの前で、ウェブ層プロキシとして Nginx を使用します。

WebSocket がサポートされていない場合、Socket.io にはポーリング戦略に戻る手段があります。ただし、単純に ALB と Nginx に設定を変更することで、WebSocket のサポートを有効にし、サーバーからクライアントへのプッシュベースのデータストリームを使用することができます。ALB で WebSocket サポートを有効にするには、クライアントが 2 つの連続した HTTP リクエストを行って接続をアップグレードして Websocket を使用したときに、同じインスタンスが応答するように、スティッキーセッションを有効化する必要があります。Nginx で WebSocket サポートを有効にするには、Elastic Beanstalk の .ebextensions メカニズムを使用して、Nginx の設定を少し変更する必要があります。コンテナコマンドは、アプリケーションアーカイブが展開された後、アクティブアプリケーションとしてインストールされる前に、アプリケーションに変更を導入する方法を提供します。

container_commands:
  enable_websockets:
    command: |
      sed -i '/\s*proxy_set_header\s*Connection/c \
              proxy_set_header Upgrade $http_upgrade;\
              proxy_set_header Connection "upgrade";\
      ' /tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf

前述のコードスニペットは、Nginx 設定ファイルを所定の位置で変更します。/tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf。これは、sed コマンドを使用して「プロキシセットヘッダー」行を検索し、WebSocket をサポートする設定に置き換えます。アプリケーションがインストールされると、Elastic Beanstalk は設定ファイルを /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf にコピーします。この手順を実行すると、Nginx サービスが再起動され変更がアクティブになります。

まとめ

このブログ記事では、publish-subscribe パターンについて見てきました。また、それを ElastiCache for Redis 内で使用して、チャットアプリケーションの複数のクライアントの双方向ストリーミング通信をサポートする方法についても説明しました。

リマインダーとして、awslabs GitHub リポジトリにこのサンプルアプリケーションの完全なソースがあります。このサンプルアプリケーションを起動したら、ユーザー認証、添付ファイル、または自分のチャットや PubSub アプリケーションに役立つその他の機能を追加して、チャットアプリケーションに独自のアイデアを組み込んで拡張することをお勧めします。

Amazon ElastiCache for Redis の リードレプリカの自動フェイルオーバのテスト

“信頼するが、確認する”

– ロナルド・レーガン米国大統領、1987

このコメントで、レーガン大統領は、核軍縮条約の哲学について、ロシアの諺を引用しました。DevOpsにも同じ考え方が当てはまります!

 

Amazon ElastiCache for Redisは、自動化されたフェイルオーバとリカバリを備え、高可用性を提供しています。ElastiCacheクラスタを作成したければ、 Redis Cluster モードを使用し、クラスタ内のシャード数を設定します。各シャードには、1つのプライマリノード(読み取りと書き込み用)と0〜5つのレプリカノード(読み取りとフェールオーバー保護用)があります。1つのクラスタは、レプリカがゼロ(1ノード)のシャードと同じくらい小さくても、5つのシャード(5つのレプリカ(合計90ノード))を持つことができます。

 

AWSでは障害が頻繁に発生することはありませんが、いずれのマシンでも障害は発生します。レプリカノードに障害が発生すると、障害が検出され、数分でノードが置き換えられます。

 

Redis Cluster では、プライマリノードに問題が起きた時に、Redis Cluster は障害を検知しレプリカノードを新しいシャードのプライマリとして昇格させます。このプロセスは大体30秒程で実施されます。問題の起きたノードは入れ替えられ、クラスタのレプリカノードとして復帰します。Redis ClusterとElstiCacheを、エンジンとして Redis 3.2.4 と Cluster モードを有効にすることによって使うことができます。

 

これを信じてください…しかし検証するべきです。

 

ElastiCacheのテストフェイルオーバのおかげで検証は簡単です。マネジメントコンソールかAWS CLIを使用し、ElasiCache クラスタのいずれかのノード障害をシミュレートする事ができます、そして、どのようなプロセスで貴方のアプリケーションが動くのかも見ることができます。マルチシャード、もしくはシングルシャードの環境でもテストは可能です。さらに古いRedis 2.8の環境でもテストは可能です。
コンソールまたはAWS CLIを使用して、ElastiCacheクラスタ内の任意のノードの障害をシミュレートし、自分のアプリケーションでフェールオーバープロセスがどのように機能するかを確認できます。マルチシャード環境とシングルシャード環境、さらに古いRedis 2.8ブランチ環境でもフェールオーバーをテストできます。コンソールでこれを行うには、クラスタとシャードを選択してノードビューを表示し、次にフェールオーバープライマリを選択します。マネジメントコンソールでテストを行う際には、クラスタとシャードを選択してNode Viewを確認し、次にFailover primaryを選択します。

 

使用には注意してください。どのようなクラスタでも同じように動きます。なぜならどのクラスタも開発環境、テスト、本番かをAWSが知るすべが無いためです。その本番のクラスタで実行しても良い事が確信していない限りは本番ノードでは障害を発生させないでください。

 

自動フェイルオーバのテストを試してみてください!

 

翻訳は桑野が担当しました。原文はこちら

発表: Amazon ElastiCache で Redis バックアップおよび復元を実現、クラスターのサイズ変更も可能に

インメモリキャッシュは、アプリケーション設計時またはソリューション構築時の大規模なパフォーマンス強化やコスト削減と同等に扱われます。ここで、サービスが 1 つのみの場合は、スケーリングする機能を強化しながら、継続的にクラウド内のインメモリキャッシュをより簡単にデプロイおよび活用できるようにします。冗談はさておき、この優れた機能を実現するクラウドサービスとは、もちろん Amazon ElastiCache です。Amazon ElastiCache は、パフォーマンスの高いインメモリデータストアまたはキャッシュをクラウドで実現する AWS マネージドサービスです。I/O 集約型または計算量の多いデータの低レイテンシー、安全性、アクセスを実現するための分散環境を作成、スケール、管理する簡単な方法を提供します。また、ElastiCache では、Amazon CloudWatch を通じて、キャッシュシステムのノードの主要なパフォーマンスメトリクスの可視性を強化すると同時に、障害が発生したノードを検出して置換することで、インメモリデータ構造サーバやキャッシュのインフラストラクチャを管理するオーバーヘッドを抑えることができます。この優れたサービスで、Redis バックアップおよび復元とクラスターのサイズ変更を実現しました。

Amazon ElastiCache に精通している方であれば、ElastiCache で次の 2 つのインメモリキー値エンジンがサポートされていることをご存じでしょう。

  • Memcached: パフォーマンスの高いオープンソースの分散メモリオブジェクトキャッシュシステム。データベースの負荷を軽減して動的なウェブアプリケーションを高速化することを当初の目的として 2003 年に開発されました。
  • Redis: オープンソースのインメモリデータ構造ストア。Redis クラスターを使用して、組み込みレプリケーション、アトミックオペレーションサポート、さまざまなレベルのオンディスクの永続性、高可用性を実現しながら、キャッシュ、メッセージング、データベースのブローカーとして開発され、2009 年に発表されました。

2016 年 10 月、Redis 3.2.4 使用の Redis クラスターがサポートされるようになり、ElastiCache Redis のユーザーは Redis クラスターを活用できるだけでなく、次のことが行えるようになりました。

  • クラスターレベルのバックアップの作成
  • バックアップ内のクラスターのシャード単位でのスナップショットの生成
  • 最大 15 シャードの間で 3.5TiB のデータのワークロードのスケール

ElastiCache や関連する機能を活用した Redis の使用については、「Amazon ElastiCache for Redis」の製品ページを参照してください。Redis バックアップおよび復元とクラスターのサイズ変更機能が発表され、ElastiCache は、マネージド型の Redis クラスター経験に確実に移行できるように Redis のサポートをますます強化しています。この機能には、ElastiCache と Redis ユーザーのどちらにとっても、次のような利点があります。

  • シャード数やスロット配置が異なる Redis クラスターにバックアップを復元可能
  • ユーザーによる Redis ワークロードのサイズ変更が可能
  • シャードされた Redis クラスターを作成する入力として、Redis データベースファイル (RDB) スナップショットを使用可能
  • シャードされた Redis クラスターの作成に必要なデータ入力として、EC2 実装 (Redis クラスターと単一シャード Redis の両方) で Redis のスナップショットを使用可能

これらのタスクを実現するために、ElastiCache は、バックアップの各スナップショット間で Redis キー空間を解析し、要求されたシャードやハッシュスロットの数に応じて、新しいクラスター内にキーを再分散します。RDB スナップショットを作成して、S3 に格納し、ElastiCache で希望のシャードおよびスナップファイルの数を指定します。Redis データストアを Redis クラスターに格納する面倒な作業も ElastiCache で行うことができます。中には、ElastiCache の「Redis バックアップおよび復元とクラスターのサイズ変更」機能を本当に簡単に活用できるのか、と考える方がいるかもしれません。検証している時間はありません。AWS マネジメントコンソールに移動し、ElastiCache を用いて外部の RDB スナップショットを新しいクラスターで復元し、新たにリリースされた拡張を使用します。まず、AWS マネジメントコンソールで、「Amazon S3 コンソール」に移動します。ElastiCache への外部の Redis スナップショットの復元をテストするために、AWS の一部ピアから受け取った Redis .rdb スナップショットファイルがいくつかあります。ElastiCache Redis クラスターの入力としてスナップショットにアクセスできるように、これらのファイルを Amazon S3 に配置します。

S3 コンソールで、テストおよび開発の目的で作成した S3 バケット、aws-blog-tew-posts に移動します。提供された .rdb スナップショットファイルをこの S3 バケットにアップロードします。

S3 バケットの名前は DNS 標準に準拠しなければなりません。DNS 標準に準拠するために、バケット名は 3 文字以上で、小文字の英文字、数字、ハイフンのみを使用し、先頭と末尾の文字は小文字の英文字または数字でなければなりません。当たり前かもしれませんが、バケット名は IP アドレスフォーマットにすることはできません。S3 バケットの制約の詳細については、「こちら」を参照してください。.rdb ファイルが正常に aws-blog-tew-posts バケットにアップロードされたら、これらのバックアップファイルへの S3 パスをメモします。上記のファイルの場合、パスは、aws-blog-tew-posts/dump_1.rdb または aws-blog-tew-posts/dump_10.rdb になります。ファイルをフォルダに配置する場合は、フォルダ名をこのパスに含む必要があります。例: thebucketname/thefoldername/thefilename

ElastiCache からこれらのファイルにアクセスするには、該当サービスに各ファイルの読み取り権限があることを確認します。アクセス権限を付与するには、被付与者を対象リージョンの正規化 ID として割り当てることで各 .rdb ファイルの読み取り権限をアップデートしてから、開く/ダウンロードのアクセス権限をこのユーザーに付与します。中国 (北京)、AWS GovCloud (米国) を除くすべてのリージョンの正規化 ID は次のとおりです。

540804c33a284a299d2547575ce1010f2312ef3da9b3a053c8bc45bf233e4353

[Save] ボタンをクリックすると、ElastiCache Redis クラスターの入力としてこれらのファイルを使用するように設定されます。

次に、ElastiCache コンソールに移動します。ここで、新たに ElastiCache Redis クラスターを作成し、このクラスターを S3 バケット内のファイルに配置された RDB スナップショットのいずれかのデータに追加します。dump_1.rdb スナップショットファイルを選択して、この新しいクラスターを追加するデータ入力として使用します。今年 10 月に Redis Cluster 3.2.4 のサポートによって追加された ElastiCache Redis 機能や、クラスターのサイズ変更に対応した新しいバックアップおよび復元機能について説明するため、Redis クラスターを新たに作成し、クラスターモードが有効であることを確認します。この時点で、Redis (クラスターモードが有効) クラスターを使用して作成されたバックアップから Redis (クラスターモードが無効) クラスターに復元することはできません。まず、ElastiCache コンソールダッシュボードの [今すぐ始める] ボタン、またはコンソールビューの [作成] ボタンをクリックします。

[Amazon ElastiCache クラスターの作成] ダイアログウィンドウで、キャッシュの [Redis] を選択し、[有効なクラスターモード (スケールアウト)] のチェックボックスがオンになっていることを確認します。新しいクラスターの名前は、tew-rediscluster となり、クラスターモードを有効にしているため、ElastiCache Redis のエンジンバージョンは、3.2.4 です。このクラスターの Redis ポートは、デフォルトの 6379 のままにしておきます。

ElastiCache 拡張の Redis バックアップおよび復元機能の主な利点は、元々バックファイルで使用されていた数と異なるシャード数で新しいクラスターを構築することができるクラスターのサイズ変更機能です。新しい Redis クラスターを構成するために、唯一の RDB スナップショットファイルである dump_1.rdb を使用します。このファイルは、シャードが 1 つのみの小さな Redis インスタンスバックアップです。ただし、新しい tew-rediscluster を作成する際、1 つのシャードに 2 つのレプリカを含む 3 つのシャードを選択しました。さらに、RDB スナップショットより、元のインスタンスとは異なるサイズの新しいクラスターのノードタイプを指定することもできます。前述の通り、dump_1.rdb は、以下に示した tew-rediscluster 向けに選択したノードタイプより大幅に小さい Redis インスタンスのバックアップです。

他にも、ElastiCache Redis クラスターを作成するために必要なオプションやデータ入力がありますが、このブログ投稿には記載していません。ただし、ElastiCache Redis クラスターの作成に必要な各ステップを行う場合は、『クラスターを起動する』の「AWS ElastiCache の開始」のドキュメントで詳細を確認してください。ElastiCache Redis クラスターの作成に必要な情報をすべて入力したら、ElastiCache で S3 バケットのファイル場所を指定して、クラスターに .rdb ファイルを追加する方法を指定します。作成ダイアログの「クラスターへのデータのインポート」セクションにおいて、「シードする RDB ファイルの S3 の場所」テキストボックスdump_1.rdb への S3 パスを入力します。S3 ファイルパスの命名法は、Bucket/Folder/ObjectName が適用されるため、S3 内の RDB ファイルへのパスとして、aws-blog-tew-posts/dump_1.rdb を入力します。最後に、[作成] ボタンをクリックします。

これで完了です。ElastiCache で新しい Redis クラスターを作成できるようになりました。間もなくすると、新しい Amazon ElastiCache Redis クラスターが ElastiCache コンソールで選択できるようになります。外部の RDB スナップショットファイルより復元されたデータでこのクラスターを作成することができました。

外部の RDB スナップショットを用いて ElastiCache Redis クラスターを作成する方法を説明しましたが、既存の ElastiCache Redis クラスターのバックアップを使用してバックアップを作成または復元することもできます。新たにリリースされた本機能の詳細をお知りになりたい場合は、『Amazon ElastiCache User Guide』の「バックアップからの復元とクラスターのサイズ変更」を参照してください。Amazon ElastiCache を使用したアプリケーションのパフォーマンス強化の詳細については、「AWS Amazon ElastiCache」の製品の詳細、リソース、お客様の声を参照してください。

Tara

Amazon ElastiCache for Redisアップデート – Sharded Clusterの追加やエンジンバージョンを更新 –

多くのAWSのお客様に高速で、インメモリデータストアとして Amazon ElastiCacheをご利用頂いています。

2013年にAmazon ElastiCache for Redisリリースし、スナップショットのS3へのエクスポート機能エンジンバージョンの更新スケールアップ機能タグMulti-AZで自動フェイルオーバー機能を数年で追加してきました。

本日、新機能をElastiCache for Redisへ追加しました。詳細は以下の通りです。

  • Sharded Clusterサポート – 3.5 TiB以上をインメモリデータとして扱う事が出来るsharded clusterを作成可能になりました
  • コンソールの改善 – 以前より簡単にクラスタの作成やメンテナンスが行えるようになりました
  • エンジンアップデート – Redis 3.2の機能をご利用頂けるようになりました
  • Geospatial Data – 地理空間データの処理を行え、利用可能になりました

更に詳細を見ていきましょう!

 

Sharded Clusterサポート / 新コンソール

今まではElastiCache for Redisは1つのプライマリノードと5つまでのread replicaを含んだクラスタに対応していました。この構成では、メモリサイズがクラスタあたり237 GiBに制限されていました。

1クラスタで15シャードまで作成出来るようになったことで、クラスタ全体のメモリ容量を拡大することが可能になり、最大3.5 TiBまでのデータをインメモリデータとして格納出来ます。それぞれのシャードは5つまでread replicaを作成可能で、1秒あたり2,000万読み込み、450万書き込みの性能を提供します。

シャードモデルは、read replicaと合わせて利用することでクラスタ全体の可用性とパフォーマンスを向上します。データは複数のノードに分散され、read replicaは高速で自動的なフェイルオーバーをプライマリノードに障害が起こった際に提供します。

シャードモデルの利点を活かすために、cluster対応のRedisクライアントを使う必要があります。クライアントは、16,384個のスロットをシャード毎に均等に分散されたハッシュテーブルとして扱い、保存するデータを各シャードにマップします。

ElastiCache for Redisはクラスタ全体を1つのバックアップとリストア用途のユニットとして扱います。そのため、各シャード毎のバックアップを管理したり考えたりする必要がありません。

コンソールの機能が改善され、スケールアウトクラスタを簡単に作成可能になりました。(Redisエンジンを選択した後にCluster Mode enabled (Scale Out)にチェックを入れている点を注目して下さい)

ec_redis_create_so_cluster

新しい便利なメニューで適切なノードタイプを選択を手助けしてくれます

ec_redis_pick_your_node_not_your_nose

sharded clusterは、AWS Command Line Interface (CLI)AWS Tools for Windows PowerShellElastiCache APIAWS CloudFormationテンプレートからも作成可能です。

 

エンジンアップデート

Amazon ElastiCache for RedisはRedis3.2に対応しました。このバージョンは3つの興味深い機能を持っています。

  • Enforced Write Consistency – 新しいWAITコマンドはそれ以前の全ての書き込みコマンドに対して、プライマリノードと設定された数のread replicaから確認応答が返ってくるまで呼び出し元をブロックします。この変更はRedisを強い一貫性を持ったデータストアにするものではありません。しかし、新しく昇格したread replicaが直前にプライマリノードに書き込まれた出たデータを保持している可能性を向上します
  • SPOP with COUNTSPOPコマンドはsetからランダムにエレメントを削除してそれを返却します。1つ以上のエレメントを1回でリクエスト出来るようになりました
  • Bitfields – BitfieldsはRedis stringとして保存されていた小さい整数のコレクションをビットマップとして保存することでメモリ効率を良くする方法です。BITFIELDコマンドを使うことで、 (GET) や(SET, increment, decrement) をbyteアライメントや文字境界を気にすること無く扱うことが出来るようになります

私達のRedisの実装は、サーバプロセスをフォークする事なくスナップショットを取得する機能を持っています。過負荷状態では標準のフォークを利用したスナップショットはスワップによるパフォーマンスの低下を引き起こす可能性があります。私達の実装ではメモリ利用率が50%を越えていたとしても動作をし、問題を解決します。この機能は少し低速なため、必要になった場合にこの機能を利用します。

その他にも私達の実装では新しいread replicaがプライマリノードと同期する時のパフォーマンスを向上しています。これと似た性能向上を新しく昇格したプライマリノードと既存のread replicaが同期する際にも実装しています。

今までお伝えした通り、私達のエンジンはオープンソースバージョンのものと互換性があり、皆様のアプリケーションに変更は必要ありません。

 

Geospatial Data

地理空間データ(緯度・経度)を保存したりクエリ出来るようになりました。以下が関連するコマンドです。

  • GEOADD – 地理空間データを保存
  • GEODIST – 2地点間の距離を取得
  • GEOHASHGeohash (geocoding)を取得
  • GEOPOS – keyでしてされた地点のアイテムを取得
  • GEORADIUS – 指定された地点の半径内のアイテムを取得
  • GEORADIUSBYMEMBER – 他のアイテムの半径内に収まるアイテムを取得

 

今日からご利用頂けます

Sharded clusterを含む今回ご紹介した全ての機能は全てのAWSリージョン今日からご利用頂けます。

Jeff; (翻訳は星野が担当しました。原文はこちら)

Amazon ElastiCache アップデート – RedisのSnapshotをAmazon S3へエクスポートする事が可能になりました

Amazon ElastiCache はポピュラーなインメモリキャッシュエンジンであるMemcachedRedisをサポートしています。
Memcachedは低速なディスクベースのデータベース等の結果を高速にキャッシュする事ができ、Redisは永続的にデータを保存できるデータストアとして高速に動作します。Redisはレプリケーションやフェイルオーバなどをサポートすることでの高可用性の確保と、複雑なデータ構造での保存を標準サポートしています。

 

本日Redisユーザのみなさんに、非常に興味深く、注目すべき新しい機能をリリースしました。まず、実行されているRedis キャッシュクラスタのスナップショットを作成する事は既に可能です。スナップショットは永続的なバックアップや、新しいキャッシュクラスタを作成するために使用することができます。おさらいとしてキャッシュクラスタのスナップショットを作成する方法は次のとおりです:

RedisスナップショットがS3バケットへとエクスポートできるようになりました。S3バケットはスナップショットと同一リージョンに存在し、ElastiCacheに適切なIAMの権限(List、Upload/Delete、View 権限)を付与する必要があります。この機能はいくつかの用途を想定しています:

 

ディザスタリカバリ – スナップショットを他の場所にコピーし保管する

分析 – S3上のスナップショットのデータから使用状況等を分析する

種データ配布 – 別のリージョンにスナップショットを元にした新しいRedisキャッシュクラスタを構築する

スナップショットのエクスポート
スナップショットをエクスポートするのは簡単です、スナップショットを選択し、Copy Snapshot をクリックしてください:

バケットのアクセス許可を確認してください。(詳細はExporting Your Snapshotを参照ください):

次に、新しくエクスポートするスナップショット名と希望する保存先のS3バケット名を入力してください:

ElastiCache はスナップショットをエクスポートし、指定したS3バケットにスナップショットを置きます:

ファイルは標準のRedisのRDBファイルとして保存され、使用することができます。

また、アプリケーション等のコードや、コマンドラインからも同様の作業を実行することができます。対象のS3バケットを指定し、プログラムコードから呼び出す場合はCopySnapshot、コマンドラインからはcopy-snapshotコマンドを使用してください。

この機能は既に利用可能で、本日から使い始めることができます!エクスポートには料金はかからず、通常のS3ストレージ料金だけで使うことができます。

— Jeff;

翻訳はSA桑野(@kuwa_tw)が担当しました。原文はこちら

 

 

ElastiCache for Redis の更新 – エンジンのアップグレードとスケールアップ

Amazon ElastiCache は、クラウド内のインメモリデータベースのデプロイ、運用、スケールの実行を簡単にするサービスです。ご存知のとおり、ElastiCache では Memcached エンジンと Redis エンジンがサポートされています。

Redis 向けの強化
本日、Redis ベースの ElastiCache クラスターがより詳細に制御できるようになる、ElastiCache の更新をローンチしました。You can now scale up to a larger node type while 保存された情報を ElastiCache に保持したまま (ベストエフォートベース)、より大きなノードタイプにスケールアップできるようになりました。ElastiCache for Redis のエンジンバージョンのアップグレードはこれまでずっと可能でしたが、保存された情報を保ったままでこれを実行できるようになりました。これらの変更は、両方とも、即時適用またはクラスターのメンテナンス期間の適用が実行できます。

ElastiCache for Redis のスケールアップとエンジンアップグレードのために、見えないところでさまざまな戦略が活用されています。スケーリングは Redis レプリケーションをベースにしています。エンジンのアップグレードでは、マルチ AZ がオフになったときにまずフォアグラウンドのスナップショットが使用され (SAVE)、マルチ AZ がオンのときにレプリケーションに続いて DNS スイッチが実行されます。

大きなノードタイプにスケールアップするには、AWS マネジメントコンソールで [Cache Cluster] を選択し、[Modify] をクリックします。新しい [Node Type] を選択し、変更を即時適用するかどうかを決定してから、[Modify] をクリックします。

同様に、新しいバージョンの Redis エンジンにアップグレードするには、新しいバージョンを選択してから [Modify] をクリックします。

この機会に、Redis のバージョン 2.8.24 と互換性のあるエンジンにアップグレードすることをお勧めします。このバージョンには Redis の安定性と堅牢性を高めるための多数の修正と強化が実施されています (その一部は ElastiCache チームによるものであり、詳細については「最新情報」を参照してください)。

これまで同様、同じオペレーションを ElastiCache API によって実行することもできます。 PHP 中での簡単な例を示します (AWS SDK for PHP による)。

PHP
// Scale to larger node size
$res = $client->modifyCacheCluster(['CacheNodeType' => 'cache.r3.4xlarge', 
                                    'ApplyImmediately' => true]);

// Upgrade engine version
$res = $client->modifyCacheCluster(['EngineVersion' => '2.8.24',
                                    'ApplyImmediately' => true]);

// Do both at once
$res = $client->modifyCacheCluster(['CacheNodeType' => 'cache.r3.4xlarge', 
                                    'EngineVersion' => '2.8.24', 
                                    'ApplyImmediately' => true]);

これら 3 つの例すべてで、ApplyImmediately パラメーターは変更がメンテナンス期間ではなく、直ちに適用されることを示しています。

詳細については、Scaling Your Redis Cluster をご覧ください。

サービス開始
この機能は今すぐ利用できます。


Jeff