Redis のプロトコルの理解のために Redis client を書いてみた

reimpl/redis_client at main · ganmacs/reimpl · GitHub

Redis のプロトコルがシンプルでだというのを聞きつけた1のでいくつかのコマンドをサポートした Redis client を書いた。 Redis は普段仕事で使ってる割にはどういうプロトコルを使っているか知らなかったのでいい勉強になった。

使い方

ping, get, set, incr, decr, subscribe, publish をサポートしている。それ以外のコマンドはほぼ同じようなことの繰り返していてやる気が無くなった。 使い方は以下。https://github.com/ganmacs/reimpl/tree/main/redis_client/examples 以下に他のコマンドを使った例もある。

#[tokio::main]
async fn main() -> Result<(), RErr> {
    let mut client = client::connect("127.0.0.1:6379").await?;
    client.ping().await?;

    client.set("key1", "value".into()).await?;

    let r = client.get("key").await?;
    dbg!(r); // => "value"
}

Redis のプロトコル

仕様はこれ https://redis.io/topics/protocol 。聞いていたとおり使用は非常にシンプルだった。データ型は全部で5つ。

  • Simple String
  • Error
  • Integer
  • Bulk String
  • Array

クラアントはコマンドを Bulk Stirngs の Array な形でサーバに送り、サーバ側は何かしらのデータを返す。 サーバにデータを送るとき、データはシリアライズされて送られるがそれも非常にシンプル。 Simple String のときは 1byte 目が +、 Error の場合は 1byte 目が -、というという感じで 1byte 目を見ればデータ型がわかるようにシリアライズされる。 Simple String と Array だけはシリアライズされたとき長さを持ってないとデシリアライズできないので長さを持つ必要がある2。 あとは、区切り文字として \r\n が使われている。

例えば get コマンドをシリアライズして送る場合はだいたいこんなイメージになる。

["get", "ky"]

これをシリアライズすると以下になる。

*2\r\n$3\r\nGET\r\n$3\r\nky\r\n

読み方はこんな感じ。

*2\r\n         | $3\r\nGET\r\n                  | $2\r\nky\r\n
Array(size=2)  | Bulk String(size=3) (1st elem) | Bulk String(size=2) (2nd elem)

これをサーバに送ると結果が帰ってくる。 どんな結果が帰ってくるかは https://redis.io/commands/get の Return value を読むとわかる。

Bulk string reply: the value of key, or nil when key does not exist.

と書いてあるので、もし key が存在するならその値が返ってくる。なければ nil を返す。 例えば、値が value で キーが ky のデータが保存されているとすると、キーky で get コマンドを発行すると$5\r\nvalue\r\nを返す。

感想

久しぶりに Rust を書いて良かった。やはりこういうタイプのソフトウェアに向いた言語だなと思った。 Redis のプロトコルはどんなバイナリフォーマットが来るのかと思っていたら非常にわかりやすく簡単に実装できた。 次は Rebuilding Redis in Ruby をやって、このクライアントを使って話をさせたい。


  1. Rebuild.fm の episode 321 で話していたの気になっていた。

  2. https://redis.io/topics/protocol の RESP Bulk Strings のあたりに詳しく書いてある