Redis 为什么要引入 Pipeline机制?

在 Redis 中有一种 Pipeline(管道)机制,其目的是提高数据传输效率和吞吐量。那么,Pipeline是如何工作的?它又是如何提高性能的?Pipeline有什么优缺点?我们该如何使用 Pipeline?

1、Redis Pipeline是什么?

Redis Pipeline 是一种批量执行命令的技术,允许客户端在不等待服务器响应的情况下,一次性发送多个命令到 Redis 服务器。传统的请求-响应模式中,客户端每发送一个命令,就需要等待服务器响应后才能发送下一个命令,这种模式在高延迟网络环境下,严重影响 Redis 的性能表现。

Pipeline 通过消除或减少网络往返次数(Round-Trip Time, RTT),能够显著提高命令执行的吞吐量,客户端可以将多个命令打包发送,服务器则依次执行这些命令并将结果返回给客户端,从而有效地提升了网络利用率和整体性能。

2、为什么引入 Pipeline?
在了解 Redis为什么引入 Pipeline之前,我们先来了解传统请求-响应模式,在传统的请求-响应模式中,客户端与服务器之间的通信流程如下:

  • 客户端发送一个命令到服务器。
  • 服务器接收命令并执行。
  • 服务器将执行结果返回给客户端。
  • 客户端接收结果后,发送下一个命令。

在这里插入图片描述

在这种传统的模式下,每个命令都需要经历完整的 RTT,这在高延迟网络环境下会导致显著的性能瓶颈。

而 Pipeline的核心思想是“命令打包,高效传输”。其工作流程可以总结成下面 5个步骤:

  • 打包命令: 客户端将多个 Redis 命令按照特定的格式打包成一个请求包。
  • 发送命令: 将打包好的请求一次性发送给 Redis 服务器。
  • 执行命令: Redis 服务器按顺序执行接收到的所有命令。
  • 接收响应: 服务器将所有命令的执行结果按顺序返回给客户端。
  • 解析响应: 客户端解析接收到的响应,并将结果对应到各个命令。

在这里插入图片描述

这种方式通过减少网络往返次数,有效降低网络延迟对性能的影响,特别适合于需要执行大量 Redis 命令的高并发场景。

尽管 Pipeline带来了性能的提升,但它也有一些缺点:

  • 资源消耗: 发送大量命令一次性执行,可能会消耗较多的服务器资源,导致 Redis 其他操作的响应时间增加。
  • 错误处理复杂: 在批量执行命令时,单个命令的错误处理可能变得复杂,需要逐一检查每个命令的执行结果。
  • 顺序依赖: 如果命令之间存在顺序依赖,Pipeline 的批量执行需要确保正确的命令顺序。
  • 不支持事务功能: Pipeline 只是批量执行命令的工具,不具备事务的原子性和隔离性。
  • 客户端支持: 不同的 Redis 客户端对 Pipeline 的支持程度不同,使用时需考虑所选客户端库的特性和限制。

3、源码分析
在 Java中,常见的 Redis 客户端库有 Jedis 和 Lettuce两种,下面我们将分别分析这两个库实现 Pipeline功能。

3.1 使用 Jedis 库
Jedis 是一个简单、直观的 Redis 客户端,支持 Pipeline 功能。下面的示例展示如何使用 Jedis实现 Pipeline操作。

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

import java.util.ArrayList;
import java.util.List;

