Redigoを使う(3) トランザクションを行う
はじめに
RedisのGo言語向けクライアントライブラリRedigoの使い方を見ます。
本記事ではトランザクションの行い方を見ていきます。
環境
- OS: Windows 10
- Redis: win-3.2.100
- Go言語: 1.11
基本の流れ
Redisにおけるトランザクションについては、Redis Documentation (Japanese Translation) をまずは一読しておきましょう。
以下は、Redigoでトランザクションを行う典型的なコードです。SETとSADDという2つのコマンドを実行します。Conn.Send
関数を使うのが定石です。
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.Send("MULTI")
if err != nil {
panic(err)
}
// 文字列型の追加
err = conn.Send("SET", "money", 1000)
if err != nil {
panic(err)
}
// セット型の追加
err = conn.Send("SADD", "fruits", "apple")
if err != nil {
panic(err)
}
// コマンドの実行
r, err := redis.Values(conn.Do("EXEC"))
if err != nil {
panic(err)
}
fmt.Println(r) // [OK 1]
}
Conn.Send
は、クライアントの送信バッファにコマンドをため込むための関数です。この関数を呼んだ時点では、サーバに対する通信は発生しません。ここでは、MULTI、SET、SADDの3つがため込まれます。そしてConn.Do("EXEC")
が呼ばれたタイミングで、送信バッファの内容がまとめてサーバに送信されます。戻り値で実行結果が得られます。戻り値をredis.Values
関数に通すことで、interface{}型の配列に結果を変換できます。
このように、Send("MULTI")
⇒ Send(任意のコマンド)
×(複数回) ⇒ Do("EXEC")
というのが、トランザクションの基本の流れです。もちろん、Send
を使わずにDo
だけで同じトランザクションを実現することも(おそらく)可能ですが、定石どおりが無難と思います。
なお、Send
関数はサーバとの通信を伴わないため、戻り値がエラーとなる可能性は小さいです。しかし、メモリ不足などの事態を想定し、エラーチェックは行っておいた方がよいと思います。
CAS(Check-And-Set)操作
さて、実際の利用場面では、あるキーで読み出した値から何か処理を行い、その結果でキーの値を更新する、というケースがあると思います。このとき、複数クライアントからの並行処理による競合の可能性を考慮しなければなりません。
Redisでは、WATCHというコマンドで競合を検出できます。以下のコードは、moneyというキーで数値を読み出し、200を加えた値で更新します。
package main
import (
"fmt"
"time"
"github.com/gomodule/redigo/redis"
)
func main() {
// 接続
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
defer conn.Close()
// キーの監視を開始
w, err := redis.String(conn.Do("WATCH", "money"))
if err != nil {
panic(err)
}
fmt.Println(w) // OK
// 値の取得
m, err := redis.Int(conn.Do("GET", "money"))
if err != nil {
panic(err) // 値が存在しなければ終了
}
fmt.Println(m) // 更新前の値を出力
// トランザクションの開始
err = conn.Send("MULTI")
if err != nil {
panic(err)
}
// 値の更新
err = conn.Send("SET", "money", m+200)
if err != nil {
panic(err)
}
// コマンドの実行
r, err := redis.Values(conn.Do("EXEC"))
if err != nil {
panic(err) // トランザクション失敗時はpanic
}
fmt.Println(r) // トランザクション成功時は[OK]を出力
}
始めにWATCHで、キーmoneyの監視を開始しています。
次にGETでmoneyの値を取得します。そしてMULTIリクエストでSETを行っています。
他のクライアントからの値の書き換えが発生しなければ、トランザクションは成功し、moneyに200足された値が書き込まれてコードは[OK]を出力して終了します。
反対に、WATCHを行ってからEXECを行うまでの間に、他のクライアントから値が別の値に書き換えられたとします。このときトランザクションは失敗し、EXECの行はエラーとなります。(実用上のコードでは、エラーの検出時には、リトライするなり適切にエラーハンドリングを行う必要があります。)
ちなみに、WATCHの後で、何らかのエラーにより、EXECの前に処理を中断する場合は、通常はUNWATCHコマンドをサーバに送って監視の終了を教える必要があります。RedigoではConn.Close
の実行時に自動でUNWATCHを送る実装となっているため、利用者側が明示的にUNWATCHする必要はなさそうです。
ところで本記事では、説明のために足し算の処理を示しましたが、RedisではINCRBYというコマンドで一発で足し算が行えます。同じことをしたいときはこちらのコマンドを使いましょう。
おわりに
Redigoでトランザクションを行う手順を見ました。
今後、できたらRedigoの以下の機能についても書きたいです。
- コネクションプール
- 各種ユーティリティ関数
- パブリッシュ/サブスクライブ
参考
Author And Source
この問題について(Redigoを使う(3) トランザクションを行う), 我々は、より多くの情報をここで見つけました https://qiita.com/riverplus/items/5379111adda57f9ac18a著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .