Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >golang实现基于redis和consul的可水平扩展的排行榜服务范例

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

作者头像
李海彬
发布于 2018-03-27 03:18:36
发布于 2018-03-27 03:18:36
1.1K00
代码可运行
举报
文章被收录于专栏:Golang语言社区Golang语言社区
运行总次数:0
代码可运行
本文的完整代码见 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,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
  增加初始玩家信息()。
  注册服务器接口,此部分详细说明请参考《go通过反射使用json字符串调用struct的指定方法及返回json结果》http://changjixiong.com/reflect-invoke-method-of-struct-and-get-json-format-result/
reflectinvoke.RegisterMethod(rankservice.DefaultRankService)
  将服务注册到consul,此部分详细说明请参考《go使用服务发现系统consul》http://changjixiong.com/use-consul-in-go/
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服务(来源 《go使用服务发现系统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以提供服务。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C#与Redis】--实践案例--案例 3:使用 Redis 实现排行榜
实现一个基本的排行榜系统通常涉及到对分数进行排序,而 Redis 的 Sorted Set 数据结构非常适合这种用途。以下是一个使用 StackExchange.Redis 库在 C# 中实现排行榜的简单案例:
喵叔
2024/01/01
2900
基于go使用redis实现简易排行榜功能
本文将使用golang实现两个可以通过postman调用的接口,一个为点击增加热度/播放量接口。一个为获取排行榜接口。为方便起见,将本文章接口将不涉及数据库联动,仅实现简单的ID、热度两个字段。
陈杪秋
2024/05/22
2310
Golang深入浅出之-Go语言中的服务注册与发现机制
服务注册与发现是微服务架构中的重要组件,它允许服务实例在启动时向注册中心注册自己,同时其他服务能够动态地发现并调用这些已注册的服务。在Go语言生态中,这一机制的实现尤为关键,本文将深入浅出地探讨其原理、常见问题、易错点及解决方案,并提供代码示例。
Jimaks
2024/05/04
2930
花5分钟用Redis撸一个东京奥运会金牌排行榜
不知道各位最近有没有看东京奥运会啊,昨晚看完是被小日子过得不错的日本人气得不行。好家伙,这届奥运会奥林匹克精神我是没看到,抗日精神硬是给我唤醒了,刚打开了金牌排行榜看了一下,还好暂时还是第一。
北游
2021/07/29
4680
基于Redis的低成本高可用排行榜服务构建
业务运营活动中排行榜的使用很广泛,因此在三年前组内已经将排行榜服务组件化。整个服务是基于Redis的zset数据结构实现的。
廖可知
2018/07/10
9850
基于Redis的低成本高可用排行榜服务构建
what???领导让我实现一个redis Zset多维度排行榜
一:背景 实现一个多维度的排行榜(已自然周为一个周期),考虑得分和时间维度。当得分一样时,获得此排名越早的排名越靠前 需要监听原始数据,这里分为三个动作:收到、已读、通过。根据三个动作进行各项数据指标的统计 用户当前自然周收到、查看、标记的数量 根据三个动作等进行多条件过滤,准备出各个条件下的文案提示 二:方案设计 针对自然周的定义,可以参考雪花算法的实现。通过设计一个固定不可变基准开始日期A,来将某个日期B化为距离基准日A的周数X来作为周期数来表示 针对排行榜的实现,我们可以采用Redis的ZSe
chinotan
2022/01/04
2.2K0
Redis Sorted Set 底层实现原理深度解读与排行榜实战
Sorted Sets 与 Sets 类似,是一种集合类型,集合中不会出现重复的数据(member)。区别在于 Sorted Sets 元素由两部分组成,分别是 member 和 score。
码哥字节
2023/08/22
1.6K0
Redis Sorted Set 底层实现原理深度解读与排行榜实战
你知道怎么基于 redis 实现排行榜吗
同事: 最近我在做一个在线游戏网站,需要实现一个排行榜功能,用来展示每个玩家的积分排名。
灬沙师弟
2023/05/18
6320
你知道怎么基于 redis 实现排行榜吗
自己动手实现 Go 的服务注册与发现(下)
你好,我是aoho,今天我们继续来介绍自己动手实现 Go 的服务注册与发现(结束)。
aoho求索
2021/12/27
1.1K0
自己动手实现 Go 的服务注册与发现(下)
golang重构博客统计服务
作为一个后端开发,在docker,etcd,k8s等新技术不断涌现的今天,其背后的功臣golang在语言排行榜上持续走高,因此楼主也就开了这次使用golang自己开发的基础功能的二次装逼之旅。
haifeiWu
2018/09/11
6000
Go | Go 使用 consul 做服务发现
我们可以直接使用官方提供的二进制文件来进行安装部署,其官网地址为 https://www.consul.io/downloads
双鬼带单
2020/10/29
2.9K0
Go | Go 使用 consul 做服务发现
Redis除了做缓存,还能做什么???
Redis(Remote Dictionary Server)是一款开源的、基于内存的数据结构存储系统,常用于构建高性能、可扩展的应用程序。
ma布
2024/11/26
1530
Redis除了做缓存,还能做什么???
GoLang 操作 Redis
使用redis首先要部署redis,载个安装包,部署下即可,本文不赘述了。redis官网:https://redis.io/
JulyWhj
2022/06/01
1.1K0
GoLang 操作 Redis
golang实现Redis分布式锁
Redis的分布式锁是通过利用Redis的单线程特性以及原子操作来实现的 Redis的SET命令具有原子性,这意味着只有一个客户端能够成功地设置该键,其他客户端将无法获得锁。如果SET命令成功,表示该客户端成功获得了锁。 Redis锁示例代码
地球流浪猫
2023/10/14
4360
Go语言Redis API基本功能实践
本来想着放弃Go了,没想到人算不如天算,还是得继续Go的学习和练习。由于之前提到的原因,又要把Java版本操作Redis也要迁移到Go版本了。
FunTester
2022/07/08
7340
golang如何使用原生RPC及微服务简述
1、对于公司间的系统调用, 如果性能要求在100ms以上的服务,基于XML的SOAP协议 是一个值得考虑的方案。
花落花相惜
2021/11/21
8990
go微服务系列之三
在前两篇系列博文中,我已经实现了user-srv、web-srv、api-srv,在新的一篇博文中,我要讲解的是如何在项目中如何使用redis存储session。如果想直接查阅源码或者通过demo学习的,可以访问ricoder_demo。
李海彬
2019/05/08
7330
go微服务系列之三
从 API 设计开始,了解一下 Golang 的新框架 Twirp
作者 | Reshef Sharvit 译者 | 王强 策划 | 田晓旭 本文最初发布于 Medium 网站,经原作者授权由 InfoQ 中文站翻译并分享。 在这篇博文中我想谈谈 API,讲一下针对微服务该如何设计 API。 准备工作: Golang——https://golang.org/doc/install Protobuf 编译器——https://grpc.io/docs/protoc-installation/ 项目源码在这里: https://github.com/subzero112233
深度学习与Python
2023/04/01
7960
从 API 设计开始,了解一下 Golang 的新框架 Twirp
3.Go语言项目操作Redis数据实践
快速了解 Redis 数据库 描述: Redis是一个开源的内存数据库, Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。
全栈工程师修炼指南
2022/09/29
1.5K0
一行报错,让我探究起了go-redis连接池
关于连接池,想必大家耳熟能详。从其定义上来说,连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。简单点来说,就是当我们的程序在运行时,将数据库的连接进行实例化,每个连接当成对象存储在内存中,并且用一个数量大小的池子将其管理起来,当后续需要与数据库进行网络通信的时候再从池子中取出已有且正常的连接对象进行复用即可。因此,其所带来的好处显而易见,比如:1.减少连接的创建时间;2.提高资源的复用性减少资源浪费;3.精简编程模式简化开发模型等 ..... 在刚入职从事后端开发的时候,就听前辈们说过我们的项目使用了数据库的连接池模型,而当时也一直没有深入的去理解和研究连接池底层的原理以及实现,而就在上周,突然发现服务器的日志上,多了一条redis连接池的报错日志,其内容如下图所示:
_春华秋实
2024/07/10
4040
相关推荐
【C#与Redis】--实践案例--案例 3:使用 Redis 实现排行榜
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验