redis实战——go-redis的使用与redis基础数据类型的使用场景(二)

一.go-redis操作hash

常用命令:

go 复制代码
redisClient.HSet("map", "name", "jack")
// 批量设置
redisClient.HMSet("map", map[string]interface{}{"a": "b", "c": "d", "e": "f"})
// 单个访问
redisClient.HGet("map", "a").Val()
// 批量访问
redisClient.HMGet("map", "a", "b").Val()
// 获取整个map
redisClient.HGetAll("map").Val()
// 删除map的一个字段
redisClient.HDel("map", "a")
// 判断字段是否存在
redisClient.HExists("map", "a")
// 获取所有的map的键
redisClient.HKeys("map")
// 获取map长度
redisClient.HLen("map")
// 遍历map中的键值对
redisClient.HScan("map", 0, "", 1)

Hash的常用场景主要有两种:

  • 缓存对象
  • 做购物车

首先是缓存对象,hash命令中的key,valuefiled很好的能够对应对象的结构我们可以利用Hash来缓存结构,比如像下面我写了一个json文件,我们来看如何将它缓存起来:

json 复制代码
[
  {
    "message":{
      "name": "张三",
      "age": 30,
      "email": "zhangsan@example.com",
      "isStudent": false,
      "subjects": ["数学", "英语", "物理"]
    }
  },
  {
    "message": {
      "name": "李四",
      "age": 25,
      "email": "lisi@example.com",
      "isStudent": true,
      "subjects": ["化学", "生物"]
    }
  }
]

缓存的代码如下:

go 复制代码
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/redis/go-redis/v9"
	snoyflake "go-redis/sonyflake"
	"io"
	"os"
	"strconv"
)

type Student struct {
	id      uint64
	Message map[string]any `json:"message"`
}

var rdb *redis.Client
var ctx context.Context

func Init() {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})
	err := snoyflake.Init()
	if err != nil {
		fmt.Println("sonyflake init failed,err:", err)
		return
	}
	ctx = context.Background()
}

func main() {
	//初始化
	Init()

	var students []Student

	//读取json文件
	file, err := os.Open("student.json")
	if err != nil {
		fmt.Println("file Open failed,err:", err)
	}
	defer file.Close()
	str, _ := io.ReadAll(file)
	err = json.Unmarshal(str, &students)
	if err != nil {
		fmt.Println("json Unmarshal failed,err:", err)
		return
	}

	//生成id
	for _, student := range students {
		student.id, _ = snoyflake.GetID()
	}

	//写入redis
	filed := "message"
	for _, student := range students {
		value, _ := json.Marshal(student.Message)
		err = rdb.HSet(ctx, strconv.FormatUint(student.id, 10), filed, value).Err()
		if err != nil {
			fmt.Println("redis HSet failed,err:", err)
			return
		}
	}

	//读取redis
	for _, student := range students {
		value, err := rdb.HGet(ctx, strconv.FormatUint(student.id, 10), filed).Result()
		if err != nil {
			fmt.Println("redis HGet failed,err:", err)
			return
		}
		fmt.Println(value)
	}
}

运行结果:

text 复制代码
{"age":25,"email":"lisi@example.com","isStudent":true,"name":"李四","subjects":["化学","生物"]}
{"age":25,"email":"lisi@example.com","isStudent":true,"name":"李四","subjects":["化学","生物"]}

那购物车我们应该怎么做呢?假设我们现在要清空我们的购物车,购物车其实就三个属性:

  • 谁买
  • 买什么
  • 买多少个

知道了这个我们就可以尝试实现一个简单的购物车了,这里我们选择将RedisMysql联合使用,将商品的具体信息储存在Mysql中,Redis中只实现购物车的相关功能:

首先我们生成一个表来存储商品信息:

然后我们现在模拟一个场景:

  • 我们将商品将入购物车
  • 我们添加购物车中商品的数量
  • 计算总价格

