Edited at

ざっくり分かるRedis入門 ~データ構造と主な機能について~

勤務先のプロダクトでRedisと呼ばれるミドルウェアがキャッシュサーバとして使われているが

雰囲気でやってたので体系的に学んでみる事にした。

(記事内にテーブルを多用してしまったのでPCでの閲覧を推奨。スマホだとテーブルは見にくいですね・・・)


Redisとは?

インメモリ Key-Value ストア

インメモリなので、Redisを再起動すると格納したデータが全て消える。(揮発性)

(後述の永続化を行えばその限りではない)


Redisのアーキテクチャ

Redis ServerRedis 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-policynoevictionになっており当該機能は使われない。


transactions

コマンドをまとめて発行する仕組み(別名パイプライン)

RDBMSとは異なり、ロールバックやロックは出来ない。

1連の処理の原子性は保証される(全て実行 or 全て未実行)が、データの整合性は担保されない。

コマンドをまとめて発行するのでラウンドトリップが少なくなりパフォーマンスが向上する

MULTIコマンドでトランザクションを開始する

EXECコマンドでトランザクション内のコマンドをまとめて実行する。


persistence

Redisを再起動してもデータを維持するための仕組み。

RDBAOFと呼ばれる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ってキャッシュサーバ以外にも使えるんだぜ!!!