勤務先のプロダクトでRedisと呼ばれるミドルウェアがキャッシュサーバとして使われているが
雰囲気でやってたので体系的に学んでみる事にした。
(記事内にテーブルを多用してしまったのでPCでの閲覧を推奨。スマホだとテーブルは見にくいですね・・・)
Redisとは?
インメモリ Key-Value ストア
インメモリなので、Redisを再起動すると格納したデータが全て消える。(揮発性)
(後述の永続化を行えばその限りではない)
Redisのアーキテクチャ
Redis ServerとRedis Clientがある
Redis Serverは(デフォルトでは)6379
ポートで待ち受ける
この6379ポートにtelnetで接続してコマンドを実行する事でredis serverを操作できる。
シングルスレッドのイベントドリブンで動作する。
CPUは1コアのみ使用する。
Redisで扱えるデータ構造
RedisではデータをKeyとValueのペアで保存する。
Valueとして扱えるデータ構造がいくつかある。
名称 | 説明 | 主な操作コマンド |
---|---|---|
strings | 文字列。バイナリセーフなので画像なども保存できる。ただし最大1GB |
SET GET
|
hashes | フィールドと値のマップ。 |
HSET HGET
|
lists | 文字列型のリスト。 |
LPUSH RPUSH LRANGE
|
sets | 順不同の集合。メンバの重複を許可しない。 |
SADD SINTER SUNION
|
sorted sets | 文字列型の集合。 |
ZADD ZREM
|
bit arrays | bitの配列。ビット単位で操作できる。 | BITFIELD |
hyperloglogs | ユニークなデータを数えるためのデータ構造。HLLアルゴリズムを使用し高速かつ省メモリで推定できる |
PFADD PFCOUNT
|
geospatial indexes | 位置情報(緯度、経度、名前) 。中心位置と半径を指定して検索などが出来る。 |
GEOADD GEORADIUS
|
streams | 時系列データ。発生時刻+フィールドと値のマップ。 |
XADD XREVRANGE
|
コマンドの接頭辞を見れば扱うデータ構造が大体わかる。Hならハッシュ、Xなら時系列データ、など。
これらのデータ構造を上手く使うことでRedisを単なるキャッシュサーバとしてではなく
排他制御のロック管理やソート処理に活用する事ができる
設定ファイルについて
設定ファイルは、/etc/redis.conf
にある。
dockerコンテナの場合は、/usr/local/etc/redis/redis.conf
にある。
以下の形式で設定を記述する(ディレクティブと呼ばれる)
keyword argument1 argument2 ... argumentN
ファイルが存在しない場合は、デフォルトの設定が使用される。
設定ディレクティブの一覧、およびその意味と使用目的は以下にある(Redis Version 5.0.3の場合)
redis/redis.conf at 5.0.3 · antirez/redis · GitHub
Redisの主な機能
大まかに7つある
| 名称 | 説明 |
|---|---|---|
| replication | マスタースレーブ間でデータを同期する仕組み |
| Lua scripting | EVAL
コマンドとEVALSHA
コマンドでLuaスクリプトを実行できる |
| LRU eviction | メモリ使用量が上限に達した際に、使われていないデータを追い出してメモリを確保する仕組み。 |
| transactions | コマンドをまとめて発行する仕組み(別名パイプライン) |
| persistence | Redisサーバを再起動してもデータを維持するための仕組み。|
| Redis Sentinel |Redisサーバの死活監視/通知および自動フェイルオーバー機能を提供する仕組み|
| Redis Cluster | 複数のRedisサーバにデータを分散させる仕組み|
replication
マスタースレーブ間でデータを同期する仕組み
マスタースレーブ間のリンクが切れた場合、スレーブは自動的にマスターに再接続する。
可用性を向上させるために使用する。
Lua scripting
EVAL
コマンドとEVALSHA
コマンドでLuaスクリプトを実行できる
EVAL
コマンドはLuaスクリプトを直接実行できる
EVALSHA
コマンドはあらかじめ登録しておいたLuaスクリプトを呼び出す事ができる。
SCRIPT LOAD
コマンドを使用してLuaスクリプトを登録する。
登録時にSHA1ハッシュが発行されるので、これを使って、EVALSHA
コマンドで呼び出す事ができる。
アプリケーションからSHA1ハッシュでLuaスクリプトを呼び出す事で
「Luaでデータを加工してから保存」といった事ができる。
実行中は他のリクエストをブロックする。
LRU eviction
メモリ使用量が上限に達した際に、使われていないデータを追い出してメモリを確保する仕組み。
LRUとはLeast Recently Usedの略で「最近最も使われなかったもの」を表す
maxmemory
ディレクティブでメモリ使用量の上限を設定できる。
メモリ使用量の上限に達した際のふるまいはmaxmemory-policy
ディレクティブで設定できる。
デフォルトでは、maxmemory-policy
がnoeviction
になっており当該機能は使われない。
transactions
コマンドをまとめて発行する仕組み(別名パイプライン)
RDBMSとは異なり、ロールバックやロックは出来ない。
1連の処理の原子性は保証される(全て実行 or 全て未実行)が、データの整合性は担保されない。
コマンドをまとめて発行するのでラウンドトリップが少なくなりパフォーマンスが向上する
MULTI
コマンドでトランザクションを開始する
EXEC
コマンドでトランザクション内のコマンドをまとめて実行する。
persistence
Redisを再起動してもデータを維持するための仕組み。
RDB
とAOF
と呼ばれる2つの方式がある
| 方式 | 説明 |
|---|---|---|
| RDB | 指定した間隔でスナップショットを作成する |
| AOF | すべての書き込みコマンドを記録する。起動時にログをリプレイし、元のデータを再構成する。 |
RDB
に関連する設定(ディレクティブ)
ディレクティブ | 説明 | デフォルト値 |
---|---|---|
save | 保存間隔 | save 900 1 save 300 10 save 60 10000 |
dbfilename | ファイル名 | dump.rdb |
dir | 保存先ディレクトリ(AOFと共通) | ./ |
rdbcompression | 圧縮を行うか | yes |
AOF
に関連する設定(ディレクティブ)
ディレクティブ | 説明 | デフォルト値 |
---|---|---|
appendonly | AOFを有効にするかどうか | no |
appendfilename | ファイル名 | no |
dir | 保存先ディレクトリ(RDBと共通) | ./ |
appendfsync | ディスクにフラッシュするタイミング | everysec(毎秒) |
auto-aof-rewrite-percentage | AOFファイルのサイズが何%増えたらAOFファイルの再構築を行うか | 100 |
auto-aof-rewrite-min-size | AOFファイルが指定サイズ以下の場合は再構築を行わない。 | 64mb |
Redis Sentinel
Redisサーバの死活監視/通知および自動フェイルオーバー機能を提供する仕組み
Redis Cluster
複数のRedisサーバ(ノードと呼ぶ)にデータを分散させる仕組み(シャーディング)
データのキー値に応じて格納先のノードを振り分ける。
同じデータが複数のノードに格納される事はない。
とりあえず触ってみる
ひとまずdockerでredisサーバを立てる。
Docker Hub
docker run --name some-redis -d redis
サーバを立てたものの、接続するためのクライアントが無い事に気づいた
調べるとredis-cliというツールがあるらしい
他にもGUIのツールなどもあるらしいが、まずは、スタンダードっぽいredis-cliを使ってみる。
Docker Hubを眺めていると、上記で立てたコンテナに対して
redis-cliで接続できる起動方法を見つけたのでこれを試す。
docker run -it --link some-redis:redis --rm redis redis-cli -h redis -p 6379
こんな感じでプロンプトが表示された
ここからredisのコマンドを叩くことでデータを追加したり削除したりできそう。
コマンドリファレンスに従っていくつか叩いてみる
コマンドリファレンス — redis 2.0.3 documentation
まずはINFO
サーバの情報が表示される。
redisのバージョンは5.0.3らしい。
値に有効期限
を持たせて、有効期限を超えた値は自動的に削除させたりできるらしい。
PHPからRedisサーバに接続してみる
phpからredisを扱うためのライブラリは多数ある
PHP Bindings
Laravelではpredis/predisパッケージとRedis PHP拡張をサポートしている。
PHP拡張の方が処理が高速だが、インストールが複雑。
今回は前者のpredisパッケージからredisを触ってみる。
以下が、試しに書いてみたPHPスクリプトです。
いくつかのデータ構造を登録して取得してvar_dumpで出力しています。
出力はvar_dumpの下の行にコメント記載しています。
<?php
require_once 'vendor/autoload.php';
// Redisサーバに接続
$redis = new Predis\Client([
'host' => 'some-redis',
'port' => 6379,
]);
// redisの全データ削除
$redis->flushdb();
/*
* データ構造:文字列
*/
$redis->set('my-key', 'my-value');
var_dump($redis->get('my-key'));
// 出力: 'my-value'
/*
* データ構造:リスト
*/
$redis->rpush('my-list', 'first');
$redis->rpush('my-list', 'second');
$redis->rpush('my-list', 'third');
var_dump($redis->llen('my-list'));
// 出力: 3
var_dump($redis->lrange('my-list', 0, 2));
// 出力: 'first', 'second', 'third'
/*
* データ構造:セット
*/
$redis->sadd('my-set', 'alpha');
$redis->sadd('my-set', 'bravo');
$redis->sadd('my-set', 'charlie');
var_dump($redis->smembers('my-set'));
// 出力: 'alpha', 'charlie', 'bravo'
/*
* データ構造:ハッシュ
*/
$redis->hmset('my-hash', [
'title' => 'redisを分かった気になる本',
'author' => 'me',
'publisher' => 'sya-syu-syo',
'publish-date' => '2018-01-01 01:01:01',
]);
var_dump($redis->hmget('my-hash', [
'title',
'author',
'publisher'
]));
// 出力: 'redisを分かった気になる本', 'me', 'sya-syu-syo'
/**
* Pipelineを使用して、複数のコマンドをまとめて発行する
*/
$replies = $redis->pipeline(function($pipe) {
$pipe->incr('my-counter');
$pipe->incr('my-counter');
$pipe->incr('my-counter');
$pipe->incr('my-counter');
$pipe->incr('my-counter');
});
var_dump($redis->get('my-counter'));
// 出力: 5
まとめ
Redisってキャッシュサーバ以外にも使えるんだぜ!!!