Redis 批处理优化

news2024/11/15 14:21:55

一、优化建议

1、使用Pipeline

Redis 的 Pipeline 可以将多个命令打包成一个请求,从而减少通信次数和网络开销。在批处理时,可以使用 Pipeline 来提高效率。

2、使用批量插入

Redis 支持批量插入,可以将多个数据一次性插入数据库,从而减少通信次数和网络开销。可以使用 Redis 的 MSET 或者 MSETNX 命令进行批量插入。

3、使用 Hash 结构

Redis 的 Hash 结构可以存储多个字段和值,可以使用 Hash 结构来存储一组相关的数据,从而减少通信次数和网络开销。

4、使用管道

Redis 支持管道技术,可以将多个命令打包成一个请求,从而减少通信次数和网络开销。在批处理时,可以使用管道技术来提高效率。

5、控制批量大小

在批处理时,应该适当控制批量大小,不应该一次批处理太多数据,否则会影响性能。可以通过多次迭代的方式来进行批处理,每次迭代处理一部分数据。

6、使用多线程

在批处理时,可以使用多线程技术来提高效率。可以将数据分成多个批次,每个线程处理一个批次,最后将结果合并。

7、使用 Redis Cluster

Redis Cluster 提供了分布式存储和负载均衡的能力,可以将数据分散到多个节点上,从而减少单个节点的负载。在批处理时,可以使用 Redis Cluster 来提高效率。

二、我们的客户端与redis服务器是如何交互

1. 单个命令的执行流程

在这里插入图片描述

2. N个命令的执行流程

在这里插入图片描述

  • redis处理指令是很快的,主要花费的时候在于网络传输。于是乎很容易想到将多条指令批量的传输给redis

在这里插入图片描述

三、优化实例

1、使用MSet

Redis提供了很多Mxxx这样的命令,可以实现批量插入数据,例如:

  • mset
  • hmset

利用mset批量插入10万条数据

@Test
void testMxx() {
    String[] arr = new String[2000];
    int j;
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        j = (i % 1000) << 1;
        arr[j] = "test:key_" + i;
        arr[j + 1] = "value_" + i;
        if (j == 0) {
            jedis.mset(arr);
        }
    }
    long e = System.currentTimeMillis();
    System.out.println("time: " + (e - b));
}

2. 使用Pipeline优化

MSET虽然可以批处理,但是却只能操作部分数据类型,因此如果有对复杂数据类型的批处理需要,建议使用Pipeline

@Test
void testPipeline() {
    // 创建管道
    Pipeline pipeline = jedis.pipelined();
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        // 放入命令到管道
        pipeline.set("test:key_" + i, "value_" + i);
        if (i % 1000 == 0) {
            // 每放入1000条命令,批量执行
            pipeline.sync();
        }
    }
    long e = System.currentTimeMillis();
    System.out.println("time: " + (e - b));
}

四、集群下的批处理

1、解决方案

如MSET或Pipeline这样的批处理需要在一次请求中携带多条命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则就会导致执行失败。大家可以想一想这样的要求其实很难实现,因为我们在批处理时,可能一次要插入很多条数据,这些数据很有可能不会都落在相同的节点上,这就会导致报错了

这个时候,我们可以找到4种解决方案
在这里插入图片描述
第一种方案:串行执行,所以这种方式没有什么意义,当然,执行起来就很简单了,缺点就是耗时过久。

第二种方案:串行slot,简单来说,就是执行前,客户端先计算一下对应的key的slot,一样slot的key就放到一个组里边,不同的,就放到不同的组里边,然后对每个组执行pipeline的批处理,他就能串行执行各个组的命令,这种做法比第一种方法耗时要少,但是缺点呢,相对来说复杂一点,所以这种方案还需要优化一下

第三种方案:并行slot,相较于第二种方案,在分组完成后串行执行,第三种方案,就变成了并行执行各个命令,所以他的耗时就非常短,但是实现呢,也更加复杂。

