Redis2

Name

ngx_redis2 - Redis 2.0プロトコルのためのNGINX upstreamモジュール。

注意

このモジュールはNGIXのソースと一緒に配布されません。 インストレーションの説明を見てください。

状態

このモジュールは既にプロダクションの準備ができています。

バージョン

この文章は2014年4月24日にリリースされたngx_redis2 v0.11 を説明します。

概要

location = /foo {
    set $value 'first';
    redis2_query set one $value;
    redis2_pass 127.0.0.1:6379;
}

# GET /get?key=some_key
location = /get {
    set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
    redis2_query get $key;
    redis2_pass foo.com:6379;
}

# GET /set?key=one&val=first%20value
location = /set {
    set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
    set_unescape_uri $val $arg_val;  # this requires ngx_set_misc
    redis2_query set $key $val;
    redis2_pass foo.com:6379;
}

# multiple pipelined queries
location = /foo {
    set $value 'first';
    redis2_query set one $value;
    redis2_query get one;
    redis2_query set one two;
    redis2_query get one;
    redis2_pass 127.0.0.1:6379;
}

location = /bar {
    # $ is not special here...
    redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
    redis2_pass 127.0.0.1:6379;
}

location = /bar {
    # variables can be used below and $ is special
    redis2_raw_query 'get one\r\n';
    redis2_pass 127.0.0.1:6379;
}

# GET /baz?get%20foo%0d%0a
location = /baz {
    set_unescape_uri $query $query_string; # this requires the ngx_set_misc module
    redis2_raw_query $query;
    redis2_pass 127.0.0.1:6379;
}

location = /init {
    redis2_query del key1;
    redis2_query lpush key1 C;
    redis2_query lpush key1 B;
    redis2_query lpush key1 A;
    redis2_pass 127.0.0.1:6379;
}

location = /get {
    redis2_query lrange key1 0 -1;
    redis2_pass 127.0.0.1:6379;
}

説明

これはNGINXにRedis 2.x サーバにブロッキング無しで通信させるNGINXアップストリームモジュールです。完全なRedis 2.0統合プロトコルはRedisパイプラインサポートを含めて実装されています。

Luaと接続するために使われる場合は、lua-resty-redis <openresty/lua-resty-redis>ライブラリが柔軟でメモリが効果的なため、このモジュールの代わりに使うことをお勧めします。

get redis コマンドを使いたいだけの場合は、HTTP Redisを試してみることができます。実装するには get だけが必要なため、Redis応答のパースされたコンテント部分を返します。

他の選択はクライアント側で自分自身でredis応答をパースすることです。

ディレクティブ

redis2_query

構文:redis2_query <cmd> [arg1] [arg2]...
デフォルト:none
コンテキスト:location, location if

redis-cli ユーティリティと似たやり方で個々の引数(Redisコマンド名自身も含む)を指定することがで、Redisコマンドを指定する。

一つのlocationにこのディレクティブの複数のインスタンスを置くことができ、これらのクエリはパイプラインされるでしょう。例えば:

location = /pipelined {
    redis2_query set hello world;
    redis2_query get hello;

    redis2_pass 127.0.0.1:$TEST_NGINX_REDIS_PORT;
}

そして、GET /pipelined は連続する2つの生のRedis応答をもたらすでしょう。

+OK
$5
world

while newlines here are actually CR LF (\r\n).

redis2_raw_query

構文:redis2_raw_query QUERY
デフォルト:none
コンテキスト:location, location if

Specify raw Redis queries and NGINX variables are recognized in the QUERY argument.

QUERY引数には 1つだけの Redis コマンドが許されます。そうでなければエラーを受け取るでしょう。一つのクエリ内で複数のパイプラインされたコマンドを指定したい倍アは、代わりにredis2_raw_queries ディレクティブを使ってください。

redis2_raw_queries

構文:redis2_raw_queries N QUERIES
デフォルト:none
コンテキスト:location, location if

QUERIES 引数にN コマンドを指定します。N および QUERIES 引数はNGINX変数を取ることができます。

幾つかの例です:

location = /pipelined {
    redis2_raw_queries 3 "flushall\r\nget key1\r\nget key2\r\n";
    redis2_pass 127.0.0.1:6379;
}

