go-redis 框架基本使用

2023-10-27

redis使用场景

  • 缓存系统,减轻主数据库(MySQL)的压力。
  • 计数场景,比如微博、抖音中的关注数和粉丝数。
  • 热门排行榜,需要排序的场景特别适合使用ZSET。
  • 利用 LIST 可以实现队列的功能。
  • 利用 HyperLogLog 统计UV、PV等数据。
  • 使用 geospatial index 进行地理位置相关查询。

下载框架和连接redis

Go 社区中目前有很多成熟的 redis client 库,比如redigogo-redis,读者可以自行选择适合自己的库。本文章使用 go-redis 这个库来操作 Redis 数据库。

1. 安装go-redis

# redis 6
go get github.com/go-redis/redis/v8
# redis 7
go get github.com/go-redis/redis/v9

2. 连接redis

var Rdb *redis.Client

func Connect() {
	Rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
		PoolSize: 10,
	})
}

字符串操作

只要Redis命令足够熟悉,那么对于这个框架的API的学习基本就没有什么问题。由于Redis命令太多,在此只列出了字符串和有序集合这两种数据类型的操作示例。

func String() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	//set命令
	_, err := connect.Rdb.Set(ctx, "name", "bing", 0).Result()
	if err != nil {
		fmt.Println(err.Error())
	}
	name, err := connect.Rdb.Get(ctx, "name").Result()
	fmt.Println(name)

	//GetSet命令
	v1, _ := connect.Rdb.GetSet(ctx, "name", "xyz").Result()
	fmt.Println("旧值: " + v1) //bing
	name, err = connect.Rdb.Get(ctx, "name").Result()
	fmt.Println("新值: " + name) //xyz

	//MSet和MGet命令
	connect.Rdb.MSet(ctx, "age", 18, "password", "1234")
	v2 := connect.Rdb.MGet(ctx, "name", "age", "password").Val()
	for _, v := range v2 {
		fmt.Println(v)
	}

	//IncrBy命令
	v3 := connect.Rdb.IncrBy(ctx, "age", 2).Val() //20
	fmt.Println(v3)

	//append命令
	connect.Rdb.Append(ctx, "password", "abc")
	v4 := connect.Rdb.Get(ctx, "password").Val() //1234abc
	fmt.Println(v4)

	//SetRange命令
	connect.Rdb.SetRange(ctx, "password", 0, "987654")
	v5 := connect.Rdb.Get(ctx, "password").Val() //987654c
	fmt.Println(v5)

	//GetRange命令
	v6 := connect.Rdb.GetRange(ctx, "password", 4, -1).Val() //54c
	fmt.Println(v6)
	v7 := connect.Rdb.Get(ctx, "password").Val() //987654c
	fmt.Println(v7)

	//StrLen命令
	v8 := connect.Rdb.StrLen(ctx, "name").Val() //3
	fmt.Println(v8)

	//获取编码方式
	v9 := connect.Rdb.ObjectEncoding(ctx, "age").Val() //int
	fmt.Println(v9)
    
    //redis.Nil的用法
	v10, err := connect.Rdb.Get(ctx, "no_existing").Result()
	if redis.Nil == err {
		fmt.Println("key不存在")
	} else if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(v10)
	}
}

有序集合操作

func ZSet() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	ZSetKey := "languages"
	languages := []redis.Z{
		{Score: 90, Member: "Go"},
		{Score: 85, Member: "Python"},
		{Score: 99, Member: "C"},
		{Score: 95, Member: "Java"},
		{Score: 99, Member: "Rust"},
		{Score: 80, Member: "PHP"},
	}

	err := connect.Rdb.ZAdd(ctx, ZSetKey, languages...).Err()
	if err != nil {
		fmt.Println(err.Error())
	}

	//按照分数从低到高遍历
	v1 := connect.Rdb.ZRange(ctx, ZSetKey, 0, -1).Val()
	fmt.Println(v1) //[PHP Python Go Java C Rust]

	v2 := connect.Rdb.ZRangeWithScores(ctx, ZSetKey, 0, -1).Val()
	fmt.Println(v2) //[{80 PHP} {85 Python} {90 Go} {95 Java} {99 C} {99 Rust}]

	opt1 := &redis.ZRangeBy{
		Min:    "0",  //查询的最小分数值
		Max:    "95", //查询的最大分数值
		Offset: 0,    //查询的起始位置
		Count:  6,    //需要查询的元素个数
	}
	v3 := connect.Rdb.ZRangeByScoreWithScores(ctx, ZSetKey, opt1).Val()
	fmt.Println(v3) //[{80 PHP} {85 Python} {90 Go} {95 Java}]

	opt2 := &redis.ZRangeBy{
		Min:    "[K", //查询的最小字典序值
		Max:    "[X", //查询的最大字典序值
		Offset: 0,    //查询的起始位置
		Count:  5,    //需要查询的元素个数
	}
	v4 := connect.Rdb.ZRangeByLex(ctx, ZSetKey, opt2).Val()
	fmt.Println(v4) //[PHP Python Go Java C]

	v5 := connect.Rdb.ZCard(ctx, ZSetKey).Val()
	fmt.Println("集合长度: " + strconv.FormatInt(v5, 10)) // 6
}