第四种:hash_tag,redis计算key的slot的时候,其实是根据key的有效部分来计算的,通过这种方式就能一次处理所有的key,这种方式耗时最短,实现也简单,但是如果通过操作key的有效部分,那么就会导致所有的key都落在一个节点上,产生数据倾斜的问题,所以我们推荐使用第三种方式。

2、串行化执行代码实践

public class JedisClusterTest {

    private JedisCluster jedisCluster;

    @BeforeEach
    void setUp() {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        HashSet<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.150.101", 7001));
        nodes.add(new HostAndPort("192.168.150.101", 7002));
        nodes.add(new HostAndPort("192.168.150.101", 7003));
        nodes.add(new HostAndPort("192.168.150.101", 8001));
        nodes.add(new HostAndPort("192.168.150.101", 8002));
        nodes.add(new HostAndPort("192.168.150.101", 8003));
        jedisCluster = new JedisCluster(nodes, poolConfig);
    }

    @Test
    void testMSet() {
        jedisCluster.mset("name", "Jack", "age", "21", "sex", "male");

    }

    @Test
    void testMSet2() {
        Map<String, String> map = new HashMap<>(3);
        map.put("name", "Jack");
        map.put("age", "21");
        map.put("sex", "Male");
        //对Map数据进行分组。根据相同的slot放在一个分组
        //key就是slot,value就是一个组
        Map<Integer, List<Map.Entry<String, String>>> result = map.entrySet()
                .stream()
                .collect(Collectors.groupingBy(
                        entry -> ClusterSlotHashUtil.calculateSlot(entry.getKey()))
                );
        //串行的去执行mset的逻辑
        for (List<Map.Entry<String, String>> list : result.values()) {
            String[] arr = new String[list.size() * 2];
            int j = 0;
            for (int i = 0; i < list.size(); i++) {
                j = i<<2;
                Map.Entry<String, String> e = list.get(0);
                arr[j] = e.getKey();
                arr[j + 1] = e.getValue();
            }
            jedisCluster.mset(arr);
        }
    }

    @AfterEach
    void tearDown() {
        if (jedisCluster != null) {
            jedisCluster.close();
        }
    }
}

3. Spring集群环境下批处理代码

 @Test
    void testMSetInCluster() {
        Map<String, String> map = new HashMap<>(3);
        map.put("name", "Rose");
        map.put("age", "21");
        map.put("sex", "Female");
        stringRedisTemplate.opsForValue().multiSet(map);


        List<String> strings = stringRedisTemplate.opsForValue().multiGet(Arrays.asList("name", "age", "sex"));
        strings.forEach(System.out::println);

    }

原理分析

在RedisAdvancedClusterAsyncCommandsImpl 类中

首先根据slotHash算出来一个partitioned的map,map中的key就是slot,而他的value就是对应的对应相同slot的key对应的数据

通过 RedisFuture mset = super.mset(op);进行异步的消息发送

@Override
public RedisFuture<String> mset(Map<K, V> map) {

    Map<Integer, List<K>> partitioned = SlotHash.partition(codec, map.keySet());

    if (partitioned.size() < 2) {
        return super.mset(map);
    }

    Map<Integer, RedisFuture<String>> executions = new HashMap<>();

    for (Map.Entry<Integer, List<K>> entry : partitioned.entrySet()) {

        Map<K, V> op = new HashMap<>();
        entry.getValue().forEach(k -> op.put(k, map.get(k)));

        RedisFuture<String> mset = super.mset(op);
        executions.put(entry.getKey(), mset);
    }

    return MultiNodeExecution.firstOfAsync(executions);
}

五、Redis 批处理优化可能出现的问题

虽然 Redis 的批处理优化可以提高数据库操作的效率,但是在实施这些优化时,也可能会出现一些问题。以下是可能出现的问题:

  • 内存占用过大:使用 Pipeline 和多线程技术时,可能会占用大量的内存,导致 Redis 性能下降。应该合理控制批处理的大小,避免占用过多内存。
  • 网络延迟:使用 Pipeline 和多线程技术时,可能会增加网络延迟。应该合理控制批处理的大小,避免增加网络延迟。
  • 线程安全问题:使用多线程技术时,可能会出现线程安全问题。应该使用线程安全的操作方式,保证数据的安全性。
  • 数据丢失:使用批量插入时,可能会造成数据丢失。应该确保数据的安全性,避免数据丢失。
  • 性能波动:使用 Redis Cluster 时,可能会造成性能波动。应该合理配置 Redis Cluster,保证性能的稳定性。

