前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何处理大量数据批量写入redis问题?批处理该如何优化?

如何处理大量数据批量写入redis问题?批处理该如何优化?

原创
作者头像
小草飞上天
发布于 2025-01-15 05:54:10
发布于 2025-01-15 05:54:10
4440
举报
文章被收录于专栏:java学习java学习

前言

在我们的业务中,会存在一些数据迁入的问题,在迁入时,原业务的数据的核心数据都是基于redis存储的,所以需要将批量的核心数据批处理到redis中。那如何来批量操作呢?

如果我们使用set方法一条一条的写入会有什么问题呢?

如果不使用set的话我们应该如何来处理呢?

基于以上的一些问题,我们有了今天的这篇文章 。

首先可以明确一点,对于批量写入redis的操作,肯定是不能直接用set这种单一命令来写入的,批量的连接和网络传输对于redis来说性能损耗是非常巨大的。

使用类似set这样单个命令的执行流程

单个命令的执行

前言中已经说了使用set肯定是不行的。那我们来具体分析一下为什么不行。

对于单个命令来说,执行一个set命令总的分为3步:

第一步:客户端建立连接并向服务端发送 set name zhangsan 这样一条命令

第二步:服务端解析并执行命令。

第三步:服务端返回执行结果给客户端.

N条命令的执行

上面是执行一条命令。那如果执行N条命令呢。那就是将单条命令重复N次。

总结一个结果就是:N次的发送命令和返回结果(通过网络传输) 、N次的内存执行命令。

我们应该也清楚,网络传输可以说是redis性能的瓶颈所在,所以通过N条这样重复的命令并发请求redis时,可能会导致redis出现异常阻塞。导致其他正常业务命令执行也阻塞。

最理想的N条命令的执行

其实我们最理想的方案应该是,我们一次批量发送多条命令,redis批量执行多条命令后,直接返回多个接口。

我们用生活中一个例子解释一下:

比如我们割麦子,如果我们一根麦子一根麦子的割,这样是不是会耗费大量的人力,大家都去割麦子了导致棉花都没人收了。

那如果我们用收割机一排一排的割,是不是就减少了人力的投入,其他人该干啥干啥,互不耽误。

详细说说N次命令往返执行所消耗的资源

上面我们大概介绍了命令往返的流程分为3步。接下来我们具体说一下这三步为什么说在N次频繁处理时会出现性能瓶颈问题。

对于发送命令、返回结果这样的一个操作,它的一次数据包往返于两端的时间我们称作Round Trip Time(简称RTT)。

那对于N次命令来说,则会有N次的RTT,同时redis需要调用N次的read()和write()这两个系统方法将数据从用户态转移到内核态。然后再N次调用系统来发送服务端到客户端的响应网络请求。

以上就是N次命令大概所消耗的资源了。

实现批处理的方案

原生方法

我们可以通过原生的方法来进行批量的写入,redis自身就提供了很多这种方法,比如mset 、hmset等。

代码语言:txt
AI代码解释
复制
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void mset100kData() {
        Map<String, String> dataMap = new HashMap<>(100000);
        for (int i = 0; i < 100000; i++) {
            dataMap.put("key" + i, "value" + i);
        }
        stringRedisTemplate.opsForValue().multiSet(dataMap);
    }
}

mset、hmset这些原生方法其实是非常好用的,但有一个缺点就是:它只能处理对应的数据类型。如果我们有更复杂或者有多种混合结构的数据,那它就无法处理了。所以我们引入第二种处理方式:pipeline 。

pipeline

pipeline是批处理命令变种优化措施,它非常类似于redis的mset 。

命令行执行

我们通过命令行操作的话非常简单,将需要执行的内容写好,一次性执行。

可以看到。我们在cmd.txt中写下了3条命令:hset k300 age 20 、lpush list 1 2 3 4 5 另外一条就不在这里凑字数了。

通过命令一次性就写入到了redis中

代码语言:txt
AI代码解释
复制
cat cmd.txt | redis-cli -a 111111 --pipe 

java代码执行

代码语言:txt
AI代码解释
复制
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void mset100kDataWithPipeline() {
        final Map<String, String> dataMap = new HashMap<>(100000);
        for (int i = 0; i < 100000; i++) {
            dataMap.put("key" + i, "value" + i);
        }

        stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Map.Entry<String, String> entry : dataMap.entrySet()) {
                connection.set(entry.getKey().getBytes(), entry.getValue().getBytes());
            }
            return null;
        });
    }
}

集群下如何解决批处理key的插槽问题?

对于MSET或Pipeline这样的批处理来说,会在一次请求中携带多条命令。

对于Redis集群,必须保证批处理命令的多个key落在一个插槽中,否则就会导致执行失败

