インメモリで高速に!GoでRedisを扱う方法と排他制御の実装について
業務でRedisを扱うこととなり、Redisはどんなときに使うべき?というところから学習しました。
バックエンドではGoを使用しているので、Goでの簡単な使い方をまとめたいと思います。
Redisとは
Redisとは、Key-Value型のNoSQL(Not only SQL)データベースです。
インメモリのNoSQLデータベースのため、MySQLなどのRDB(Relational Database)よりも高速に処理することができます。
しかし常にメモリを消費し、メモリに乗るデータ量しか扱えないため、一度に大容量のデータを扱うような処理はできません。
Redisの準備
まずはRedisの環境を構築するため、Dockerでコンテナを用意します。
イメージはDockerHubのredisを使用します。
※永続化させる場合は redis-server –appendonly yes を追加する必要があります。
$ docker run --name redis -d -p 6379:6379 redis
コンテナに入り、Redisにアクセスします。
$ docker exec -it <CONTAINER ID> bash
$ redis-cli
127.0.0.1:6379>
いくつかRedisの簡単なコマンドを試してみます。
ここでテスト用に使用したデータは、後で混同しないように全て削除して終了しています。
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379> SET key1 value1
OK
127.0.0.1:6379> KEYS *
1) "key1"
127.0.0.1:6379> GET key1
"value1"
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379> EXIT
GoでRedisにアクセスする
それではGoでRedisの実装をしていきます。
今回はRedisの公式ページで推奨されているgo-redis/redisを使用して実装します。
この例では、Data構造体に入れたkeyとvalueを、登録して取得するという単純なものです。
package main
import (
"context"
"github.com/go-redis/redis/v8"
)
type Data struct {
key string
value string
}
func main() {
c := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
d := Data{
key: "key1",
value: "value1",
}
var ctx = context.Background()
// 登録
if err := c.Set(ctx, d.key, d.value, 0).Err(); err != nil {
panic(err)
}
// 取得
val, err := c.Get(ctx, d.key).Result()
switch {
case err == redis.Nil:
panic("key does not exist")
case err != nil:
panic(err)
case val == "":
panic("value is empty")
}
fmt.Println(d.key, val)
}
出力結果は以下のようになり、入れた値が正しく取得できていることが確認できました。
key1 value1
念のため、先ほど作成したコンテナでRedisにデータが登録されているか確認してみましょう。
127.0.0.1:6379> KEYS *
1) "key1"
127.0.0.1:6379> GET key1
"value1"
こちらでもkey1とvalue1が入っているので、正常に動作していることが確認できました。
排他制御について
先ほどはデータを登録して取得するだけでしたが、次は排他制御ができるようにしてみたいと思います。
先ほど使用したデータは削除し、実装にはSetNXメソッドを使用します。
SetNXでは、キーがまだ存在しない場合にのみキーを設定することができます。
Set関数を用意し、今回は20秒でexpireするように設定します。
func Set(c *redis.Client, d Data, ctx context.Context) error {
ok, err := c.SetNX(ctx, d.key, d.value, 20*time.Second).Result()
if err != nil {
return err
}
if !ok {
return fmt.Errorf("failed to set")
}
return nil
}
key1に対して、value1とvalue100の2つの値を用意し、既に存在しているキーにアクセスした場合はエラーを返すようにします。
func main() {
c := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
d := Data{
key: "key1",
value: "value1",
}
dd := Data{
key: "key1",
value: "value100",
}
var ctx = context.Background()
go func() {
if err := Set(c, d, ctx); err != nil {
fmt.Printf("key:%s value:%s %v\n", d.key, d.value, err)
}
}()
go func() {
if err := Set(c, dd, ctx); err != nil {
fmt.Printf("key:%s value:%s %v\n", dd.key, dd.value, err)
}
}()
time.Sleep(3 * time.Second)
}
main関数を実行すると、以下ような出力結果となりました。
key:key1 value:value100 failed to set
goroutineでアクセスしているので結果は実行する度に異なりますが、今回はvalue100でエラーが返ってきました。
Redisで確認してみても、value100ではなく、value1が登録されていることが確認できました。
127.0.0.1:6379> GET key1
"value1"
今回は20秒で揮発するように実装しているので、コンテナで値を確認する場合には、その時間以内に確認をするようにしてください。
まとめ
GoでRedisを扱う基本的な方法と、排他制御の実装について確認ができました。
インメモリのKVSには高速処理が可能というメリットがある一方、メモリを消費するというデメリットがあります。 特徴を理解した上で、使用用途に応じてRDBとの使い分けをしていきたいと思います。
この記事を書いた人
- 創造性を最大限に発揮するとともに、インターネットに代表されるITを活用し、みんなの生活が便利で、豊かで、楽しいものになるようなサービスやコンテンツを考え、創り出し提供しています。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー