redis管道

redis管道用过么,用来做什么,它的原理是,保证原子性么,和事务的区别

Redis 管道是什么?

管道技术(Pipeline)是客户端提供的一种批处理技术,通过一次发出多个命令而无需等待每个命令的响应来提高性能的技术。大多数 Redis 客户端都支持管道操作。

管道原理

Redis 是一个使用客户端-服务器模型和请求/响应协议的 TCP 服务器。

这意味着通常请求是通过以下步骤完成的:

  • 客户端向服务器发送查询,并通常以阻塞方式从套接字读取服务器响应。
  • 服务器处理命令并将响应发送回客户端。

普通命令模式,网络通信过程如下图所示:

管道模式,如下图所示:

如果我们 set x1 1 , set x2 2,执行这两个指令存两个值,显然这两次操作并不相关,可以将这两个set指令一起发送过去,再读取返回值,省去中间一次读返回值的阻塞耗时,即调整读写顺序,将 写 -> 读,写 -> 读 的顺序改为 写写读读。

两次连续的写操作和连续的读操作只会花费一次网络来回的时间,连续的写操作被合并成一个,连续的读操作也被合并成一个。

所以在性能方面, Pipelining 有下面两个优势:

  • 节省了RTT:将多条命令打包一次性发送给服务端,减少了客户端与服务端之间的网络调用次数
  • 减少了上下文切换:当客户端/服务端需要从网络中读写数据时,都会产生一次系统调用,系统调用是非常耗时的操作,其中涉及到程序由用户态切换到内核态,再从内核态切换回用户态的过程。当我们执行 10 条 redis 命令的时候,就会发生 10 次用户态到内核态的上下文切换,但如果我们使用 Pipeining 将多条命令打包成一条一次性发送给服务端,就只会产生一次上下文切换。

当客户端使用管道发送命令时,服务器将使用内存对响应进行排队。因此,如果需要通过管道发送大量命令,最好将它们分批发送,每个批次包含合理的数量,例如 10k 个命令,读取回复,然后再次发送另外 10k 个命令,依此类推。速度几乎相同,但使用的额外内存最多是对这 10k 命令的回复进行排队所需的内存量。

所以使用管道技术需要注意避免发送的命令过大,或管道内的数据太多而导致的网络阻塞。

要注意的是,管道技术本质上是客户端提供的功能,而非 Redis 服务器端的功能。

使用场景

  • Redis的多次操作毫不相关时,即后一个指令不依赖于上一个指令的执行结果,则可以使用管道进行处理,提高性能
  • 如果后续的指令需要依赖前面的指令结果,则不适合用管道去做 (管道也是支持脚本的,可以在管道中发送一些脚本做依赖逻辑处理) ,从这种角度看,pipeline和批处理也比较相似。

性能测试

java 复制代码
package com.redis.pipeline.demo;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

public class App {
    public static synchronized Jedis buildJedis() {
        return new Jedis("localhost", 6379);
    }

    public static void main(String[] args) {
        System.out.println("非pipeline: " + noPipeline());
        System.out.println("pipeline: " + pipeline());
    }

    public static long noPipeline() {
        Jedis jedis = buildJedis();
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 6_000; i++) {
            jedis.set("i" + i, i + "");
        }
        long end = System.currentTimeMillis();
        return (end - begin) / 1000;
    }

    public static long pipeline() {
        Jedis jedis = buildJedis();
        Pipeline pipeline = jedis.pipelined();
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 6_000; i++) {
            pipeline.set("i" + i, i + "");
        }
        pipeline.sync();
        long end = System.currentTimeMillis();
        return (end - begin) / 1000;
    }
}

测试结果如下:

pipeline压测

redis自带一个压力测试工具 redis-benchmark,借此可以对管道进行测试,试试管道的威力。

对一个普通的set指令进行压测,QPS大约为 145985.41/s。

bash 复制代码
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -q
 
SET: 145985.41 requests per second, p50=0.167 msec

下面加入-P参数,表示单个管道内并行的请求数量,可以看到下面的结果,数量每加1个,QPS会多出十万个左右,从第14个往后,QPS无法提升,甚至开始下降,这是因为CPU的消耗已经达到100%的极限,无法继续提升了。

shell 复制代码
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 2 -q
 
SET: 273224.03 requests per second, p50=0.175 msec                    
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 3 -q
 
SET: 378795.47 requests per second, p50=0.183 msec                    
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 4 -q
 
SET: 483091.78 requests per second, p50=0.183 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 5 -q
 
SET: 549450.56 requests per second, p50=0.191 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 6 -q
 
SET: 653607.88 requests per second, p50=0.199 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 7 -q
 
