20210228のGoに関する記事は4件です。

golangで、RedisのSorted Sets型のmemberに複数の値を保持したい

初めに

普段はインターン先でフロントエンドでの開発を行なっているのですが、この度短期インターンでGoでのバックエンド開発を行うことになりました。
ゲームクライアントがすでに用意されていて、そのゲームのバックエンドを開発するというものです。
その中で、ランキング機能をRedisを用いて実装しようと思い、その時に軽く詰まったことを
備忘録として書いておこうと思います。

Sorted Sort型について

今回初めてRedisを触り、Redisが持つ各データ型を調べている際に、Sorted Sorts型というまさにランキングに適した型を知りました。
この記事を読む方はすでにご存知かもしれませんが、簡単にRedisのSorted Sorts型について説明したいと思います。
ご存知の方は飛ばしていただけたらと思います。

RedisのSorted Sets型は、key単位で集合を定義することができ、それぞれのデータはMemberとScoreというフィールドを持ちます。
以下の図のイメージですね。
image.png
各Memberはそれぞれスコアというフィールドを持ち、そのスコアによって集合内で自動的にランク付けを行なってくれます。
例として、以下の表のように、今回はkeyはranking、MemberにユーザーのID、Scoreにゲーム内でのそのユーザーの最高点数を入れることとします(ディズニーツムツム的なゲームを想像してください)。
AliceのScoreを10000点以上に更新すれば自動でAliceを一番上に配置してくれます。便利ですね。
image.png
(※今回はkeyをrankingとしていますが、RedisのDBをranking用に用意して、それぞれのkeyを日付にすることで、日別ランキングやそれらの和集合をとって週別ランキングをxつ作成することも可能だったりします。)

MemberはRedis文字列型、Scoreは浮動小数値で扱われます。

golangでRedisを扱う

今回、golangでRedisを用いるためのライブラリとして、go-redisを使いました。
そこで、go-redisでSorted Sets型を表す構造体である、*redis.Zの定義を見てみたところ、
型定義は以下になってます。

type Z struct {
  Score float64
  Member interface{}
}

Memberってinterface型なんや、、、、
これをみた時に、Member型ってもしかして複数の値持てるんじゃね??って思いました。
自分はmember型は1つしか値を持てない、つまりユーザーIDしか入れることができないと思っていましたが、ユーザー名も入れちゃうことができる(複数の値を持つことができる)ポテンシャルがあるのではないかと思いました。

そこで、Member用の構造体を定義して、そのデータをJSONにシリアライズしてそのデータをMemberに保存する持つようにすればいいのではないかと思い、
以下のようにコードを書きました。

func SetUserRankInfo(ctx context.Context, userRankInfo *UserRankInfo) error {
        // memberに入れるデータを定義
    member := &RankingMember{
        UserID:   "1",
        UserName: "satofumi",
    }
    // シリアライズする
    serializedMember, err := json.Marshal(member)
    if err != nil {
        log.Printf("failed to marshal json: %v", err)
        return err
    }
         // Sorted Sorts型のデータを用意する
    members := &redis.Z{
        Score:  float64(10000),
        Member: serializedMember,
    }

    _, err = db.RedisClient.ZAdd(ctx, "ranking", members).Result()
    if err != nil {
        log.Printf("failed in ZAdd: %v", err)
        return err
    }

    return nil
}

redis-cliを使って、データが入ったか確認してみましょう。

127.0.0.1:6379> ZRevRange ranking 0 -1 WITHSCORES
 1) "{\"user_id\":\"7d350e11-b09d-4a17-9e5c-f56dc2b8f012\",\"user_name\":\"satofumi\"}"
 2) "100000000"

1行目がMember、2行目がScoreですね。
無事JSONとしてデータを入れることが可能になっています。
Redisの場合、Memberの値は一意であり、基本的に1つのMemberは1つのScoreしか持たないようになっています。
今回の場合、同じユーザーIDと名前でデータをJSONでシリアライズすると毎回全く同じ文字列になるので、一意性が担保できています。

今度は逆に取り出してみましょう。
返ってきたデータの値を[]byteにアサートしてJSONにunmarshalすれば取り出せるな!!と思い、
以下のコードを書きました。
(以下のコードは、複数のデータが存在していることを想定して書いています?)

        type sortedSet struct {
           Member: RankingMember
           Score: float64
        }

        // type RankingMember {
            userID string
            userName string
        }

    Z, err := db.RedisClient.ZRevRangeWithScores(ctx, "ranking", 0, -1).Result()
    if err != nil {
        log.Println(err)
                return 
    }

     sortedSets := make([]*SortedSet, len(ZList), len(ZList))

    for i, Z := range ZList {
        var sortedSet SortedSet
         // Scoreを取り出す
         score := int32(Z.Score)
         // Memberを取り出す
         member := Z.Member.([]byte)
         var rankingMember RankingMember
     if err = json.Unmarshal([]byte(member), &rankingMember); err != nil {
             log.Println(err)
             return
         }

        sortedSets[i] = &SortedSet{
            Member: rankingMember,
            Score: score 
        }
     log.Printf("sortedSets[i]=%v", sortedSets[i])
    }

これで動かしてみて、うまくいくかと思いきや、エラーが発生↓

http: panic serving [::1]:61321: interface conversion: interface {} is string, not []uint8
member := Z.Member.([]byte)
↑ここでの型アサーションでのエラーですね、、

