2019/03/27

手を動かして学ぶ Redis 入門

会社でRedisを使っているサービスがあり、そのメンテナーになった。RedisがIn-Memory Databaseということは知っていたのだが、その他の特徴や操作方法などまったくわからないので、チュートリアルを中心に手を動かしながら学んだことをまとめていく。またNode.jsからRedisにアクセスする方法もあわせて紹介する。


Redis の特徴


Redisはメモリー上にデータを保存するKey-Value型のNoSQLデータベースのひとつ。用途はデータベースだけにとどまらず、キャッシュやメッセージブローカーとしても利用される。

In-Memory Database

RedisはIn-Memory Databaseなので、On-Disk Databaseと比べ非常に高速に動作する。ちなみにIn-Memory DatabaseとOn-Disk Databaseの違いは以下のとおり。
  • インメモリデータベース(In-Memory Database, Main-Memory Database, IMDM)
    • メモリー上にデータを格納するDBMS(データベース管理システム)
    • オンディスクデータベースと比較すると、高速で安定したパフォーマンスを提供できる
    • 揮発性メモリー上で動作する性質上、電源喪失などによりデータが失われるリスクがある(永続化も可能)
  • オンディスクデータベース(On-Disk Database, Disk-Based Database)
    • ディスク上にデータを格納するDBMS
    • ディスクに保存するので、データが失われるリスクは最小限に抑えられる
    • インメモリデータベースと比較すると、ディスクにアクセスする分、低速になる
  • ハイブリッド型データベース
    • インメモリデータベースとオンディスクデータベースのいいとこ取り


シングルスレッドで動作する

Redisはシングルスレッドで動作するため、必然的にアトミックなデータ操作(排他的)になる。

その反面、CPUのコア数が増えてもスケールしないため、CPU使用率がボトルネックになりやすい。マルチコアで使いたい場合は、コア数分のRedisサーバーを起動する必要がある。

Luaスクリプティング

Redis上でスクリプト言語のLuaを実行できる。あらかじめRedisにスクリプトを登録しておくことで、高度な計算処理などをRedis側に任せることができる。

ただし、シングルスレッドという特徴があるので、スクリプト実行中は他のリクエストをブロックしてしまう。


レプリケーション

MASTER-SLAVE型のレプリケーションを構築できる。マスターは複数のスレーブを持つことができ、非同期でレプリケーションを行っている。そのため完全に動悸されるまでの間はマスターとスレーブに差異が生じることがある。


データの永続化

Redisはデータの永続化オプションを提供している。

  • 揮発性ベース
    • メモリーでデータを管理する
    • サーバーを落とすとすべてのデータが失われる
  • RDBベース
    • 特定間隔でデータのスナップショットを保存する
    • バックアップファイルがコンパクト
    • 永続化はできるが、非同期スナップショットなので一部データが失われる可能性がある
  • AOFベース
    • サーバーが受け付けたすべての書き込みコマンドを保存する
    • デフォルトは毎秒同期保存する
      • fsyncを行わない、毎秒fsyncする、すべてのクエリごとにfsyncするから選択できる
    • RDBファイルよりも大きくなる


などなど。
他にもクラスタ機能やPub/Sub機能などがある。



Redisをインストールする


Redisを触るためにインストールしなければならないのだが、ローカルマシンに直接インストールするのは気が引けるのでDockerを使う。イメージはRedisのOfficial Image
# インストール
$ docker run -p 6379:6379 --name learning-redis -d redis

# コンテナにログイン
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                          NAMES
71d9b07574f4        redis               "docker-entrypoint.s…"   3 seconds ago       Up 1 second         0.0.0.0:6379->6379/tcp         learning-redis

$ docker exec -it learning-redis /bin/bash

これでredis-server, redis-cliが使えるようになった。
他の方法でインストールする場合は、Downloadドキュメントを参照ください。



Redisを起動する


# Redisサーバーを起動する
$ redis-server
23:C 15 Feb 2019 07:05:26.641 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
23:C 15 Feb 2019 07:05:26.641 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=23, just started
23:C 15 Feb 2019 07:05:26.641 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
23:M 15 Feb 2019 07:05:26.642 # Could not create server TCP listening socket *:6379: bind: Address already in use

# Redisサーバーが動いているか確認する
$ redis-cli ping
PONG
引数なしでredis-serverコマンドを実行すると、デフォルト設定の状態でRedisが起動する。通常は/etc/redis.confに設定ファイルがあるのだが、Dockerのオフィシャルイメージには含まれていないので注意。

参考: Additionally, If you want to use your own redis.conf | redis - Docker Hub




Redisのデータ構造


Redisはいくつかのデータ構造がサポートしている。
  • 文字列型: Binary-safe strings
    • すべてのデータの基本となる型
    • バイナリセーフでどんな値でも扱える
    • 最大1GBまで
  • リスト型: Lists(Linked list)
    • 文字列型のリスト
    • 先頭/末尾に値の追加ができる
    • リスト長をキャッシュして高速に取得できる
  • セット型: Sets
    • 文字列型の順不同のコレクション
    • 同じメンバを重複して登録できない
    • 2つのセットで集合演算が使える
  • ソート済みセット型: Sorted sets
    • 基本はセット型と同じ
    • スコアの値でソートされた順位を持っている
  • ハッシュ型: Hashes
    • 順序がない文字列型のフィールドと値のマップ
    • フィールド値で検索できる
    • 値指定の検索は不可
  • Bit arrays
    • 特殊なコマンドで文字列をビット配列のように扱える
  • HyperLogLogs
    • 確率的なデータ構造
  • Streams
    • 時系列データを扱う
    • インデックスにはタイムスタンプを使う


各データ型の基本操作(追加/取得/削除)


# Redisに接続
$ redis-cli
127.0.0.1:6379>

文字列型

# データの追加: set key value
127.0.0.1:6379> set mykey somevalue
OK

# データの取得: get key
127.0.0.1:6379> get mykey
"somevalue"

# データの更新: set key value [xx|nx]
## 上書きを許可する場合
127.0.0.1:6379> set mykey hoge
OK
# or
127.0.0.1:6379> set mykey hoge xx
OK
127.0.0.1:6379> get mykey
"hoge"

## 上書きを許可しない場合
127.0.0.1:6379> set mykey fuga nx
(nil)
127.0.0.1:6379> get mykey
"hoge"

# まとめて追加: mset key value [key value ...]
127.0.0.1:6379> mset a hoge b fuga c piyo
OK

# まとめて取得: mget key [key ...]
127.0.0.1:6379> mget a b c
1) "hoge"
2) "fuga"
3) "piyo"

# 削除: del key [key ...]
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> get mykey
(nil)

リスト型

# リストの末尾に追加: rpush key value [value ...]
127.0.0.1:6379> rpush mylist A
(integer) 1
127.0.0.1:6379> rpush mylist B
(integer) 2

# リストの先頭に追加: lpush key value [value ...]
127.0.0.1:6379> rpush mylist first

# リストの要素を取得: lrange key start stop
# start, stopはリストのインデックスを指定。負の値は末尾からカウント(-1は最後の要素、-2は最後から2番目の要素)
127.0.0.1:6379> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"

# リストの末尾から要素を取り出す: rpop key
127.0.0.1:6379> rpop mylist
"B"

127.0.0.1:6379> lrange mylist 0 -1
1) "first"
2) "A"

# リストの先頭から要素を取り出す: lpop key
127.0.0.1:6379> lpop mylist
"first"

127.0.0.1:6379> lrange mylist 0 -1
1) "A"

セット型

# セットの追加: sadd key member [member ...]
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3

# メンバーを取得: smembers key
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"

# セット内に含まれるメンバーか確認: sismember key member
127.0.0.1:6379> sismember myset 2
(integer) 1

127.0.0.1:6379> sismember myset 20
(integer) 0

ソート済みセット型

# ソート済みセットの追加: zadd [nx|xx] [ch] [incr] score member [score member ...]
127.0.0.1:6379> zadd hackers 1940 "Alan Kay"
(integer) 1
127.0.0.1:6379> zadd hackers 1957 "Sophie Wilson" 1953 "Richard Stallman" 1949 "Anita Borg"
(integer) 3

# ソート済みセットの取得: zrange key start stop [withscores]
# scoreでソートされた状態で取得できる
127.0.0.1:6379> zrange hackers 0 -1
1) "Alan Kay"
2) "Anita Borg"
3) "Richard Stallman"
4) "Sophie Wilson"

# 逆順に取得: zrevrange key start stop [withscores]
127.0.0.1:6379> zrevrange hackers 0 -1
1) "Sophie Wilson"
2) "Richard Stallman"
3) "Anita Borg"
4) "Alan Kay"

# スコア付きで取得
127.0.0.1:6379> zrange hackers 0 -1 withscores
1) "Alan Kay"
2) "1940"
3) "Anita Borg"
4) "1949"
5) "Richard Stallman"
6) "1953"
7) "Sophie Wilson"
8) "1957"

ハッシュ型

# ハッシュの追加: hmset key field value [field value ...]
127.0.0.1:6379> hmset user:1000 username bc_rikko age 19 verified 1
OK

# ハッシュのフィールドを取得: hget key field [field ...]
127.0.0.1:6379> hget user:1000 username
"bc_rikko"
127.0.0.1:6379> hget user:1000 age
"19"

# すべて取得: hgetall key
127.0.0.1:6379> hgetall user:1000
1) "username"
2) "bc_rikko"
3) "age"
4) "19"
5) "verified"
6) "1"


よく使うコマンド

# 登録されているKeyを取得: keys pattern
127.0.0.1:6379> keys *
1) "hackers"
2) "a"
3) "c"
4) "b"
5) "user:1000"
6) "myset"
7) "mylist"

# globで指定できる
127.0.0.1:6379> keys my*
1) "myset"
2) "mylist"


# ヘルプを表示: help command
127.0.0.1:6379> help set

  SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
  summary: Set the string value of a key
  since: 1.0.0
  group: string

# 全ヘルプを表示
127.0.0.1:6379> help @list

  BLPOP key [key ...] timeout
  summary: Remove and get the first element in a list, or block until one is available
  since: 2.0.0

  BRPOP key [key ...] timeout
  summary: Remove and get the last element in a list, or block until one is available
  since: 2.0.0

  BRPOPLPUSH source destination timeout
  summary: Pop a value from a list, push it to another list and return it; or block until one is available
  since: 2.2.0

......


Node.jsからRedisに接続する。


redis-cliを使った基本的な操作は学んだので、次はNode.jsからRedisに接続する。

公式ドキュメントのクライアントページにいくつかサードパーティ製のライブラリが紹介されている。公式的にはioredisnode_redisを推奨しているみたい。

今回はioredisを使う。理由はなんとなく。


# ライブラリのインストール
$ npm i ioredis

const Redis = require("ioredis");
// 接続
const redis = new Redis({
  host: "127.0.0.1",
  port: 6379
});

redis.on("error", console.error);
redis.on("connect", () => {
  console.info("Connected");
});
redis.on("close", () => {
  console.info("Disconnected");
});

// データ保存
redis.set("key", "value");
// データ取得
redis
  .get("key")
  .then(result => {
    console.log(result);
  })
  .catch(err => {
    console.error(err);
  });

// 切断
redis.quit();



参考サイト






以上

written by @bc_rikko

0 件のコメント :

コメントを投稿