目 录CONTENT

文章目录
Go

go-redis操作redis事务

Hello!你好!我是村望~!
2023-02-19 / 0 评论 / 1 点赞 / 227 阅读 / 1,978 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

参考链接

Redis 事务

Redis 是单线程执行命令的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。

但是,Multi/exec能够确保在multi/exec两个语句之间的命令之间没有其他客户端正在执行命令。

MULTI、EXEC、DISCARD、WATCH 这四个指令构成了 redis 事务处理的基础。

  1. MULTI 用来组装一个事务;
  2. EXEC 用来执行一个事务;
  3. DISCARD 用来取消一个事务;
  4. WATCH 用来监视一些 key,一旦这些 key 在事务执行之前被改变,则取消事务的执行。

在 Redis 中,通过使用MULTI命令启动事务,然后需要传递应在事务中执行的命令列表,之后整个事务由EXEC命令执行。

RDM Redis Console
Connecting...
Connected.

6379-1:0>MULTI
"OK"
6379-1:0>SET A 1
"QUEUED"
6379-1:0>INCR A
"QUEUED"
6379-1:0>EXEC
1) "OK"
2) "OK"
3) "OK"
4) "2"
5) "OK"

6379-1:0>GET A
"2"
6379-1:0>

在上面的例子中,我们看到了 QUEUED 的字样,这表示我们在用 MULTI 组装事务时,每一个命令都会进入到内存队列中缓存起来,如果出现 QUEUED 则表示我们这个命令成功插入了缓存队列,在将来执行 EXEC 时,这些被 QUEUED 的命令都会被组装成一个事务来执行。

对于事务的执行来说,如果 redis 开启了 AOF 持久化的话,那么一旦事务被成功执行,事务中的命令就会通过 write 命令一次性写到磁盘中去

如果在向磁盘中写的过程中恰好出现断电、硬件故障等问题,那么就可能出现只有部分命令进行了 AOF 持久化,这时 AOF 文件就会出现不完整的情况,这时,我们可以使用 redis-check-aof 工具来修复这一问题,这个工具会将 AOF 文件中不完整的信息移除,确保 AOF 文件完整可用。

Redis 事务错误

有关事务,大家经常会遇到的是两类错误:

  1. 调用 EXEC 之前的错误
  2. 调用 EXEC 之后的错误

调用 EXEC 之前的错误

有可能是由于语法有误导致的,也可能时由于内存不足导致的。

只要出现某个命令无法成功写入缓冲队列的情况,redis 都会进行记录,在客户端调用 EXEC 时,redis 会拒绝执行这一事务。

(这是 2.6.5 版本之后的策略。在 2.6.5 之前的版本中,redis 会忽略那些入队失败的命令,只执行那些入队成功的命令)。我们来看一个这样的例子:

RDM Redis Console
Connecting...
Connected.

6379-1:0>MULTI
"OK"
6379-1:0>GET A
"QUEUED"
6379-1:0>INCR A
"QUEUED"
6379-1:0>ERROR CODE ! # 这个命令报错!
"ERR unknown command 'ERROR', with args beginning with: 'CODE' '!' "
6379-1:0>EXEC # 提交事务就拒绝执行了!
"EXECABORT Transaction discarded because of previous errors."
6379-1:0>GET A
"2"
6379-1:0>

调用 EXEC 之后的错误

调用 EXEC 之后的错误 指的是:

当 EXEC 执行后,才出现的错误!比如给某个字符串类型的数据,使用了集合命令,这个在EXEC 之前是不会报错的!在事务中会正常被QUEUED

而对于调用 EXEC 之后的错误,redis 则采取了完全不同的策略,即 redis 不会理睬这些错误,而是继续向下执行事务中的其他命令。

这是因为,对于应用层面的错误,并不是 redis 自身需要考虑和处理的问题,所以一个事务中如果某一条命令执行失败,并不会影响接下来的其他命令的执行。

来看一个例子:

RDM Redis Console
Connecting...
Connected.

6379-1:0>MULTI
"OK"
6379-1:0>set age 1
"QUEUED"
6379-1:0>sadd age 2 # 对字符串类型使用了 集合的命令!但是依然被  QUEUED
"QUEUED"
6379-1:0>set a 2
"QUEUED"
6379-1:0>EXEC
1) "OK"
2) "OK"
3) "OK"
4) "WRONGTYPE Operation against a key holding the wrong kind of value"
5) "OK"
6) "OK"
7) "OK"

6379-1:0>GET A
"2"
6379-1:0>

可以看到! set a 2 这个指令,虽然他的前一个命令报错了!但是它还是被正确执行了!

Watch

指令WATCH,这是一个很好用的指令,它可以帮我们实现类似于“乐观锁”的效果,即CAS(check and set)

WATCH 本身的作用是监视 key 是否被改动过,而且支持同时监视多个 key,只要还没真正触发事务,WATCH 都会尽职尽责的监视,一旦发现某个 key 被修改了,在执行 EXEC 时就会返回 nil,表示事务无法触发。

Feb-19-2023 14-25-18

上面的操作是模拟两个客户端!

  • 客户端A监听了一个age,想开启一个事务对其修改!
  • 然后另一个客户端B 在 A 客户端事务提交之前,对 A 的值进行了修改!
  • 那么A提交事务的时候,就返回了nil,事务出发失败!

这个场景的话,可以想象我们的游戏摆摊交易系统!当玩家交易的时候,多个玩家对一个商品浏览,那么每一个客户端都监听了这个商品,当其中一个购买了,相当于修改了商品的值!那么其他玩家就购买失败了!

go-redis 事务

在go-redis库中使用事务,需要用 TxPipeline 或 TxPipelined 方法!!

https://pkg.go.dev/github.com/go-redis/redis#Client.TxPipeline

TxPipeline 就像 Pipeline,不过其内部包装了 MULTI/ EXEC 命令。

// TxPipeline demo
pipe := rdb.TxPipeline()

incr := pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
_, err := pipe.Exec(ctx)

fmt.Println(incr.Val(), err)
// TxPipelined demo
var incr2 *redis.IntCmd
_, err = rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
	incr2 = pipe.Incr(ctx, "tx_pipeline_counter")
	pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
	return nil
})
fmt.Println(incr2.Val(), err)

上面代码相当于在一个RTT下执行了下面的redis命令:

MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

go-redis Watch

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

Watch(fn func(*Tx) error, keys ...string) error
func WatchTx() {
	err := rdb.Watch(doSomeTxThing, "count")
	if err != nil {
		panic(any("Watch error " + err.Error()))
	}
}
func doSomeTxThing(tx *redis.Tx) error {
	// 1. watch count !
	n, err := tx.Get("count").Int()
	if err != nil && err != redis.Nil {
		return err
	}
	time.Sleep(5 * time.Second)

	// 2.在事务中对 count 进行修改!
	_, err = tx.TxPipelined(func(pipe redis.Pipeliner) error {
		pipe.Set("count", n+1, time.Hour)
		return nil
	})
	return err
}

上面的代码操作:

  • 开启了一个 Watch 监听 key “count”,然后监听期间,对其进行事务的操作!
  • 此时如果有其他客户端,改动了监听值,那么事务就会报错!

Feb-19-2023 16-27-14

Go redis 提供了一个 redis.TxFailedErr 来检查事务是否失败!

更多请查看 文档 https://pkg.go.dev/github.com/go-redis/redis

1

评论区