前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于go使用redis实现简易排行榜功能

基于go使用redis实现简易排行榜功能

原创
作者头像
陈杪秋
发布2024-05-22 23:20:46
770
发布2024-05-22 23:20:46

前言

本文将使用golang实现两个可以通过postman调用的接口,一个为点击增加热度/播放量接口。一个为获取排行榜接口。为方便起见,将本文章接口将不涉及数据库联动,仅实现简单的ID、热度两个字段。

主要使用Redis中的Zset数据结构和简单的Key Value对。

项目运行环境为Windows10go1.21.0

环境安装

Redis

Windows下的Redis有很多安装方法,在此给出个人解决方案。

1.通过微软应用商店下载windows terminal

2.通过应用商店下载Ubuntu,通过Ubuntu直接安装即可

Ubuntu安装Redis时具体可以参考Redis官方教程

依赖包

Gin:本文将使用Gin进行路由注册。官方教程

Go-Redis: golang流行的Redis操作工具之一。官方教程

代码语言:go
复制
go get -u github.com/gin-gonic/gin
go get github.com/redis/go-redis/v9

项目初始化

使用Go-Redis连接Redis

使用go-redis连接至自己的Redis,以下给出使用go-redis的个人初始化方法

代码语言:go
复制
package cache

import (
	"context"
	"os"
	"strconv"

	"github.com/redis/go-redis/v9"
)

var RedisClient *redis.Client

// init redis
func Redis() {
	db, _ := strconv.ParseUint(os.Getenv("REDIS_DB"), 10, 64)
	client := redis.NewClient(&redis.Options{
		Addr:       "127.0.0.1:6379", // 一般Redis服务均使用6379端口 
		Password:  "", // 填入自己的Redis密码默认没有
		DB:         int(db),
		MaxRetries: 1,
	})

	_, err := client.Ping(context.Background()).Result()

	if err != nil {
		panic("can't connect redis")
	}

	RedisClient = client
}

服务注册与初始化Redis

在main.go中注册基本服务和调用连接Redis函数,此处增加热度接口涉及到Gin的路由参数的概念,感兴趣可以查阅官方文档路由参数,如果不想看也没有关系,通过下面的调用示例图,知道有什么用即可。

代码语言:go
复制
package main

import (
	"fmt"
	"rank/cache"

	"github.com/gin-gonic/gin"
)

func main() {
	cache.Redis() // 连接redis
	server := gin.Default()

	server.GET("show/:id", func(ctx *gin.Context) {
		id := ctx.Param("id")
		ctx.JSON(200, fmt.Sprintf("show %s", id))
	}) // 将增加热度接口注册在 localhost:3000/show 地址

	server.GET("rank", func(ctx *gin.Context) {
		ctx.JSON(200, "rank")
	}) // 将排行榜注册在 localhost:3000/rank 地址

	server.Run(":3000") // 运行在本地3000端口
}

使用go run main.go运行,并尝试使用postman调用一下

调用Show接口
调用Show接口
调用rank接口
调用rank接口

成功实现基础接口!!!

具体接口实现

思路

增加播放量即调用接口时将Redis中key对应的值+1,排行榜则维护一个Zset(小根堆),在每次调用增加播放量接口时同步增加Zset中的播放量数。

接口实现思路

获取播放量接口时从redis获取对应播放量并增加返回。

获取排行榜接口时直接将Zset中的后十个返回。

存储在Redis中的Key设置

由于Redis是键值对存储方式,所以我们需要对Redis存储的Key进行前缀定义,方便管理以及后续操作

代码语言:go
复制
package cache

import "fmt"

const (
	// DailyRankKey 排行榜的zset key
	DailyRankKey = "redis-test-rank:daily"
)

// ShareKey 为每个ID加上指定前缀
func ShareKey(id string) string {
	return fmt.Sprintf("redis-test-share:%s", id)
}

返回的结构体和增加播放量函数

代码语言:go
复制
package main

import (
	"context"
	"rank/cache"
	"strconv"
)

type Share struct {
	Id        string
	ViewCount int64
}

// 获取播放量函数
func (share *Share) GetViewCount() (num int64) {
	countStr, _ := cache.RedisClient.Get(context.Background(), cache.ShareKey(share.Id)).Result()
	if countStr == "" {
		return 0
	}
	num, _ = strconv.ParseInt(countStr, 10, 64)
	return
}

// AddViewCount 增加播放量函数
func (share *Share) AddViewCount() {
	// 增加播放量
	cache.RedisClient.Incr(context.Background(), cache.ShareKey(share.Id))
	// 增加在排行榜中的播放量
	cache.RedisClient.ZIncrBy(context.Background(), cache.DailyRankKey, 1, share.Id)
}

Show接口(获取播放量接口)

代码语言:go
复制
func ShowViewCount(ctx *gin.Context) {
	id := ctx.Param("id")
	share := Share{
		Id: id,
	}
	// 增加播放量
	share.AddViewCount()
	// 获取Redis数据
	share.ViewCount = share.GetViewCount()
	ctx.JSON(200, share)
}

接口演示

排行榜接口

代码语言:go
复制
func GetRank(ctx *gin.Context) {
	shares := make([]Share, 0, 16)

	// 从Redis中获取排行榜
	shareRank, err := cache.RedisClient.ZRevRange(context.Background(), cache.DailyRankKey, 0, 9).Result()
	if err != nil {
		ctx.JSON(200, err.Error())
		return
	}

	// 获取排行榜内对应排名的播放量
	if len(shareRank) > 0 {
		for _, shareId := range shareRank {
			share := Share{
				Id: shareId,
			}
			share.ViewCount = share.GetViewCount()
			shares = append(shares, share)
		}
	}

	// 填充空的排行榜排名至十个
	emptyShare := Share{
		Id:        "虚位以待",
		ViewCount: 0,
	}
	for len(shares) < 10 {
		shares = append(shares, emptyShare)
	}

	// 由于获取排行榜时有可能排行榜的Zset发生变动,需要按照确定的播放数重新排名一次
	sort.Slice(shares, func(i, j int) bool {
		return shares[i].ViewCount > shares[j].ViewCount
	})

	ctx.JSON(200, shares)
}

调用接口演示

最终代码

main.go

代码语言:go
复制
package main

import (
	"context"
	"rank/cache"
	"sort"

	"github.com/gin-gonic/gin"
)

func ShowViewCount(ctx *gin.Context) {
	id := ctx.Param("id")
	share := Share{
		Id: id,
	}
	// 增加播放量
	share.AddViewCount()
	// 获取Redis数据
	share.ViewCount = share.GetViewCount()
	ctx.JSON(200, share)
}

func GetRank(ctx *gin.Context) {
	shares := make([]Share, 0, 16)

	// 从Redis中获取排行榜
	shareRank, err := cache.RedisClient.ZRevRange(context.Background(), cache.DailyRankKey, 0, 9).Result()
	if err != nil {
		ctx.JSON(200, err.Error())
		return
	}

	// 获取排行榜内对应排名的播放量
	if len(shareRank) > 0 {
		for _, shareId := range shareRank {
			share := Share{
				Id: shareId,
			}
			share.ViewCount = share.GetViewCount()
			shares = append(shares, share)
		}
	}

	// 填充空的排行榜排名至十个
	emptyShare := Share{
		Id:        "虚位以待",
		ViewCount: 0,
	}
	for len(shares) < 10 {
		shares = append(shares, emptyShare)
	}

	// 由于获取排行榜时有可能排行榜的Zset发生变动,需要按照确定的播放数重新排名一次
	sort.Slice(shares, func(i, j int) bool {
		return shares[i].ViewCount > shares[j].ViewCount
	})

	ctx.JSON(200, shares)
}

func main() {
	cache.Redis() // 连接redis
	server := gin.Default()

	server.GET("show/:id", ShowViewCount) // 将增加播放量接口注册在 localhost:3000/show 地址

	server.GET("rank", GetRank) // 将排行榜注册在 localhost:3000/rank 地址

	server.Run(":3000") // 运行在本地3000端口
}

share.go

代码语言:go
复制
package main

import (
	"context"
	"rank/cache"
	"strconv"
)

type Share struct {
	Id        string
	ViewCount int64
}

// 获取播放量函数
func (share *Share) GetViewCount() (num int64) {
	countStr, _ := cache.RedisClient.Get(context.Background(), cache.ShareKey(share.Id)).Result()
	if countStr == "" {
		return 0
	}
	num, _ = strconv.ParseInt(countStr, 10, 64)
	return
}

// AddViewCount 增加播放量函数
func (share *Share) AddViewCount() {
	// 增加播放量
	cache.RedisClient.Incr(context.Background(), cache.ShareKey(share.Id))
	// 增加在排行榜中的播放量
	cache.RedisClient.ZIncrBy(context.Background(), cache.DailyRankKey, 1, share.Id)
}

cache/cache.go

代码语言:go
复制
package cache

import (
	"context"
	"os"
	"strconv"

	"github.com/redis/go-redis/v9"
)

var RedisClient *redis.Client

// init redis
func Redis() {
	db, _ := strconv.ParseUint(os.Getenv("REDIS_DB"), 10, 64)
	client := redis.NewClient(&redis.Options{
		Addr:       os.Getenv("REDIS_ADDR"),
		Password:   os.Getenv("REDIS_PW"),
		DB:         int(db),
		MaxRetries: 1,
	})

	_, err := client.Ping(context.Background()).Result()

	if err != nil {
		panic("can't connect redis")
	}

	RedisClient = client
}

cache/key.go

代码语言:go
复制
package cache

import "fmt"

const (
	// DailyRankKey 排行榜的zset key
	DailyRankKey = "redis-test-rank:daily"
)

// ShareKey 为每个ID加上指定前缀
func ShareKey(id string) string {
	return fmt.Sprintf("redis-test-share:%s", id)
}

结尾

如果你想学习排行榜与数据库之间的联动以及更复杂的Redis使用方法,可以参考我的项目https://github.com/ChenMiaoQiu/go-cloud-disk欢迎提Issue促进我的项目变得更好!

如果有更多疑问,可以在评论区留言。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 环境安装
    • Redis
      • 依赖包
      • 项目初始化
        • 使用Go-Redis连接Redis
          • 服务注册与初始化Redis
          • 具体接口实现
            • 思路
              • 存储在Redis中的Key设置
                • 返回的结构体和增加播放量函数
                  • Show接口(获取播放量接口)
                    • 排行榜接口
                      • 最终代码
                      • 结尾
                      相关产品与服务
                      云数据库 Redis
                      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档