综上所述,实施 Redis 的批处理优化时,应该注意内存占用、网络延迟、线程安全、数据丢失和性能波动等问题,合理控制批处理的大小,保证数据的安全性和性能的稳定性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文看完Vue3的渲染过程

Vue3官网中有下面这样一张图&#xff0c;基本展现出了Vue3的渲染原理&#xff1a; 本文会从源码角度来草率的看一下Vue3的运行全流程&#xff0c;旨在加深对上图的理解&#xff0c;从下面这个很简单的使用示例开始&#xff1a; import { createApp, ref } from "vue"…

Python3 列表与元组 | 菜鸟教程(六)

目录 一、Python3 列表 &#xff08;一&#xff09;简介相关 1、序列是 Python 中最基本的数据结构。 2、序列中的每个值都有对应的位置值&#xff0c;称之为索引&#xff0c;第一个索引是 0&#xff0c;第二个索引是 1&#xff0c;依此类推。 3、Python 有 6 个序列的内置…

Qt编写手机版本视频播放器和Onvif工具(可云台和录像)

一、前言 用Qtffmpeg写播放器很多人有疑问&#xff0c;为何不用Qt自己的多媒体框架来写&#xff0c;最重要的原因是Qt自带的目前都依赖具体的本地解码器&#xff0c;如果解码器不支持&#xff0c;那就是歇菜的&#xff0c;最多支持个MP4格式&#xff0c;而且在手机上也都是支持…

有效的括号

数据结构与算法应用往往隐藏在我们看不到的地方 20. 有效的括号 力扣题目链接 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串&#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括…

【Python 随练】打印楼梯与笑脸

题目&#xff1a; 打印楼梯&#xff0c;并在楼梯上方打印两个笑脸 简介&#xff1a; 在本篇博客中&#xff0c;我们将使用 Python 代码打印一个楼梯&#xff0c;并在楼梯上方打印两个笑脸。我们将给出问题的解析&#xff0c;并提供一个完整的代码示例来实现这个效果。 问题…

多目标优化算法:多目标浣熊优化算法(multi-objective Coati Optimization Algorithm,MOCOA)

一、浣熊优化算法COA 浣熊优化算法&#xff08;Coati Optimization Algorithm&#xff0c;COA&#xff09;由Dehghani Mohammad等人于2022年提出的模拟浣熊狩猎行为的优化算法&#xff0c;该算法具有进化能力强&#xff0c;收敛速度快&#xff0c;收敛精度高等特点。 COA具体…

【算法与数据结构】454、LeetCode 四数相加 II

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;这道题不仅要计算元素的和为0&#xff0c;还要计算元素和为零出现的次数&#xff0c;说明这道题map比较…

【Python 随练】输出国际象棋棋盘

题目&#xff1a; 输出国际象棋棋盘 简介&#xff1a; 在本篇博客中&#xff0c;我们将使用 Python 代码输出国际象棋棋盘。国际象棋棋盘是一个8x8的方格矩阵&#xff0c;交替使用黑色和白色方格。我们将给出问题的解析&#xff0c;并提供一个完整的代码示例来生成这个棋盘。…

C语言之指针详解(1)

目录 本章重点 1. 字符指针 2. 数组指针 3. 指针数组 4. 数组传参和指针传参 5. 函数指针 6. 函数指针数组 7. 指向函数指针数组的指针 8. 回调函数 9. 指针和数组面试题的解析 大家对比前面C语言之指针初阶来看&#xff0c;指针进阶明显看起来难度大了很多&#xff…

TLS协议详解,一文带你了解TLS协议

前言 TLS&#xff08;Transport Layer Security&#xff09;是一种安全协议&#xff0c;用于保护网络通信的安全性和隐私性。它是SSL&#xff08;Secure Sockets Layer&#xff09;的后继者&#xff0c;用于在互联网上建立安全的通信连接。本文将介绍TLS的概论、工作原理、发展…