publicclass JedisPipelineExample {

    public static void main(String[] args) {
        // Redis 连接参数
        String redisHost = "localhost";
        int redisPort = 6379;
        String redisPassword = null; // 若有密码,填写密码

        // 连接 Redis
        try (Jedis jedis = new Jedis(redisHost, redisPort)) {
            if (redisPassword != null && !redisPassword.isEmpty()) {
                jedis.auth(redisPassword);
            }
            // 批量设置键值对
            batchSet(jedis);

            // 批量获取键值对
            batchGet(jedis);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用 Pipeline 批量设置键值对
     *
     * @param jedis Jedis 实例
     */
    public static void batchSet(Jedis jedis) {
        System.out.println("开始批量设置键值对...");

        Pipeline pipeline = jedis.pipelined();

        int numCommands = 1000;
        for (int i = 0; i < numCommands; i++) {
            pipeline.set("key-" + i, "value-" + i);
        }

        // 执行所有命令
        pipeline.sync();

        System.out.println("批量设置完成,共设置 " + numCommands + " 个键值对。");
    }

    /**
     * 使用 Pipeline 批量获取键值对
     */
    public static void batchGet(Jedis jedis) {
        System.out.println("开始批量获取键值对...");

        Pipeline pipeline = jedis.pipelined();

        int numCommands = 1000;
        List<Response<String>> responses = new ArrayList<>(numCommands);
        for (int i = 0; i < numCommands; i++) {
            Response<String> response = pipeline.get("key-" + i);
            responses.add(response);
        }

        // 执行所有命令
        pipeline.sync();

        // 处理结果
        for (int i = 0; i < numCommands; i++) {
            String value = responses.get(i).get();
            System.out.println("key-" + i + " = " + value);
        }

        System.out.println("批量获取完成,共获取 " + numCommands + " 个键值对。");
    }
}

代码解析

上面的代码主要总结为 4个步骤:

1、连接 Redis:

使用 Jedis 类连接 Redis 服务器。如果 Redis 服务器设置了密码,需要调用 jedis.auth 进行认证。

2、批量设置键值对:

  • 调用 jedis.pipelined() 获取一个 Pipeline 对象。
  • 使用循环将多个 set 命令添加到 Pipeline 中。
  • 调用 pipeline.sync() 发送所有命令并等待执行结果。
  • 通过 Pipeline 一次性提交所有命令,减少了网络往返次数。

3、批量获取键值对:

  • 同样使用 pipelines 获取 Pipeline 对象。
  • 使用 pipeline.get 方法批量添加 get 命令,并将 Response 对象保存到列表中。
  • 调用 pipeline.sync() 发送所有命令并等待执行结果。
  • 遍历 Response 对象列表,获取每个键的值。

4、关闭连接:

使用 try-with-resources 语法自动关闭 Jedis 连接,确保资源的正确释放。

3.2 使用 Lettuce 库
Lettuce 是一个基于 Netty 的可伸缩、多线程的 Redis 客户端,支持异步和反应式编程模型,同样支持 Pipeline 功能。

以下示例展示如何使用 Lettuce 实现 Pipeline 操作,包括批量设置和获取键值对。

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.api.sync.SyncCommands;
import io.lettuce.core.api.sync.RedisScriptingCommands;
import io.lettuce.core.api.sync.RedisClusterCommands;

import java.util.ArrayList;
import java.util.List;

publicclass LettucePipelineExample {

    public static void main(String[] args) {
        // Redis 连接参数
        String redisHost = "localhost";
        int redisPort = 6379;
        String redisPassword = null; // 若有密码,填写密码

        // 创建 RedisURI
        RedisURI redisURI = RedisURI.Builder.redis(redisHost)
                .withPort(redisPort)
                .withPassword(redisPassword != null ? redisPassword.toCharArray() : null)
                .build();

        // 创建 RedisClient
        RedisClient redisClient = RedisClient.create(redisURI);

        // 建立连接
        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisCommands<String, String> syncCommands = connection.sync();

            // 批量设置键值对
            batchSet(syncCommands);

            // 批量获取键值对
            batchGet(syncCommands);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭客户端
            redisClient.shutdown();
        }
    }

    /**
     * 使用 Lettuce 的 Pipeline 批量设置键值对
     *
     * @param syncCommands 同步命令接口
     */
    public static void batchSet(RedisCommands<String, String> syncCommands) {
        System.out.println("开始批量设置键值对...");

        int numCommands = 1000;
        for (int i = 0; i < numCommands; i++) {
            syncCommands.set("key-" + i, "value-" + i);
        }

        // 批量执行所有命令
        syncCommands.getStatefulConnection().flushCommands();

        System.out.println("批量设置完成,共设置 " + numCommands + " 个键值对。");
    }

    /**
     * 使用 Lettuce 的 Pipeline 批量获取键值对
     *
     * @param syncCommands 同步命令接口
     */
    public static void batchGet(RedisCommands<String, String> syncCommands) {
        System.out.println("开始批量获取键值对...");

        int numCommands = 1000;
        List<String> keys = new ArrayList<>(numCommands);
        for (int i = 0; i < numCommands; i++) {
            keys.add("key-" + i);
        }

        List<String> values = syncCommands.mget(keys.toArray(new String[0]))
                .stream()
                .map(res -> res.getValue())
                .toList();

        for (int i = 0; i < numCommands; i++) {
            System.out.println(keys.get(i) + " = " + values.get(i));
        }

        System.out.println("批量获取完成,共获取 " + numCommands + " 个键值对。");
    }
}

代码解析

上面的代码主要总结为 4个步骤:

1、连接 Redis:

使用 RedisClient 创建连接,RedisURI 封装了连接参数。如果 Redis 服务器设置了密码,需要在 RedisURI 中指定。

2、批量设置键值对:

  • 使用 syncCommands.set 方法批量添加 set 命令。
  • 调用 flushCommands() 方法将所有积累的命令一次性发送到服务器。

注:Lettuce 的 Pipeline 支持隐式的 Pipeline,即没有显式的 Pipeline API,通过积累命令并调用 flushCommands() 实现批量发送。

3、批量获取键值对:

使用 mget 方法一次性获取多个键的值,这是 Lettuce 提供的批量获取命令,天然支持 Pipeline。
mget 返回一个包含每个键值的 List,通过流处理提取值。

4、关闭连接:

使用 try-with-resources 语法自动关闭连接,最后调用 redisClient.shutdown() 关闭 Redis 客户端。

尽管 Lettuce 支持 Pipeline,但其 API 不如 Jedis 那样显式。要实现更细粒度的 Pipeline 控制,可以使用 Lettuce 的命令缓冲机制或异步 API。上述示例中展示的是同步方式,适用于简单的批量操作。

4、使用场景

  • 批量设置键值对: 将大量键值对一次性写入 Redis,适用于数据初始化或大规模更新。
  • 批量获取键值对: 在需要同时获取多个键的值时,通过 Pipeline 减少请求次数,提高效率。
  • 分布式计数器: 高并发情况下,使用 Pipeline 聚合多个计数操作,提升吞吐量。
  • 缓存预热: 在应用启动或重启时,通过 Pipeline 将常用数据加载到缓存中,提高应用启动性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952971.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cesium小知识:粒子系统的参数详解

Cesium 的粒子系统通过 ParticleSystem 类提供了一套丰富的参数来控制粒子的生成、行为和外观。以下是这些参数的详细说明,帮助你更好地理解和使用 Cesium 的粒子系统。 基本参数 image (String) - 粒子图像的URL路径。这个图像是每个粒子在渲染时使用的纹理。 startColor (Co…

【数据结构-堆】力扣1834. 单线程 CPU

给你一个二维数组 tasks &#xff0c;用于表示 n​​​​​​ 项从 0 到 n - 1 编号的任务。其中 tasks[i] [enqueueTimei, processingTimei] 意味着第 i​​​​​​​​​​ 项任务将会于 enqueueTimei 时进入任务队列&#xff0c;需要 processingTimei 的时长完成执行。 现…

OSPF - 2、3类LSA(Network-LSA、NetWork-Sunmmary-LSA)

前篇博客有对常用LSA的总结 2类LSA&#xff08;Network-LSA&#xff09; DR产生泛洪范围为本区域 作用:  描述MA网络拓扑信息和网络信息&#xff0c;拓扑信息主要描述当前MA网络中伪节点连接着哪几台路由。网络信息描述当前网络的 掩码和DR接口IP地址。 影响邻居建立中说到…

【强化学习】演员评论家Actor-Critic算法(万字长文、附代码)

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…

【2024年华为OD机试】 (C卷,100分)- 密钥格式化(Java JS PythonC/C++)

一、问题描述 题目描述 给定一个非空字符串 S&#xff0c;其被 N 个‘-’分隔成 N1 的子串&#xff0c;给定正整数 K&#xff0c;要求除第一个子串外&#xff0c;其余的串每 K 个用‘-’分隔&#xff0c;并将小写字母转换为大写。 输入描述 正整数 K 和‘-’分割的字符串&a…

基于单片机的指纹密码锁

【摘要】 本设计是一款基于单片机的指纹识别电子密码锁系统。该系统以STC89C52单片机作为模块核心同时结合ZFM-60指纹模块实现录取指纹并存储指纹数据的功能&#xff0c;并且通过HS12864-15C液晶显示比对流程及比对结果&#xff0c;该指纹电子密码锁通过直流继电器与发光二极管…

企业总部和分支通过GRE VPN互通

PC1可以ping通PC2 1、首先按照地址表配置ip地址 2、分别在AR1和AR3上配置nat 3、配置GRE a 创建tunnel接口&#xff0c;并选择tunnel协议为GRE&#xff0c;为隧道创建一个地址&#xff0c;用作互联 b 为隧道配置源地址或者源接口&#xff0c;这里选择源接口&#xff1b;再为…

回归预测 | MATLAB实MLR多元线性回归多输入单输出回归预测

回归预测 | MATLAB实MLR多元线性回归多输入单输出回归预测 目录 回归预测 | MATLAB实MLR多元线性回归多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实MLR多元线性回归多输入单输出回归预测。 程序设计 完整代码&#xff1a;回…

计算机网络(五)运输层

5.1、运输层概述 概念 进程之间的通信 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时…

【PPTist】插入形状、插入图片、插入图表

一、插入形状 插入形状有两种情况&#xff0c;一种是插入固定的形状&#xff0c; 一种是插入自定义的形状。 插入固定的形状时&#xff0c;跟上一篇文章 绘制文本框 是一样一样的&#xff0c;都是调用的 mainStore.setCreatingElement() 方法&#xff0c;只不多传的类型不一…

Elasticsearch—索引库操作(增删查改)

Elasticsearch中Index就相当于MySQL中的数据库表 Mapping映射就类似表的结构。 因此我们想要向Elasticsearch中存储数据,必须先创建Index和Mapping 1. Mapping映射属性 Mapping是对索引库中文档的约束&#xff0c;常见的Mapping属性包括&#xff1a; type&#xff1a;字段数据类…

ROS Action接口

实现自主导航是使用Action接口的主要目的 在实际使用navigation导航系统的时候&#xff0c;机器人需要自主进行导航。不能每次都手动设置导航的目标点。所以需要编写程序代码来实现导航控制。这就需要使用到navigation的导航接口。Navigation的这个导航接口有好几个。Rose官方…

macOS 安装tomcat9

macOS 安装tomcat9 URL&#xff1a;https://tomcat.apache.org/download-90.cgi 解压之后放到指定目录 /Users/lanren/install/tomcat-9 自己取个名字就行 给权限&#xff1a; ① 先进行权限修改&#xff1a;终端输入sudo chmod 755 /Users/lanren/install/tomcat-9/bin/…

PatchTST:通道独立的、切片的 时序 Transformer

出处&#xff1a;ICLR 2023 代码链接&#xff1a;yuqinie98/PatchTST: An offical implementation of PatchTST: "A Time Series is Worth 64 Words: Long-term Forecasting with Transformers." (ICLR 2023) https://arxiv.org/abs/2211.14730 一 模型主要思想及…

QT c++ 样式 设置 按钮(QPushButton)的渐变色美化

上一篇文章中描述了标签的渐变色美化,本文描述按钮的渐变色美化。 1.头文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> //#include "CustomButton.h"#include <QVBoxLayout> #include <QLinearGradient> #include <QPushButton&…

OPT: Open Pre-trained Transformer语言模型

摘要 大规模语言模型通常需要数十万计算日的训练时间&#xff0c;展现了在零样本和小样本学习中的显著能力。鉴于其计算成本之高&#xff0c;这些模型在没有大量资本投入的情况下难以复现。对于那些通过API提供的少数模型&#xff0c;研究者无法获取完整的模型权重&#xff0c…

力扣257(关于回溯算法)二叉树的所有路径

257. 二叉树的所有路径 一.问题描述 已解答 简单 相关标签 相关企业 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5…

《OpenCV计算机视觉实战项目》——银行卡号识别

文章目录 项目任务及要求项目实现思路项目实现及代码导入模块设置参数对模版图像中数字的定位处理银行卡的图像处理读取输入图像&#xff0c;预处理找到数字边框使用模版匹配&#xff0c;计算匹配得分 画出并打印结果 项目任务及要求 任务书&#xff1a; 要为某家银行设计一套…

Python学习(三)基础入门(数据类型、变量、条件判断、模式匹配、循环)

目录 一、第一个 Python 程序1.1 命令行模式、Python 交互模式1.2 Python的执行方式1.3 SyntaxError 语法错误1.4 输入和输出 二、Python 基础2.1 Python 语法2.2 数据类型1&#xff09;Number 数字2&#xff09;String 字符串3&#xff09;List 列表4&#xff09;Tuple 元组5&…

系统思考—要素连接

“改变你的思维&#xff0c;就能改变你的世界”— 诺曼皮尔 世界上的所有事物&#xff0c;都在规律的支配下&#xff0c;以系统的方式运转。显性的部分是我们能看到的“要素”&#xff0c;而那些看不见的力量&#xff0c;正是推动系统运作的要素之间的相互作用。更隐秘的&…