# GET /pipelined2?n=2&cmds=flushall%0D%0Aget%20key%0D%0A
location = /pipelined2 {
    set_unescape_uri $n $arg_n;
    set_unescape_uri $cmds $arg_cmds;

    redis2_raw_queries $n $cmds;

    redis2_pass 127.0.0.1:6379;
}

注意

上の2つ目の例の中で、set_unescape_uri ディレクティブはSet Miscによって提供されました。

redis2_literal_raw_query

構文:redis2_literal_raw_query QUERY
デフォルト:none
コンテキスト:location, location if

生のRedisクエリを指定しますが、その中のNGINX変数は理解されないでしょう。別の言い方をすると、QUERY引数の中で自由にドル記号文字($) を使っても構いません。

QUERY 引数には、1つのredisコマンドだけが許されます。

redis2_pass

構文:redis2_pass <upstream_name>
構文:redis2_pass <host>:<port>
デフォルト:none
コンテキスト:location, location if
Phase:content

Redisサーバのバックエンドを指定します。

redis2_connect_timeout

構文:redis2_connect_timeout <time>
デフォルト:60s
コンテキスト:http, server, location

Redisサーバへの接続のタイムアウト。デフォルトでは秒数です。

混乱を避けるために常に明示的に時間単位を設定するのが懸命です。時間単位は、s(seconds), ms(milliseconds), y(years), M(months), w(weeks), d(days), h(hours) および m(minutes) です。

この時間は597時間未満に設定すべきです。

redis2_send_timeout

構文:redis2_send_timeout <time>
デフォルト:60s
コンテキスト:http, server, location

RedisサーバにTCPリクエストを送信するためのタイムアウト。デフォルトでは秒数。

混乱を避けるために常に明示的に時間単位を設定するのが懸命です。時間単位は、s(seconds), ms(milliseconds), y(years), M(months), w(weeks), d(days), h(hours) および m(minutes) です。

redis2_read_timeout

構文:redis2_read_timeout <time>
デフォルト:60s
コンテキスト:http, server, location

redisサーバからTCP応答を受信するためのデフォルトで秒のタイムアウト。

混乱を避けるために常に明示的に時間単位を設定するのが懸命です。時間単位は、s(seconds), ms(milliseconds), y(years), M(months), w(weeks), d(days), h(hours) および m(minutes) です。

redis2_buffer_size

構文:redis2_buffer_size <size>
デフォルト:4k/8k
コンテキスト:http, server, location

このバッファサイズはRedis応答を読み込むために使われますが、Redisが応答できる最大のサイズにする必要はありません。

デフォルトのサイズはページサイズで、おそらく4kあるいは8kです。

redis2_next_upstream

構文:redis2_next_upstream [ error | timeout | invalid_response | off ]
デフォルト:error timeout
コンテキスト:http, server, location

どの失敗条件が他のupstreamサーバへの転送へのリクエストを発生させるべきかを指定します。redis2_passの値が2つ以上のサーバのupstreamの時のみ適用されます。

恣意的な例は以下のようになります:

upstream redis_cluster {
    server 127.0.0.1:6379;
    server 127.0.0.1:6380;
}

server {
    location = /redis {
        redis2_next_upstream error timeout invalid_response;
        redis2_query get foo;
        redis2_pass redis_cluster;
    }
}

接続プール

RedisのためのTCPコネクションプールを提供するためにこのモジュールを使って洗練されたkeepaliveを使うことができます。

例の設定の断片は以下のように見えます:

http {
    upstream backend {
      server 127.0.0.1:6379;

      # a pool with at most 1024 connections
      # and do not distinguish the servers:
      keepalive 1024;
    }

    server {
        ...
        location = /redis {
            set_unescape_uri $query $arg_query;
            redis2_query $query;
            redis2_pass backend;
        }
    }
}

Luaの相互運用性

このモジュールは Luaのための非ブロッキングredis2クライアントとして使うことができます (しかし、今では代わりにlua-resty-redisライブラリを使うことをお勧めします。これはほとんどの場合において使いやすく効果的です)。以下はGETサブリクエストを使う例です:

location = /redis {
    internal;

    # set_unescape_uri is provided by ngx_set_misc
    set_unescape_uri $query $arg_query;

    redis2_raw_query $query;
    redis2_pass 127.0.0.1:6379;
}

location = /main {
    content_by_lua '
        local res = ngx.location.capture("/redis",
            { args = { query = "ping\\r\\n" } }
        )
        ngx.print("[" .. res.body .. "]")
    ';
}

