在使用Redis的时候,如果需要批量操作或是提高命令执行性能,可以使用Redis管道传输(Pipeline)机制,Pipeline可以将多个命令同时发给Redis服务端,减少网络延迟,提高性能。
介绍
一般情况下,客户端和Redis通信的时候采用的是Ping-pong模式,即客户端发送命令后会等待服务端返回的命令执行的结果,在客户端收到服务端返回的结果后再发送下一个命令。
而Pipeline模式类似流水线的模式:客户端发送一个命令后无需等待执行结果,会继续发送其他的命令。当所有的命令都发送完后,客户端关闭请求,开始接收响应,收到执行结果后再和之前发送的命令按顺序匹配。大部分客户端采用批处理的方式,即一次发送多个命令,等接收完所有命令执行结果后再返回。
两者的示意图如下所示:
使用Pipeline模式可以通过降低网络往返时延RTT,减少read()和write()的系统调用以及进程上下文切换次数,以提升程序的执行效率与性能。
Pipeline在某些场景下非常有效,例如有多个操作命令需要被迅速提交至服务器端,但用户并不依赖每个操作返回的响应结果,对结果响应也无需立即获得,那么Pipeline就可以用来作为优化性能的批处理工具。
注意事项
独占连接
使用Pipeline时客户端将独占与服务器端的连接,此期间将不能进行其他“非Pipeline”类型操作,直至Pipeline被关闭;如果要同时执行其他操作,可以为Pipeline操作单独建立一个连接,将其与常规操作分开
无法保证原子性
Pipeline模式只是将客户端发送命令的方式改为发送批量命令,而服务端在处理批量命令的数据流时,仍然是解析出多个单命令并按顺序执行,各个命令相互独立,即服务端仍有可能在该过程中执行其他客户端的命令。如需保证原子性,需要使用事务或Lua脚本。
不支持回滚
若Pipeline执行过程中发生错误,不支持回滚。Pipeline没有事务的特性,如待执行命令的前后存在依赖关系,请勿使用Pipeline。
缓存区限制
由于服务端以及部分客户端存在缓存区限制,建议单次Pipeline中不要使用过多的命令。
服务端架构
Pipeline的本质为客户端与服务端的交互模式,与服务端的架构无关,因此集群架构代理模式、集群架构直连模式以及读写分离架构实例均支持Pipeline。
go-redis 使用pipeline
go-redis支持pipeline,调用redisCli.Pipeline()
可以获取pipeline的变量,后续的操作跟普通的redis client一样。
简单的写了个Test函数比较了下pipeline的性能,往redis的list里推100个数据。
func Test_RedisPipe(t *testing.T) {
redisCli := redis.NewClient(&redis.Options{
Addr: "xxxx:6379",
Password: "xxxx",
PoolSize: 1,
DialTimeout: time.Second,
ReadTimeout: time.Second * 5,
WriteTimeout: time.Second / 2,
MinIdleConns: 1,
DB: 44,
})
startTime1 := time.Now()
pipe := redisCli.Pipeline()
key1 := "test:pipeline:first"
for i := 0; i <= 100; i++ {
pipe.LPush(context.Background(), key1, i)
}
_, err := pipe.Exec(context.Background())
if err != nil {
t.Fatal(err)
}
t.Logf("%s cost [%v]ms", key1, time.Since(startTime1).Milliseconds())
key2 := "test:pipeline:second"
startTime2 := time.Now()
for i := 0; i <= 100; i++ {
redisCli.LPush(context.Background(), key2, i)
}
t.Logf("%s cost [%v]ms", key2, time.Since(startTime2).Milliseconds())
}