Redis基础学习之管道机制详析

前言

Redis服务是一种C/S模型,提供请求-响应式协议的TCP服务,所以当客户端请求发出,服务端处理并返回结果到客户端,一般是以阻塞形式等待服务端的响应,但这在批量处理连接时延迟问题比较严重,所以Redis为了提升或弥补这个问题,引入了管道技术:可以做到服务端未及时响应的时候,客户端也可以继续发送命令请求,做到客户端和服务端互不干涉影响,服务端并最终返回所有服务端的响应,这在促进原有C/S模型交互的响应速度上有了质的提高。

以下是对 Redis管道机制的一个学习记录

Pipeline简介

Redis客户端执行一条命令:

  • 发送命令
  • 命令排队
  • 执行命令
  • 返回结果

其中发送命令和返回结果可以称为 Round Trip Time (RTT,往返时间)。在Redis中提供了批量操作命令,例如mget、mset等,有效地节约了RTT。但是大部分命令是不支持批量操作的。

为此Redis提供了一个称为管道(Pipeline) 的机制将一组Redis命令进行组装,通过一次 RTT 传输给 Redis,再将这些 Redis 命令的执行结果按顺序传递给客户端。即使用pipeline执行了n次命令,整个过程就只需要一次 RTT。

对Pipeline进行性能测试

我们使用redis-benchmark 对Pipeline进行性能测试,该工具提供了 -P 的选项,此选项表示使用管道机制处理 n 条Redis请求,默认值为1。测试如下:

# 不使用管道执行get set 100000次请求
[root@iz2zeaf3cg1099kiidi06mz ~]# redis-benchmark -t get,set -q -n 100000
SET: 55710.31 requests per second
GET: 54914.88 requests per second
# 每次pipeline组织的命令个数 为 100
[root@iz2zeaf3cg1099kiidi06mz ~]# redis-benchmark -P 100 -t get,set -q -n 100000
SET: 1020408.19 requests per second
GET: 1176470.62 requests per second
# 每次pipeline组织的命令个数 为 10000
[root@iz2zeaf3cg1099kiidi06mz ~]# redis-benchmark -P 10000 -t get,set -q -n 100000
SET: 321543.41 requests per second
GET: 241545.89 requests per second

从上面测试可以看出,使用pipeline的情况下 Redis 每秒处理的请求数远大于 不使用 pipeline的情况。

当然每次pipeline组织的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加 客户端等待时间,另一方面会造成一定的网络阻塞。

从上面的测试中也可以看出,如果一次pipeline组织的命令个数为 10000,但是它对应的QPS 却小于 一次pipeline命令个数为 100的。所以每次组织 Pipeline的命令个数不是越多越好,可以将一次包含大量命令的 Pipeline 拆分为 多个较小的 Pipeline 来完成。

Pipeline关于RTT的说明

在官网上有一段这样的描述:

大致意思就是 :

Pipeline管道机制不单单是为了减少RTT的一种方式,它实际上大大提高了Redis的QPS。原因是,在没有使用管道机制的情况下,从访问数据结构和产生回复的角度来看,为每个命令提供服务是非常便宜的。但是从底层套接字的角度来看,这是非常昂贵的,这涉及read()和write()系统调用,从用户态切换到内核态,这种上下文切换开销是巨大。而使用Pipeline的情况下,通常使用单个read()系统调用读取许多命令,然后使用单个write()系统调用传递多个回复,这样就提高了QPS

批量命令与Pipeline对比

  • 批量命令是原子的,Pipeline 是非原子的
  • 批量命令是一个命令多个 key,Pipeline支持多个命令
  • 批量命令是 Redis服务端实现的,而Pipeline需要服务端和客户端共同实现

使用jedis执行 pipeline

public class JedisUtils {
 private static final JedisUtils jedisutils = new JedisUtils();

 public static JedisUtils getInstance() {
 return jedisutils;
 }

 public JedisPool getPool(String ip, Integer port) {
 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
 jedisPoolConfig.setMaxIdle(RedisConfig.MAX_IDLE);
 jedisPoolConfig.setMaxTotal(RedisConfig.MAX_ACTIVE);
 jedisPoolConfig.setMaxWaitMillis(RedisConfig.MAX_WAIT);
 jedisPoolConfig.setTestOnBorrow(true);
 jedisPoolConfig.setTestOnReturn(true);
 JedisPool pool = new JedisPool(jedisPoolConfig, ip, port,RedisConfig.TIMEOUT,RedisConfig.PASSWORD);
 return pool;
 }

