当前位置: 首页 > 科技观察

巧用 Redis pipeline 命令,解决真实的生产问题

时间:2023-03-21 15:47:44 科技观察

熟练使用Redis管道命令解决实际生产问题本文转载请联系Java极客技术公众号。大家好,我是阿粉~最近阿粉接到一个业务请求,需要开发一个业务接口,对Redis中的数据进行批量删除。这个功能点其实很简单,只要从外部传入要删除的关键信息,然后在接口内部遍历调用删除命令即可。按照这个思路,很快就开发了功能,然后顺利上线。上线后运行一段时间,打电话给业务方反馈。当需要删除的数据比较多的时候,这个接口的响应时间比较长,那么我们希望可以在这里优化一下,减少响应时间。优化的方法其实有很多,比如使用多线程删除等,但是这次我没有使用这个,最后还是使用了Redis管道(pipeline)命令进行优化。那么今天这篇文章就给大家介绍一下Redis管道命令以及相关原理。文中涉及的知识点如下图所示:为什么多次调用Redis命令会比较慢?Redis客户端执行一条命令需要经过的流程,如下图所示:一共需要经过四个流程:客户端发送一条命令。Redis服务收到命令,等待处理。Redis服务器处理命令。Redis服务返回执行结果。Redis客户端和服务可能部署在不同的机器上。这里我们假设Redis客户端部署在北京,而Redis服务器部署在广州,两地网络延迟为50ms。对于一个Redis命令,1和4这两个过程耗时100ms,而2和3是在Redis服务器上执行的,所以执行速度会很快,可以忽略不计。这时候如果客户端需要执行N次Redis命令,我们就需要花费2N*100ms的时间。执行的命令越多,花费的时间就越长。这就是文章开头的Redisdeletemultiple命令慢的主要原因。Redispipeline管道执行命令如何解决这类问题?有三种解决方法。第一种利用多线程机制并行执行命令,提高执行速度。二是调用mget等命令,可以一次操作多个key,Redis服务器收到命令后会分批执行。但是像mget这样的批处理命令毕竟很少见,很多时候我们是不能直接使用的,就像上面的例子。这种情况只能使用最后一种方法,使用Redispipeline命令。启动Redis管道后,执行其他Redis命令。命令不会发送到服务器,而是暂时存储在客户端。所有命令执行完毕后,会统一发送给服务器。服务器将根据命令发送的顺序依次运行计算。然后,结果也暂时存储在服务器上,所有命令执行完毕后,统一返回给客户端。这样,减少了多个命令之间的网络交互,有效提高了多个命令的执行速度。如上图所示,开启RedisPipeline后,客户端运行的5条命令会一起发送到服务端。该服务一条一条地运行命令并一致返回它们。介绍完原理,我们来看看如何使用RedisPipeline。以下代码以Jedis为例。JedisPoolConfigpoolConfig=newJedisPoolConfig();poolConfig.setMaxIdle(100);poolConfig.setTestOnBorrow(false);poolConfig.setTestOnReturn(false);JedisPooljedisPool=newJedisPool(poolConfig,"127.0.0.1",Integer.parse9Int("1000,"1234qwer");Jedisjedis=jedisPool.getResource();Pipelinepipelined=jedis.pipelined();for(inti=0;i<100;i++){pipelined.set("key"+i,"value"+i);}流水线。sync();Jedis#pipelined会开启RedisPipeline,Pipeline类提供Redis可以使用的所有命令:当所有命令执行完毕后,调用Pipelined#sync命令,所有命令数据会统一发送到Redis。在上面的例子中,Pipelined#sync方法调用不会返回任何结果。如果此时我们需要处理Redis的返回值,那么就需要调用Pipelined#syncAndReturnAll方法。该方法的返回值将是一个集合,返回结果按照Redis命令的顺序进行排序。解密管道实现原理Redis管道命令的实现实际上需要客户端和服务端双方的支持,而在实际执行过程中,Redis管道会根据命令的大小拆分命令数据,拆分成多个数据包发送。这样做的主要原因是,如果一个管道中的数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络拥塞。不同的Redis客户端管道发送的最大字节数是不一样的。比如jedis-pipeline每次发送的最大字节数是8192。接下来,我们从源码端看一下jedispipeline的实现机制。对于Pipeline的所有命令方法,底层最终都会调用Protocol#sendCommand方法,主要是向RedisOutputStream输出流写入数据。RedisOutputStream#write方法如下图所示:在该方法中,一旦缓冲数据的大小超过指定大小,目前为8192,所有数据将立即写入到真正的输出流中。在管道中实际发送多条命令的流程图如下:一旦Redis客户端将管道中执行的部分命令发送给Redis服务端,服务端会立即运行这些命令,然后返回给客户端。但是此时客户端不会去读取,所以返回的响应数据会暂时存放在客户端的Socket接收缓冲区中。如果响应数据比较大,缓冲区满了,client会使用TCP流控机制,ACK会返回WIN=0(接收窗口)来控制server不再发送数据。此时响应数据会暂存在Redis服务器的输出缓存中。如果数据很多,会占用很多内存。因此,在使用RedisPipeline机制时,一定要注意返回的数据量。如果数据量很大,建议将包含大量命令的流水线拆分成多个更小的流水线来完成。总结Redis的pipeline命令可以批量执行多条redis命令,通过减少网络调用次数有效提高了多条命令的执行速度。但是我们在使用进程的时候,主要是要执行数据的大小。如果数据太大,我们可以考虑拆掉一个pipeline。