1. 程式人生 > >golang實現基於redis和consul的可水平擴充套件的排行榜服務範例

golang實現基於redis和consul的可水平擴充套件的排行榜服務範例

概述

  排行榜在各種網際網路應用中廣泛存在。本文將用一個範例說明如何利用redis和consul實現可水平擴充套件的等級排行榜服務。

redis的使用

  實現排行榜有2個地方需要用到redis:

  1.儲存玩家的排行資訊,這裡使用的是Sorted Sets,程式碼如下

err := Rds.ZAdd(
        PlayerLvRankKey,
        redis.Z{
            Score:  lvScoreWithTime(playerInfo.Lv, time.Now().Unix()),
            Member: playerInfo.PlayerID,
        },
    ).Err()

  其中lvScoreWithTime根據玩家等級及到達的時間計算score用於排名,等級相同的情況下,先到達等級的計算分值大於後達到的。

  2.儲存玩家自身的資訊(名字,ID等),用於在排行榜中顯示,畢竟僅僅只有排行的ID是不夠的。這裡採用hashset,程式碼如下

// ma的型別為map[string]string
err := Rds.HMSet(fmt.Sprintf("playerInfo:%d", playerID), ma).Err()

伺服器端

  先初始化redis連線

rdsClient := redis.NewClient(&redis.Options{
        Addr:     fmt.Sprintf("%s:%d"
, "127.0.0.1", 6379), Password: "123456", DB: 0, }) playercache.Rds = rdsClient rankservice.Rds = rdsClient

  增加初始玩家資訊(略)。

reflectinvoke.RegisterMethod(rankservice.DefaultRankService)
go registerServer()

  在埠9528上開啟服務用於結構client請求並返回結果

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) }

  增加玩家經驗及設定玩家的排行榜資料的介面如下

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
}

  獲取指定排行的玩家資訊的介面

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

}

客戶端

  連線到consul並查到到排行榜服務的地址,連線併發送請求

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"])

    }
}

執行情況

  consul上註冊了2個自定義的服務,一個是名為serverNode的echo服務(來源 《golang使用服務發現系統consul》),另一個是本文的排行榜服務rankNode。

  伺服器接收到的請求片段

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]}

  客戶端在consul中查詢到服務並連線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}

  客戶端收到的迴應片段

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}

一點說明

  為什麼說是可水平擴充套件的排行榜服務呢?文中已經看到,目前有2個自定的服務註冊在consul上,client選擇了rankNode_1,那麼如果註冊了多個rankNode,則可以在其中某些節點不可用時,client可以選擇其他可用的節點獲取服務,而當不可用的節點重新可用時,可以繼續註冊到consul以提供服務。