PR

Go言語(Golang)でRedisの使い方|Docker環境構築+サンプルAPI

3. 応用

こんにちは。Tomoyuki(@tomoyuki65)です。

データベースには様々な種類がありますが、キャッシュによるパフォーマンス改善やセッション管理などの一時的なデータ保存、そしてリアルタイム性が求められる場面で利用されるデータストアとして、Redisがあります。

ということでこの記事では、Go言語(Golang)でRedisの使い方についてまとめます。

 

Go言語(Golang)でRedisの使い方|Docker環境構築+サンプルAPI

Redisとは?

Redisは、メモリ(RAM)上でデータを管理するインメモリ型のデータストアで、非常に高速にデータを処理できるのが特徴です。

基本的にはキーと値の形式で保存しますが、リスト、セット、ハッシュ、ソート済みセットなどもサポートしています。

ただし、データの永続保存には向いていないため、メインのデータベースを補完する役割として使われることが一般的です。

 

Redisの主な用途

・キャッシュ

・データベースへのアクセス回数を減らして処理速度を向上
・WebページやAPIレスポンスの高速化

 

・セッション管理

・ユーザーのログイン情報や状態管理
・TTLで自動的に期限切れにできる

 

・リアルタイムランキング / スコア管理

・ゲームやSNSでのスコア、いいね数のリアルタイム集計
・ソート付きセットを活用

 

・一時データ / TTL付きデータの保存

・認証コード(OTP)
・ワンタイムトークン
・レート制限(アクセス制御)

 

・メッセージキュー / 非同期処理

・バックグラウンドジョブ(メール送信、画像処理など)
・Pub/Subでチャットや通知機能

 

・リアルタイム分析 / データ集計

・カウンターやランキングの即時更新
・アクセス数やイベントの集計

 

DockerでRedisのコンテナを立てる方法

ではDockerを使い、ローカル開発環境としてRedisのコンテナを立ててみます。

まずは以下のコマンドを実行し、ファイルを作成します。

$ mkdir go-redis && cd go-redis
$ touch compose.yml

 

次に作成したファイルを以下のように記述します。

・「compose.yml」

services:
  redis:
    image: redis:8.6.2
    container_name: redis
    command: redis-server
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
volumes:
  redis-data:

 

次に以下のコマンドを実行し、Dockerコンテナの起動を行います。

$ docker compose up -d

 

次に以下のコマンドを実行し、Dockerコンテナの起動を確認します。

$ docker compose ps

 

コマンド実行後、以下のようにredisコンテナが起動していればOKです。

 

スポンサーリンク

Redisの使い方

Redisに接続する

Redisに接続して利用したい場合は、まずは以下のコマンドを実行してRedisコンテナへ入ります。

$ docker compose exec redis bash

 

次にRedisコンテナ内で以下のコマンドを実行し、Redisに接続します。

redis-cli --raw

※マルチバイト文字列の文字化けを防ぐため、オプション「–raw」付けています。

 

Redisに接続後、以下のように表示されればOKです。

 

以下を入力して実行し、レスポンスが返ってくるのを確認します。

PING

 

実行後、以下のようにPONが返ってこればOKです。

 

キーバリュー形式で文字列の値を登録

次に基本となるキーバリュー形式で文字列の値を登録する方法をご紹介します。

キーを「name」、値を「田中太郎」とし、以下のようにSETコマンドを実行します。

SET name 田中太郎

 

実行後、以下のようにOKが表示されれば成功です。

 

キーから登録済みの値を取得

次にキーを使って登録済みの値を取得するには、以下のようにGETコマンドを実行します。

GET name

 

実行後、以下のように値が取得できればOKです。

 

登録済みのキーバリューを削除

次に登録済みのキーバリューを削除したい場合は、以下のようにDELコマンドを実行します。

DEL name

 

実行後、以下のように1が返って来れば成功です。

※キーが存在しない場合は0が返ります。

 

再度GETコマンドを実行し、値が取得できなければOKです。

 

文字列の値を登録する際のオプション一覧

オプション 用途 コマンド例
EX seconds 有効期限を秒単位で設定。指定秒後に自動削除される SET mykey “Hello” EX 60
PX milliseconds 有効期限をミリ秒単位で設定 SET mykey “Hello” PX 1500
NX キーが存在しない場合のみ設定(新規作成専用) SET mykey “Hello” NX
XX キーが存在する場合のみ設定(更新専用) SET mykey “Hello” XX
KEEPTTL 既存のTTL(有効期限)がある場合、それを保持して値を更新 SET mykey “Hello” KEEPTTL
GET 古い値を返す。設定前の値を取得可能(Redis 6.2以降) SET mykey “Hello” GET

