はじめに
RedisのGo言語向けクライアントライブラリRedigoの使い方を見ます。
この記事では様々なデータ型の扱い方を見ます。
Redisには下記の5つのデータ型があります。
- 文字列型
- リスト型
- セット型
- ソート済みセット型
- ハッシュ型
文字列型については(1)の記事で見ました。本記事では他の4つについて、Redigoでの基本的な扱い方を見ていきます。
環境
- OS: Windows 10
- Redis: win-3.2.100
- Go言語: 1.11
リスト型
リスト型は、文字列型の双方向リストです。コマンド一覧はリファレンスを参照して下さい。まずは単純な要素の追加と取得を行います。
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// 要素の追加
_, err = conn.Do("RPUSH", "seasons", "spring")
_, err = conn.Do("RPUSH", "seasons", "summer")
_, err = conn.Do("RPUSH", "seasons", "autumn")
_, err = conn.Do("RPUSH", "seasons", "winter")
// 要素の取得
a, err := redis.String(conn.Do("LINDEX", "seasons", 2))
if err != nil {
panic(err)
}
fmt.Println(a) // autumn
// 全体の取得
s, err := redis.Strings(conn.Do("LRANGE", "seasons", 0, -1))
if err != nil {
panic(err)
}
fmt.Println(s) // [spring summer autumn winter]
}
RPUSHはリストの末尾に要素を追加するコマンドです(ここでは戻り値チェックを省略しています)。seasonsというキーのリストに4つの要素を追加しています。
LINDEXは、リストの要素のうち、インデクスで指定された要素を返します。
LRANGEは、リストの要素のうち、2つのインデクスで指定された範囲の要素を返します。「0」は先頭、「-1」は末尾を意味します(すなわち全ての要素を返す)。さらに結果をredis.Strings
に渡しています。これはconn.Do
の結果を[]stringに変換するためのユーティリティ関数です。
要素の削除は次のように行います。
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// 要素の追加
_, err = conn.Do("RPUSH", "words", "xxxxxx")
_, err = conn.Do("RPUSH", "words", "yyy")
_, err = conn.Do("RPUSH", "words", "z")
_, err = conn.Do("RPUSH", "words", "xxxxxx")
// 要素の削除(先頭に近いxxxxxxを1個だけ消す)
n, err := redis.Int(conn.Do("LREM", "words", 1, "xxxxxx"))
if err != nil {
panic(err)
}
fmt.Println(n) // 1
// 要素の削除(先頭の要素を消す)
a, err := redis.String(conn.Do("LPOP", "words"))
if err != nil {
panic(err)
}
fmt.Println(a) // yyy
// 全体の取得
s, err := redis.Strings(conn.Do("LRANGE", "words", 0, -1))
if err != nil {
panic(err)
}
fmt.Println(s) // [z xxxxxx]
}
wordsというキーのリストに4つの文字列を追加したあと、LREMとLPOPで要素の削除を行っています。
LREMは、リストを先頭から見て、指定された値の要素を指定された個数だけ削除します。この場合は、一番最初に追加したxxxxxxが削除されます。戻り値は削除された要素数です。結果をredis.Int
でGoのint型に変換しています。
LPOPは、リストの先頭の要素を削除します。このときの状態は[yyy, z, xxxxxx]ですので、先頭のyyyが削除されます。戻り値は削除された要素です。redis.String
に渡してGoのstring型に変換しています。
今回は紹介しませんでしたが、リスト型のコマンドについては、ほかに、要素を先頭と末尾から切り詰めるLTRIM、特性の要素の値を書き換えるLSET、2つのリスト間で要素の移動をするRPOPLPUSHなどがあります。
セット型
セット型は、文字列型の集合です。リスト型と異なり、要素の重複を許しません。コマンド一覧はリファレンスを参照して下さい。要素の追加・削除・取得の一連の流れを示します。
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// 要素の追加
_, err = conn.Do("SADD", "numbers", "one")
_, err = conn.Do("SADD", "numbers", "two")
_, err = conn.Do("SADD", "numbers", "three")
_, err = conn.Do("SADD", "numbers", "four")
// 要素の追加(既に追加済み)
n, err := redis.Int(conn.Do("SADD", "numbers", "one"))
if err != nil {
panic(err)
}
fmt.Println(n) // 0
// 要素の削除
m, err := redis.Int(conn.Do("SREM", "numbers", "three"))
if err != nil {
panic(err)
}
fmt.Println(m) // 1
// 要素の存在確認
k, err := redis.Int(conn.Do("SISMEMBER", "numbers", "five"))
if err != nil {
panic(err)
}
fmt.Println(k) // 0
// 全体の取得
s, err := redis.Strings(conn.Do("SMEMBERS", "numbers"))
if err != nil {
panic(err)
}
fmt.Println(s) // [one two four]
}
SADDはセットに要素を追加します。既に存在する要素を再度追加しようとすると、戻り値は0になります(成功時は1が返る)。
SREMは、セットから指定された要素を返します。
SISMEMBERは、指定された要素がセットに存在するか否かを返します。存在すれば1、しなければ0が返ります。
SMEMBRESはセットのすべての要素を取得します。
セット型のコマンドについては、他に、ランダムに要素を削除するSPOP(いつ使うんだろ?)、要素の個数を得るSCARD、2つのセット間で要素の移動をするSMOVEなどがあります。ほか、複数のセットの積集合(SINTER)や和集合(SUNION)、差集合(SDIFF)など、集合関係のコマンドが充実しています。
ソート済みセット型
ソート済みセット型は、スコアという数値にもとづいてソートされた文字列型の集合です。C++のstd::multimap<float,string>型のようなものだと思います。コマンド一覧はリファレンスを参照して下さい。5人の生徒のテスト点数をモチーフに、よくある操作の例を示します。
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// 要素の追加
_, err = conn.Do("ZADD", "math_test", 60, "alice")
_, err = conn.Do("ZADD", "math_test", 50, "bob")
_, err = conn.Do("ZADD", "math_test", 100, "charlie")
_, err = conn.Do("ZADD", "math_test", 10, "dave")
_, err = conn.Do("ZADD", "math_test", 20, "eve")
// 最低スコアの要素(とそのスコア)を得る
s, err := redis.Strings(conn.Do("ZRANGE", "math_test", 0, 0, "WITHSCORES"))
if err != nil {
panic(err)
}
fmt.Println(s) // [dave 10]
// スコア40以上70以下の要素(とそのスコア)を得る
t, err := redis.Strings(conn.Do("ZRANGEBYSCORE", "math_test", 40, 70, "WITHSCORES"))
if err != nil {
panic(err)
}
fmt.Println(t) // [bob 50 alice 60]
// スコアが小さい順に要素を2個削除する
n, err := redis.Int(conn.Do("ZREMRANGEBYRANK", "math_test", 0, 1))
if err != nil {
panic(err)
}
fmt.Println(n) // 2
// 全体の取得
u, err := redis.Strings(conn.Do("ZREVRANGE", "math_test", 0, -1))
if err != nil {
panic(err)
}
fmt.Println(u) // [charlie alice bob]
}
点数の低い順でソートされた状態で要素が保持されます。
ランク | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
スコア | 10 | 20 | 50 | 60 | 100 |
値 | dave | eve | bob | alice | charlie |
特定の順位の生徒を得たいときは、ZRANGEを使います。例のように、ZRANGE, 0, 0
と指定したときは、ランクが0以上0以下の生徒、すなわち最低点の生徒が得られます(0-index)。さらにWITHSCORESというオプションを指定するとスコアも合わせて得られます。反対に、最高点の生徒を得たいときはZREVRANGEを使います。
点数の範囲で絞り込むときは、ZRANGEBYSCOREを使います。例のように、ZRANGEBYSCORE, 40, 70
と指定したときは、40以上70以下のスコアの要素が得られます。
要素の削除にはZREMRANGEBYRANKを用います。ZREMRANGEBYRANK, 0, 1
は、ランクが0以上1以下の生徒、つまりdaveとeveの削除を意味します。
ハッシュ型
ハッシュ型は、文字列型の対(フィールドと値)のマップです。Pythonの辞書型のようなものだと思います(値は文字列限定ですが)。コマンド一覧はリファレンスを参照して下さい。以下は、profileというキーに対し、フィールドと値の対を追加・更新する例を示します。
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// 要素の追加
_, err = conn.Do("HSET", "profile", "name", "yamada")
_, err = conn.Do("HSET", "profile", "age", "30")
_, err = conn.Do("HSET", "profile", "country", "america")
// 要素の追加と更新
t, err := redis.String(conn.Do("HMSET", "profile", "country", "japan", "city", "tokyo"))
if err != nil {
panic(err)
}
fmt.Println(t) // OK
// 要素の取得
s, err := redis.String(conn.Do("HGET", "profile", "country"))
if err != nil {
panic(err)
}
fmt.Println(s) // japan
// 値をインクリメントする
n, err := redis.Int(conn.Do("HINCRBY", "profile", "age", "2"))
if err != nil {
panic(err)
}
fmt.Println(n) // 32
// 全体の取得
u, err := redis.Strings(conn.Do("HGETALL", "profile"))
if err != nil {
panic(err)
}
fmt.Println(u) // [name yamada age 32 country japan city tokyo]
}
フィールドと値を追加するにはHSETを使います。ここでは戻り値を捨てていますが、フィールドが既に存在するなら0が戻り(値は上書きされます)、しないなら1が戻ります。
HMSETで複数の対を追加することもできます。フィールド1, 値1, フィールド2, 値2, ・・のように、フィールドと値を交互に並べます。
HINCRBYで値の増減を行うことも可能です。値が64bit符号付き整数の範囲内である必要があります。
またHGETALLで全ての対を取得できます。フィールドと値が交互に現れます。
おわりに
Redisの様々なデータ型をRedigoでの扱い方を見ていきました。