twemproxyでRedisのパーティショニングを実装する


## はじめに

twemproxyを使用してRedisのパーティショニングを実装する方法を記述します。

## 環境

* CentOS 6.5
* Redis 2.8.16
* twemproxy 0.3.0

## Redisの構成

|役割  |名称   |ポート|
|:-----|:------|:-----|
|master|redis  |6379  |
|master|redis02|6380  |
|master|redis03|6381  |

Redisのインストールについては、[RedisをSentinelで監視する](http://qiita.com/toshiro3/items/931ededeece6b8b1c680)をご参照ください
※ Redisのスレーブ設定を削除し、Sentinelを停止した状態にしています。

## twemproxyによるパーティショニング

* twemproxyのインストール

```shell
$ git clone https://github.com/twitter/twemproxy.git
$ cd twemproxy
$ autoreconf -fvi
$ ./configure --prefix=/usr/local/nutcracker 2>&1 | tee configure.log
$ make 2>&1 | tee make.log
$ sudo sh -c "make install 2>&1 | tee make-install.log"
$ sudo mkdir -p /usr/local/nutcracker/conf
```
* twemproxyの設定ファイルを作成します。

```yaml:/usr/local/nutcracker/conf/nutcracker.yml
alpha:
  listen: 127.0.0.1:22121
  hash: fnv1a_64
  distribution: ketama
  auto_eject_hosts: true
  redis: true
  server_retry_timeout: 2000
  server_failure_limit: 1
  servers:
   - 127.0.0.1:6379:1
   - 127.0.0.1:6380:1
   - 127.0.0.1:6381:1
```

* 設定ファイルの検証をしておきます。

```shell
$ sudo /usr/local/nutcracker/sbin/nutcracker -t -c /usr/local/nutcracker/conf/nutcracker.yml
nutcracker: configuration file '/usr/local/nutcracker/conf/nutcracker.yml' syntax is ok
```

* twemproxyを起動します。

```shell
$ sudo /usr/local/nutcracker/sbin/nutcracker     \
    -c /usr/local/nutcracker/conf/nutcracker.yml \
    -d                                           \
    -o /var/log/nutcracker.log                   \
    -p /var/run/nutcracker.pid
```
* tewmproxyの状態を確認します。

```shell
$ telnet 127.0.0.1 22222 2> /dev/null | tail -n 1 | jq '.'
{
  "alpha": {
    "127.0.0.1:6381": {
      "out_queue_bytes": 0,
      "out_queue": 0,
      "in_queue_bytes": 0,
      "in_queue": 0,
      "response_bytes": 0,
      "server_eof": 0,
      "server_err": 0,
      "server_timedout": 0,
      "server_connections": 0,
      "server_ejected_at": 0,
      "requests": 0,
      "request_bytes": 0,
      "responses": 0
    },
    "client_eof": 0,
    "client_err": 0,
    "client_connections": 0,
    "server_ejects": 0,
    "forward_error": 0,
    "fragments": 0,
    "127.0.0.1:6379": {
      "out_queue_bytes": 0,
      "out_queue": 0,
      "in_queue_bytes": 0,
      "in_queue": 0,
      "response_bytes": 0,
      "server_eof": 0,
      "server_err": 0,
      "server_timedout": 0,
      "server_connections": 0,
      "server_ejected_at": 0,
      "requests": 0,
      "request_bytes": 0,
      "responses": 0
    },
    "127.0.0.1:6380": {
      "out_queue_bytes": 0,
      "out_queue": 0,
      "in_queue_bytes": 0,
      "in_queue": 0,
      "response_bytes": 0,
      "server_eof": 0,
      "server_err": 0,
      "server_timedout": 0,
      "server_connections": 0,
      "server_ejected_at": 0,
      "requests": 0,
      "request_bytes": 0,
      "responses": 0
    }
  },
  "curr_connections": 1,
  "total_connections": 1,
  "timestamp": 1410967210,
  "uptime": 2569,
  "version": "0.3.0",
  "source": "localhost.localdomain",
  "service": "nutcracker"
}
```

出力された項目の内容は以下で確認できます。

```shell
$ /usr/local/nutcracker/sbin/nutcracker --describe-stats
This is nutcracker-0.3.0

pool stats:
  client_eof          "# eof on client connections"
  client_err          "# errors on client connections"
  client_connections  "# active client connections"
  server_ejects       "# times backend server was ejected"
  forward_error       "# times we encountered a forwarding error"
  fragments           "# fragments created from a multi-vector request"

server stats:
  server_eof          "# eof on server connections"
  server_err          "# errors on server connections"
  server_timedout     "# timeouts on server connections"
  server_connections  "# active server connections"
  server_ejected_at   "timestamp when server was ejected in usec since epoch"
  requests            "# requests"
  request_bytes       "total request bytes"
  responses           "# responses"
  response_bytes      "total response bytes"
  in_queue            "# requests in incoming queue"
  in_queue_bytes      "current request bytes in incoming queue"
  out_queue           "# requests in outgoing queue"
  out_queue_bytes     "current request bytes in outgoing queue"
```

## 動作確認

* twemproxy経由でRedisへデータをsetします。

```shell
$ /usr/local/redis/bin/redis-cli -p 22121 set key value
OK
$ /usr/local/redis/bin/redis-cli -p 22121 get key
"value"
```

* Redisのデータを確認します。

```shell
$ /usr/local/redis/bin/redis-cli -p 6379 get key
(nil)
$ /usr/local/redis/bin/redis-cli -p 6379 dbsize
(integer) 0
```

* Redis02のデータを確認します。

```shell
$ /usr/local/redis/bin/redis-cli -p 6380 get key
(nil)
$ /usr/local/redis/bin/redis-cli -p 6380 dbsize
(integer) 0
```

* Redis03のデータを確認します。

```shell
$ /usr/local/redis/bin/redis-cli -p 6381 get key
"value"
$ /usr/local/redis/bin/redis-cli -p 6381 dbsize
(integer) 1
```

* twemproxy経由でRedisにデータを投入します。

```bash:set_test_data.sh
#!/bin/sh

redis_cli=/usr/local/redis/bin/redis-cli
proxy_port=22121

datestr=$(date +"%s")

for i in {1..1000}
do
  # 数字のみの任意の文字列(長さ10)を生成します。
  val=$(cat /dev/urandom | tr -dc '[:digit:]' | head -c 10)
  # アルファベットのみの任意の文字列(長さ10)を生成します。
  val2=$(cat /dev/urandom | tr -dc '[:alpha:]' | head -c 10)
  key="key_${datestr}_$val"
  value="value_${datestr}_$val2"
  echo "$key" >> keys.$datestr.txt
  echo "$value" >> values.$datestr.txt
  $redis_cli -p $proxy_port set $key $value > /dev/null 2>&1
done
```
keys.$datestr.txt　 => keys.1410995286.txt
values.$datestr.txt => values.1410995286.txt

* twemproxy経由でデータを取得します。

```shell
$ xargs -a keys.1410995286.txt -l /usr/local/redis/bin/redis-cli -p 22121 get > get_result.txt
$ wc -l values.1410995286.txt
1000 values.1410995286.txt
$ wc -l get_result.txt
1000 get_result.txt
$ diff values.1410995286.txt get_result.txt
※ (出力なし)
```
* Redisへ直接接続して、データの件数を確認します。

```shell
$ /usr/local/redis/bin/redis-cli -p 6379 dbsize
(integer) 335
$ /usr/local/redis/bin/redis-cli -p 6380 dbsize
(integer) 356
$ /usr/local/redis/bin/redis-cli -p 6381 dbsize
(integer) 309
```

## おまけ

* autconfのインストール

twemproxyのコンパイルには、autoconf2.64以上が必要となりました。
CentOS6の標準のリポジトリには存在しないようなので、今回はsource rpmよりリビルドする方法でインストールしました。

```shell
$ sudo yum install rpm-build
$ mkdir -p rmkdir $HOME/rpmbuild/{BUILD,SRPMS,SPECS,SOURCES,RPMS}
$ echo "_topdir $HOME/rpm" > $HOME/.rpmmacros
$ wget ftp://ftp.pbone.net/mirror/ftp5.gwdg.de/pub/opensuse/repositories/home%3A/monkeyiq%3A/centos6updates/CentOS_CentOS-6/src/autoconf-2.69-12.2.src.rpm
$ rpmbuild --rebuild autoconf-2.69-12.2.src.rpm
$ sudo yum localinstall rpmbuild/RPMS/noarch/autoconf-2.69-12.2.noarch.rpm
```

* libtoolのインストール

以下のエラーが出る場合は、libtoolをインストールします。

```shell
$ autoreconf -fvi
..(省略)..
configure.ac:36: error: possibly undefined macro: AC_PROG_LIBTOOL
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.
autoreconf: /usr/bin/autoconf failed with exit status: 1
```

## 参考

* [Partitioning: how to split data among multiple Redis instances.](http://redis.io/topics/partitioning)

* [twemproxy (nutcracker)](https://github.com/twitter/twemproxy)

* [3.5 Using autoreconf to Update configure Scripts](https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/autoreconf-Invocation.html)