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

news2024/11/15 17:51:55

一.go-redis操作hash

常用命令:

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文件,我们来看如何将它缓存起来:

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

缓存的代码如下:

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

运行结果:

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

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

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

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

首先我们生成一个表来存储商品信息:
在这里插入图片描述
然后我们现在模拟一个场景:

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

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

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文件内容如下:

[
  {
    "用户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

常用命令:

 // 往一个集合里面添加元素
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中一般会在以下场景中使用:

  • 点赞
  • 共同关注
  • 抽奖

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

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

常用命令如下:

// 往有序集合中加入元素
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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2067616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springmvc实现文件上传

1.导入jar包 2.修改配置类 在springmvc.xml添加bean <!-- 配置文件上传处理器 --><bean id"multipartResolver" class"org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置在内存中允许的最大文件大小&#x…

趣味算法------柠檬水摊

目录 题目概述&#xff1a; 解题思路&#xff1a; 具体代码&#xff1a; 总结&#xff1a; 题目概述&#xff1a; 在柠檬水摊上&#xff0c;每个柠檬水售价 5 元。客户正在排队向您购买&#xff0c;并且一次订购一份柠檬水。 每位顾客只会购买一份柠檬水&#xff0c;并支付 5…

【python】火灾检测图像处理方法设计(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

深入探讨Java多线程

我的主页&#xff1a;2的n次方_ 1. 多线程的概念 多线程是指在同一个程序中同时执行多个线程的技术。线程是操作系统能够独立调度和执行的最小单位。在Java中&#xff0c;线程由Thread类来表示&#xff0c;所有的线程都是通过这个类或其子类来创建和控制的。通过合理的多线…

解决ERROR: No matching distribution found for imp报错问题

一、问题描述 当我们使用Python3.4及其以上版本运行Python项目时&#xff0c;提示【ModuleNotFoundError: No module named imp】&#xff0c;但是我们使用【pip install imp】命令安装imp时却提示如下错误信息&#xff1a; ERROR: Could not find a version that satisfies t…

深入理解Java代理模式:从静态到动态的实现与应用

1、引言 在Java编程中&#xff0c;代理模式是一种常见的设计模式&#xff0c;用于在不修改原始代码的情况下&#xff0c;为对象添加额外的功能。代理模式有两种主要类型&#xff1a;静态代理和动态代理。本文将全面探讨这两种代理模式&#xff0c;包括它们的基本概念、实现方式…

增材制造(3D打印):为何备受制造业瞩目?

在科技浪潮的推动下&#xff0c;增材制造——即3D打印技术&#xff0c;正逐步成为制造业领域的璀璨新星&#xff0c;吸引了航空航天、汽车、家电、电子等众多行业的目光。那么&#xff0c;是什么让3D打印技术如此引人注目并广泛应用于制造领域&#xff1f;其背后的核心优势又是…

VSCODE SSH连接失败

前提&#xff1a;以前连接得好好的 突然有一天就连接不上了 打开C盘下的known_hosts文件删除如下内容&#xff0c;重新登陆即可

天正如何保存低版本

打开天正cad的界面。左边找到文件布图这个菜单&#xff0c;点击进入找到图形导出这个子菜单&#xff0c;之后会出现下面这一界面。 第2步 可以看到保存类型&#xff0c;一进去是天正3文件的&#xff0c;这时候你要点开下拉选择天正6文件&#xff0c;其它可以不用修o改&#x…

Keepalived和Nginx一起在Centos7上实现Nginx高可用设计

方案概览 如需详细信息可点击下列链接进行视频观看 B站 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP 抖音 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP Centos7 yum更新 安装阿里yum源 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Cent…

TCP/UDP的对比,粘包分包抓包,http协议

服务器端&#xff1a; 一、loop 127.0.0.1本地回环测试地址 二、tcp特点 面向连接、可靠传输、字节流 粘包问题&#xff1a;tcp流式套接字&#xff0c;数据与数据之间没有套接字&#xff0c;导致可能多次的数据粘到一起 解决方法&#xff1a;&#xff08;1&#xff09;规…

Linux数据相关第1个服务_备份服务rsync

1、备份服务概述 备份服务&#xff1a;需要使用到脚本&#xff0c;打包备份&#xff0c;定时任务 备份服务&#xff1a;rsyncd 服务&#xff0c;不同主机之间数据传输 特点: rsync是个服务也是命令使用方便&#xff0c;具有多种模式传输数据的时候是增量传输 增量与全量&am…

Nginx: 配置项之http模块connection和request的用法以及limit_conn和limit_req模块

connection和request connection 就是一个连接, TCP连接 客户端和服务器想要进行通信的话&#xff0c;有很多种方式比如说, TCP的形式或者是UDP形式的通常很多应用都是建立在这个TCP之上的所以, 客户端和服务器通信&#xff0c;使用了TCP协议的话&#xff0c;必然涉及建立TCP连…

一分钟告诉你毕业季大学都在用在线版招生简章是如何制作?

毕业季临近&#xff0c;各大高校纷纷进入招生宣传的关键时期。在数字化时代背景下&#xff0c;在线版招生简章成为了高校之间竞争的焦点。一分钟带你了解&#xff0c;这些吸引眼球的在线版招生简章是如何制作出来的。 1. 准备好制作工具&#xff1a;FLBOOK在线制作电子杂志平台…

【论文分享】Graviton: Trusted Execution Environments on GPUs 2018’OSDI

目录 AbstractIntroductioncontributions BackgroundGPUSoftware stackHardwareContext and channel managementCommand submissionProgramming modelInitializationMemory allocationHost-GPU transfersKernel dispatch Sharing Intel SGX Threat ModelOverviewGraviton Archi…

World of Warcraft [CLASSIC] Engineering 335-420

World of Warcraft [CLASSIC] Engineering 工程学冲技能点 335 - 420 [冰霜冲击雷管] 335-345 [冰霜手雷] 346-358 这部分知道可以不看了 在地狱火半岛&#xff0c;萨尔玛&#xff0c;找70级工程学大师学习新的技能&#xff0c;用来充技能都不划算 回【达拉然】找80级工程…

【电子数据取证】提升案件分析准确性的去重技术

前言 紧随《AES解密侵犯隐私案件数据》一文的讨论&#xff0c;本文将深入探讨数据解密后的处理工作。解密只是数据恢复的第一步&#xff0c;确保数据的准确性和分析的有效性同样重要。本文将重点介绍数据去重技术&#xff0c;阐述在解密数据后如何细致地进行去重处理&#xff…

wx.choosemedia 无反应 不生效 不弹出图片视频

调整开发者工具基础版本&#xff1a;打开微信开发者工具 > 右上方点击详情 > 本地设置 >调试基础库&#xff08;更换版本&#xff09; 这里我试了几个&#xff0c;发现 3 开头的版本都不行。。最后选了 2.30.4版本&#xff0c;官方文档给的答案是 2.10.0 开始支持 。…

特殊管道资源采购

管道资源物料是从管道&#xff08;如输油管&#xff09;或其它线管&#xff08;如输电线&#xff09;中直接进入生产流程的物料。与寄售物料不同的是&#xff0c;管道资源物料不必一定经过“采购”获得。管道库存不是在仓库中实际可用的库存。管道中的物料始终可用&#xff0c;…

Datawhale X 李宏毅苹果书 AI夏令营-深度学习入门班-task1

机器学习就是去拟合一种函数&#xff0c;它可能在高维上&#xff0c;十分抽象&#xff0c;但是却可以有丰富的语义含义&#xff0c;从而完成一系列任务 回归任务是预测一个准确的值&#xff0c;例如拟合一条直线的时候&#xff0c;我们希望每一个点的值都能对应上 分类任务则…