グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



インメモリで高速に!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を、登録して取得するという単純なものです。

Redis公式ページ
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

最新の情報をお届けします