流水线

使用流水线就是将多个执行的命令放入 pipeline 中,然后使用1次读写操作就像执行单个命令一样执行它们,就相当于把多个命令打包,然后一起发送给redis服务器,让redis服务器一次性执行完毕。这样做的好处是节省了执行命令的网络往返时间(RTT)。

注意:如果redis采用了分布式集群模式,不可以直接使用pipeline命令进行操作,因为访问的key可能并不在同一个节点上。

下面的示例代码中演示了使用 pipeline 将pipeline_counter键的值加1和设置过期时间。

func PipeLine() {
   ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
   defer cancel()

   //创建一个Pipeline对象:pipe
   pipe := connect.Rdb.Pipeline()

   //将名为"pipeline_counter"的键的值加1
   incr := pipe.Incr(ctx, "pipeline_counter")
   //设置"pipeline_counter"键的过期时间为1分钟
   pipe.Expire(ctx, "pipeline_counter", time.Minute)
   //执行所有的命令。
   _, err := pipe.Exec(ctx)
   if err != nil {
      panic(err)
   }

   // 在执行pipe.Exec之后才能获取到结果
   fmt.Println(incr.Val())
}

上面的代码相当于将以下两个redis命令一次发给 Redis Server 端执行,与不使用 Pipeline 相比能减少一次RTT。

INCR pipeline_counter
EXPIRE pipeline_counts 60

或者,你也可以使用Pipelined 方法,它会在当前函数退出时调用 Exec。

func PipeLine() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	var incr *redis.IntCmd

	cmdS, err := connect.Rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
		incr = pipe.Incr(ctx, "pipelined_counter")
		pipe.Expire(ctx, "pipelined_counter", time.Minute)
		return nil
	})
	if err != nil {
		panic(err)
	}

	// 在pipeline执行后获取到结果
	fmt.Println(incr.Val())
    
    //使用类型断言特性来对 cmd 进行类型检查
	for _, cmd := range cmdS {
		switch v := cmd.(type) {
		case *redis.StringCmd:
			fmt.Println(v.Val())
		case *redis.IntCmd:
			fmt.Println(v.Val())
		case *redis.BoolCmd:
			fmt.Println(v.Val())
		default:
			fmt.Printf("unexpected type %T\n", v)
		}
	}
}

运行结果如下:

image-20230919111235190

所以,在那些我们需要一次性执行多个命令的场景下,就可以考虑使用 pipeline 来优化。

事务

1. 普通事务

Redis 是单线程执行命令的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。使用事务后,Redis会按照命令的顺序执行这些命令,并且在执行过程中不会立即返回结果,只有在所有命令都执行完毕后,才会一次性返回所有命令的执行结果。也就是在执行过程中保证了原子性,即要么所有命令都执行成功,要么所有命令都不执行。

同时,Redis事务还支持WATCH命令,可以在事务执行之前监视一个或多个键,如果在事务执行期间这些键发生了改变,事务会被中断。这样可以确保在执行事务期间,被监视的键没有被其他客户端修改。

"Tx"是"Transaction"的缩写,意为"事务”。TxPipeline 和 TxPipelined 的使用方法如下所示:

func Work() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	pipe := connect.Rdb.TxPipeline()
	incr := pipe.Incr(ctx, "tx_pipeline_counter")
	pipe.Expire(ctx, "tx_pipeline_counter", time.Minute)
	_, err := pipe.Exec(ctx)
	fmt.Println(incr.Val(), err)

	var incr2 *redis.IntCmd
	_, err = connect.Rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
		incr2 = pipe.Incr(ctx, "tx_pipeline_counter")
		pipe.Expire(ctx, "tx_pipeline_counter", time.Minute)
		return nil
	})
	fmt.Println(incr2.Val(), err)
}

运行结果如下:

image-20230919140331961

2. Watch

我们通常搭配 WATCH命令来执行事务操作。从使用WATCH命令监视某个 key 开始,直到执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的 key 进行了替换、更新、删除等操作,那么当用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。

