- 投稿日:2021-02-28T10:47:07+09:00
golangで、RedisのSorted Sets型のmemberに複数の値を保持したい
初めに
普段はインターン先でフロントエンドでの開発を行なっているのですが、この度短期インターンでGoでのバックエンド開発を行うことになりました。
ゲームクライアントがすでに用意されていて、そのゲームのバックエンドを開発するというものです。
その中で、ランキング機能をRedisを用いて実装しようと思い、その時に軽く詰まったことを
備忘録として書いておこうと思います。Sorted Sort型について
今回初めてRedisを触り、Redisが持つ各データ型を調べている際に、Sorted Sorts型というまさにランキングに適した型を知りました。
この記事を読む方はすでにご存知かもしれませんが、簡単にRedisのSorted Sorts型について説明したいと思います。
ご存知の方は飛ばしていただけたらと思います。RedisのSorted Sets型は、key単位で集合を定義することができ、それぞれのデータはMemberとScoreというフィールドを持ちます。
以下の図のイメージですね。
各Memberはそれぞれスコアというフィールドを持ち、そのスコアによって集合内で自動的にランク付けを行なってくれます。
例として、以下の表のように、今回はkeyはranking、MemberにユーザーのID、Scoreにゲーム内でのそのユーザーの最高点数を入れることとします(ディズニーツムツム的なゲームを想像してください)。
AliceのScoreを10000点以上に更新すれば自動でAliceを一番上に配置してくれます。便利ですね。
(※今回は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 []uint8member := 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/
- 投稿日:2021-02-28T09:46:01+09:00
【初心者向け】Go 言語の勉強法を具体的に教えるよ!(英語多め)
何かを学ぶとき、自分の
- 現状のスキル
- 目指すゴール
を明確にすることはとても重要です。
Go 言語の勉強に関しては、Go Developer Roadmap を使って、何をどのタイミングで勉強するかの指針になると思います。
本記事では、もう少し具体的にどうしたら良いかを書いてみましたので、ぜひ参考にしてください!!
具体的なロードマップ
リンク集
ウェブ
- Cheat Sheet: ダウンロードして手元に置いておく
- A Tour of Go: ハンズオンでざっくりと学ぶ
- Effective Go: 都度参照する程度で良い。はじめは飛ばしても問題ない
- Writing Web Applications: オフィシャルのウェブアプリチュートリアル。データベースではなく、ファイルを使った Wiki ページが作れる
本
- Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る: ウェブアプリ開発が一通りできるようになる
- Go Web Programming: 上のオリジナル英語本
Udemy
YouTube
- Go Concurrency Patterns: Concurrency といえば「"Rob Pike" の動画」と言われるくらい有名な動画
- YouTube で "Golang" で検索: たくさんあるので、興味や好きに合わせて勉強
その他、情報収集
- Go Package: なんだかんだで、オフィシャルドキュメントが一番勉強になる
- Go Wiki: スキルレベルに合わせた情報が見つかる。ただ、情報が多すぎるかも。
- Awesome Go: 「へー、こんなのあるんだー。どう書くんだろ」で参考になる。
- Go Time: Go 周りの情報が得られる Podcast
- 投稿日:2021-02-28T09:46:01+09:00
【初心者向け】Go 言語の勉強法を具体的に教えるよ!(英語の参考情報が多め)
何かを学ぶとき、自分の
- 現状のスキル
- 目指すゴール
を明確にすることはとても重要です。
Go 言語の勉強に関しては、Go Developer Roadmap を使って、何をどのタイミングで勉強するかの指針になると思います。
本記事では、もう少し具体的にどうしたら良いかを書いてみましたので、ぜひ参考にしてください!!
具体的なロードマップ
リンク集
ウェブ
- Cheat Sheet: ダウンロードして手元に置いておく
- A Tour of Go: ハンズオンでざっくりと学ぶ
- Effective Go: 都度参照する程度で良い。はじめは飛ばしても問題ない
- Writing Web Applications: オフィシャルのウェブアプリチュートリアル。データベースではなく、ファイルを使った Wiki ページが作れる
本
- Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る: ウェブアプリ開発が一通りできるようになる
- Go Web Programming: 上のオリジナル英語本
Udemy
YouTube
- Go Concurrency Patterns: Concurrency といえば「"Rob Pike" の動画」と言われるくらい有名な動画
- YouTube で "Golang" で検索: たくさんあるので、興味や好きに合わせて勉強
その他、情報収集
- Go Package: なんだかんだで、オフィシャルドキュメントが一番勉強になる
- Go Wiki: スキルレベルに合わせた情報が見つかる。ただ、情報が多すぎるかも。
- Awesome Go: 「へー、こんなのあるんだー。どう書くんだろ」で参考になる。
- Go Time: Go 周りの情報が得られる Podcast
- 投稿日:2021-02-28T09:01:22+09:00
Go言語に入門する -MacOS * VSCode で開発環境を構築して”Hello World"を出力する-
目標
本記事の目標は下記です。
- MacOSにGo言語の開発環境を構築する。
- "Hello World" を出力する。
環境情報
$ sw_vers ProductName: macOS ProductVersion: 11.2.2 BuildVersion: 20D80開発環境構築
1. GOをインストールする
$ brew install go1.1. インストールできたことを確認する
$ go version go version go1.16 darwin/amd642 VSCodeに拡張機能をいれる
2.1 MarketPlaceでgoと検索して、一番上に出てくる拡張機をインストールする
2.2 インストールした拡張機能を動かすのに必要なパッケージをインストールする
2.2.1 コマンドパレットで Go: Install/Update Tools と入力する
2.2.2 インストールしたいパッケージにチェックを入れてOK押下
全部入れなくてもいいっぽいんだけど、とりあえず全部いれておけば動くでしょう。
インストールできました。
3. "Hello World" する
3.1 適当なディレクトリにhelloworld.go を作成して、VSCodeで実行する。
helloworld.gopackage main import "fmt" func main() { fmt.Printf("Hello world") }無事動きました!!
参考