redis基础第一篇:pipeline、transaction
pipeline
如果想连续执行多个redis命令,且后面的命令不依赖于前面命令的结果,则可以把这些命令用pipeline包起来,从而只向redis服务器发起一次情求,在响应中获取所有命令的执行结果,这样可以节省请求响应的往返时间RTT,在应用服务器和redis服务器非局域网时效果尤其显著。
pipeline和事务没关系,假如一次性执行三个命令,中间命令失败的话(可以通过sadd一个string类型的key来模拟失败),第一个命令不会回滚,第三个命令仍会执行。
transaction
redis的事务和mysql的事务不一样,redis的事务如果有命令执行失败,事务是不会回滚的 ,也不会停止,而是会继续往下执行,和pipeline一样。redis事务相关的命令有watch、multi、exec/discard。假设想给一个string类型的key加1,那么就需要先get出旧值,然后再计算出新值,最后再set回去。假如有多个客户端同时执行这套流程,那么就会有数据丢失。存在并发性问题。redis事务可以保证一个事务中的多个命令在执行过程中不会被其他事务的命令乱入,即保证原子性。用multi命令开启事务,执行多个命令后,用exec命令提交事务,用discard命令取消事务。在multi之前,可以用watch命令监听想要更新的那些key,这样,在exec执行前,如果有key发生了变化,那么事务会自动取消。这其实就是cas乐观锁,cas是check-and-set。如果事务取消了,需要再重试,直到事务提交成功。
第一步,执行watch key1,对key1添加监听。
第二步,执行get key1,获取key1的值。
第三步,执行multi,开启事务。
第四步,执行set key1 ${value},value是第二步获取的值加1。
第四步,执行exec,提交事务。
如果在第四步前,key1的值发生了变化,那么事务会自动discard。
见https://redis.io/docs/latest/develop/interact/transactions/
在golang应用中,使用go-redis组件定义increment函数,模拟redis自带的incr命令:
// increment 方法,使用 GET + SET + WATCH 来实现Key递增效果,类似命令 INCR func increment(key string) error { ctx := context.Background() // 事务函数 txf := func(tx *redis.Tx) error { n, err := tx.Get(ctx, key).Int() if err != nil && !errors.Is(err, redis.Nil) { return err } n++ _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { pipe.Set(ctx, key, n, 0) return nil }) return err } maxRetries := 1000 for i := 0; i < maxRetries; i++ { err := conn.Watch(ctx, txf, key) if err == nil { // Success. return nil } if errors.Is(err, redis.TxFailedErr) { // 乐观锁失败 continue } return err } return errors.New("increment reached maximum number of retries") }
使用go-redis组件事务操作redis,见https://redis.uptrace.dev/zh/guide/go-redis-pipelines.html
使用事务能完成的工作,使用lua脚本都可以完成,而且更简单、更快。
浙公网安备 33010602011771号