Watch方法接收一个函数和一个或多个key作为参数。

Watch(fn func(*Tx) error, keys ...string) error

假设我们有一个应用程序,它需要保持用户的积分。我们需要一个函数,可以安全地减少用户的积分。为了避免并发问题,我们将使用WATCH命令来监视用户的积分,并在事务中更新积分。

func WatchUserPoints(userID string, points int) error {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	for {
		// 监控
		err := connect.Rdb.Watch(ctx, func(tx *redis.Tx) error {
			// 得到当前用户的积分n
			n, err := tx.Get(ctx, userID).Int()

			//扣除积分时开启事务,points表示要扣除的积分
			_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
				err := pipe.Set(ctx, userID, n-points, 0).Err()
				return err
			})
			return err
		}, userID) //监控的键为userID,也就是当这个键的值(积分)如果在事务执行过程中被其他客户端修改,那么当前事务就会执行失败。

		//对错误的判断
		if err == redis.TxFailedErr {
			//表示监视的键在事务执行过程中被其他客户端修改了,因此事务执行失败了。
			continue
		} else if err != nil {
			//其他类型的错误
			return err
		} else {
			//没有错误
			break
		}
	}
	//能够跳出循环说明一切正常
	return nil
}

这段代码的目的是监视用户的当前积分,如果在事务执行过程中,其他客户端改变了这个键的值(也就是用户的积分),那么 Watch 会发现这个变化并使得事务失败,返回 redis.TxFailedErr 错误。