データを入れる時はstring(member)をしてくれていて、取り出すときはstringで返ってきてくれているようです。

確かにさっきみたように、RedisのMemberにMarshalされた[]byte型で入れたはずなのに実際redisに格納されているデータを見てみると、

stringに変換してくれているんですね便利、、

と言うことで、該当部分を

member := Z.Member.(string)

と直し、もう一度動かしてみると、

    log.Printf("userRankInfoList[0]=%v", *userRankInfoList[0])

        -> userRankInfoList[0]={{1 satofumi} 10000}

うまく取り出すことができました。

まとめ

色々書きましたが、この記事の目標はRedisのSorted Sorts型のMemberに複数の値を入れることでした。
memberの構造体を定義してデータをいれ、JSONにMarshalすることで格納することが可能になりました。

初めて記事を書いたので、稚拙な部分等あるかと思いますが暖かい目でみてくださると幸いです。
また、何か誤りやアドバイスがありましたら歓迎しますのでぜひ教えていただきたいです。

参考記事

http://redis.shibu.jp/datatypes.html
https://redis.io/topics/data-types#sorted-sets
https://siguniang.wordpress.com/2014/09/15/access-ranking-with-redis-sorted-sets/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】Go 言語の勉強法を具体的に教えるよ!(英語多め)

何かを学ぶとき、自分の

  • 現状のスキル
  • 目指すゴール

を明確にすることはとても重要です。

Go 言語の勉強に関しては、Go Developer Roadmap を使って、何をどのタイミングで勉強するかの指針になると思います。

本記事では、もう少し具体的にどうしたら良いかを書いてみましたので、ぜひ参考にしてください!!

具体的なロードマップ

GoStudyRoadmap.jpg

リンク集

ウェブ

  • Cheat Sheet: ダウンロードして手元に置いておく
  • A Tour of Go: ハンズオンでざっくりと学ぶ
  • Effective Go: 都度参照する程度で良い。はじめは飛ばしても問題ない
  • Writing Web Applications: オフィシャルのウェブアプリチュートリアル。データベースではなく、ファイルを使った Wiki ページが作れる

Udemy

YouTube

その他、情報収集

  • Go Package: なんだかんだで、オフィシャルドキュメントが一番勉強になる
  • Go Wiki: スキルレベルに合わせた情報が見つかる。ただ、情報が多すぎるかも。
  • Awesome Go: 「へー、こんなのあるんだー。どう書くんだろ」で参考になる。
  • Go Time: Go 周りの情報が得られる Podcast
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】Go 言語の勉強法を具体的に教えるよ!(英語の参考情報が多め)

何かを学ぶとき、自分の

  • 現状のスキル
  • 目指すゴール

を明確にすることはとても重要です。

Go 言語の勉強に関しては、Go Developer Roadmap を使って、何をどのタイミングで勉強するかの指針になると思います。

本記事では、もう少し具体的にどうしたら良いかを書いてみましたので、ぜひ参考にしてください!!

具体的なロードマップ

GoStudyRoadmap.jpg

リンク集

ウェブ

  • Cheat Sheet: ダウンロードして手元に置いておく
  • A Tour of Go: ハンズオンでざっくりと学ぶ
  • Effective Go: 都度参照する程度で良い。はじめは飛ばしても問題ない
  • Writing Web Applications: オフィシャルのウェブアプリチュートリアル。データベースではなく、ファイルを使った Wiki ページが作れる

Udemy

YouTube

その他、情報収集

  • Go Package: なんだかんだで、オフィシャルドキュメントが一番勉強になる
  • Go Wiki: スキルレベルに合わせた情報が見つかる。ただ、情報が多すぎるかも。
  • Awesome Go: 「へー、こんなのあるんだー。どう書くんだろ」で参考になる。
  • Go Time: Go 周りの情報が得られる Podcast
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語に入門する -MacOS * VSCode で開発環境を構築して”Hello World"を出力する-

目標

本記事の目標は下記です。

  • MacOSにGo言語の開発環境を構築する。
  • "Hello World" を出力する。

環境情報

$ sw_vers
ProductName:    macOS
ProductVersion: 11.2.2
BuildVersion:   20D80

開発環境構築

1. GOをインストールする

$ brew install go

1.1. インストールできたことを確認する

$ go version
go version go1.16 darwin/amd64

2 VSCodeに拡張機能をいれる

2.1 MarketPlaceでgoと検索して、一番上に出てくる拡張機をインストールする

スクリーンショット 2021-02-27 17.49.17.png

2.2 インストールした拡張機能を動かすのに必要なパッケージをインストールする

2.2.1 コマンドパレットで Go: Install/Update Tools と入力する

スクリーンショット 2021-02-27 18.10.40.png

2.2.2 インストールしたいパッケージにチェックを入れてOK押下

スクリーンショット 2021-02-27 18.11.44.png

全部入れなくてもいいっぽいんだけど、とりあえず全部いれておけば動くでしょう。

スクリーンショット 2021-02-27 18.17.20.png

インストールできました。

3. "Hello World" する

3.1 適当なディレクトリにhelloworld.go を作成して、VSCodeで実行する。

helloworld.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello world")
}

スクリーンショット 2021-02-27 19.15.51.png

無事動きました!!

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む