SET: 724652.19 requests per second, p50=0.191 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 8 -q
 
SET: 806451.62 requests per second, p50=0.215 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 9 -q
 
SET: 877263.19 requests per second, p50=0.255 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 10 -q
 
SET: 1408450.62 requests per second, p50=0.255 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 11 -q
 
SET: 917440.38 requests per second, p50=0.375 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 11 -q
 
SET: 952390.50 requests per second, p50=0.375 msec      
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 12 -q
 
SET: 1020489.81 requests per second, p50=0.399 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 13 -q
 
SET: 1333453.25 requests per second, p50=0.367 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 14 -q
 
SET: 1075290.25 requests per second, p50=0.455 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 15 -q
 
SET: 1020459.19 requests per second, p50=0.479 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 16 -q
 
SET: 1000000.00 requests per second, p50=0.519 msec     
 
\
 
 
root@f5cd3ecb4cd8:/data# redis-benchmark -t set -P 17 -q
 
SET: 1020520.44 requests per second, p50=0.551 msec

保证原子性么

pipeline不是原子性的,中间可能会存在部分失败的情况 ,也就是说不能保证每条命令都能执行成功,如果中间有命令出现错误,redis不会中断执行,而是直接执行下一条命令,然后将所有命令的执行结果(执行成果或者执行失败)放到列表中统一返回,如果需要每条命令都执行成功,我们在批量执行过程中需要监控执行数量和返回的成功数量是否一致

和redis事务的区别

redis 事务介绍

redis的事务是部分事务,redis使用MULTI、EXEC、WATCH等命令实现事务功能。MULTI开始事务,EXEC执行事务。DISCARD取消事务,WATCH监视key;

shell 复制代码
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

使用 MULTI命令后可以输入多个命令。Redis不会立即执行这些命令,而是将它们放到队列,当调用了EXEC命令将执行所有命令。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制 ,所以Redis 事务的执行并不是原子性的。
Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。
Redis官网也解释了自己为啥不支持回滚。简单来说就是Redis开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中

redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

  1. 若在事务队列中存在命令性错误(类似于java编译性错误) ,则执行EXEC命令时,所有命令都不会执行
  2. 若在事务队列中存在语法性错误(类似于java的1/0的运行时异常) ,则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。

区别

  • Pipeline是客户端的行为,对于服务器来说是透明的。所以服务器是无法知道客户端发送过来的命令是以一种普通命令的形式还是使用Pipeline的形式发送过来的
  • 事务是是现在服务器端的行为。在用户执行MULTI命令时候,服务器会将对应这个用户的客户端对象设置成一个特殊状态,在该状态下用户执行的查询命令不会被真正执行。而是被服务器缓存起来,直到用户执行EXEC命令之后,服务器才会将对应客户端对象缓存中的命令按照提交的顺序依次执行
  • 使用Pipeline是可以提高服务器的吞吐能力,并提高服务器查询处理Redis的请求能力。但是需要注意的是,当通过Pipeline提交的查询数据命令较少的时候,可以被内核缓冲区所容纳的时候,Redis可以保证这些命令执行的原子性。然而当数据量一旦过大,超过了内核缓冲区接收的大小,命令的执行将会被打断,原子性也就无法得到保证。因为Pipeline只是一种提高服务器吞吐能力的机制。想要命令以事务的方式原子性的被执行,还是需要使用事务机制。或者使用更加高级的脚本功能以及模块功能
  • 可以将Pipeline和事务结合起来使用,能减少事务的命令在网络上的传输时间,将多次网络IO缩减为一次网络IO

参考文章

Redis 常见面试题之redis-管道有什么用
Redis pipelining
Redis之管道_redis 管道_多动手,勤思考的博客-CSDN博客
Redis中的pipeline和事务的区别是什么? - 知乎

相关推荐
xmh-sxh-13143 分钟前
java 数据存储方式
java
huapiaoy13 分钟前
Redis中数据类型的使用(hash和list)
redis·算法·哈希算法
liu_chunhai17 分钟前
设计模式(3)builder
java·开发语言·设计模式
bug菌¹32 分钟前
滚雪球学Oracle[6.2讲]:Data Guard与灾难恢复
数据库·oracle·data·灾难恢复·guard
一般路过糸.35 分钟前
MySQL数据库——索引
数据库·mysql
ya888g1 小时前
GESP C++四级样题卷
java·c++·算法
Cengineering1 小时前
sqlalchemy 加速数据库操作
数据库
【D'accumulation】1 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
小叶学C++1 小时前
【C++】类与对象(下)
java·开发语言·c++
2401_854391081 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端