总的来说,这段代码的目的是确保在减少用户积分的过程中,用户的积分没有被其他客户端修改。这是通过Redis的 WATCH 命令来实现的,这个命令可以将一个或多个键标记为监视,然后在执行事务之前检查这些键是否已经被修改。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go-redis 框架基本使用 的相关文章

  • 有没有办法在 Redis 和关系数据库中使用带有 @RedisHash 的实体?

    我正在使用Spring引导 为了将我的实体保存在关系数据库上 我配置了一个数据源和我的域类 例如 Entity Table schema schema name name tb name public class table name ex
  • 是否可以使用带有 FUSE 文件系统的 Linux VFS 缓存?

    默认情况下 Linux VFS 缓存似乎不适用于 FUSE 文件系统 例如 read 调用似乎被系统地转发到 FUSE 文件系统 我在 FUSE 特定的远程文件系统上工作 我需要一个非常积极的缓存 我需要实现自己的页面缓存吗 或者是否可以为
  • 连接到 localhost:6379 时出现错误 99。无法分配请求的地址

    设置 我有一个虚拟机 并在虚拟机中运行三个容器 一个 nginx 代理 一个非常简约的 Flask 应用程序和 redis Flask 应在端口 5000 上提供服务 而 redis 应在 6379 上提供服务 这些容器中的每一个都可以作为
  • 找不到模块“socket.io/node_modules/redis”

    当尝试做的时候 var redis require socket io node modules redis 我收到错误 找不到模块 socket io node modules redis 我不明白为什么 我正在运行 Windows 并运
  • AWS Redis 从外部连接

    有没有办法从外部 AWS 网络连接 AWS 上托管的 Redis 实例 我有一个基于 Windows 的 EC2 实例在 AWS 上运行 另一个是 Redis 缓存节点 我知道有人问过这个问题 但答案是在基于 Linux 的系统中 但我的是
  • connect-redis - 如何保护会话对象免受竞争条件影响

    我使用 nodejs 和 connect redis 来存储会话数据 我将用户数据保存在会话中 并在会话生命周期中使用它 我注意到两个更改会话数据的请求之间可能存在竞争条件 我尝试过使用 redis lock 来锁定会话 但这对我来说有点问
  • Stackexchange.redis 缺乏“WAIT”支持

    我在客户端应用程序正在使用的负载均衡器后面有 3 个 Web API 服务器 我正在使用这个库来访问具有一个主服务器和几个从服务器的 Redis 集群 目前不支持 WAIT 操作 我需要此功能来存储新创建的用户会话并等待它复制到所有从属服务
  • 仅当尚未设置时才进行原子设置

    仅当尚未在 Redis 中设置时 是否有办法执行原子设置 具体来说 我正在创建一个像 myapp user user email 这样的用户 并且希望 Redis 在 user email 已被占用时返回错误 而不是默默地替换旧值 比如声明
  • 如何在多个Lua State(多线程)之间传递数据?

    我在中启动Redis连接池redis lua 通过从 C 调用 我得到了redis lua state 此 Lua 状态全局启动一次 仅在其他线程中启动get从中 当有一个 HTTP 请求 工作线程 时 我需要从redis lua stat
  • 如何让客户端下载动态生成的非常大的文件

    我有一个导出功能 可以读取整个数据库并创建一个包含所有记录的 xls 文件 然后文件被发送到客户端 当然 导出完整数据库的时间需要大量时间 并且请求很快就会以超时错误结束 处理这种情况的最佳解决方案是什么 例如 我听说过使用 Redis 创
  • 如何统计 Redis 流中未读或已确认的消息?

    使用 Redis 5 0 3 假设我们创建一个名为streamy和一个消费群体consumers XGROUP CREATE streamy consumers MKSTREAM 然后向其中添加一些消息 XADD streamy messa
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • Redis hash写入速度非常慢

    我面临一个非常奇怪的问题 使用 Redis 时 我的写入速度非常糟糕 在理想的情况下 写入速度应该接近 RAM 上的写入速度 这是我的基准 package redisbenchmark import redis clients jedis
  • 从redis中检索大数据集

    一台服务器上的应用程序查询另一台服务器上运行的 Redis 查询的结果数据集约为 250kzrangebyscore objects locations inf inf这在应用程序服务器上似乎需要 40 秒 当使用命令执行时redis cl
  • Redis Docker compose无法处理RDB格式版本10

    我无法在 docker compose 文件中启动 redis 容器 我知道docker compose文件没问题 因为我的同事可以成功启动项目 我读到有一个删除 dump rdb 文件的解决方案 但我找不到它 我使用Windows机器 任
  • 有没有办法在 ruby​​ 中重新定义 []=+

    我正在尝试编写一个简单的 DSL 针对 Redis 并且我想自己定义 I have def key val redis zadd name val key end 我想定义 def key val redis zincrby name va
  • Redis、会话过期和反向查找

    我目前正在构建一个网络应用程序 并想使用 Redis 来存储会话 登录时 会话会使用相应的用户 ID 插入到 Redis 中 并且过期时间设置为 15 分钟 我现在想实现会话的反向查找 获取具有特定用户 ID 的会话 这里的问题是 由于我无
  • 有没有办法让特定的key在集群模式下定位到特定的redis实例上?

    我想让我的多锁位于不同的redis实例上 我发现redission可以指定一个实例来执行命令 但是如果该命令与key相关 则指定的实例会将命令传输到另一个实例 你能给我一些建议吗 你可以 但这并不是微不足道的 首先 Redis 在键中使用大
  • StackExchange.Redis的正确使用方法

    这个想法是使用更少的连接和更好的性能 连接会随时过期吗 对于另一个问题 redis GetDatabase 打开新连接 private static ConnectionMultiplexer redis private static ID
  • 在 Redis 上为 Django 和 Express.js 应用程序共享会话存储

    我想创建一个包含一些登录用户的 Django 应用程序 另一方面 由于我想要一些实时功能 所以我想使用 Express js 应用程序 现在的问题是 我不希望身份不明的用户访问 Express js 应用程序的日期 因此 我必须在 Expr

