This is a creation in Article, where the information may have evolved or changed.
For the complete code of this article see Https://github.com/changjixiong/goNotes/tree/master/redisnote, https://github.com/changjixiong/goNotes/ Tree/master/utils and Https://github.com/changjixiong/goNotes/tree/master/reflectinvoke. If the link is not displayed in the text, the link is killed when it is forwarded, please search to find the original reading.
Overview
Leaderboards are widely available in a variety of Internet applications. This article will use an example to illustrate how Redis and consul can be used to achieve horizontally scaled leaderboard services.
Use of Redis
There are 2 places to use Redis for the leaderboard:
1. Store the player's ranking information, which is used sorted sets, the code is as follows
err := Rds.ZAdd( PlayerLvRankKey, redis.Z{ Score: lvScoreWithTime(playerInfo.Lv, time.Now().Unix()), Member: playerInfo.PlayerID, },).Err()
Where Lvscorewithtime based on the player level and the time of arrival calculation score for ranking, the same level of the situation, the first to reach the level of the calculation score is greater than after.
2. Store the player's own information (name, ID, etc.) for display in the leaderboard, after all, only the rank of the ID is not enough. Here the hashset is used, the code is as follows
// ma的类型为map[string]stringerr := Rds.HMSet(fmt.Sprintf("playerInfo:%d", playerID), ma).Err()
Server-side
To initialize a Redis connection first
rdsClient := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", "127.0.0.1", 6379), Password: "123456", DB: 0,})playercache.Rds = rdsClientrankservice.Rds = rdsClient
Increase the initial player information (slightly).
Register the server interface, which is detailed in this section, refer to "Go" using JSON string to invoke the specified method of the struct and return the JSON result through reflection http://changjixiong.com/ reflect-invoke-method-of-struct-and-get-json-format-result/
reflectinvoke.RegisterMethod(rankservice.DefaultRankService)
To register the service with Consul, please refer to "Go using service discovery System Consul" in this section for more information http://changjixiong.com/use-consul-in-go/
go registerServer()
Open service on port 9528 for structuring client requests and returning results
ln, err := net.Listen("tcp", "0.0.0.0:9528")if nil != err { panic("Error: " + err.Error())}for { conn, err := ln.Accept() // 对Accept()产生的临时错误的处理,可以参考net/http/server.go中的func (srv *Server) Serve(l net.Listener) if err != nil { panic("Error: " + err.Error()) } go RankServer(conn)}
The interface for adding player experience and setting the player's leaderboard data is as follows
func (rankService *RankService) AddPlayerExp(playerID, exp int) bool { player := playercache.GetPlayerInfo(playerID) if nil == player { return false } player.Exp += exp // 固定经验升级,可以按需要修改 if player.Exp >= playercache.LvUpExp { player.Lv += 1 player.Exp = player.Exp - playercache.LvUpExp rankService.SetPlayerLvRank(player) } playercache.SetPlayerInfo(player) return true}func (rankService *RankService) SetPlayerLvRank(playerInfo *playercache.PlayerInfo) bool { if nil == playerInfo { return false } err := Rds.ZAdd( PlayerLvRankKey, redis.Z{ Score: lvScoreWithTime(playerInfo.Lv, time.Now().Unix()), Member: playerInfo.PlayerID, }, ).Err() if nil != err { log.Println("RankService: SetPlayerLvRank:", err) return false } return true}
Gets the interface for the specified rank of player information
func (rankService *RankService) GetPlayerByLvRank(start, count int64) []*playercache.PlayerInfo { playerInfos := []*playercache.PlayerInfo{} ids, err := Rds.ZRevRange(PlayerLvRankKey, start, start+count-1).Result() if nil != err { log.Println("RankService: GetPlayerByLvRank:", err) return playerInfos } for _, idstr := range ids { id, err := strconv.Atoi(idstr) if nil != err { log.Println("RankService: GetPlayerByLvRank:", err) } else { playerInfo := playercache.LoadPlayerInfo(id) if nil != playerInfos { playerInfos = append(playerInfos, playerInfo) } } } return playerInfos}
Client
Connect to consul and find the address of the leaderboard service, connect and send requests
func main() { client, err := consulapi.NewClient(consulapi.DefaultConfig()) if err != nil { log.Fatal("consul client error : ", err) } for { time.Sleep(time.Second * 3) var services map[string]*consulapi.AgentService var err error services, err = client.Agent().Services() log.Println("services", strings.Repeat("-", 80)) for _, service := range services { log.Println(service) } if nil != err { log.Println("in consual list Services:", err) continue } if _, found := services["rankNode_1"]; !found { log.Println("rankNode_1 not found") continue } log.Println("choose", strings.Repeat("-", 80)) log.Println("rankNode_1", services["rankNode_1"]) sendData(services["rankNode_1"]) }}
Operating conditions
Consul registered 2 Custom services, one is the Echo service named Servernode (source "Go using service discovery System Consul") and the other is the leaderboard service Ranknode of this article.
Request fragments received by the server
get: {"func_name":"AddPlayerExp","params":[4,41]}get: {"func_name":"AddPlayerExp","params":[2,35]}get: {"func_name":"AddPlayerExp","params":[5,27]}get: {"func_name":"GetPlayerByLvRank","params":[0,3]}
The client finds the service in consul and connects to the Ranknode_1
services ----------------------------------------------------------&{consul consul [] 8300 false}&{rankNode_1 rankNode [serverNode] 9528 127.0.0.1 false}&{serverNode_1 serverNode [serverNode] 9527 127.0.0.1 false}choose ------------------------------------------------------------rankNode_1 &{rankNode_1 rankNode [serverNode] 9528 127.0.0.1 false}
Response fragments received by the client
get: {"func_name":"AddPlayerExp","data":[true],"errorcode":0}get: {"func_name":"AddPlayerExp","data":[true],"errorcode":0}get: {"func_name":"AddPlayerExp","data":[true],"errorcode":0}get: {"func_name":"GetPlayerByLvRank","data":[[{"player_id":3,"player_name":"玩家3","exp":57,"lv":4,"online":true},{"player_id":2,"player_name":"玩家2","exp":31,"lv":4,"online":true},{"player_id":1,"player_name":"玩家1","exp":69,"lv":3,"online":true}]],"errorcode":0}
A little explanation
Why is it a level-scalable leaderboard service? It has been seen that there are currently 2 custom services registered on Consul, the client chose Ranknode_1, then if more than one ranknode is registered, the client can choose other available nodes to get the service when some of them are unavailable. And when the unavailable node is re-usable, you can continue to register to consul to provide the service.
661 Reads