go-redis 框架基本使用

news2024/11/25 13:52:39

文章目录

    • redis使用场景
    • 下载框架和连接redis
      • 1. 安装go-redis
      • 2. 连接redis
    • 字符串操作
    • 有序集合操作
    • 流水线
    • 事务
      • 1. 普通事务
      • 2. Watch

redis使用场景

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

下载框架和连接redis

Go 社区中目前有很多成熟的 redis client 库,比如redigo和go-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 命令来实现的,这个命令可以将一个或多个键标记为监视,然后在执行事务之前检查这些键是否已经被修改。

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

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

相关文章

【zotero】解决换新电脑后PDF打不开

关于zoteroPDF无法打开,并且提示:它可能已被移动或删除到 Zotero 之外,或者一台计算机上的链接附件基本目录可能设置不正确。 第一步:下载zutilo第二步:zutilo获取路径第三步 修改路径最后:新旧路径对比 第…

笔记1.6:计算机网络发展历史

1961-1972:早期分组交换原理的提出与应用 1972-1980:网络互连,大量新型、私有网络的涌现 1980-1990:新型网络协议与网络的激增 1999、2000‘s:商业化、Web、新应用 2005- ? :

ChatGPT AIGC 完成各省份销售动态可视化分析

像这样的动态可视化由人工智能ChatGPT AIGC结合前端可视化技术就可以实现。 Prompt:请使用HTML,JS,Echarts 做一个可视化分析的案例,地图可视化,数据可以随机生成,请写出完整的代码 完整代码复制如下: <!DOCTYPE html> <html> <head><meta char…

汽油辛烷值的测定 马达法

声明 本文是学习GB-T 503-2016 汽油辛烷值的测定 马达法. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 8 试剂和标准物 8.1 气缸夹套冷却液 若实验室所处海拔的水沸点为100℃1.5℃(212 F3F), 应使用水作为气缸夹套冷却液。当 实验室海拔高度不确定…

Android 滑动事件消费监控,Debug 环境下通用思路

Android Debug 环境下滑动事件消费监控通用思路 背景 Android 开发中&#xff0c;经常会遇到滑动事件冲突。在一些简单的场景下&#xff0c;我们如果能够知道是那个 View 拦截了事件&#xff0c;那我们能够很容易得解决。解决方法通常就是内部拦截法或者外部拦截法。ViewPage…

「深度学习之优化算法」(十九)蚁狮算法

