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

前言

本文将使用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 get -u github.com/gin-gonic/gin
go get github.com/redis/go-redis/v9

项目初始化

使用Go-Redis连接Redis

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

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的路由参数的概念,感兴趣可以查阅官方文档路由参数,如果不想看也没有关系,通过下面的调用示例图,知道有什么用即可。

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接口
调用rank接口

成功实现基础接口!!!

具体接口实现

思路

增加播放量即调用接口时将Redis中key对应的值+1,排行榜则维护一个Zset(小根堆),在每次调用增加播放量接口时同步增加Zset中的播放量数。
接口实现思路
获取播放量接口时从redis获取对应播放量并增加返回。
获取排行榜接口时直接将Zset中的后十个返回。

存储在Redis中的Key设置

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

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)
}

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

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接口(获取播放量接口)

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)
}

调用接口演示
请添加图片描述

最终代码

main.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

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

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

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促进我的项目变得更好!
如果有更多疑问,可以在评论区留言。

  • 23
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值