 public Jedis getJedis(String ip, Integer port) {
 Jedis jedis = null;
 int count = 0;
 while (jedis == null && count < RedisConfig.RETRY_NUM) {
  try {
  jedis = getInstance().getPool(ip, port).getResource();
  } catch (Exception e) {
  System.out.println("get redis failed");
  }
  count++;
 }
 return jedis;
 }

 public void closeJedis(Jedis jedis) {
 if (jedis != null) {
  jedis.close();
 }
 }

 public static void main(String[] args) throws InterruptedException {
 Jedis jedis = JedisUtils.getInstance().getJedis("127.0.0.1", 6379);
 Pipeline pipeline = jedis.pipelined();
 pipeline.set("hello", "world");
 pipeline.incr("counter");
 System.out.println("还没执行命令");
 Thread.sleep(100000);
 System.out.println("这里才开始执行");
 pipeline.sync();
 }
}

在睡眠100s的时候查看 Redis,可以看到此时在pipeline中的命令并没有执行,命令都被放在一个队列中等待执行:

127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> get counter
(nil)

睡眠结束后,使用 pipeline.sync()完成此次pipeline对象的调用。

127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> get counter
"1"

必须要执行pipeline.sync() 才能最终执行命令,当然可以使用 pipeline.syncANdReturnAll回调机制将pipeline响应命令进行返回。