这个基本上就是一整个大致流程了,我们来看一下怎么实现:

go 复制代码
package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/redis/go-redis/v9"
	"github.com/spf13/viper"
	snoyflake "go-redis/sonyflake"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"io"
	"os"
	"strconv"
	"time"
)

type MySQL struct {
	DataName     string
	DataUser     string
	DataPassword string
	DataHost     string
	DataPort     string
}

type Redis struct {
	Addr     string
	Password string
	DB       int
}

type Setting struct {
	MySQL MySQL
	Redis Redis
}

// 用户
type User struct {
	UserID int     `gorm:"primaryKey;autoIncrement"`
	Price  float64 `gorm:"type:float;not null"`
}

// 商品
type Goods struct {
	GoodsID       int     `gorm:"primaryKey;autoIncrement"`
	ProductName   string  `gorm:"type:varchar(255);not null"`
	Category      string  `gorm:"type:varchar(50);not null"`
	Brand         string  `gorm:"type:varchar(50);not null"`
	Price         float64 `gorm:"type:decimal(12,2);not null"`
	StockQuantity int     `gorm:"not null"`
	Description   string  `gorm:"type:text"`
	ListingDate   string  `gorm:"type:date"`
}

type Message struct {
	Userid  int `json:"用户ID"`
	Goodsid int `json:"商品ID"`
	Number  int `json:"数量"`
}

var ConfMessage = new(Setting)
var db *gorm.DB
var rdb *redis.Client
var ctx context.Context
var messages []Message
var userlist []string
var costlist map[string]float64 = make(map[string]float64)

func main() {
	//初始化
	Init()
	//读取json文件
	file, err := os.Open("shopping.json")
	if err != nil {
		fmt.Println("file Open failed,err:", err)
	}
	defer file.Close()
	str, _ := io.ReadAll(file)
	err = json.Unmarshal(str, &messages)
	if err != nil {
		fmt.Println("json Unmarshal failed,err:", err)
		return
	}

	//写入redis
	for _, message := range messages {
		userlist = append(userlist, strconv.Itoa(message.Userid))
		filed := strconv.Itoa(message.Goodsid)
		err = rdb.HSet(ctx, strconv.Itoa(message.Userid), filed, message.Number).Err()
		if err != nil {
			fmt.Println("redis HSet failed,err:", err)
			return
		}
	}

	//选择其中某种商品添加一定数量
	rdb.HIncrBy(ctx, "1001", "1001", 3)

	//打印一下查看是否操作成功
	fmt.Println(rdb.HGetAll(ctx, "1001").Val())

	//计算总价格
	for _, userid := range userlist {
		cost := TotalPrice(userid)
		costlist[userid] = cost
	}

	for k, v := range costlist {
		fmt.Println(k, v)
	}
}

func TotalPrice(userid string) float64 {
	var user User
	res, _ := rdb.HGetAll(ctx, userid).Result()
	for k, v := range res {
		id, _ := strconv.Atoi(k)
		number, _ := strconv.Atoi(v)
		goods := Goods{}
		db.Where("goods_id = ?", id).First(&goods)
		db.Where("user_id = ?", userid).First(&user)
		user.Price += goods.Price * float64(number)
	}
	return user.Price
}

func InitConfig() error {
	viper.AddConfigPath(".")
	viper.SetConfigName("config")
	viper.SetConfigType("ini")
	err := viper.ReadInConfig()
	if err != nil {
		return err
	}
	if err = viper.Unmarshal(&ConfMessage); err != nil {
		return err
	}
	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		if err = viper.Unmarshal(&ConfMessage); err != nil {
			return
		}
	})
	return nil
}

// 初始化redis
func InitRedis() error {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})
	ctx = context.Background()
	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		return err
	}
	return nil
}

