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,代码如下

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

一点说明

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s "strings" import "fmt" var p = fmt.Println func main() { p("Contains: ", s.Contains("test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考