※オプションを利用すれば有効期限の設定等が可能です。

 

その他のデータ型を操作するコマンド一覧

データ型 主なコマンド 主なパラメータ 主なオプション 用途 コマンド例
ハッシュ (Hash) HSET, HGET, HGETALL, HDEL フィールド名、値 なし オブジェクトの属性管理(例:ユーザー情報) HSET user:1 name “Alice”
HGET user:1 name
HGETALL user:1
リスト (List) LPUSH, RPUSH, LPOP, RPOP, LRANGE 追加する値、取得範囲(start stop) LPUSHX / RPUSHX(既存キーのみ追加) キューやスタック、順序付きコレクション LPUSH tasks “task1”
RPUSH tasks “task2”
LRANGE tasks 0 -1
セット (Set) SADD, SREM, SMEMBERS, SISMEMBER, SINTER セットに追加・削除・検索する要素(値) なし ユニーク要素の管理、集合演算 SADD tags “redis”
SREM tags “oldtag”
SMEMBERS tags
ソート済みセット (Sorted Set / ZSet) ZADD, ZREM, ZRANGE, ZRANGEBYSCORE 追加する要素(値)とスコア、スコア範囲 WITHSCORES(スコア表示) ランキング、リーダーボード ZADD leaderboard 100 “Alice”
ZRANGE leaderboard 0 -1 WITHSCORES
ビットマップ SETBIT, GETBIT, BITCOUNT オフセット、値(0 or 1) なし フラグ管理、アクティブユーザー判定 SETBIT login:2026-04-01 123 1
GETBIT login:2026-04-01 123
HyperLogLog PFADD, PFCOUNT, PFMERGE 追加する要素(値) なし 大量データのユニーク数近似 PFADD uv “user1”
PFCOUNT uv
Geo(地理空間) GEOADD, GEORADIUS, GEORADIUSBYMEMBER, GEODIST, GEOPOS 経度・緯度・メンバー、中心座標・半径・単位(m/km/mi/ft) なし 位置情報管理、半径検索 GEOADD cities 139.6917 35.6895 Tokyo
GEORADIUS cities 139 35 50 km
GEODIST cities Tokyo Osaka km

※文字列以外にも色々対応してます。

 

スポンサーリンク

Go言語(Golang)でRedisを操作するサンプルAPIを作って試す

Go言語(Golang)でサンプルAPIの準備

次にGoのサンプルAPIを準備します。まずは以下のコマンドを実行し、上記で起動中のDockerコンテナを止めます。

$ docker compose down

 

次に以下のコマンドを実行し、各種ファイルを作成します。

$ mkdir -p docker/local/go && touch docker/local/go/Dockerfile
$ mkdir src && touch src/main.go
$ touch .env

 

次に作成したファイルをそれぞれ以下のように記述します。

・「docker/local/go/Dockerfile」

FROM golang:1.26.1-alpine3.23

WORKDIR /go/src

COPY ./src .

# go.modがあれば依存関係をインストール
RUN if [ -f ./go.mod ]; then \
      go install; \
    fi

# 開発用のライブラリをインストール
RUN go install github.com/air-verse/air@v1.64.5
RUN go install honnef.co/go/tools/cmd/staticcheck@latest

EXPOSE 8080

 

・「src/main.go」

package main

import (
    "log/slog"
    "net/http"

    "github.com/labstack/echo/v4"
)

func main() {
    // echoのルーター設定
    e := echo.New()

    e.GET("/", func(c echo.Context) error {
        // レスポンス結果の設定
        res := map[string]string{
            "message": "Hello World !!",
        }

        return c.JSON(http.StatusOK, res)
    })

    // ログ出力
    slog.Info("start go-redis")

    // サーバー起動
    e.Logger.Fatal(e.Start(":8080"))
}

 

・「.env」

ENV=local
REDIS_ADDR=redis:6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_DIAL_TIMEOUT=5
REDIS_READ_TIMEOUT=3
REDIS_WRITE_TIMEOUT=3
REDIS_TLS=false
REDIS_RETRY_COUNT=5

※環境変数「REDIS_PASSWORD」と「REDIS_TLS」は本番環境で利用する可能性があります。

 

次にファイル「compose.yml」を以下のように修正します。