// 初始化mysql
func InitMysql() error {
	dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
		ConfMessage.MySQL.DataUser,
		ConfMessage.MySQL.DataPassword,
		ConfMessage.MySQL.DataHost,
		ConfMessage.MySQL.DataPort,
		ConfMessage.MySQL.DataName)
	var err error
	db, err = gorm.Open(mysql.Open(dns), &gorm.Config{
		//跳过默认事务,提高性能
		SkipDefaultTransaction: true,
		//禁用外键约束
		DisableForeignKeyConstraintWhenMigrating: true,
		NamingStrategy: schema.NamingStrategy{
			//禁用默认表名复数
			SingularTable: true,
		},
	})

	if err != nil {
		fmt.Println("连接数据库失败", err)
		os.Exit(1)
	}

	var sqlDB *sql.DB
	sqlDB, err = db.DB()
	if err != nil {
		return err
	}

	_ = db.AutoMigrate(&User{}, &Goods{})

	//设置连接池最大连接数量
	sqlDB.SetMaxOpenConns(100)
	//设置连接池最大空闲连接数
	sqlDB.SetMaxIdleConns(10)
	//设置连接连接可重用的最大时长
	sqlDB.SetConnMaxLifetime(10 * time.Second)
	return nil
}

func Init() {
	err := snoyflake.Init()
	if err != nil {
		fmt.Println("sonyflake init failed,err:", err)
		return
	}
	err = InitConfig()
	if err != nil {
		fmt.Println("config init failed,err:", err)
		return
	}
	err = InitMysql()
	if err != nil {
		fmt.Println("mysql init failed,err:", err)
		return
	}
	err = InitRedis()
	if err != nil {
		fmt.Println("redis init failed,err:", err)
		return
	}
}

