流水线

在一般情况下,用户每执行一个 Redis 命令,Redis 客户端和 Redis 服务器就需要执行以下步骤:

  • 客户端向服务器发送命令请求。

  • 服务器接收命令请求,并执行用户指定的命令调用,然后产生相应的命令执行结果。

  • 服务器向客户端返回命令的执行结果。

  • 客户端接收命令的执行结果,并向用户进行展示。

跟大多数网络程序一样,执行 Redis 命令所消耗的大部分时间都用在了发送命令请求和接收命令结果上面:Redis 服务器处理一个命令请求通常只需要很短的时间,但客户端将命令请求发送给服务器以及服务器向客户端返回命令结果的过程却需要花费不少时间。通常情况下,程序需要执行的 Redis 命令越多,它需要进行的网络通讯操作也会越多,程序的执行速度也会因此而变慢。

为了解决这个问题,我们可以使用 Redis 提供的流水线特性:这个特性允许客户端把任意多条 Redis 命令请求打包在一起,然后一次性地将它们全部发送给服务器,而服务器则会在流水线包含的所有命令请求都处理完毕之后,一次性地将它们的执行结果全部返回给客户端。

通过使用流水线特性,我们可以将执行多个命令所需的网络通讯次数从原来的 N 次降低为一次,这可以大幅度地减少程序在网络通讯方面耗费的时间,使得程序的执行效率得到显著的提升。

作为例子,图 13-1 展示了在没有使用流水线的情况下,执行三个 Redis 命令产生的网络通讯示意图,而 13-2 则展示了在使用流水线的情况下,执行相同 Redis 命令产生的网络通讯示意图。可以看到,在使用了流水线之后,程序进行网络通讯的次数从原来的三次降低成了一次。


图 13-1 在不使用流水线的情况下执行三个 Redis 命令产生的网络通讯操作_images/IMAGE_NON_PIPE.png


图 13-2 在使用流水线的情况下执行三个 Redis 命令产生的网络通讯操作_images/IMAGE_USE_PIPE.png


虽然 Redis 服务器提供了流水线特性,但这个特性还需要客户端支持才能使用。幸运的是,包括 redis-py 在内的绝大部分 Redis 客户端都提供了对流水线特性的支持,因此 Redis 用户在绝大部分情况下都能够享受到流水线特性带来的好处。

为了在 redis-py 客户端中使用流水线特性,我们需要用到 pipeline() 方法,调用这个方法会返回一个流水线对象,用户只需要像平时执行 Redis 命令那样,使用流水线对象调用相应的命令方法,就可以把想要执行的 Redis 命令放入到流水线里面。

作为例子,以下代码展示了如何以流水线方式执行 SETINCRBYSADD 命令:

  1. >>> from redis import Redis
  2. >>> client = Redis(decode_responses=True)
  3. >>> pipe = client.pipeline(transaction=False)
  4. >>> pipe.set("msg", "hello world")
  5. Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
  6. >>> pipe.incrby("pv_counter::12345", 100)
  7. Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
  8. >>> pipe.sadd("fruits", "apple", "banana", "cherry")
  9. Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
  10. >>> pipe.execute()
  11. [True, 100, 3]

这段代码首先使用 pipeline() 方法创建了一个流水线对象,并将这个对象储存到了 pipe 变量里面(pipeline() 方法中的 transaction=False 参数表示不在流水线中使用事务,这个参数的具体意义将在本章后续内容中说明)。在此之后,程序通过流水线对象分别调用了 set() 方法、 incrby() 方法和 sadd() 方法,将这三个方法对应的命令调用放入到了流水线队列里面。最后,程序调用流水线对象的 execute() 方法,将队列中的三个命令调用打包发送给服务器,而服务器则会在执行完这些命令之后,把各个命令的执行结果依次放入到一个列表里面,然后将这个列表返回给客户端。

图 13-3 展示了以上代码在执行期间,redis-py 客户端与 Redis 服务器之间的网络通讯情况。


图 13-3 使用流水线发送 Redis 命令_images/IMAGE_PIPE_EXAMPLE.png


注解

流水线使用注意事项

虽然 Redis 服务器并不会限制客户端在流水线里面包含的命令数量,但是却会为客户端的输入缓冲区设置默认值为 1GB 的体积上限:当客户端发送的数据量超过这一限制时,Redis 服务器将强制关闭该客户端。因此用户在使用流水线特性时,最好不要一下子把大量命令或者一些体积非常庞大的命令放到同一个流水线里面执行,以免触碰到 Redis 的这一限制。

除此之外,很多客户端本身也带有隐含的缓冲区大小限制,如果你在使用流水线特性的过程中,发现某些流水线命令没有被执行,又或者流水线返回的结果不完整,那么很可能就是你的程序触碰到了客户端内置的缓冲区大小限制。在遇到这种情况时,请缩减流水线命令的数量及其体积,然后再进行尝试。