キャッシュのためにRedisクラスタを使う方法


このポストでは、我々は我々のアプリケーションのためのキャッシュ層としてRedisを使用する方法を探索し、さらにそれを探索すると、Redisクラスタがどのようによりスケーラビリティと信頼性を提供できるかを見るでしょう.

TLDR: If you are already familiar with Redis and it's just looking for a way to spin-up a fully configured Redis Cluster using Docker, here is the Github repo. Just clone this repo, go to your terminal, run docker-compose up and you should be good to go.



Redisとは
Redisはキーバリューストアです.ラフな条件では、それはデータベースのように動作しますが、それはメモリにデータを保持します.つまり、読み込みと書き込みは、リレーショナルデータベースと比較して、より高速なオーダーですPostgreSQL. REDISがリレーショナルデータベースに取って代わらないことに言及することは重要です.それは独自のユースケースを持っています、そして、我々はこのポストで彼らのいくつかを調査します.
Redisの詳細については、彼らのウェブサイトをご覧くださいhere. そこには良いドキュメントとどのようにあなたのマシンにインストールする方法を見つける.しかし、我々はこのポストの間にデモを構築する予定ですDocker and docker-compose それはスピンし、あなたのために全体のRedisクラスタを構成します.必要な唯一のものはDockerです.

キャッシュのためのREDISの使用
ある種のデータへの高速アクセスが必要な場合は、このデータをできるだけアプリケーション層に近づける方法を考える必要があります.データ量が十分小さい場合は、このデータをローカルメモリに保持するのが一般的です.しかし、私たちがWebアプリケーションについて話し、特に無条件で、潜在的に複数のサーバで実行できるものについて話をすると、クラスタ内の他のサーバがこの同じデータに素早くアクセスできることを確認するのと同様に、必要なデータが存在することを保証することはできません.
これは、データベースが便利です.私たちは中央の場所にこのデータを書くことができます、そして、他のサーバーは彼らが必要とするときはいつでも、このデータを得ることができます.いくつかのデータベースの問題は、あなたが本当に燃える高速アクセスを必要とするならば、それらのいくつかは弾丸速度でそれを届けることができないでしょう.Redisは一般的にデータの特定のビットへの高速かつ信頼性の高いアクセスを必要とするたびにデータベースへの移動です.また、期限切れのときに自動的に削除されるように、期限切れのポリシーをそのデータに設定する方法も用意しています.
REDISは通常、保存するための良い選択です.
  • ユーザーセッション
  • 認証トークン
  • レート制限カウンタ
  • Redisは、上記のユースケースに限定されるものではありませんが、高速なデータアクセスを必要とするときにはよく適合します.

    どのようなクラスタを使用するポイントですか?
    通常、単一のサーバーインスタンスで起動するのが一般的です.おそらく、データベースサーバに接続されていて、おそらく長い間、サーバーを接続できます.しかし、一度、さまざまな国と時折別の大陸間でアプリケーションをスケールする必要があります、それはおそらくあなたのアプリケーションは24日、週7日利用できるようにする必要があることを意味します.また、ロバスト性と信頼性をアプリケーションに埋め込む必要があります.
    ネットワーク内の問題や故障したハードウェアのために、データベースサーバのいずれかが下がったときに何が起こるかを考える必要があります.あなたが1つのインスタンスだけを持っているならば、あなたは水で死んでいます.バックアップがある場合は、新しいインスタンスを回転させることができるまでの時間がかかるだろう、すべてのあなたの標準に設定し、バックアップを復元し、ビジネスでそれを戻す.
    あなたのアプリケーションがミッションクリティカルであるならば、あなたは数時間の間オフラインになる余裕がありません.一部のアプリケーションでも、全体の年の数分でオフラインにすることはできません.これは、そのような問題が発生したときにレプリカを持つクラスタがあなたの肌を保存できる場所です.
    Redisクラスタは、データが自動的に信頼性と可用性の高いレベルを与える複数のREDISインスタンス間で共有されることを確認します.これらのインスタンスのいずれかが何らかの種類の失敗を経験した場合、他のノードは、アプリケーションに対して正常にコンテンツを提供することができます.

    Redisクラスタの回転
    私は最近、1つのRedisインスタンスを使用してクラスタからの大規模なWebアプリケーションを移動しましたmultiple shards , 複数のレプリカを含む.私たちがクラスタ構成全体を提供するAWSインフラストラクチャを使用している間、私は単にすべてが生産で働くと信じることができませんでした.私は開発中にRedisクラスタをサポートすることができたことを確認しなければならなかったので、いくつかのREDISコンテナを配布し、自動的にクラスタを形成するためのセットアップを作成しました.
    アプリケーションからRedisに接続するには、それを実行できるライブラリが必要です(そうでなければホイールを再起動する必要があります).使っている間IORedis このデモでNODEJSアプリケーションの場合は、別の言語を使用している場合は、別のコネクタを探す必要がありますLettuce for Java またはgo-redis for Go .

    The entire setup is ready for you in this Github repository here, so you don't have to worry about creating anything from scratch. You can clone it and give it a spin while we will be walking through the files from this repo along the rest of this blogpost.



    Dockerfileの作成
    私たちはDockerHubから利用できる標準的なREDISイメージを使用している間、いくつかのREDISコンテナを紡ぎます.それは我々がクラスタを形成することができる方法でREDISにコマンドを出すことができる特別なコンテナーを構築するところです.
    アット redis/Dockerfile 次のコンテンツがあります.
    FROM redis:latest
    
    COPY ./entrypoint.sh /entrypoint.sh
    RUN chmod 755 /entrypoint.sh
    
    ENTRYPOINT ["/entrypoint.sh"]
    
    REDISに基づいてカスタムDockerイメージを構築するために、このDockerFileを使用します.ここの秘密のソースは、実際に redis/entrypoint.sh . このスクリプトを見てみましょう.
    #!/bin/sh
    
    # Using the redis-cli tool available as default in the Redis base image
    # we need to create the cluster so they can coordinate with each other
    # which key slots they need to hold per shard
    
    # wait a little so we give some time for the Redis containers
    # to spin up and be available on the network
    sleep 5
    # redis-cli doesn't support hostnames, we must match the
    # container IP addresses from our docker-compose configuration.
    # `--cluster-replicas 1` Will make sure that every master 
    # node will have its replica node.
    echo "yes" | redis-cli --cluster create \
      173.18.0.2:6379 \
      173.18.0.3:6379 \
      173.18.0.4:6379 \
      173.18.0.5:6379 \
      173.18.0.6:6379 \
      173.18.0.7:6379 \
      --cluster-replicas 1
    
    echo "🚀 Redis cluster ready."
    
    ここで我々はredis-cli コマンドを発行する.このコマンドはクラスタを作成し、このスクリプトを起動するときに到達できる特定のREDISインスタンスを指しています.私たちはハードコード化されたIPアドレスを使用していますdocker-compose.yml 後でファイル.
    このクラスタは3シャードで構成されている.各シャードはすべての書き込みに対して責任があるマスターノードを持っていますが、データのコピーを保持するレプリカノードもあります.Redisクラスタシャードは最大500レプリカ(少なくともAWSで)を持つことができます.現在のマスターが利用できなくなるならば、複製ノードは引き継ぐ力とマスターノードになります.
    今、我々の中にredis フォルダにもredis.conf . このファイルはそれぞれのRedisコンテナにコピーされ、REDISインスタンスをクラスタの一部として動作させるように指示します.その内容を見てみましょう.
    # Custom config file to enable cluster mode
    # on all Redis instances started via Docker
    port 6379
    cluster-enabled yes
    # The cluster file is created and managed by Redis
    # We just need to declare it here
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    
    そこにはあまり行かない.重要な部分はcluster-enabled yes これにより、Redisインスタンスがクラスタの一部として動作することができます.我々は今、いくつかのredisコンテナをスピンして、彼らがお互いに話すことを確認する方法が必要です.我々のプロジェクトのルートフォルダでdocker-compose.yml . 見てみましょう
    volumes:
      redis_1_data: {}
      redis_2_data: {}
      redis_3_data: {}
      redis_4_data: {}
      redis_5_data: {}
      redis_6_data: {}
      # This volume is specific for the demo Express application
      # built in this repo. You probably won't need that on your own setup.
      node_modules: {}
    
    services:
    
      app:
        container_name: express_app
        image: express_app
        build:
          context: .
        environment:
          PORT: 4000
          NODE_ENV: production
          REDIS_CLUSTER_URLS: 'redis_1:6379,redis_2:6379,redis_3:6379,redis_4:6379,redis_5:6379,redis_6:6379'
        volumes:
          - .:/app
          - node_modules:/app/node_modules
        command: [ "npm", "run", "dev" ]
        depends_on:
          - redis_1
          - redis_2
          - redis_3
          - redis_4
          - redis_5
          - redis_6
          - cluster_initiator
        ports:
          - "4000:4000"
        stdin_open: true
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.10
    
      # Here we have six Redis containers with Cluster mode enabled,
      # three of them will work as master nodes and each one of
      # will have a replica, so in case of failures, the replica becomes the master.
      # They are configured by the `cluster_initiator` container.
      redis_1:
        image: 'redis:latest'
        container_name: redis_1
        ports:
          - "6379"
        volumes:
          - redis_1_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.2
    
      redis_2:
        image: 'redis:latest'
        container_name: redis_2
        ports:
          - "6379"
        volumes:
          - redis_2_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.3
    
      redis_3:
        image: 'redis:latest'
        container_name: redis_3
        ports:
          - "6379"
        volumes:
          - redis_3_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.4
    
      redis_4:
        image: 'redis:latest'
        container_name: redis_4
        ports:
          - "6379"
        volumes:
          - redis_4_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.5
    
      redis_5:
        image: 'redis:latest'
        container_name: redis_5
        ports:
          - "6379"
        volumes:
          - redis_5_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.6
    
      redis_6:
        image: 'redis:latest'
        container_name: redis_6
        ports:
          - "6379"
        volumes:
          - redis_6_data:/data
          - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
        command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.7
    
      # Ephemeral container to create the Redis cluster connections.
      # Once the setup is done, this container shuts down
      # and the cluster can be used by the service app container
      cluster_initiator:
        container_name: cluster_initiator
        build:
          context: redis
          dockerfile: Dockerfile
        tty: true
        depends_on:
          - redis_1
          - redis_2
          - redis_3
          - redis_4
          - redis_5
          - redis_6
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.8
    
      # Web UI to browse through our Redis data across all nodes
      redis_commander:
        image: rediscommander/redis-commander:latest
        container_name: redis_web
        environment:
          REDIS_HOSTS: "local:redis_1:6379,local:redis_2:6379,local:redis_3:6379"
        ports:
          - "5000:8081"
        depends_on:
          - redis_1
          - redis_2
          - redis_3
          - redis_4
          - redis_5
          - redis_6
          - cluster_initiator
        networks:
          redis_cluster_net:
            ipv4_address: 173.18.0.9
    
    # Rename the default network so we can easily identify it
    # Across all containers
    networks:
      redis_cluster_net:
        driver: bridge
        ipam:
          driver: default
          config:
            - subnet: 173.18.0.0/16
    
    これは長いものですが、ここはこれですdocker-compose.yml です
  • Excelアプリケーションを使用してコンテナを作成します
  • Redisのいくつかのインスタンスを作成する
  • IPアドレスを設定するentrypoint.sh スクリプト
  • コピーするredis.conf ファイルをクラスタとして機能することができます
  • 実行に必要なクラスタイニシエータコンテナを作成しますentrypoint.sh スクリプトとクラスタ接続を作成する
  • コンテナを作成するRedis Commander UI 私たちのRedisクラスタに格納されているブラウジングのための素晴らしいWeb UIです
  • 私たちがこれを経験した今、これを試みましょう.端末に行って実行します.
    docker-compose up
    
    すべてが準備ができたら、あなたのブラウザを開いてlocalhost:4000 . そこには、キー/値のペアを入力することができますRedisとそれを保存する前に入力している特定のキーを検索することができますので、redisから取得することができますし、画面上のコンテンツを表示するデモのWebアプリケーションを持っている.

    JavaScript側で接続がどのように設定されているのか疑問に思っている場合は、見てみましょうsrc/service/redisClient.js ファイル.
    const Redis = require('ioredis')
    
    /**
     * Get an existing Redis client instance. Build one if necessary
     * @return {Cluster|null} redis client
     * */
    function buildRedisClient() {
    
      try {
        // cluster URLs should be passed in with the following format:
        // REDIS_CLUSTER_URLS=10.0.0.1:6379,10.0.0.2:6379,10.0.0.3:6379
        const nodes = process.env.REDIS_CLUSTER_URLS.split(',').map(url => {
          const [host, port] = url.split(':')
          return { host, port }
        })
    
        const client = new Redis.Cluster(nodes, {
          redisOptions: {
            enableAutoPipelining: true,
          },
        })
    
        client.on('error', error => {
          console.error('Redis Error', error)
        })
    
        // Redis emits this error when an something 
        // occurs when connecting to a node when using Redis in Cluster mode
        client.on('node error', (error, node) => {
          console.error(`Redis error in node ${node}`, error)
        })
    
        return client
      } catch (error) {
        console.error('Could not create a Redis cluster client', error)
    
        return null
      }
    }
    
    module.exports = buildRedisClient
    
    この部分はとても簡単です.クラスタのURLを環境から読み込み、Redis.Cluster Resisioライブラリの使用そこから、我々は問題のコマンドを始めることができますredis.set , redis.get or redis.exists アプリケーション全体.ここではどのように我々はこのレポ内のデモエクスプレスアプリケーションでそれを行う:
    const buildRedisClient = require('./service/redisClient')
    const redis = buildRedisClient()
    
    // Have a look at src/index.js for a complete implementation
    app.post('/save-data', async (request, response) => {
      const { key, value } = request.body
      await redis.set(key, value)
      return response.status(201).render('home/index', {
        layout: 'default',
        dataSaved: true,
      })
    })
    
    app.post('/search', async (request, response) => {
      const { key } = request.body
      const value = await redis.get(key)
      return response.status(200).render('home/index', {
        layout: 'default',
        value,
      })
    })
    
    クラスタに格納されているデータを調べたい場合はlocalhost:5000 とレッズコマンダーUIを参照します.そこにすべてのマスターノードを見ることができるし、すべてのキーと値を探索する必要があります.

    いくつかのキーが1つのマスターノードに格納され、他のキーが他のノードに格納されます.これはRedisによって行われたデータ配布で、クラスタ全体に負荷分散を提供します.
    私は、このDockerセットアップがあなたの開発ワークフローを助けることができることを望みます.ご質問がございましたらお気軽に.