golang实现基于redis和consul的可水平扩展的排行榜服务范例

前端之家收集整理的这篇文章主要介绍了golang实现基于redis和consul的可水平扩展的排行榜服务范例前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

  本文的完整代码https://github.com/changjixiong/goNotes/tree/master/redisnotehttps://github.com/changjixiong/goNotes/tree/master/utilshttps://github.com/changjixiong/goNotes/tree/master/reflectinvoke如果文中没有显示链接说明链接在被转发的时候被干掉了,请搜索找到原文阅读。

概述

  排行榜在各种互联网应用中广泛存在。本文将用一个范例说明如何利用redis和consul实现可水平扩展的等级排行榜服务。

redis的使用

  实现排行榜有2个地方需要用到redis:

  1.存储玩家的排行信息,这里使用的是Sorted Sets,代码如下

  1. err := Rds.ZAdd(
  2. PlayerLvRankKey,redis.Z{
  3. score: lvscoreWithTime(playerInfo.Lv,time.Now().Unix()),Member: playerInfo.PlayerID,},).Err()

  其中lvscoreWithTime根据玩家等级及到达的时间计算score用于排名,等级相同的情况下,先到达等级的计算分值大于后达到的。

  2.存储玩家自身的信息(名字,ID等),用于在排行榜显示,毕竟仅仅只有排行的ID是不够的。这里采用hashset,代码如下

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

服务器端

  先初始化redis连接

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

  增加初始玩家信息(略)。

  注册服务器接口,此部分详细说明请参考《golang通过反射使用json字符串调用struct的指定方法及返回json结果》http://changjixiong.com/reflect-invoke-method-of-struct-and-get-json-format-result/

  1. reflectinvoke.RegisterMethod(rankservice.DefaultRankService)

  将服务注册到consul,此部分详细说明请参考《golang使用服务发现系统consul》http://changjixiong.com/use-consul-in-golang/

  1. go registerServer()

  在端口9528上开启服务用于结构client请求并返回结果

  1. ln,err := net.Listen("tcp","0.0.0.0:9528")
  2.  
  3. if nil != err {
  4. panic("Error: " + err.Error())
  5. }
  6.  
  7. for {
  8. conn,err := ln.Accept()
  9. // 对Accept()产生的临时错误的处理,可以参考net/http/server.go中的func (srv *Server) Serve(l net.Listener)
  10. if err != nil {
  11. panic("Error: " + err.Error())
  12. }
  13.  
  14. go RankServer(conn)
  15. }

  增加玩家经验及设置玩家的排行榜数据的接口如下

  1. func (rankService *RankService) AddPlayerExp(playerID,exp int) bool {
  2.  
  3. player := playercache.GetPlayerInfo(playerID)
  4. if nil == player {
  5. return false
  6. }
  7.  
  8. player.Exp += exp
  9. // 固定经验升级,可以按需要修改
  10. if player.Exp >= playercache.LvUpExp {
  11. player.Lv += 1
  12. player.Exp = player.Exp - playercache.LvUpExp
  13. rankService.SetPlayerLvRank(player)
  14. }
  15.  
  16. playercache.SetPlayerInfo(player)
  17.  
  18. return true
  19. }
  20.  
  21. func (rankService *RankService) SetPlayerLvRank(playerInfo *playercache.PlayerInfo) bool {
  22.  
  23. if nil == playerInfo {
  24. return false
  25. }
  26.  
  27. err := Rds.ZAdd(
  28. PlayerLvRankKey,).Err()
  29.  
  30. if nil != err {
  31. log.Println("RankService: SetPlayerLvRank:",err)
  32. return false
  33. }
  34.  
  35. return true
  36. }

  获取指定排行的玩家信息的接口

  1. func (rankService *RankService) GetPlayerByLvRank(start,count int64) []*playercache.PlayerInfo {
  2.  
  3. playerInfos := []*playercache.PlayerInfo{}
  4.  
  5. ids,err := Rds.ZRevRange(PlayerLvRankKey,start,start+count-1).Result()
  6.  
  7. if nil != err {
  8. log.Println("RankService: GetPlayerByLvRank:",err)
  9. return playerInfos
  10. }
  11.  
  12. for _,idstr := range ids {
  13. id,err := strconv.Atoi(idstr)
  14.  
  15. if nil != err {
  16. log.Println("RankService: GetPlayerByLvRank:",err)
  17. } else {
  18. playerInfo := playercache.LoadPlayerInfo(id)
  19.  
  20. if nil != playerInfos {
  21. playerInfos = append(playerInfos,playerInfo)
  22. }
  23. }
  24. }
  25.  
  26. return playerInfos
  27.  
  28. }

客户端

  连接到consul并查到到排行榜服务的地址,连接并发送请求

  1. func main() {
  2.  
  3. client,err := consulapi.NewClient(consulapi.DefaultConfig())
  4.  
  5. if err != nil {
  6. log.Fatal("consul client error : ",err)
  7. }
  8.  
  9. for {
  10.  
  11. time.Sleep(time.Second * 3)
  12. var services map[string]*consulapi.AgentService
  13. var err error
  14.  
  15. services,err = client.Agent().Services()
  16.  
  17. log.Println("services",strings.Repeat("-", 80))
  18. for _,service := range services {
  19. log.Println(service)
  20. }
  21.  
  22. if nil != err {
  23. log.Println("in consual list Services:",err)
  24. continue
  25. }
  26.  
  27. if _,found := services["rankNode_1"]; !found {
  28. log.Println("rankNode_1 not found")
  29. continue
  30. }
  31. log.Println("choose", 80))
  32. log.Println("rankNode_1",services["rankNode_1"])
  33. sendData(services["rankNode_1"])
  34.  
  35. }
  36. }

运行情况

  consul上注册了2个自定义的服务,一个是名为serverNode的echo服务(来源 《golang使用服务发现系统consul》),另一个是本文的排行榜服务rankNode。

  服务器接收到的请求片段

  1. get: {"func_name":"AddPlayerExp","params":[4,41]}
  2. get: {"func_name":"AddPlayerExp","params":[2,35]}
  3. get: {"func_name":"AddPlayerExp","params":[5,27]}
  4. get: {"func_name":"GetPlayerByLvRank","params":[0,3]}

  客户端在consul中查找到服务并连接rankNode_1

  1. services ----------------------------------------------------------
  2. &{consul consul [] 8300 false}
  3. &{rankNode_1 rankNode [serverNode] 9528 127.0.0.1 false}
  4. &{serverNode_1 serverNode [serverNode] 9527 127.0.0.1 false}
  5. choose ------------------------------------------------------------
  6. rankNode_1 &{rankNode_1 rankNode [serverNode] 9528 127.0.0.1 false}

  客户端收到的回应片段

  1. get: {"func_name":"AddPlayerExp","data":[true],"errorcode":0}
  2. get: {"func_name":"AddPlayerExp","errorcode":0}
  3. 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,{"player_id":1,"player_name":"玩家1","exp":69,"lv":3,"online":true}]],"errorcode":0}

一点说明

  为什么说是可水平扩展的排行榜服务呢?文中已经看到,目前有2个自定的服务注册在consul上,client选择了rankNode_1,那么如果注册了多个rankNode,则可以在其中某些节点不可用时,client可以选择其他可用的节点获取服务,而当不可用的节点重新可用时,可以继续注册到consul以提供服务。

猜你在找的Go相关文章