golang實現基於redis和consul的可水平擴充套件的排行榜服務範例
本文的完整程式碼見https://github.com/changjixiong/goNotes/tree/master/redisnote ,https://github.com/changjixiong/goNotes/tree/master/utils 及https://github.com/changjixiong/goNotes/tree/master/reflectinvoke如果文中沒有顯示連結說明連結在被轉發的時候被幹掉了,請搜尋找到原文閱讀。
概述
排行榜在各種網際網路應用中廣泛存在。本文將用一個範例說明如何利用 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
增加初始玩家資訊 (略)。
註冊伺服器介面,此部分詳細說明請參考《golang 通過反射使用 json 字串呼叫 struct 的指定方法及返回 json 結果》http://changjixiong.com/reflect-invoke-method-of-struct-and-get-json-format-result/
reflectinvoke.RegisterMethod(rankservice.DefaultRankService)
將服務註冊到 consul,此部分詳細說明請參考《golang 使用服務發現系統 consul》http://changjixiong.com/use-consul-in-golang/
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 以提供服務。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 基於PHP擴充套件的WAF實現PHP套件
- 構建可擴充套件的有態服務套件
- [擴充套件] Hyperf-redis-lock 基於hyperf的分散式鎖的實現套件Redis分散式
- 基於Golang的微服務——ConsulGolang微服務
- 服務的擴充套件性套件
- 基於 Wasm 和 ORAS 簡化擴充套件服務網格功能ASM套件
- dubbo是如何實現可擴充套件的?套件
- [Open Source] .NET 基於StackExchange.Redis的擴充套件Redis套件
- 基於 Golang 構建高可擴充套件的雲原生 PaaS(附 PPT 下載)Golang套件
- 基於Docker + Consul + Nginx + Consul-template的服務負載均衡實現DockerNginx負載
- [WCF許可權控制]透過擴充套件自行實現服務授權套件
- dubbo是如何實現可擴充套件的?(二)套件
- 18.基於Consul的服務發現和ConsulManager管理
- 乾貨丨如何水平擴充套件和垂直擴充套件DolphinDB叢集?套件
- .NET Core 3.0之建立基於Consul的Configuration擴充套件元件套件元件
- 基於MongoDB.Driver的擴充套件MongoDB套件
- 基於 Bootstrap 的 UI 擴充套件:StyleBootstrapbootUI套件
- php7安裝redis擴充套件和memcache擴充套件PHPRedis套件
- 基於中臺思想的物流系統設計(五):設計可擴充套件的產品服務平臺套件
- Redis 實戰 —— 13. 擴充套件 RedisRedis套件
- 原生js實現的物件複製和擴充套件程式碼例項JS物件套件
- 使用Spring Session實現Spring Boot水平擴充套件SessionSpring Boot套件
- ETL的可擴充套件性和可維護性套件
- 實用的可選項(Optional)擴充套件套件
- gochat - 純go實現的im即時通訊系統(支援水平擴充套件)Go套件
- yii2-websocket | 基於 yii2 實現的 WebSocket 擴充套件Web套件
- golang使用服務發現系統consulGolang
- Eureka:擴充套件ClientFilter實現服務註冊自定義過濾套件clientFilter
- 基於 GatewayWorker 開發的 Laravel 擴充套件GatewayLaravel套件
- 使用 Python 構建可擴充套件的社交媒體情感分析服務Python套件
- 簡要剖析:可擴充套件的微服務架構套件微服務架構
- 可擴充套件性套件
- Docker compose 水平擴充套件 和負載均衡Docker套件負載
- 可擴充套件的搜尋元件套件元件
- 基於Redis的低成本高可用排行榜服務構建Redis
- Redis複製與可擴充套件叢集搭建Redis套件
- 關於block的ITL和dump的擴充套件BloC套件
- 【Redis】基於consul的Redis高可用方案Redis