随机推荐

  • IDEA 2016免费下载(附安装教程)

    下载地址 软件名称 IntelliJ IDEA 2016 软件大小 790MB 安装环境 Windows 下载链接 https pan baidu com s 1Hy0bVzh9uemWMnhRgx8HkA 提 取 码 geek 建议复制粘
  • QT之Layout类

    这个类是用来布局的 它有各种各样既定风格的盒子 往这个盒子里添加控件 这些控件就会按照这个盒子的风格来找到自己的位置 举个例子 一个水平盒子往里面添加控件 是按照从左往右的顺序依次添加 QHBoxLayout layout 首先创建一个水平
  • IDEA中编译及运行ssm(非maven)项目

    一直用springboot框架 所有回顾下ssm项目环境配置及启动 1 导入项目 2 配置项目环境 2 1然后添加项目自带的jar包 2 2添加 tomcat server服务器 要不运行时代码会报错 缺少依赖 选择自己电脑上的tomcat
  • 写一篇关于chatGPT的心得体会

    这次使用ChatGPT训练的大型语言模型 让我真正感受到了自然语言处理的强大能力 ChatGPT可以根据用户输入的文本 快速生成准确 流畅的回复 拥有丰富的语义表达能力 可以识别各种语句的结构和意义 快速建立起人机之间的交互 它不仅可以帮助
  • MobaXterm插件连接Linux虚拟机

    一 前言 在VirtualBox里面打开的虚拟机系统界面是非常小的 而且看不到鼠标的光标显示 无法去随意点击和进行文件的手动操作 所以老师这里有一个可以连接虚拟机的插件 MobaXterm插件 这个就相当于是手机的投屏器 可以放大系统界面
  • 微信小程序开发架构——JavaScript的基本概述 和 JavaScript在 Nodejs、小程序中、浏览器中的使用方法

    轻量 是指在入门JavaScript语言时候觉得JavaScript 没有其它语言学习起来那么重 解释性 是指所编写的JavaScript语言它在运行时 机器会把JavaScript语言翻译成机器语言 JavaScript语法接近于Java
  • 数字图像字符识别——数字识别

    本文简单介绍图片字符识别的原理 主要识别图片中的数字 其他字符识别原理类似 大家应该知道 对于人类来说 可以很容易理解一张图片所表达的信息 这是人类视觉系统数万年演变进化的结果 但对于计算机这个诞生进化不到百年的 新星 要让它理解一张图像上
  • 如何从头手写一个富文本编辑器(解析slate源码,连载)

    背景 最近文档很火 老板也要 我也很感兴趣 于是入坑学习实践了一番 一眨眼就是一年过去了 项目初见成效 但是发现困难和挑战也越来越棘手 于是深入研究改编了一下源码 为后面重写源码做准备 我们的项目的成果截图 镇宅一下 文章末尾有demo源码
  • 聊聊2017 OWASP Top 10

    关于OWASP Top10 OWASP项目最具权威的就是其 十大安全漏洞列表 OWASPTop 10 OWASP Top 10不是官方文档或标准 而只是一个被广泛采用的意识文档 被用来分类网络安全漏洞的严重程度 目前被许多漏洞奖励平台和企业
  • moduleName is declared but its value is never read.ts(6133)报错解析

    问题重现 当使用ts语法导入第三方库时 比如koa模块 这时会发现出现这个错误 这个错误因为没有默认导出 问题原因 一般我们使用ts导入其他模块时都会有一个声明文件 不明白可以看我另外一篇 博客 我们进入声明文件中可以发现模块是通过expo
  • 使用Easyexcel对Excel进行读写操作

    1 概述 EasyExcel是一个基于Java的简单 省内存的读写Excel的开源项目 在尽可能节约内存的情况下支持读写百M的Excel github地址 GitHub alibaba easyexcel 快速 简洁 解决大文件内存溢出的j
  • ES6模块

    项目目录 node modules package json server js public index html index js math js babelrc dist 搭建验证环境 npm init y npm install s
  • 小程序中使用for循环,并动态添加class

    前言 小程序中使用for循环 并动态添加class 实现效果 实现代码 index wxml中
  • 考研高数数二 一元函数积分学内容框架

    完整的思维导图链接 https zhimap com m NojzfjkC
  • 第46讲 Android Camera2 API AWB自动白平衡

    本讲是Android Camera专题系列的第46讲 我们介绍Android Camera2 API专题的AWB自动白平衡 包括如下内容 为什么要做白平衡 什么是自动白平衡 Android Camera颜色处理流程 AWB模式 AWB Lo
  • Java运算符优先级顺序

    Java运算符优先级顺序 图集说明 1 算数运算符 补充两个 单目运算符 正号 10 10 单目运算符 负号 n 10 n 10 1 除法规则 若两个操作数都是整型 结果也是整型 除数不能为0 若两个操作数有一个是浮点型 结果是浮点型 Sy
  • python爬虫十三:详细了解scrapy

    1 Scrapy log信息的认知 2019 01 19 09 50 48 scrapy utils log INFO Scrapy 1 5 1 started bot tencent 2019 01 19 09 50 48 scrapy
  • Matlab安装 MinGW-w64 编译器的方法

    最近用Matlab实现机器学习算法 学习到支持向量机时 提示需要运行lib svm包需要安装 MinGW w64 C 编译器 在这里把步骤列一下 1 下载MinGW w64 C 编译器 点击下载 安装时注意选择32位还是64位的 1 安装时
  • Windows混音器API使用

    1 首先用mixerGetNumDevs 函数获取系统中的混音器设备的数量 一般 机器上都至少有一个混音器设备 声卡 如果机器上没有连接其它的音频设备 那么也就只有声卡这一个混音器设备 我的机器上接有一个名为USB EMP Audio De
  • go-redis 框架基本使用

    文章目录 redis使用场景 下载框架和连接redis 1 安装go redis 2 连接redis 字符串操作 有序集合操作 流水线 事务 1 普通事务 2 Watch redis使用场景 缓存系统 减轻主数据库 MySQL 的压力 计数