计算机网络——物理层-数据通信的基础知识

物理层的基本概念 在计算机网络中&#xff0c;物理层是网络协议栈中的第一层&#xff0c;负责处理网络中传输数据的物理介质和信号传输的细节。它定义了传输数据的电气、光学和机械特性&#xff0c;以及物理连接的规范和接口标准。 物理层的主要任务是将比特流&#xff08;0和…

C++之AVL树

目录 一.介绍二.简单实现AVL树1. 基本框架2. 插入结点(Insert)a. 更新平衡因子b. 左单旋c. 右单旋d. 左右双旋e. 右左双旋 3. 删除节点(Erase)a. 更新平衡因子b. 旋转c. 代码 4. 测试 一.介绍 作为对二叉搜索树的优化版本。AVL树是由俄罗斯的两位数学家G.M.Adelson-Velskii和E.…

Python 3 基本语法与基本数据类型 | 菜鸟教程(二)

目录 一、Python3 基础语法 &#xff08;一&#xff09;编码 &#xff08;二&#xff09;标识符 &#xff08;三&#xff09;python保留字 &#xff08;四&#xff09;注释 ​&#xff08;五&#xff09;行与缩进 &#xff08;六&#xff09;多行语句 &#xff08;七&am…

操作系统复习笔记3

1、条件变量和互斥锁 条件变量一般和互斥锁一起使用&#xff0c;来弥补互斥锁的不足。总得来说&#xff0c;互斥锁用来规范线程对共享数据的竞争使用&#xff0c;条件变量用来协调各个线程合作完成任务。 2、enum枚举类型 enum typeName { valueName1, valueName2, valueName3…

Workerman在线客服系统源码 附搭建文档

Workerman在线客服系统源码 模块化开发 强大的一键生成功能极速简化你的开发流程&#xff0c;加快你的项目开发 响应式布局 自动适配&#xff0c;无需要担心兼容性问题 完善的权限管理 自由分配子级权限、一个管理员司同时属于多个组别 通用的会员和API模块 共用同一账…

【MySQL多表查询】:让你的数据检索更高效

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL中多表查询相关知识的讲解 目录 前言一、多表关系二、多表查询1、交叉连接2、内连接3、外连接 三、集合运算四、七种JOINS实现五、多表查询练习六、总结 一、多表关系 ✨项目开发中&#xff0c;在进行数据库表结…

6月人工智能论文推荐

Prompt Space Optimizing Few-shot Reasoning Success with Large Language Models https://arxiv.org/abs/2306.03799 Prompt engineering 是通过提供明确和具体的指令来增强大型语言模型(llm)能力的基本技术。它使LLM能够在各种任务中脱颖而出&#xff0c;例如算术推理、问…

列表、表格、表单

day02&#xff1a;列表、表格、表单 目标&#xff1a;掌握嵌套关系标签的写法&#xff0c;使用列表标签布局网页 01-列表 作用&#xff1a;布局内容排列整齐的区域。 列表分类&#xff1a;无序列表、有序列表、定义列表。 无序列表 作用&#xff1a;布局排列整齐的不需要规…

Bean 的生命周期

观前提示:本篇博客演示使用的 IDEA 版本为2021.3.3版本,使用的是Java8(又名jdk1.8) 电脑使用的操作系统版本为 Windows 10 目录 Spring 的执行流程 1. 启动容器 2. 根据配置完成 Bean 的初始化 3. 注册 Bean 对象到容器中 4. 装配 Bean 的属性 Bean 的生命周期 Bean 的…

掌握Python的X篇_2_Python的安装

掌握Python的X篇_2_Python的安装 1. 软件安装包下载1.1 Python版本的区别1.2 同一版本不同安装包的区别 2. 安装过程3. 验证是否安装成功 1. 软件安装包下载 下载地址&#xff1a;https://www.python.org/downloads/ 可以看到最新版本和历史版本 1.1 Python版本的区别 就像…