参考资料 & 鸣谢

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • .NET客户端实现Redis中的管道(PipeLine)与事物(Transactions)

    序言 Redis中的管道(PipeLine)特性:简述一下就是,Redis如何从客户端一次发送多个命令,服务端到客户端如何一次性响应多个命令. Redis使用的是客户端-服务器模型和请求/响应协议的TCP服务器,这就意味着一个请求要有以下步骤才能完成:1.客户端向服务器发送查询命令,然后通常以阻塞的方式等待服务器相应.2.服务器处理查询命令,并将相应发送回客户端.这样便会通过网络连接,如果是本地回环接口那么就能特别迅速的响应,但是如果走外网,甚至外网再做一系列的层层转发,那就显的格外蛋疼.无论网

  • 详解redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作

    前段时间在做用户画像的时候,遇到了这样的一个问题,记录某一个商品的用户购买群,刚好这种需求就可以用到Redis中的Set,key作为productID,value就是具体的customerid集合,后续的话,我就可以通过productid来查看该customerid是否买了此商品,如果购买了,就可以有相关的关联推荐,当然这只是系统中的一个小业务条件,这时候我就可以用到SADD操作方法,代码如下: static void Main(string[] args) { ConnectionMultip

  • Redis基础学习之管道机制详析

    前言 Redis服务是一种C/S模型,提供请求-响应式协议的TCP服务,所以当客户端请求发出,服务端处理并返回结果到客户端,一般是以阻塞形式等待服务端的响应,但这在批量处理连接时延迟问题比较严重,所以Redis为了提升或弥补这个问题,引入了管道技术:可以做到服务端未及时响应的时候,客户端也可以继续发送命令请求,做到客户端和服务端互不干涉影响,服务端并最终返回所有服务端的响应,这在促进原有C/S模型交互的响应速度上有了质的提高. 以下是对 Redis管道机制的一个学习记录 Pipeline简介 R

  • Java基础篇之反射机制详解

    目录 1.反射概述 1.1什么是反射 1.2.反射能干什么 2.解剖类 2.1反射构造方法 2.1.1反射无参的构造函数 2.1.2反射“一个参数”的构造函数 2.1.3反射“多个参数”的构造函数 2.1.4反射“私有”的构造函数 2.1.5反射得到类中所有的构造函数 2.2反射类中的方法 2.3反射类中的属性字段 思考:在讲反射之前,先思考一个问题,java中如何创建一个对象,有哪几种方式? Java中创建对象大概有这几种方式: 1.使用new关键字:这是我们最常见的也是最简单的创建对象的方式

  • Java基础之垃圾回收机制详解

    一.GC的作用 进行内存管理 C语言中的内存,申请内存之后需要手动释放:一旦忘记释放,就会发生内存泄漏! 而Java语言中,申请内存后会由GC来释放内存空间,无需手动释放 GC虽然代替了手动释放的操作,但是它也有局限性: 需要消耗更多的资源: 没有手动释放那么及时: STW(Stop The World)会影响程序的执行效率 二.GC主要回收哪些内存 (1)堆:主要回收堆中的内存 (2)方法区:需要回收 (3)栈(包括本地方法栈和JVM虚拟机栈):不需要回收,栈上的内存什么时候释放是明确的(线程

  • JavaScript基础学习之splice()函数详解

    目录 splice()函数详解 一.情况一(只有一个参数) 二.情况二 (两个参数) 三.情况三 (大于等于三个参数) 总结 splice()函数详解 splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目. 注释:该方法会改变原始数组. 参数: index —— 必需.整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置.howmany —— 必需.要删除的项目数量.如果设置为 0,则不会删除项目.item1, …, itemX —— 可选.向数组添加的新项目. 返回

  • Git基础学习之分支基本操作详解

    目录 1.创建分支 (1)创建分支 (2)图示理解 2.查看分支列表 3.分支切换 4.查看所有分支的最后一个提交 5.删除分支 1.创建分支 (1)创建分支 Git 是怎么创建新分支的呢? 很简单,就是要创建一个可以移动的新的指针. 比如,创建一个testing分支, 你需要使用命令:git branch testing. 示例: # 1.查看本地版本库历史提交 L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/learngit (master) $ gi

  • Python基础学习之基本数据结构详解【数字、字符串、列表、元组、集合、字典】

    本文实例讲述了Python基础学习之基本数据结构.分享给大家供大家参考,具体如下: 前言 相比于PHP,Python同样也是脚本解析语言,所以在使用Python的时候,变量和数据结构相对于编译语言来说都会简单许多,但是Python相比于PHP来说,变量类型的定义会比较严格:string->int的转换没有PHP那么方便.但这也让程序稳定性有所提升,例如和客户端交互的时候,数据库取出来的数字int和缓存取出来的数字(默认是string)需要手动进行转换(否则会有报错提示),而PHP不需要手动转换的

  • Java基础学习笔记之数组详解

    本文实例讲述了Java基础学习笔记之数组.分享给大家供大家参考,具体如下: 数组的定义于使用 1:数组的基本概念 一组相关变量的集合:在Java里面将数组定义为引用数据类型,所以数组的使用一定要牵扯到内存分配:想到了用new 关键字来处理. 2:数组的定义格式 区别: 动态初始化后数组中的每一个元素的内容都是其对应数据类型的默认值,随后可以通过下标进行数组内容的修改: 如果希望数组定义的时候就可以提供内容,则采用静态初始化的方式: a:数组的动态初始化(声明并初始化数组): 数据类型 数组名称

  • VUE3基础学习之click事件详解

    目录 1. 概述 2. click 事件 2.1 实现数字递减 2.2 事件方法中获取 event 对象 2.3 事件方法中增加参数 2.4 有参事件方法中获取 event 对象 2.5 点击按钮执行多个方法 2.6 事件冒泡 2.7 阻止冒泡 2.8 事件捕获 2.9 事件只执行一次 3. 综述 1. 概述 老话说的好:努力帮别人解决难题,你的难题也就不难解决了. 言归正传,今天我们来聊聊 VUE3 的 click 事件的相关知识. 2. click 事件 2.1 实现数字递减 <body>

  • Java基础学习之反射机制原理详解

    目录 一.什么是反射 二.反射的原理 三.反射的优缺点 四.反射的用途 五.反射机制常用的类 六.反射的基本使用 一.什么是反射 (1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法.本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息. (2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM.通过反射,可

  • .NET Core中使用Redis与Memcached的序列化问题详析

    前言 在使用分布式缓存的时候,都不可避免的要做这样一步操作,将数据序列化后再存储到缓存中去. 序列化这一操作,或许是显式的,或许是隐式的,这个取决于使用的package是否有帮我们做这样一件事. 本文会拿在.NET Core环境下使用Redis和Memcached来当例子说明,其中,Redis主要是用StackExchange.Redis,Memcached主要是用EnyimMemcachedCore. 先来看看一些我们常用的序列化方法. 常见的序列化方法 或许,比较常见的做法就是将一个对象序列

随机推荐