首页 > 数据库 >Go Redis 管道和事务之 go-redis

Go Redis 管道和事务之 go-redis

时间:2023-06-16 18:24:26浏览次数:59  
标签:return nil err Redis redis ctx key go

Go Redis 管道和事务之 go-redis

Go Redis 管道和事务官方文档介绍

Redis pipelines(管道) 允许一次性发送多个命令来提高性能,go-redis支持同样的操作, 你可以使用go-redis一次性发送多个命令到服务器,并一次读取返回结果,而不是一个个命令的操作。

Go Redis 管道和事务: https://redis.uptrace.dev/zh/guide/go-redis-pipelines.html

#管道

通过 go-redis Pipeline 一次执行多个命令并读取返回值:

pipe := rdb.Pipeline()

incr := pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)

cmds, err := pipe.Exec(ctx)
if err != nil {
	panic(err)
}

// 结果你需要再调用 Exec 后才可以使用
fmt.Println(incr.Val())

或者你也可以使用 Pipelined 方法,它将自动调用 Exec:

var incr *redis.IntCmd

cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	incr = pipe.Incr(ctx, "pipelined_counter")
	pipe.Expire(ctx, "pipelined_counter", time.Hour)
	return nil
})
if err != nil {
	panic(err)
}

fmt.Println(incr.Val())

同时会返回每个命令的结果,你可以遍历结果集:

cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	for i := 0; i < 100; i++ {
		pipe.Get(ctx, fmt.Sprintf("key%d", i))
	}
	return nil
})
if err != nil {
	panic(err)
}

for _, cmd := range cmds {
    fmt.Println(cmd.(*redis.StringCmd).Val())
}

#Watch 监听

使用 Redis 事务, 监听key的状态,仅当key未被其他客户端修改才会执行命令, 这种方式也被成为 乐观锁

Redis 事务https://redis.io/docs/manual/transactions/

乐观锁

WATCH mykey

val = GET mykey
val = val + 1

MULTI
SET mykey $val
EXEC

#事务

你可以使用 TxPipelinedTxPipeline 方法,把命令包装在 MULTIEXEC 中, 但这种做法没什么意义:

cmds, err := rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
	for i := 0; i < 100; i++ {
		pipe.Get(ctx, fmt.Sprintf("key%d", i))
	}
	return nil
})
if err != nil {
	panic(err)
}

// MULTI
// GET key0
// GET key1
// ...
// GET key99
// EXEC

你应该正确的使用 Watch + 事务管道, 比如以下示例,我们使用 GET, SETWATCH 命令,来实现 INCR 操作, 注意示例中使用 redis.TxFailedErr 来判断失败:

const maxRetries = 1000

// increment 方法,使用 GET + SET + WATCH 来实现Key递增效果,类似命令 INCR
func increment(key string) error {
	// 事务函数
	txf := func(tx *redis.Tx) error {
   // // 获得当前值或零值 
		n, err := tx.Get(ctx, key).Int()
		if err != nil && err != redis.Nil {
			return err
		}

		n++  // 实际操作

    // 仅在监视的Key保持不变的情况下运行
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			pipe.Set(ctx, key, n, 0)
			return nil
		})
		return err
	}
	
	for i := 0; i < maxRetries; i++ {
		err := rdb.Watch(ctx, txf, key)
		if err == nil {
			// Success.
			return nil
		}
		if err == redis.TxFailedErr {
			// 乐观锁失败
			continue
		}
		return err
	}

	return errors.New("increment reached maximum number of retries")
}

Go Redis 管道和事务 实操

package main

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

// 声明一个全局的 rdb 变量
var rdb *redis.Client

// 初始化连接
func initRedisClient() (err error) {
	// NewClient将客户端返回给Options指定的Redis Server。
	// Options保留设置以建立redis连接。
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // 没有密码,默认值
		DB:       0,  // 默认DB 0 连接到服务器后要选择的数据库。
		PoolSize: 20, // 最大套接字连接数。 默认情况下,每个可用CPU有10个连接,由runtime.GOMAXPROCS报告。
	})

	// Background返回一个非空的Context。它永远不会被取消,没有值,也没有截止日期。
	// 它通常由main函数、初始化和测试使用,并作为传入请求的顶级上下文
	ctx := context.Background()

	_, err = rdb.Ping(ctx).Result()
	if err != nil {
		return err
	}
	return nil
}

