こんにちは。KOUKIです。
とあるWeb系企業で、Webエンジニアをしています。
最近、Go言語でRedisを扱うことがあったので、備忘がてら記事にしました。
表題の通り、Redisを操作して遊びましょう。
Redisとは
Redisは、Key-Valueで値を保存できるNoSQLデータベースです。
データベースのキャッシュに使ったり、メッセージブローカーにしたりできます。
環境準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
mkdir golang-redis cd golang-redis/ go mod init golang-redis touch Dockerfile touch docker-compose.yml touch main.go mkdir repository touch repository/redis.go mkdir models touch models/user.go mkdir command touch command/testdata.go mkdir sort touch sort/sort.go go get github.com/gofiber/fiber go get github.com/go-redis/redis/v8 go get -u github.com/bxcodec/faker/v3 |
Docker
アプリケーションは、Dockerで動かしましょう。
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
FROM golang:1.16 WORKDIR /app COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin CMD ["air"] |
golangのairを導入しているので、ホットリロード(コンテナを止めずに、Goのコードの変更を検知する仕組み)ができます。
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version: "3.3" services: backend: build: . ports: - 8080:8080 volumes: - .:/app depends_on: - redis redis: image: redis:latest ports: - 6379:6379 |
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "github.com/gofiber/fiber/v2" func main() { app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World 👋!") }) app.Listen(":8080") } |
コンテナの立ち上げ
1 2 |
docker-compose build docker-compose up |
ブラウザから「http://localhost:8080/」へアクセスしてみましょう。

Redisへアクセスする
Redisへアクセスするコードを書きます。
接続には、go-redisを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// repository/redis.go package repository import ( "github.com/go-redis/redis/v8" ) var Cache *redis.Client func SetupRedis() { Cache = redis.NewClient(&redis.Options{ // docker-compose.ymlに指定したservice名+port Addr: "redis:6379", DB: 0, }) } |
SetupRedis関数は、main.goから呼び出しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... func main() { // reids repository.SetupRedis() app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World 👋!") }) app.Listen(":8080") } |
これで、Redisへ接続できるようになりました。
APIのレスポンス
とりあえず、以下のデータを取得するAPIを作ってみようかと思います。
1 2 3 4 5 6 7 |
{ userList: [ {"account_id": 1, "name": "hoge", "email": "hoge@com.com"}, {"account_id": 2, "name": "hoge", "email": "hoge@com.com"}, {"account_id": 3, "name": "hoge", "email": "hoge@com.com"}, ] } |
Userモデルの作成
Userモデルを作成しましょう。
1 2 3 4 5 6 7 8 |
// models/user.go package models type User struct { AccountID int `json:"account_id"` Name string `json:"name"` Email string `json:"email"` } |
テストデータをRedisに突っ込む
BookデータRedisに突っ込みましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
// command/testdata.go package main import ( "context" "encoding/json" "fmt" "golang-redis/models" "log" "time" "github.com/bxcodec/faker/v3" "github.com/go-redis/redis/v8" ) type SomeStructWithTags struct { Email string `faker:"email"` Name string `faker:"name"` UUID string `faker:"uuid_digit"` AccountID int `faker:"oneof: 15, 27, 61"` } func SetupRedis() *redis.Client { return redis.NewClient(&redis.Options{ // docker-compose.ymlに指定したservice名+port Addr: "redis:6379", DB: 0, }) } func main() { // fakerの準備 a := SomeStructWithTags{} err := faker.FakeData(&a) if err != nil { panic(err) } // ユーザーリストを作成 var userList []models.User for i := 0; i < 1000; i++ { err = faker.FakeData(&a) if err != nil { panic(err) } userList = append(userList, models.User{ AccountID: a.AccountID, Name: a.Name, Email: a.Email, }) } // Redisに格納するため、シリアライズ serialize, err := json.Marshal(&userList) if err != nil { panic(err) } // UUIDはデータにアクセスするために必要 UUID := a.UUID fmt.Println("UUID: ", UUID) // Reedisに接続 c := context.Background() r := SetupRedis() // Redisにデータを格納 err = r.Set(c, UUID, serialize, time.Hour*24).Err() if err != nil { panic(err) } log.Println("complete") } |
UUIDをキーに、ユーザーリストを追加しました。
Redisコンテナが立ち上がっていることが前提ですが、以下のコマンドを実行してテストデータを入れましょう。
1 2 3 4 |
docker-compose run --rm backend sh -c "go run /app/command/testdata.go" UUID: 22f1fd0540b845e5a65b7a069fa9259e 2021/07/15 01:59:18 complete |
ここで取得したUUIDは後で使います。
ユーザーリストの取得
redisからユーザーリストを取得してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// repository/redis.go package repository import ( "context" "encoding/json" "golang-redis/models" "github.com/go-redis/redis/v8" ) var Cache *redis.Client func SetupRedis() { ... } func GetUserList(uuid string) ([]models.User, error) { data, err := Cache.Get(context.Background(), uuid).Result() if err != nil { return nil, err } userList := new([]models.User) err = json.Unmarshal([]byte(data), userList) if err != nil { return nil, err } return *userList, nil } |
Redisからデータを取得するための関数を定義しました。
Redisから取得したデータは、jsonパッケージのUnmarshalを使って、GoのStructへパースしています。
今回のようにリストデータもパースできるので、大変便利です。
ルートの追加
main.goにルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package main import ( "golang-redis/repository" "github.com/gofiber/fiber/v2" ) func main() { // reids repository.SetupRedis() app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World 👋!") }) app.Get("users/:uuid", getUserList) app.Listen(":8080") } func getUserList(c *fiber.Ctx) error { // リクエストからIDを取得 uuid := c.Params("uuid") // redisからデータを取得 userList, err := repository.GetUserList(uuid) if err != nil { panic(err) } return c.JSON(userList) } |
「http://localhost:8080/users/<uuid>」へGET形式でリクエストを送ると、Redisからデータを取得して呼び出し元に返す処理を追加しました。
検証
curlで確認してみましょう。データが取得できれば、OKです。
1 2 3 4 5 6 7 8 |
curl -XGET http://localhost:8080/users/22f1fd0540b845e5a65b7a069fa9259e | jq [{ "account_id": 27, "name": "Dr. Leonora Ferry", "email": "cfclLyN@EDPFxAr.info" }, ] |
※jqコマンドは、HomeBrewでインストールしています(brew install jq)
ソート
Redis Sort
Redisは、スコア(ランキング)をつけることで、データの並び替えをしてくれます。
スコアをつけるには、go-redisのZAddメソッドを使います。
テストデータを作成するtestdata.goに以下の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// command/testdata.go func main() { // fakerの準備 // ユーザーリストを作成 // Redisに格納するため、シリアライズ // UUIDはデータにアクセスするために必要 // Reedisに接続 // Redisにデータを格納 // Sort for _, user := range userList { r.ZAdd(c, "rankings", &redis.Z{ Score: float64(user.AccountID), Member: user.Name, }) } log.Println("complete") } |
AccountIDをスコアとして、Nameを登録しました。”rankings”キーワードで、データが取得できます。
実装が完了したら、以下のコマンドで、Redisにデータを入れましょう。
1 |
docker-compose run --rm backend sh -c "go run /app/command/testdata.go" |
並び替えには、「ZRevRangeByScoreWithScoresメソッドを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// repository/redis.go package repository ... func GetRankings() (map[string]float64, error) { // zrevrangebyscore //http://mogile.web.fc2.com/redis/commands/zrevrangebyscore.html rankings, err := Cache.ZRevRangeByScoreWithScores( context.Background(), "rankings", // updateRankings.goでkeyとして設定した値 &redis.ZRangeBy{ Min: "-inf", Max: "+inf", }).Result() if err != nil { return nil, err } result := make(map[string]float64) for _, ranking := range rankings { result[ranking.Member.(string)] = ranking.Score } return result, nil } |
main.go関数にRankingハンドラを実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package main ... func main() { // reids repository.SetupRedis() app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World 👋!") }) app.Get("users/:uuid", getUserList) app.Get("ranking", ranking) app.Listen(":8080") } ... func ranking(ctx *fiber.Ctx) error { result, err := repository.GetRankings() if err != nil { panic(err) } return ctx.JSON(result) } |
これで、「http://localhost:8080/ranking」にアクセスすると、ランキングされたデータが取得できます。


ユーザー名が小さい順にソートされますね。てっきり、Score(AccountID)が小さい順にソートされるものだと思いましたが…
Golang Sort
RedisのScoreは、覚えることがたくさんあり、使い勝手も悪そうなので、Go言語でソートをしてみたいと思います。
Account IDが降順になるようにプログラミングをします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// sort/sort.go package sort import ( "golang-redis/models" "sort" ) func RankingSort(userList []models.User) []models.User { sort.Slice( userList, func(i, j int) bool { return userList[i].AccountID > userList[j].AccountID }, ) return userList } |
Goのsortパッケージを使って、AccountIDが降順になるように実装しました。
これを、main.goのgetUserListに組み込みましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package main ... func getUserList(c *fiber.Ctx) error { // リクエストからIDを取得 uuid := c.Params("uuid") // redisからデータを取得 userList, err := repository.GetUserList(uuid) if err != nil { panic(err) } // 降順にする userList = sort.RankingSort(userList) return c.JSON(userList) } |
これで、AccountIDが大きい順にデータが取得できるようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[ {"account_id": 61, "name": "Ms. Michelle O\"Conner", "email": "dNpDYva@CQqUHhy.ru"}, {"account_id": 61, "name": "Mrs. Robyn Mueller", "email": "oITfpGf@pYwfnqm.com"}, {"account_id": 61, "name": "Lady Aileen Walsh", "email": "bEHJURa@hRdCMRp.org"}, {"account_id": 61, "name": "Dr. Lydia Sanford", "email": "TUcmFsN@UHTpoHG.net"}, {"account_id": 61, "name": "Ms. Shayna Runolfsson", "email": "xbUlhqa@QDxOYej.ru"}, {"account_id": 61, "name": "Princess Tatyana Gleichner", "email": "OSASpyb@mccAWbX.info"}, {"account_id": 61, "name": "Dr. Jeanne Grady", "email": "eMbygMI@guWfyoY.ru"}, {"account_id": 61, "name": "Prof. Kirstin Kemmer", "email": "vguufrL@BWfIMFx.net"}, {"account_id": 61, "name": "Lady Ettie Schmitt", "email": "wxmocJT@fqqLkEF.ru"}, {"account_id": 61, "name": "Mrs. Elva Lemke", "email": "wXoyjyj@sMMKgEg.ru"}, {"account_id": 61, "name": "Dr. Palma Skiles", "email": "dVLVdaf@MEhXaaK.biz"}, {"account_id": 27, "name": "Lady Caroline Lemke", "email": "JXOfQFe@CAlTZXf.org"}, {"account_id": 27, "name": "Lady Shemar Schaefer", "email": "oHqOXTr@xeolRPQ.info"}, {"account_id": 27, "name": "Prof. Vada Ortiz", "email": "NmpsEXN@vVrGVPA.com"}, {"account_id": 27, "name": "Miss Anais West", "email": "firkdeD@eXYJVmL.biz"}, {"account_id": 27, "name": "Princess Jessika Kirlin", "email": "DeUfGXS@EHLQUvj.ru"}, {"account_id": 27, "name": "Miss Celine Flatley", "email": "mcoaiyQ@aolwIUQ.org"}, {"account_id": 27, "name": "Ms. Shanie King", "email": "OEvkFYV@CKURZoU.info"}, {"account_id": 15, "name": "Queen Kitty Rau", "email": "RKyUbRU@SXPSwIi.com"}, {"account_id": 15, "name": "Queen Octavia Marvin", "email": "NYhicCl@gvtxwva.org"}, {"account_id": 15, "name": "Dr. Antonietta Sanford", "email": "SBgYLMf@ydbosWP.info"}, {"account_id": 15, "name": "Prof. Annabel Herzog", "email": "ZWiCEDv@jotchgw.ru"}, {"account_id": 15, "name": "Ms. Janessa Schumm", "email": "amAZVBX@mRSSZXT.ru"}, {"account_id": 15, "name": "Princess Shea Murray", "email": "UZaNMXs@bmaedne.org"}, {"account_id": 15, "name": "Miss Oleta Shields", "email": "cGDGmNT@hxvGRxO.info"}, {"account_id": 15, "name": "Ms. Kallie Lind", "email": "VxyLTAA@JfrwrLb.org"}, {"account_id": 15, "name": "Dr. Lynn Ryan", "email": "OJQWItF@kjJYMWx.net"}, {"account_id": 15, "name": "Lady Taya Heller", "email": "pkjqSMY@aNYyyxX.biz"}, {"account_id": 15, "name": "Prof. Ada Langworth", "email": "tbdMXhx@dvbNtdh.ru"}, {"account_id": 15, "name": "Mrs. Zaria O\"Hara", "email": "ofPPvHH@TUZpEhr.info"} ] |
まとめ
GoでRedisを操作するのは結構簡単ですし、何より処理速度がめちゃくちゃ早いです。
現場でも結構重宝するので、皆さんもぜひ試してみてください。
それでは、また!
Go記事まとめ
ソースコード
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
FROM golang:1.16 WORKDIR /app COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin CMD ["air"] |
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version: "3.3" services: backend: build: . ports: - 8080:8080 volumes: - .:/app depends_on: - redis redis: image: redis:latest ports: - 6379:6379 |
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package main import ( "golang-redis/repository" "golang-redis/sort" "github.com/gofiber/fiber/v2" ) func main() { // reids repository.SetupRedis() app := fiber.New() app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World 👋!") }) app.Get("users/:uuid", getUserList) app.Get("ranking", ranking) app.Listen(":8080") } func getUserList(c *fiber.Ctx) error { // リクエストからIDを取得 uuid := c.Params("uuid") // redisからデータを取得 userList, err := repository.GetUserList(uuid) if err != nil { panic(err) } // 降順にする userList = sort.RankingSort(userList) return c.JSON(userList) } func ranking(ctx *fiber.Ctx) error { result, err := repository.GetRankings() if err != nil { panic(err) } return ctx.JSON(result) } |
radis.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// repository/redis.go package repository import ( "context" "encoding/json" "golang-redis/models" "github.com/go-redis/redis/v8" ) var Cache *redis.Client func SetupRedis() { Cache = redis.NewClient(&redis.Options{ // docker-compose.ymlに指定したservice名+port Addr: "redis:6379", DB: 0, }) } func GetUserList(uuid string) ([]models.User, error) { data, err := Cache.Get(context.Background(), uuid).Result() if err != nil { return nil, err } userList := new([]models.User) err = json.Unmarshal([]byte(data), userList) if err != nil { return nil, err } return *userList, nil } func GetRankings() (map[string]float64, error) { // zrevrangebyscore //http://mogile.web.fc2.com/redis/commands/zrevrangebyscore.html rankings, err := Cache.ZRevRangeByScoreWithScores( context.Background(), "rankings", // updateRankings.goでkeyとして設定した値 &redis.ZRangeBy{ Min: "-inf", Max: "+inf", }).Result() if err != nil { return nil, err } result := make(map[string]float64) for _, ranking := range rankings { result[ranking.Member.(string)] = ranking.Score } return result, nil } |
testdata.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
// command/testdata.go package main import ( "context" "encoding/json" "fmt" "golang-redis/models" "log" "time" "github.com/bxcodec/faker/v3" "github.com/go-redis/redis/v8" ) type SomeStructWithTags struct { Email string `faker:"email"` Name string `faker:"name"` UUID string `faker:"uuid_digit"` AccountID int `faker:"oneof: 15, 27, 61"` } func SetupRedis() *redis.Client { return redis.NewClient(&redis.Options{ // docker-compose.ymlに指定したservice名+port Addr: "redis:6379", DB: 0, }) } func main() { // fakerの準備 a := SomeStructWithTags{} err := faker.FakeData(&a) if err != nil { panic(err) } // ユーザーリストを作成 var userList []models.User for i := 0; i < 30; i++ { err = faker.FakeData(&a) if err != nil { panic(err) } userList = append(userList, models.User{ AccountID: a.AccountID, Name: a.Name, Email: a.Email, }) } // Redisに格納するため、シリアライズ serialize, err := json.Marshal(&userList) if err != nil { panic(err) } // UUIDはデータにアクセスするために必要 UUID := a.UUID fmt.Println("UUID: ", UUID) // Reedisに接続 c := context.Background() r := SetupRedis() // Redisにデータを格納 err = r.Set(c, UUID, serialize, time.Hour*24).Err() if err != nil { panic(err) } // Sort for _, user := range userList { r.ZAdd(c, "rankings", &redis.Z{ Score: float64(user.AccountID), Member: user.Name, }) } log.Println("complete") } |
user.go
1 2 3 4 5 6 7 8 |
// models/user.go package models type User struct { AccountID int `json:"account_id"` Name string `json:"name"` Email string `json:"email"` } |
sort.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// sort/sort.go package sort import ( "golang-redis/models" "sort" ) func RankingSort(userList []models.User) []models.User { sort.Slice( userList, func(i, j int) bool { return userList[i].AccountID > userList[j].AccountID }, ) return userList } |
コメントを残す
コメントを投稿するにはログインしてください。