はじめに
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については,こちらの記事がすごく丁寧なのでぜひ.
データ構造やコマンドについては,TryRedisをやってみると感覚がつかみやすい気がします.
Redis を構築
まず Docker で Redis の準備を行います.
Docker Image はこちらを使います.
$ 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 というパッケージを使用します.
redigo のインストール
go get github.com/gomodule/redigo/redis
実際にやってみる
- データの登録と取得
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 をつけてみる
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
に格納できる雰囲気が....まだまだ学べそうです.
コードや解釈の間違いなどありましたら,指摘していただけると幸いです.