Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.jsでRedis Clusterを取り扱う

$
0
0

最近でNode.jsで Redis Clusterを触る機会があったのですが
意外とRedis Cluster × Node.jsの日本語記事が無かったため、備忘録を兼ねて記事に起こしておきます。

今回利用するパッケージはこちら。
GitHub - luin/ioredis: 🚀A robust, performance-focused and full-featured Redis client for Node.js.

typescriptを使う場合は 一緒に型もimportしておきましょう。

npm i ioredis
npm i -D @types/ioredis

Constructor

cluster形式の場合は利用する nodeを配列形式で指定する必要があります。
他に何もOptionを指定する必要が無いのであれば、基本この設定だけで良いです。

constdriver:IORedis.Cluster=newIORedis.Cluster([{port:6380,host:'127.0.0.1'},{port:6381,host:'127.0.0.1'},]);

redisOptions

おそらく一番触る必要が出てくる箇所。
名前の通り Redisに対するオプションを設定します。
例えばパスワードが必要な場合は以下のように redisOptionで指定することが出来ます。

constdriver:IORedis.Cluster=newIORedis.Cluster([],redisOptions:{password:config.password});

設定できるオプションはAPI - ioredis / new Redis([port], [host], [options]で確認でき、
今回は個人的に使う機会がありそうな項目を列挙しておきます。

オプション名デフォルト値詳細
db0使用するデータベースインデックス
passwordnullパスワード
dropBufferSupportFALSEバッファサポートの削除を有効化,。巨大な配列の応答を処理するときに有効にするとパフォーマンスが向上するらしい(未検証)
enableReadyCheckTRUERedisのサーバーステータスを確認してからコマンドを送信する
connectTimeout10000初期接続中にタイムアウトが発生するまでのミリ秒
tlsnullTLS接続のサポート (https://github.com/luin/ioredis#tls-options)
readOnlyFALSE読み取り専用

その他のオプション

redisOption以外にも幾つか指定できるオプションがあるため、こちらも記載。
こちらも同様にAPI - ioredis / new Cluster(startupNodes, options) に設定項目が載っています。

一応 使いそうなものをピックアップしています。

オプション名デフォルト値詳細
clusterRetryStrategyノードに到達できない場合に呼び出されるcallback。 returnした数値分待機して再接続を試みる
scaleReadsmaster読み取り対象のnodeを指定する。指定可能なのは master, slave, all のいずれか
maxRedirections16ターゲット nodeからエラー(MOVED, CLUSTERDOWN等)が返った場合に他のnodeにリダイレクトする回数
retryDelayOnFailover100ターゲット nodeが切断されている場合に指定秒後にコマンドを再送信する
constdriver:IORedis.Cluster=newIORedis.Cluster([],redisOptions:{},retryDelayOnFailover:50);

ただ、redisOptionsと違って こちらを指定するケースは殆どなさそうです。

Command

基本Commandを中心に。
特に難しくありませんが、writeに関しては書き込む際の型に注意が必要です。

read

awaitredis.get('key');

write

awaitredis.set('key','value');

expire を指定する場合は 以下のように EX と 有効期限(秒)を指定します。

redis.set('key','value','EX',10);

注意点としては Object型をvalueとしてセットすると [Object object]という形で保存されてしまうため、
Object型を保存するときは JSON.stringifyで文字型に変換して入れるようにしましょう。

delete

awaitthis.redis.del('key');

keys

正規表現マッチで取得する。
getでは 未ヒット時は nullが返りますが、こちらは空配列が返ります。

awaitthis.redis.keys('regExp');

Event

Redisへの接続状態に合わせてイベントがトリガーされます。
発火されるイベントは以下の通り。

イベント詳細
connectRedisサーバーへの接続が確立した時
readyコマンドを受付が可能な時(enableReadyCheck で trueを確認できた時)
error接続中にエラーが発生した時
closeRedisサーバー接続が閉じた時
reconnecting再接続が行われた時
endRedisサーバーとの接続が閉じた時

発火したイベントは以下のようにすることでハンドリングできます。

this.redis.on('connect',()=>{console.log('trigger connect');});

その他 Tips

自分が実際に対応した細々とした内容です。

master nodeへの負荷を低減したい

ioredisでは基本的に commandは全て masterへ送信されます。
そのため、 masterの cpu消費が激しくパフォーマンスが低下する問題を引き起こす可能性があります。

そこでコンストラクタのオプション scaleReadで read commandの向き先を slaveに変更します。

constdriver:IORedis.Cluster=newIORedis.Cluster([],redisOptions:{},scaleReads:'slave');

これで read commandは slaveに向くので masterへの負荷は軽減されます。
ただし、レプリケーション遅延により、master, slave間でデータの差分が発生する可能性があるので注意が必要です。

writeするデータを圧縮したい

例えば api cacheなどで大量のデータを Redisに書き込む場合に幾つかの問題が発生することが予想されます。

  • ネットワーク帯域を圧迫して帯域詰まりを起こす
  • レイテンシが悪化する
  • Redisの容量を圧迫する

これらの問題に対する一つの解決策としてデータを圧縮するという方法がありますが、
ioredisはデータを圧縮しないため圧縮機構を自前で用意する必要があります。

圧縮・解凍するライブラリは幾つかありますが 今回は高速が売りの lz-stringを使っていきます
GitHub - pieroxy/lz-string: LZ-based compression algorithm for JavaScript

npm i lz-string
npm i -D @types/lz-string
import*asIORedisfrom'ioredis';import{compress,decompress}from'lz-string'//// 省略///**
 * 読み込み
 * @param {string} key
 */asyncfunctionget(key:string):Promise<string|null>{constbuf:string|null=awaitredis.get(key);if(buf==null)returnnull;returndecompress(buf);}/**
 * 書き込み
 * @param {string} key
 * @param {string} value
 * @param {number} expireSec
 */asyncfunctionset(key:string,value:string,expireSec:number):Promise<void>{awaitredis.set(key,compress(value),'EX',expireSec);}

注意点としては 圧縮・解凍はCPUを消費する処理のため、パフォーマンスが低下する可能性があります。
なので導入する場合はCPUの消費を計測してリソースの再調整をする必要があります。

※レイテンシが悪化する問題に対してはデータサイズによっては逆効果だったするため、
計測して判断する必要があります。

おまけ

別段必要なさそうですが簡単に class化しておきました。

import*asIORedisfrom'ioredis';/**
 * Redis操作クラス
 * @class
 */exportclassDriver{privateredis:IORedis.Redis|IORedis.Cluster;privatestatus:ConnectionStatus=ConnectionStatus.READY;/**
   * @constructor
   * @param {IORedis.Redis} client
   */privateconstructor(client:IORedis.Redis|IORedis.Cluster){this.redis=client;this.redis.on('connect',()=>{this.status=ConnectionStatus.CONNECTED});this.redis.on('error',()=>{this.status=ConnectionStatus.ERROR});this.redis.on('reconnecting',()=>{this.status=ConnectionStatus.RECONNECT});}/**
   * 死活判定
   */publicalive():boolean{return(this.status===ConnectionStatus.CONNECTED)}/**
   * 読み込み
   * @param {string} key
   */publicasyncget(key:string):Promise<string|null>{if(!this.alive())returnnull;returnawaitthis.redis.get(key);}/**
   * 削除
   * @param {string} key
   */publicasyncdel(key:string):Promise<void>{if(!this.alive())return;awaitthis.redis.del(key);}/**
   * 書き込み
   * @param {string} key
   * @param {string} value
   * @param {number} expireSec
   */publicasyncset(key:string,value:string,expireSec:number):Promise<void>{if(!this.alive())return;awaitthis.redis.set(key,value,'EX',expireSec);}/**
   * 正規表現読み込み
   * @param {string }prefix
   */publicasynckeys(prefix:string):Promise<string[]|[]>{returnawaitthis.redis.keys(prefix);}/**
   * Creater
   * @param {ConnectionConfig} config
   */publicstaticcreate(config:ConnectionConfig):Driver{returnnewDriver(newIORedis.Cluster(config.nodes,{// この辺の設定は環境に合わせて指定redisOptions:{}}));}}/**
 * 接続設定インターフェイス
 * @interface
 */interfaceConnectionConfig{nodes:Array<{port:number,host:string}>}/**
 * コネクション状態
 * @enum
 */enumConnectionStatus{READY,CONNECTED,ERROR,RECONNECT}

参考

Redis Cluster自体の仕組みについて (非常にわかりやすかったです)
https://qiita.com/keitatata/items/44678ad472e61a4606c5


Viewing all articles
Browse latest Browse all 8691

Trending Articles