GoでRedisをかるーくいじってみた

# はじめに
n番煎じかと思いますが，自分の知識整理もかねてまとめてみました．
Golang で Redis をさくさくいじってみます．本記事では Docker のインストール済みを想定しています．

環境は以下

- macOS (ver 10.14)
- Docker (ver 19.3.1)
- Golang (ver 1.12.7)

# ざっくりRedisとは
- 永続化可能なインメモリデータベース
- KVS(Key Value Store)
- 5つのデータ型をサポート
    - string / hash / list / set / sorted set

## 利点とは
- 高速
- シングルスレッドなので，必然的に処理が排他的になる
- データの生存期間(TTL)が設定できる
- ランキングの扱い
    - ソートをやってくれるデータ構造があるので．

## 参考
Redisについては，[こちらの記事](https://qiita.com/gold-kou/items/966d9a0332f4e110c4f8)がすごく丁寧なのでぜひ.
データ構造やコマンドについては，[TryRedis](https://try.redis.io/)をやってみると感覚がつかみやすい気がします．


# Redis を構築
まず Docker で Redis の準備を行います．
Docker Image は[こちら](https://hub.docker.com/_/redis)を使います．

```
$ docker run --name redis -d -p 6379:6379 redis redis-server --appendonly yes
```

Redis をローカルにインストール．
（Go で Redis にアクセスできているか確認するときなどに使います．）

```
$ brew install redis
```

以下のようになればOK

```
$ redis-cli
127.0.0.1:6379> 
```

redisのコマンドをいくつか試してみます．

```
// key を全て取得
127.0.0.1:6379> KEYS *
(empty list or set)

// SET key value
127.0.0.1:6379> SET test-key test-val
OK

127.0.0.1:6379> KEYS *
1) "test-key"

// key の削除
127.0.0.1:6379> DEL test-key
(integer) 1

127.0.0.1:6379> KEYS *
(empty list or set)

127.0.0.1:6379> exit
```


# Go で Redis を触ってみる
いよいよ本題．Go で Redis を軽く触っていきます．
[redigo](https://github.com/gomodule/redigo) というパッケージを使用します．

## redigo のインストール

```
go get github.com/gomodule/redigo/redis
```

## 実際にやってみる

- データの登録と取得

``` redis.go
package main

import(
    "fmt"
    
    "github.com/gomodule/redigo/redis"
)

// Connection
func Connection() redis.Conn {
    const Addr = "127.0.0.1:6379"
    
    c, err := redis.Dial("tcp", Addr)
    if err != nil {
        panic(err)
    }
    return c
}

// データの登録(Redis: SET key value)
func Set(key, value string, c redis.Conn) string{
    res, err := redis.String(c.Do("SET", key, value))
    if err != nil {
        panic(err)
    }
    return res
}

// データの取得(Redis: GET key)
func Get(key string, c redis.Conn) string {
    res, err := redis.String(c.Do("GET", key))
    if err != nil {
        panic(err)
    }
    return res
}

func main() {
    // 接続
    c := Connection()
    defer c.Close()
    
    // データの登録(Redis: SET key value)
    res_set := Set("sample-key", "sample-value", c)
    fmt.Println(res_set) // OK
    
    // データの取得(Redis: GET key)
    res_get := Get("sample-key", c)
    fmt.Println(res_get) // sample-value
}
```

```
実行結果：
$ go run redis.go
OK
sample-value

Redis： 
127.0.0.1:6379> KEYS *
1) "sample-key"
```

基本的に Redis のコマンドを実行したいタイミングで，`Do()`を呼び出す．
`Do()`の戻り値は，`interface{}`と`error`なので，型変換をする際は，`redis.String()`を呼び出す．
パッケージのコードをみた感じだと，`int`や`float64`，`byte`などへの変換ができそう．呼び出し方は例によって`redis.Int()`など．

- 複数の値を同時に登録，取得．かつ TTL をつけてみる

``` redis.go
package main

import(
    "fmt"
    
    "github.com/gomodule/redigo/redis"
)

// Connection
func Connection() redis.Conn {
    const Addr = "127.0.0.1:6379"
    
    c, err := redis.Dial("tcp", Addr)
    if err != nil {
        panic(err)
    }
    return c
}

type Data struct {
    Key string
    Value string
}

// 複数のデータの登録(Redis: MSET key [key...])
func Mset(datas []Data, c redis.Conn){
    var query []interface{}
    for _, v := range datas {
        query = append(query, v.Key, v.Value)
    }
    fmt.Println(query) // [key1 value1 key2 value2]
    
    c.Do("MSET", query...)
}

// 複数の値を取得 (Redis: MGET key [key...])
func Mget(keys []string, c redis.Conn) []string{
    var query []interface{}
    for _, v := range keys {
        query = append(query, v)
    }
    fmt.Println("MGET query:", query) // [key1 key2]
    
    res, err := redis.Strings(c.Do("MGET", query...))
    if err != nil {
        panic(err)
    }
    return res
}

// TTLの設定(Redis: EXPIRE key ttl)
func Expire(key string, ttl int, c redis.Conn) {
    c.Do("EXPIRE", key, ttl)
}

func main() {
    // 接続
    c := Connection()
    defer c.Close()
    
    // 複数データの登録
    datas := []Data{
        Data{Key:"key1", Value:"value1"},
        Data{Key:"key2", Value:"value2"},
    }
    Mset(datas, c)
    
    // 複数データの取得
    keys := []string{"key1", "key2"}
    res_mget := Mget(keys, c)
    fmt.Println(res_mget)
    
    // TTLの設定
    Expire("key1", 10, c)
}
```

```
実行結果：
$ go run redis.go
MSET query: [key1 value1 key2 value2]
MGET query: [key1 key2]
[value1 value2]

Redis：
127.0.0.1:6379> KEYS *
1) "sample-key"
2) "key2"
3) "key1"

Redis(10秒以上後)：
127.0.0.1:6379> KEYS *
1) "sample-key"
2) "key2"
```

結果として複数の値を得る場合（今回はMGET），型変換を行う場合は`redis.Strings()`のように末尾に`s`がきます．他の型でも同様，`redis.Ints`など．
TTLの設定については，秒単位で設定を行う`EXPIRE`と，ミリ秒単位で設定を行う`PEXPIRE`がある．

Redis から TTL の確認は，`TTL key`で行う．

```
127.0.0.1:6379> TTL key1
(integer) 4

127.0.0.1:6379> TTL key1
(integer) -2

127.0.0.1:6379> TTL key2
(integer) -1
```

結果については以下のようになる．

- 0以上：残りのTTL
- -1 ：TTLが設定されていない場合
- -2 ：key が存在しない場合


# 終わりに
ここまで読んでいただきありがとうございます！
あまり長くなってしまっても，同じことの繰り返しになってしまう部分も多いのでこの辺りで．
パッケージのコードをみていたら，`Do()`の結果を`map`に格納できる雰囲気が...．まだまだ学べそうです．
コードや解釈の間違いなどありましたら，指摘していただけると幸いです．