そして/main にアクセスすると以下を得ます

[+PONG\r\n]

ここで\r\nCRLFです。つまり、このモジュールは遠隔のredisサーバから生の TCP 応答を返します。

インラインのLuaコードを外部の.lua ファイルに移動する場合、エスケープシーケンス\r\nを直接使用することが重要です。NGINXの文字列リテラルに入れる場合にLuaコード自身をクォートする必要があるため、上では\\r\\nを使いました。

生のRedisリクエストをリクエストボディを使って転送するためにPOST/PUTサブリクエストを使うこともできます。これはURIのエスケープおよびアンエスケープを必要としないため、幾分CPUサイクルを節約します。以下はそのような例です:

location = /redis {
    internal;

    # $echo_request_body is provided by the ngx_echo module
    redis2_raw_query $echo_request_body;

    redis2_pass 127.0.0.1:6379;
}

location = /main {
    content_by_lua '
        local res = ngx.location.capture("/redis",
            { method = ngx.HTTP_PUT,
              body = "ping\\r\\n" }
        )
        ngx.print("[" .. res.body .. "]")
    ';
}

これは、以前(GET)の例のように完全に同じ出力をもたらすでしょう。

幾分複雑なハッシュルールに基づいて具体的なRedisバックエンドをピックアップするようにLuaを使うこともできます。例えば、

upstream redis-a {
    server foo.bar.com:6379;
}

upstream redis-b {
    server bar.baz.com:6379;
}

upstream redis-c {
    server blah.blah.org:6379;
}

server {
    ...

    location = /redis {
        set_unescape_uri $query $arg_query;
        redis2_query $query;
        redis2_pass $arg_backend;
    }

    location = /foo {
        content_by_lua "
            -- pick up a server randomly
            local servers = {'redis-a', 'redis-b', 'redis-c'}
            local i = ngx.time() % #servers + 1;
            local srv = servers[i]

            local res = ngx.location.capture('/redis',
                { args = {
                    query = '...',
                    backend = srv
                  }
                }
            )
            ngx.say(res.body)
        ";
    }
}

Luaによってパイプライン化されたRedisリクエスト

このNGINXモジュールを使って複数のパイプライン化されたRedisのリクエストを発行するためにLuaを使用する方法の完全な実証例です。

まずは、以下をnginx.conf ファイルを含みます:

location = /redis2 {
    internal;

    redis2_raw_queries $args $echo_request_body;
    redis2_pass 127.0.0.1:6379;
}

location = /test {
    content_by_lua_file conf/test.lua;
}

Basically we use URI query args to pass the number of Redis requests and request body to pass the pipelined Redis request string.

そして、以下のLuaコードを含むために、conf/test.lua ファイルを作成します (このパスはNGINXのサーバrootへの相対です):

-- conf/test.lua
local parser = require "redis.parser"

local reqs = {
    {"set", "foo", "hello world"},
    {"get", "foo"}
}

local raw_reqs = {}
for i, req in ipairs(reqs) do
    table.insert(raw_reqs, parser.build_query(req))
end

local res = ngx.location.capture("/redis2?" .. #reqs,
    { body = table.concat(raw_reqs, "") })

if res.status ~= 200 or not res.body then
    ngx.log(ngx.ERR, "failed to query redis")
    ngx.exit(500)
end

local replies = parser.parse_replies(res.body, #reqs)
for i, reply in ipairs(replies) do
    ngx.say(reply[1])
end

ここで、Redisサーバがlocalhostのデフォルトのポート(6379)でlistenしていると仮定します。

curlのようなHTTPクライアントを使って/test location にアクセスすると、以下の出力を得ます。

OK
hello world

もっと現実的な設定はRedisバックエンドのための適切なupstream定義を使うことで、その中でkeepalive ディレクティブを使ってTCP接続プールを有効にします。

Redis Publish/Subscribe サポート

このモジュールはRedis publish/subscribe 機能のための制限されたサポートを持ちます。RESTおよびHTTPモデルのステートレス特性により、完全にはサポートされません。

次の例を考えます:

location = /redis {
    redis2_raw_queries 2 "subscribe /foo/bar\r\n";
    redis2_pass 127.0.0.1:6379;
}

そして、redis-cliコマンドライン内の /foo/barキーのためのメッセージをpublishします。そして、/redis locationから2つのmulti-bulk応答を受け取るでしょう。

Redis Publish/Subscribe の制限

このモジュールを使ってRedis pub/sub 機能を使いたい場合は、以下の制限に注意する必要があります:

  • このRedis upstreamを使ってkeepaliveを使うことができません。短い Redis 接続のみが動作するでしょう。
  • There may be some race conditions that produce the harmless Redis server returned extra bytes warnings in your NGINX’s error.log. そのような警告は滅多にないですが、それに備えます。
  • このモジュールによって提供される redis2_connect_timeout および redis2_read_timeoutのような様々なタイムアウト設定を調整しなければなりません。

それらの制限に耐えられない場合は、Luaのためのlua-resty-redis ライブラリに切り替えることをお勧めします。

パフォーマンス調整

  • このモジュールを使っている場合は、できる限りTCP接続プール(keepaliveによって提供される)とRedisパイプラインを使うようにしてください。これらの機能はとてもパフォーマンスを改善するでしょう。
  • 一つのRedisサーバインスタンスの連続する処理特性のために、マルチコアのマシーン上で複数のRedisサーバのインスタンスを使うこともとても役に立つでしょう。
  • ab または http_loadのようなものを使ってパフォーマンスをベンチマークしている場合は、NGINXワーカーがerror.logにフラッシュするのに多くのサイクルを消費するのを避けるために、エラーログのレベルが(warnのように)十分高くするようにしてください。error.logは常にバッファされずブロックされ、従って高価なものになります。

インストール

(NGINXコアおよび他の多くの良いものと同じく)このモジュールをngx_openresty bundleを使ってインストールすることをお勧めします。ngx_openrestyをセットアップするためにインストールの説明 を調べてください。

別のやり方として、以下のように標準のNGINXコアを再コンパイルすることでこのモジュールを手動でインストールすることができます:

  • nginx.orgから、例えばバージョン1.7.4のNGINXソースコードを取り出します(互換性を見てください)。

  • そして、ngx_redis2の ファイルリストからこのモジュールのリリースtarballの最新バージョンをダウンロードします。

  • そして、最後にこのモジュールを使ってソースをビルドします:

    wget 'http://nginx.org/download/nginx-1.7.4.tar.gz'
    tar -xzvf nginx-1.7.4.tar.gz
    cd nginx-1.7.4/
    
    # Here we assume you would install you nginx under /opt/nginx/.
    ./configure --prefix=/opt/nginx \
                --add-module=/path/to/redis2-nginx-module
    
    make -j2
    make install
    

互換性

Redis 2.0, 2.2, 2.4 以上は何も問題無く、このモジュールで動作するでしょう。Alchemy Databaseもそうです。
(aka redisql in its early days).

NGINXの以下のバージョンがこのモジュールで動作するはずです:

  • 1.7.x (最後のテスト: 1.7.4)
  • 1.6.x
  • 1.5.x (最後のテスト: 1.5.12)
  • 1.4.x (最後のテスト: 1.4.3)
  • 1.3.x (最後のテスト: 1.3.7)
  • 1.2.x (最後のテスト: 1.2.7)
  • 1.1.x (最後のテスト: 1.1.5)
  • 1.0.x (最後のテスト: 1.0.10)
  • 0.9.x (最後のテスト: 0.9.4)
  • 0.8.x >= 0.8.31 (最後のテスト: 0.8.54)

NGINXの前のバージョンは動作しないでしょう。

0.8.31以上のNGINXの特定のバージョンでこのモジュールが動作しないことを見つけたら、バグの報告を考えてみてください。

コミュニティ

英語のメーリングリスト

openresty-en メーリングリストは英語を話す人のためのものです。

中国語のメーリングリスト

openresty メーリングリストは中国語を話す人のためのものです。

バグとパッチ

バグレポート、欲しいもののリスト、あるいはパッチを下記でサブミットしてください

  1. GitHub Issue Tracker上でチケットを作成
  2. あるいは、OpenResty communityにポスト。

ソースリポジトリ

github上のopenresty/redis2-nginx-moduleで利用可能です。

TODO

  • JSONを直接発行できるようにredis2_as_jsonディレクティブを追加します。

Author

Yichun “agentzh” Zhang (章亦春) <agentzh@gmail.com>, CloudFlare Inc.

Getting involved

Authorにパッチをサブミット、あるいはGitHub上のSource Repositoryに少しコミットするように依頼することをとても歓迎します。

以下も見てください