但是直接通过MSET这种方式的执行,多个key通过hash计算出来的值肯定不会是一个插槽区间。所以应该如何解决这个问题呢?

我们有四种解决方案,但是这四种方案都有缺点。没有最完美的方案,只有最适合的方案。

第一种:串行执行(最简单的方案)

串行执行其实就是我们说的循环单次执行,每次执行1条,这样计算的key肯定是没有问题的。

缺点:性能差,时间久

第二种:串行批量执行(稍复杂,但时间较短)

串行批量执行的原理非常简单:通过调用redis的hash计算函数,将原数据进行批量的分组,将同一hash结果的分为同一组进行批量执行。这样也可以确保所有的key都是在同一个插槽。

缺点:执行时间和性能取决于分组的数量,分组数量多了性能就越差。

第三种:并行批量执行(复杂,时间最短)

并行批量执行的原理与串行批量执行类似:通过调用redis的hash计算函数,将原数据分组后,并发的执行

缺点:代码处理稍复杂,出问题了不好寻找。

第四种:使用hash_tag(方案简单且时间较短)

简单的说一下hash_tag,就是对key取一个相同的前缀,通过{}将前缀值包裹起来,redis计算哈希时就会通过包裹的数据来进行计算。这样的话可以确保数据在同一个插槽。

代码语言:txt
AI代码解释
复制
{user:init}:id:1 
{user:init}:id:2
{user:init}:id:3  
user:init 就是hash_tag。redis会对这3个key都通过user:init来计算插槽

缺点:这一批数据都在同一个插槽,会出现数据倾斜。

总结

我们介绍了批量写入redis的多种方案以及通过循环单次执行的问题所在。