1. 蚁狮算法简介 (以下描述,均不是学术用语,仅供大家快乐的阅读)    蚁狮是一种昆虫,城里长大的我没有见过这玩意儿,请教了农村长大小的伙伴,依然没见过,这玩意儿可能在我们生活的地方分布较少。 (图片及介绍来自百度百科)    蚁狮算法(Ant Lion Optimization…

自己动手写数据库:关系代数和查询树执行效率的推导

上几节我们完成了 sql 解释器的实现。通过解析 sql 语句&#xff0c;我们能知道 sql 语句想做什么&#xff0c;接下来就需要执行 sql 语句的意图&#xff0c;也就是从给定表中抽取所所需要的数据。要执行 sql 语句&#xff0c;我们需要了解所谓的“关系代数”&#xff0c;所谓代…

py基础语法

输出&#xff1a; print("wbshpnshp")输入&#xff1a; 1.raw_input() str raw_input("请输入&#xff1a;") print "你输入的内容是: ", str2.input(), input 可以接收一个Python表达式作为输入&#xff0c;并将运算结果返回。 str input(…

【C++面向对象侯捷】3.构造函数

文章目录 class 的声明inline&#xff08;内联&#xff09;函数access level&#xff08;访问级别&#xff09;构造函数构造函数可以有多个- 重载&#xff01; class 的声明 inline&#xff08;内联&#xff09;函数 access level&#xff08;访问级别&#xff09; 构造函数 构…

Flutter的基础知识、核心概念以及一些实际开发技巧

Flutter的基础知识、核心概念以及一些实际开发技巧 前言深入探讨Flutter应用程序开发一、什么是Flutter&#xff1f;Dart编程语言Widget组件模型 二、Flutter的核心概念MaterialApp和ScaffoldWidget生命周期布局和排列状态管理 三、实际开发技巧使用Hot Reload适应不同屏幕尺寸…

OpenHarmony AI框架开发指导

一、概述 1、功能简介 AI 业务子系统是 OpenHarmony 提供原生的分布式 AI 能力的子系统。AI 业务子系统提供了统一的 AI 引擎框架&#xff0c;实现算法能力快速插件化集成。 AI 引擎框架主要包含插件管理、模块管理和通信管理模块&#xff0c;完成对 AI 算法能力的生命周期管理…

用katalon解决接口/自动化测试拦路虎--参数化

不管是做接口测试还是做自动化测试&#xff0c;参数化肯定是一个绕不过去的坎。 因为我们要考虑到多个接口都使用相同参数的问题。所以&#xff0c;本文将讲述一下katalon是如何进行参数化的。 全局变量 右侧菜单栏中打开profile&#xff0c;点击default&#xff0c;打开之后…

微信小程序实现删除功能

1. 前端 项目列表展示是使用的wx&#xff1a;for遍历 每个项目展示有3个模块 1. project-title 2. project-content 3. project-foot 全部代码如下 <t-sticky><view class"search"><t-search model:value"{{conditions.keyword}}" pl…

adb shell命令查看当前屏幕可见最顶层Activity和Fragment及其调用栈

adb shell命令查看当前屏幕可见最顶层Activity和Fragment及其调用栈 &#xff08;1&#xff09;当前屏幕可见页面最顶层是哪个Activity: adb shell "dumpsys activity top | grep ACTIVITY | tail -n 1"&#xff08;2&#xff09;当前屏幕可见页面最顶层是哪个Fragm…

mac使用指南

新公司给配备了mac&#xff0c;可惜土鳖的我不会用&#xff0c;所以特地写了一篇文章记录学习mac的过程 快捷键 删除&#xff1a;commanddelete 光标移至最右/左&#xff1a;command右/左箭头 截图&#xff1a;commandshift3/4/5&#xff0c;3代表截全屏&#xff0c;4代表选…

PDF合并和拆分软件 PDF Merge PDF Splitter mac中文版v6.3.9

PDF Merge PDF Splitter mac是一款用于合并和拆分PDF文件的工具。它可以将多个PDF文件合并为一个&#xff0c;也可以将一个PDF文件拆分为多个文件。 合并PDF文件&#xff1a;用户可以将多个PDF文件合并为一个文件。合并后的PDF文件保留原有的文档结构和格式&#xff0c;包括书签…

Winform直接与Wpf交互

Winform项目中&#xff0c;可以直接使用wpf中的自定义控件和窗体 测试环境&#xff1a; vistual studio 2017 window 10 一 winform直接使用wpf的自定义控件 步骤如下&#xff1a; 1 新建winfrom项目&#xff0c;名为WinFormDemo&#xff0c;默认有一个名为Form1的窗体…

方案:基于AI烟火识别与视频技术的秸秆焚烧智能化监控预警方案

一、方案背景 为严控秸秆露天焚烧&#xff0c;改善环境空气质量&#xff0c;各省相继发布秸秆禁烧工作内容。以安徽省为例&#xff0c;大气污染防治联席会议下发了该省2020年秸秆禁烧工作部署通知。2020年起&#xff0c;气象局将对全省秸秆焚烧火点实施卫星全年全时段监测&…

Ruijie未授权访问

本文由掌控安全学院 - 杳若 投稿 漏洞成因 没进行权限校验。 影响范围 Ruijie 发现方式 一、fofa发现 1. title"Ruijie Easy-Smart Switch"利用方式 访问之后直接是进入后台的样子~ 实战 修复方式 对于鉴权类型的漏洞&#xff0c;主要的修复方式是全局增加…

BUUCTF:[GYCTF2020]FlaskApp

Flask的网站&#xff0c;这里的功能是Base64编码解码&#xff0c;并输出 并且是存在SSTI的 /hint 提示PIN码 既然提示PIN&#xff0c;那应该是开启了Debug模式的&#xff0c;解密栏那里随便输入点什么报错看看&#xff0c;直接报错了&#xff0c;并且该Flask开启了Debug模式&am…