这个代码有点长我们根据main函数中的逻辑来顺一下:

  • Init:首先在这个函数我们完成了对相关工具的初始化,主要有以下几步:

    1.初始化雪花算法

    2.初始化相关配置,这里我们选择的是viper来读取配置文件

    3.初始化redis

    4.利用gorm连接mysql数据库,完成对`mysql的初始化

  • 读取json文件:这里我们将相关的信息存储在json文件中来模拟购物车初始消息:json文件内容如下:

json 复制代码
[
  {
    "用户ID": 1001,
    "商品ID": 1001,
    "数量": 1
  },
  {
    "用户ID": 1001,
    "商品ID": 1003,
    "数量": 1
  },
  {
    "用户ID": 1002,
    "商品ID": 1002,
    "数量": 1
  },
  {
    "用户ID": 1002,
    "商品ID": 1004,
    "数量": 1
  },
  {
    "用户ID": 1002,
    "商品ID": 1005,
    "数量": 1
  }
]

我们将读取后存储到redis来作为对相关信息的缓存(如果大家想更贴合实际环境,可以添加一个过期时间)

  • 相关操作:最后我们模拟了我们平时增加/减少购买数量的操作并且通过解析redis中的相关信息并在mysql中查询实现了结账操作,完成了一个购物车的基本功能。

二.go-redis操作Set

常用命令:

go 复制代码
 // 往一个集合里面添加元素
redisClient.SAdd("set", "a", "b", "c")
// 获取集合中的所有成员
redisClient.SMembers("set")
// 判断一个元素是否属于这个集合
redisClient.SIsMember("set", "a")
// 随机返回count个元素
redisClient.SRandMemberN("set", 1)
// 获取一个集合的元素个数
redisClient.SCard("set")
// 获取集合中的所有成员
redisClient.SMembers("set")
// 判断一个元素是否属于这个集合
redisClient.SIsMember("set", "a")
// 随机返回count个元素
redisClient.SRandMemberN("set", 1)
// 获取一个集合的元素个数
redisClient.SCard("set")
// 弹出并删除该元素
redisClient.SPop("set")
// 弹出并删除N给元素
redisClient.SPopN("set", 2)
// 从源集合移动指定元素刀目标集合
redisClient.SMove("set", "set2", "a")
// 删除指定元素
redisClient.SRem("set", "a", "b")
// 遍历集合
redisClient.SScan("set", 0, "", 2)

集合主要有以下的特性:

  • 无序
  • 无重复的元素
  • 支持并交差等操作

比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等,当我们存储的数据是无序并且需要去重的情况下,比较适合使用集合类型进行存储。

注意: Set进行聚合计算(交集、差集、并集)时复杂度较大(>=N),在数据量比较大的时候,任意造成Redis实例阻塞,为了解决这种情况我们一般会选择一个从库完成聚合统计,或者把数据返回给客户端,由客户端来完成聚合统计。

让我们在使用Set中一般会在以下场景中使用:

  • 点赞
  • 共同关注
  • 抽奖

这里我们以点赞功能为例,我们来看一下我们可以如何实现一个点赞功能:

go 复制代码
package main

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
)

var (
	rdb *redis.Client
	ctx context.Context
)

func main() {
	err := InitRedis()
	if err != nil {
		fmt.Println("redis init failed,err:", err)
	}
	Upvote("1", "1")
	Upvote("2", "1")
	Upvote("3", "1")
	str, _ := GetUpvote("1")
	fmt.Println(str)
	count, _ := GetUpvoteCount("1")
	fmt.Println(count)
	CancelUpvote("1", "1")
	str, _ = GetUpvote("1")
	fmt.Println(str)
	count, _ = GetUpvoteCount("1")
	fmt.Println(count)
}

// Upvote 点赞
func Upvote(userid, articleid string) error {
	return rdb.SAdd(ctx, articleid, userid).Err()
}

// CancelUpvote 取消点赞
func CancelUpvote(userid, articleid string) error {
	return rdb.SRem(ctx, articleid, userid).Err()
}

// GetUpvoteCount 获取点赞数
func GetUpvoteCount(articleid string) (int64, error) {
	return rdb.SCard(ctx, articleid).Result()
}

// GetUpvote 获取点赞列表
func GetUpvote(articleid string) ([]string, error) {
	return rdb.SMembers(ctx, articleid).Result()
}

// GetVoteStatus 获取点赞状态
func GetVoteStatus(userid, articleid string) (bool, error) {
	return rdb.SIsMember(ctx, articleid, userid).Result()
}

func InitRedis() error {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})
	ctx = context.Background()
	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		return err
	}
	return nil
}

go-redis操作zset

常用命令如下:

go 复制代码
// 往有序集合中加入元素
redisClient.ZAdd("ss", redis.Z{
   Score:  1,
   Member: "a",
}, redis.Z{
   Score:  2,
   Member: "b",
})
// 返回有序集合中该元素的排名,从低到高排列
redisClient.ZRank("ss", "1")
// 返回有序集合中该元素的排名,从高到低排列
redisClient.ZRevRank("ss", "1")
// 返回介于min和max之间的成员数量
redisClient.ZCount("ss", "1", "2")

// 返回对元素的权值
redisClient.ZScore("ss", "a")

// 返回指定区间的元素
redisClient.ZRange("ss", 1, 2)
// 返回介于min和max之间的所有成员列表
redisClient.ZRangeByScore("ss", redis.ZRangeBy{
   Min:    "1",
   Max:    "2",
   Offset: 0,
   Count:  1,
})
// 给一个对应的元素增加相应的权值
redisClient.ZIncr("ss", redis.Z{
   Score:  2,
   Member: "b",
})
// 删除指定元素
redisClient.ZRem("ss", "a")
// 删除指定排名区间的元素
redisClient.ZRemRangeByRank("ss", 1, 2)
// 删除权值在min和max区间的元素
redisClient.ZRemRangeByScore("ss", "1", "2")

Zset 类型(Sorted Set,有序集合) 可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Sorted Set。

相关推荐
jiayou6421 小时前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤2 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区3 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1773 天前
《从零搭建NestJS项目》
数据库·typescript
花酒锄作田3 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
加号34 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏4 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐4 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再4 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip
tryCbest4 天前
数据库SQL学习
数据库·sql