// watchDemo 在key值不变的情况下将其值+1
func watchKeyDemo(key string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	// Watch准备一个事务,并标记要监视的密钥,以便有条件执行(如果有密钥的话)。
	// 当fn退出时,事务将自动关闭。
	// func (c *Client) Watch(ctx context.Context, fn func(*Tx) error, keys ...string)
	return rdb.Watch(ctx, func(tx *redis.Tx) error {
		// Get Redis `GET key` command. It returns redis.Nil error when key does not exist.
		// 获取 Key 的值 n
		n, err := tx.Get(ctx, key).Int()
		if err != nil && err != redis.Nil {
			fmt.Printf("redis get failed, err: %v\n", err)
			return err
		}
		// 假设操作耗时5秒
		// 5秒内我们通过其他的客户端修改key,当前事务就会失败
		time.Sleep(5 * time.Second)
		// txpipeline 执行事务中fn队列中的命令。
		// 当使用WATCH时,EXEC只会在被监视的键没有被修改的情况下执行命令,从而允许检查和设置机制。
		// Exec总是返回命令列表。如果事务失败,则返回TxFailedErr。否则Exec返回第一个失败命令的错误或nil
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			// 业务逻辑 如果 Key 没有变化,则在原来的基础上加 1
			pipe.Set(ctx, key, n+1, time.Hour)
			return nil
		})
		return err
	}, key)
}

func main() {
	if err := initRedisClient(); err != nil {
		fmt.Printf("initRedisClient failed: %v\n", err)
		return
	}
	fmt.Println("initRedisClient started successfully")
	defer rdb.Close() // Close 关闭客户端,释放所有打开的资源。关闭客户端是很少见的,因为客户端是长期存在的,并在许多例程之间共享。


	err := watchKeyDemo("watch_key")
	if err != nil {
		fmt.Printf("watchKeyDemo failed: %v\n", err)
		return
	}
	fmt.Printf("watchKeyDemo succeeded!\n")
}

运行

Code/go/redis_demo via 

标签:return,nil,err,Redis,redis,ctx,key,go
From: https://www.cnblogs.com/QiaoPengjun/p/17486246.html

相关文章

  • 6月有奖征文挑战,ZEGO开发者社区首季活动报名入口!
     前言哈喽开发者们:ZEGO即构科技作为一家20年技术积累的音视频云服务商,已经为全球200+个国家的企业服务,单日通话时长突破30亿+分钟,现下即构开发者社区举办首期征文活动!本次征文活动围绕音视频开发系列,从采集到播放每个环节的内容均可投稿,期待大家发挥创造力与想象力~我......
  • redis学习十:数据类型命令及落地运用 (HyperLogLog)
    需求:统计某个网站的UV,统计某个文章的UV(UV,uniquevisitor,独立访客,一般理解为客户端ip,需要去重考虑);用户搜索网站关键词的数量(非同一个ip);是什么:去重复统计功能的基数估计算法——HyperLogLog;基数:是一种数据集去重后的真实个数————全集{1,2,3,4,2,3,3}  基数{1,2,3,4}=4用......
  • golang 语法糖
    golang语法糖在Go语言中,nums...是一种语法糖,用于将切片nums展开为一个个独立的参数。在函数调用中,如果你有一个切片nums,你可以使用nums...将切片展开为独立的元素,作为函数的参数传递。以下是一个示例说明nums...的使用:gofuncsum(nums...int)int{total:......
  • redis学习九:数据类型命令及落地运用 (bitmap)
    redis位图bitmap:由0和1状态表现得二进制位的bit数组需求:用户是否登录过Y,N,比如京东每日签到送豆;电影,广告是否被点击播放过钉钉打卡上下班大厂签到必备是什么:用于状态记录,Y,N不用去mysql读写。1.bitmap的偏移量从0开始,setbitkey0/1设置对应下标值图中就是1000010,对应as......
  • 镜像golang 标准库文档
    缘起:查golang文档时,访问https://pkg.go.dev/std网站有点慢,就想做个离线版的修改日期:2023-06-16mirrorstdlibwget-c-t3-r-l1-np-p-khttps://pkg.go.dev/stdstatic/frontend/*rename.css@*->.cssreplacetextbytextforeverhttps://pkg.go.dev.......
  • 【gtest】Visual Studio 2019 单元测试学习Google Test
    前言记录在VS2019中使用自带的GoogleTest进行单元测试的方法和经验项目介绍总共2个项目,Work为项目工程,TestWork为Work工程的单元测试工程,TestWork依赖于Work工程,但是Work不依赖TestWork,TestWork是Work的旁路辅助工程,用于对代码的检查和测试。创建项目创建C++的常规Work工程......
  • --go_out: protoc-gen-go: plugins are not supported;
    记录问题:--go_out:protoc-gen-go:pluginsarenotsupported;标签(空格分隔):grpc,protoc-gen-gogrpc官网:https://grpc.io/docs/languages/go/quickstart/官网写的要安装以下:$goinstallgoogle.golang.org/protobuf/cmd/[email protected]$goinstallgoogle.golang.or......
  • Go语言之 go-redis 基本使用
    Go语言之go-redis基本使用Redis介绍Redis:https://redis.io/Redis中文网:https://www.redis.net.cn/REmoteDIctionaryServer(Redis)是一个由SalvatoreSanfilippo写的key-value存储系统。Redis是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持......
  • 从0开始,精通Go语言Rest微服务架构和开发
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • springboot整合redis
    1、添加依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>2、配置redis通过spring.redis.xxx来配置redis的信息spring.redis.hos......