services:
  api:
    container_name: go-redis-api
    build:
      context: .
      dockerfile: ./docker/local/go/Dockerfile
    command: air -c .air.toml
    volumes:
      - ./src:/go/src
    ports:
      - "8080:8080"
    env_file:
      - .env
    tty: true
    stdin_open: true
    depends_on:
      - redis
  redis:
    image: redis:8.6.2
    container_name: redis
    command: redis-server
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
volumes:
  redis-data:

 

次に以下のコマンドを実行し、Dockerコンテナをビルドします。

$ docker compose build --no-cache

 

次に以下のコマンドを実行し、Go言語の初期化処理を行います。

$ docker compose run --rm api go mod init go-redis
$ docker compose run --rm api go mod tidy
$ docker compose run --rm api air init

 

次に以下のコマンドを実行し、Dockerコンテナの再ビルドと起動を行います。

$ docker compose build --no-cache
$ docker compose up -d

 

次に以下のコマンドを実行し、Dockerコンテナの起動を確認します。

$ docker compose ps

 

コマンド実行後、以下のようにapiとredisのコンテナがそれぞれ起動していればOKです。

 

次にブラウザで「http://localhost:8080」を開き、JSON形式で想定通りのメッセージが出力されていればOKです。

 

Redisを操作するサンプルAPIを追加

次にGo言語(Golang)でRedisを操作するサンプルAPIを作って試します。

まずは以下のコマンドを実行し、各種ファイルを作成します。

$ mkdir -p src/internal/infrastructure/database && touch src/internal/infrastructure/database/redis.go

 

次に作成したファイルを以下のように記述します。

・「src/internal/infrastructure/database/redis.go」

package database

import (
    "context"
    "crypto/tls"
    "fmt"
    "os"
    "strconv"
    "time"

    "github.com/redis/go-redis/v9"
)

func NewRedisClientWithRetry(ctx context.Context) (*redis.Client, error) {
    // 環境変数から接続設定値を取得
    addr := os.Getenv("REDIS_ADDR")
    if addr == "" {
        addr = "localhost:6379"
    }

    // パスワード設定(本番環境用)
    password := os.Getenv("REDIS_PASSWORD")

    // Redisの論理データベース番号 (0〜15のインデックス。デフォルトは0)
    db := 0
    if dbStr := os.Getenv("REDIS_DB"); dbStr != "" {
        if parsedDB, err := strconv.Atoi(dbStr); err == nil {
            db = parsedDB
        }
    }

    // Redisとの接続確立までの制限時間の設定
    dialTimeout := 5 * time.Second
    if dt := os.Getenv("REDIS_DIAL_TIMEOUT"); dt != "" {
        if sec, err := strconv.Atoi(dt); err == nil {
            dialTimeout = time.Duration(sec) * time.Second
        }
    }

    // Redisからのレスポンスを待つ制限時間の設定
    readTimeout := 3 * time.Second
    if rt := os.Getenv("REDIS_READ_TIMEOUT"); rt != "" {
        if sec, err := strconv.Atoi(rt); err == nil {
            readTimeout = time.Duration(sec) * time.Second
        }
    }

    // Redisにリクエストを書き込む(送信する)処理の制限時間の設定
    writeTimeout := 3 * time.Second
    if wt := os.Getenv("REDIS_WRITE_TIMEOUT"); wt != "" {
        if sec, err := strconv.Atoi(wt); err == nil {
            writeTimeout = time.Duration(sec) * time.Second
        }
    }

    // 通信の暗号化設定(本番環境用)
    var tlsConfig *tls.Config
    if os.Getenv("REDIS_TLS") == "true" {
        tlsConfig = &tls.Config{
            MinVersion: tls.VersionTLS12,
        }
    }

    // Redisへの接続失敗時のリトライ回数
    retryCount := 5
    if rc := os.Getenv("REDIS_RETRY_COUNT"); rc != "" {
        if parsedRC, err := strconv.Atoi(rc); err == nil {
            retryCount = parsedRC
        }
    }

    // Redisクライアントの設定
    rdb := redis.NewClient(&redis.Options{
        Addr: addr,
        Password: password,
        DB: db,
        DialTimeout: dialTimeout,
        ReadTimeout: readTimeout,
        WriteTimeout: writeTimeout,
        TLSConfig: tlsConfig,
    })

    // 接続確認(リトライ有り)
    var err error
    for i := 0; i < retryCount; i++ {
        if err = rdb.Ping(ctx).Err(); err == nil {
            return rdb, nil
        }
        fmt.Printf("redis ping failed, retrying... (%d/%d)\n", i+1, retryCount)
        time.Sleep(1 * time.Second)
    }

    return nil, fmt.Errorf("failed to connect to Redis after %d retries: %w", retryCount, err)
}