批量写入对于业务来说并不是特别常见,但终究会是有的。希望大家能通过本文的学习了解一下redis的一些方面。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
【Kotlin】Kotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )
默认参数值 : Kotlin 语言 中的 函数参数 , 可以 在定义时 指定参数默认值 ;
韩曙亮
2023/03/30
2.4K0
【Kotlin】Kotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )
【Kotlin】Kotlin 与 Java 互操作 ① ( 变量可空性 | Kotlin 类型映射 | Kotlin 访问私有属性 | Java 调用 Kotlin 函数 )
在 Java 语言 中 , 任何 引用类型变量 都可以为 空 null ; Java 中 八种 基本数据类型 变量 的 默认值 为 0 或 false ;
韩曙亮
2023/03/30
1.6K0
【Kotlin】Kotlin 与 Java 互操作 ① ( 变量可空性 | Kotlin 类型映射 | Kotlin 访问私有属性 | Java 调用 Kotlin 函数 )
Android面试题之Kotlin和Java之间互操作
AntDream
2024/06/13
980
Android面试题之Kotlin和Java之间互操作
你知道吗,Java中的受查和非受查异常,其实并不存在区别……
博主在文章中提到Java中的受查异常和非受查异常之间的区别在JVM的世界中实际上并不存在。传统理念认为继承自java.lang.RuntimeException的异常是非受查异常,其他异常则是受查异常。然而,通过比较JVM字节码层面的代码表示,发现无论是受查异常还是非受查异常,在JVM字节码中并没有实质差别。此外,文章也讨论了Kotlin语言对于受查异常的规避,通过Kotlin编写的类似Java代码可以正常运行且不需要显式捕获异常。最后,文章提到Java中的受查异常机制存在争议,而Kotlin作为一种新的JVM语言,避免了这一问题。
HikariLan贺兰星辰
2024/04/04
1370
【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )
在 Java 语言 编写的程序中 , 出现最多的崩溃就是 NullPointerException 空指针异常 ,
韩曙亮
2023/03/30
1.9K0
【Kotlin】空安全总结 ( 变量可空性 | 手动空安全管理 | 空安全调用操作符 | 非空断言操作符 | 空合并操作符 | 空指针异常处理 | 先决条件函数判空 )
kotlin--与Java互操作
我们可以直接使用 "= " 对Java属性进行赋值了,对于受保护的属性,Java类需要实现对应的get、set方法 Java:
aruba
2021/12/06
5300
Kotlin与Java互操作
互操作就是在Kotlin中可以调用其他编程语言的接口,只要它们开放了接口,Kotlin就可以调用其成员属性和成员方法,这是其他编程语言所无法比拟的。同时,在进行Java编程时也可以调用Kotlin中的API接口。
xiangzhihong
2022/11/30
3.6K0
《JavaSE-第十三章》之异常体系
现实生活中当人的出现了一些毛病,可能就会出现发烧,感冒之类的问题。而我们写的程序也是如此,程序在执行的过程中难免出现一些奇奇怪怪的问题。因此,在java中这些在程序运行中发生的不正常的行为被称为异常。
用户10517932
2023/10/07
1850
《JavaSE-第十三章》之异常体系
【Kotlin】函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )
匿名函数 就是 Lambda 表达式 , 同时也是 闭包 , 三者的是相同的概念 ;
韩曙亮
2023/03/30
1.1K0
【Kotlin】函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )
《Kotin 极简教程》第10章 Kotlin与Java互操作
在前面的章节中,我们已经学习了Kotlin的基础语法、类型系统、泛型与集合类、面向对象与函数式编程等主题,在上一章中我们还看到了Kotlin提供的轻量级并发编程模型:协程的相关内容。
一个会写诗的程序员
2018/08/17
2K0
第33节:Java面向对象中的异常
Exception:RuntimeException为空指针异常,数组下标越界异常,算数异常,类型转换异常等,IO异常(IOException),SQL异常(SQLException)。
达达前端
2019/07/03
6110
第33节:Java面向对象中的异常
Java面向对象中的异常
Exception:RuntimeException为空指针异常,数组下标越界异常,算数异常,类型转换异常等,IO异常(IOException),SQL异常(SQLException)。
达达前端
2022/04/29
6800
Java面向对象中的异常
kotlin基础--匿名函数、闭包
如果我们要在Java方法中传入一个回调函数,需要定义一个接口,并使用new关键字实例化匿名类实现该方法:
aruba
2021/12/06
7160
Java异常详解
异常类Throwable有两个子类:Error(不能处理的错误)和Exception(可处理的异常),我们平常所说的异常指的是Exception;
訾博ZiBo
2025/01/06
1530
Java异常详解
kotlin基础--字符串操作、数字类型、标准库函数
substring函数支持IntRange类型参数,使用until创建的范围遵循左闭右开
aruba
2021/12/06
4890
kotlin基础--字符串操作、数字类型、标准库函数
throws 与 throw
/* * 有些时候,我们是可以对异常进行处理的,但是又有些时候,我们根本就没有权限去处理某个异常。 * 或者说,我处理不了,我就不处理了。 * 为了解决出错问题,Java针对这种情况,就提供了另一种处理方案:抛出。 * * 格式: *    throws 异常类名 *    注意:这个格式必须跟在方法的括号后面。 * * 注意: *    尽量不要在main方法上抛出异常(因为这样就没有意义了)。 *    但是我讲课为了方便我就这样做了(将来实际开发中是弹出页面)。 * * 小结: *    编译期异常的抛出,将来调用者必须处理。 *    运行期异常的抛出,将来调用可以不用处理,也可以处理(像处理编译期异常那样处理)。。 */
黑泽君
2018/10/11
8350
Kotlin实战【五】Kotlin中的异常
Kotlin中的异常处理与Java或者其他语言中的处理方式相似。一个函数可以以正常方式结束,或者当错误发生的时候抛出异常。函数调用者捕获这个异常并处理它;如果没有,异常重新在调用栈向上抛。
先知先觉
2019/03/04
2.2K0
kotlin基础--null安全、异常、先决条件
使用Java时,我们需要大量的判断一个变量是否为null,否则使用是会抛出NullPointer异常。 而kotlin使用null给一个变量赋值时,在编译时,就会报错,来防止发生这种异常
aruba
2021/12/06
5640
kotlin基础--null安全、异常、先决条件
【Java】异常处理:从基础到进阶
在编程中,异常(Exception)是指程序在运行过程中程序的错误或者意外情况,它会导致程序的控制流发生改变。通常,异常发生时程序会停止正常执行,直到找到能够处理该异常的代码或者终止程序的执行。
Yui_
2025/01/27
1860
From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
Kotlin 出自于捷克一家软件研发公司 JetBrains ,这家公司开发出很多优秀的 IDE,如 IntelliJ IDEA、DataGrip 等都是它的杰作,包括 Google 官方的 Android IDE -- Android Studio ,也是 IntelliJ IDEA 的插件版。
Seachal
2023/05/20
1.3K0
From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
推荐阅读
相关推荐
【Kotlin】Kotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 前言
  • 使用类似set这样单个命令的执行流程
    • 单个命令的执行
    • N条命令的执行
    • 最理想的N条命令的执行
  • 详细说说N次命令往返执行所消耗的资源
  • 实现批处理的方案
    • 原生方法
    • pipeline
      • 命令行执行
      • java代码执行
  • 集群下如何解决批处理key的插槽问题?
    • 第一种:串行执行(最简单的方案)
    • 第二种:串行批量执行(稍复杂,但时间较短)
    • 第三种:并行批量执行(复杂,时间最短)
    • 第四种:使用hash_tag(方案简单且时间较短)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档