Redis - 执行多个命令的方式

单个命令多次执行

没有使用Pipeline的交互如下:

package main

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

func main() {
    // 创建Redis客户端
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 单个命令执行
    start := time.Now()
    for i := 0; i < 1000; i++ {
        rdb.Ping(context.Background()).Result()
    }
    elapsed := time.Since(start)
    fmt.Printf("Elapsed time for single command execution: %s\n", elapsed)
}

一次性使用管道(Pipelined)

package main

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

func main() {
    // 创建Redis客户端
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 一次性使用管道(Pipelined)
    start := time.Now()
    pipe := rdb.Pipeline()
    for i := 0; i < 1000; i++ {
        pipe.Ping(context.Background())
    }
    _, err := pipe.Exec(context.Background())
    if err != nil {
        panic(err)
    }
    elapsed := time.Since(start)
    fmt.Printf("Elapsed time for pipelined execution: %s\n", elapsed)
}

事务管道(TxPipelined)

package main

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

func main() {
    // 创建Redis客户端
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    // 事务管道(TxPipelined)
    start := time.Now()
    pipe := rdb.TxPipeline()
    for i := 0; i < 1000; i++ {
        pipe.Ping(context.Background())
    }
    _, err := pipe.Exec(context.Background())
    if err != nil {
        panic(err)
    }
    elapsed := time.Since(start)
    fmt.Printf("Elapsed time for transactional pipelined execution: %s\n", elapsed)
}

对比压测:

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

const (
    redisAddr = "localhost:6379"
)

func singleCommand() {
    ctx := context.Background()
    client := redis.NewClient(&redis.Options{
        Addr: redisAddr,
    })

    start := time.Now()
    for i := 0; i < 1000; i++ {
        client.Set(ctx, fmt.Sprintf("key_one_%d", i), "value1", 0)
    }
    elapsed := time.Since(start)
    fmt.Printf("Single command time: %v\n", elapsed)
}

func pipelinedCommand() {
    ctx := context.Background()
    client := redis.NewClient(&redis.Options{
        Addr: redisAddr,
    })

    start := time.Now()
    pipe := client.Pipeline()
    for i := 0; i < 1000; i++ {
        pipe.Set(ctx, fmt.Sprintf("key_two_%d", i), "value2", 0)
    }
    _, err := pipe.Exec(ctx)
    if err != nil {
        fmt.Println(err)
    }
    elapsed := time.Since(start)
    fmt.Printf("Pipelined command time: %v\n", elapsed)
}

func transactionCommand() {
    ctx := context.Background()
    client := redis.NewClient(&redis.Options{
        Addr: redisAddr,
    })

    start := time.Now()
    tx := client.TxPipeline()
    for i := 0; i < 1000; i++ {
        tx.Set(ctx, fmt.Sprintf("key_three_%d", i), "value3", 0)
    }
    _, err := tx.Exec(ctx)
    if err != nil {
        fmt.Println(err)
    }
    elapsed := time.Since(start)
    fmt.Printf("TxPipelined command time: %v\n", elapsed)
}

func main() {
    singleCommand()
    pipelinedCommand()
    transactionCommand()
}

管道与事务管道的区别

Pipelined和TxPipelined都用于批量处理Redis命令,但有一些关键区别:
1)管道(Pipelined):
Pipelined用于打包一组Redis命令,但它们并不是作为一个事务一起发送的。而是在一个网络连接上,将所有命令一次性发送给Redis服务器,并尽可能快地获取所有命令的结果。
管道在客户端和服务器之间建立了一条通道,通过这个通道可以连续发送和接收多个命令,而无需等待上一个命令的响应。
管道执行的命令不具有原子性,如果其中某个命令失败,它不会影响其他命令的执行。

2)事务管道(TxPipelined):
TxPipelined用于将一组Redis命令打包成一个事务(Transaction),然后一次性地将这个事务发送给Redis服务器。
事务是一种原子性操作,要么所有命令都成功执行,要么全部失败回滚,保证了原子性。
在执行事务期间,其他客户端无法插入命令到事务之间,确保了事务的完整性。
事务执行完成后,可以通过Exec方法来执行事务并获取结果。

总结

1)Pipeline 主要是一种网络优化。本质上意味着客户端缓冲一堆命令并一次性将它们发送到服务器。这些命令不能保证在事物中执行,这样做的好处是节省每个命令的网络往返时间(RTT)。管道比单个命令执行要快很多,Redis 的事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作。在 Redis 事务中,如果有某一条命令执行失败,之前的命令不会回滚,其后的命令仍然会被继续执行。这是因为 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。需要注意的是,如果 Redis 事务中的命令执行失败,可能会导致数据不一致或其他问题。

2)管道与事务管道的主要区别在于:事务通道提供了原子性操作,而管道则只是在客户端和服务器之间建立了更有效的通信通道。在需要保证一组操作的原子性时,应该使用事务通道。而在需要提高吞吐量和降低延迟的情况下,使用管道是一个不错的选择。

3)在Redis中,如果客户端使用管道发送了多条命令,那么服务器就会将多条命令放入一个队列中,这一操作会消耗一定的内存,所以管道中命令的数量并不是越大越好(太大容易撑爆内存),而是应该有一个合理的值。

posted @ 2024-05-14 19:24  李若盛开  阅读(6)  评论(0编辑  收藏  举报