※これはRedisへの接続用の設定ファイルです。

 

次にファイル「src/main.go」を以下のように修正します。

package main

import (
    "fmt"
    "log/slog"
    "net/http"
    "time"

    "github.com/labstack/echo/v4"
    "github.com/redis/go-redis/v9"

    "go-redis/internal/infrastructure/database"
)

// Redisへのデータ登録用リクエストボディの構造体
type SetRedisRequestBody struct {
    Key        string `json:"key"`
    Value      string `json:"value"`
    TTLSeconds int    `json:"ttlSeconds"`
    Mode       string `json:"mode"`
    KeepTTL    bool   `json:"keepTTL"`
}

func main() {
    // echoのルーター設定
    e := echo.New()

    e.GET("/", func(c echo.Context) error {
        // レスポンス結果の設定
        res := map[string]string{
            "message": "Hello World !!",
        }

        return c.JSON(http.StatusOK, res)
    })

    // サンプルAPIを追加
    apiV1 := e.Group("/api/v1")

    // Redisへのデータ登録
    apiV1.POST("/redis/set", func(c echo.Context) error {
        // リクエストボディの取得
        var reqBody SetRedisRequestBody
        if err := c.Bind(&reqBody); err != nil {
            return err
        }

        // チェック処理(ライブラリは使わずKey、Value、Modeの必須チェック)
        if reqBody.Key == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "key is required")
        }
        if reqBody.Value == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "value is required")
        }
        if reqBody.Mode == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "mode is required")
        }

        // TTL設定(デフォルト0)
        var ttl time.Duration = 0
        if reqBody.TTLSeconds > 0 {
            ttl = time.Duration(reqBody.TTLSeconds) * time.Second
        }

        ctx := c.Request().Context()

        // redisクライアントの取得
        rdb, err := database.NewRedisClientWithRetry(ctx)
        if err != nil {
            errMsg := fmt.Sprintf("failed to get redis client: %v", err)
            return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
        }

        // データ登録処理
        switch reqBody.Mode {
        case "create": // オプション「NX」(キーが存在しない場合のみ登録)
            if err := rdb.SetArgs(ctx, reqBody.Key, reqBody.Value, redis.SetArgs{
                Mode: "NX",
                TTL: ttl,
            }).Err(); err != nil {
                errMsg := fmt.Sprintf("failed to set redis: %v", err)
                return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
            }
        case "update": // オプション「XX」(キーが存在する場合のみ更新)
            // KeepTTL対応
            if reqBody.KeepTTL {
                ttlVal, err := rdb.TTL(ctx, reqBody.Key).Result()
                if err != nil {
                    errMsg := fmt.Sprintf("failed to set redis: %v", err)
                    return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
                }
                if ttlVal > 0 {
                    ttl = ttlVal
                }
            }

            if err := rdb.SetArgs(ctx, reqBody.Key, reqBody.Value, redis.SetArgs{
                Mode: "XX",
                TTL: ttl,
            }).Err(); err != nil {
                errMsg := fmt.Sprintf("failed to set redis: %v", err)
                return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
            }
        case "upsert": // 通常
            // KeepTTL対応
            if reqBody.KeepTTL {
                ttlVal, err := rdb.TTL(ctx, reqBody.Key).Result()
                if err != nil {
                    errMsg := fmt.Sprintf("failed to set redis: %v", err)
                    return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
                }
                if ttlVal > 0 {
                    ttl = ttlVal
                }
            }

            if err = rdb.Set(ctx, reqBody.Key, reqBody.Value, ttl).Err(); err != nil {
                errMsg := fmt.Sprintf("failed to set redis: %v", err)
                return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
            }
        default:
            return echo.NewHTTPError(http.StatusInternalServerError, "failed to set redis: invalid mode")
        }

        return c.JSON(http.StatusOK, map[string]string{
            "message": "OK",
        })
    })

    // Redisからデータ取得(文字列)
    apiV1.GET("/redis/get/:key", func(c echo.Context) error {
        // リクエストパラメータの取得
        key := c.Param("key")
        if key == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "key is required")
        }

        ctx := c.Request().Context()

        // redisクライアントの取得
        rdb, err := database.NewRedisClientWithRetry(ctx)
        if err != nil {
            errMsg := fmt.Sprintf("failed to get redis client: %v", err)
            return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
        }

        // データ取得処理
        val, err := rdb.Get(ctx, key).Result()
        if err != nil {
            // キーが存在しない場合は空のオブジェクトを返す
            if err == redis.Nil {
                return c.JSON(http.StatusOK, map[string]interface{}{})
            }

            errMsg := fmt.Sprintf("failed to get redis: %v", err)
            return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
        }

        return c.JSON(http.StatusOK, map[string]string{
            "value": val,
        })
    })

    // Redisからデータ削除
    apiV1.DELETE("/redis/del/:key", func(c echo.Context) error {
        // リクエストパラメータの取得
        key := c.Param("key")
        if key == "" {
            return echo.NewHTTPError(http.StatusBadRequest, "key is required")
        }

        ctx := c.Request().Context()

        // redisクライアントの取得
        rdb, err := database.NewRedisClientWithRetry(ctx)
        if err != nil {
            errMsg := fmt.Sprintf("failed to get redis client: %v", err)
            return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
        }

        // データ削除処理
        res, err := rdb.Del(ctx, key).Result()
        if err != nil {
            errMsg := fmt.Sprintf("failed to delete redis: %v", err)
            return echo.NewHTTPError(http.StatusInternalServerError, errMsg)
        }

        // キーが存在しない場合はエラー
        if res == 0 {
            return echo.NewHTTPError(http.StatusNotFound, "key not found")
        }

        return c.NoContent(http.StatusNoContent)
    })

    // ログ出力
    slog.Info("start go-redis")

    // サーバー起動
    e.Logger.Fatal(e.Start(":8080"))
}

 

次に以下のコマンドを実行し、go.modの更新からDockerコンテナの再ビルドおよび再起動を行います。

$ docker compose exec api go mod tidy
$ docker compose down
$ docker compose build --no-cache
$ docker compose up -d

 

サンプルAPIを実行して試す

次に上記で作成したサンプルAPIをPostmanを使って試します。

まずは以下のリクエストボディを設定し、POSTメソッドで「http://localhost:8080/api/v1/redis/set」を実行します。

・リクエストボディ

{
  "key": "name",
  "value": "田中太郎",
  "ttlSeconds": 0,
  "mode": "create",
  "keepTTL": false
}

 

実行後、下図のようにステータス200で想定通りの結果になればOKです。

 

次にGETメソッドで「http://localhost:8080/api/v1/redis/get/{対象データのkey}」を実行し、下図のようにステータス200で想定通りの結果になればOKです。

 

次に以下のリクエストボディを設定し、再度POSTメソッドで「http://localhost:8080/api/v1/redis/set」を実行します。

・リクエストボディ

{
  "key": "name",
  "value": "田中二郎",
  "ttlSeconds": 0,
  "mode": "update",
  "keepTTL": false
}

 

実行後、下図のようにステータス200で想定通りの結果になればOKです。

 

次に再度GETメソッドで「http://localhost:8080/api/v1/redis/get/{対象データのkey}」を実行し、下図のようにステータス200で想定通りの結果になればOKです。

 

次にDELETEメソッドで「http://localhost:8080/api/v1/redis/del/{対象データのkey}」を実行し、下図のようにステータス204で想定通りの結果になればOKです。

 

次に再度GETメソッドで「http://localhost:8080/api/v1/redis/get/{対象データのkey}」を実行し、下図のようにステータス200で想定通りの結果になればOKです。

 

スポンサーリンク

クラウドインフラのRedisのサービスについて

各種クラウドインフラにはRedisのサービスがありますが、Google Cloudなら「MemoryStore for Redis」AWSなら「ElastiCache for Redis」になります。

それぞれのサービスについては、サポートしていない機能もあったりするため、使おうと思っている機能がサポートされているかは事前に確認して下さい。

 

スポンサーリンク

最後に

今回はGo言語(Golang)でRedisの使い方についてまとめました。

サンプルAPIでは基本となるデータ型の文字列を操作する処理をご紹介しましたが、Redisでは他にも様々なデータ型を扱えます。

用途に応じて使う機能が変わるので、その点については適宜調べていただく必要がありますが、基本的な使い方についてまとめたので、興味がある方はぜひ参考にしてみて下さい。

 

この記事を書いた人
Tomoyuki

SE→ブロガーを経て、現在はSoftware Engineer(Web/Gopher)をしています!

Tomoyukiをフォローする
3. 応用
スポンサーリンク
Tomoyukiをフォローする

コメント

タイトルとURLをコピーしました