前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >如何处理大量数据批量写入redis问题?批处理该如何优化?

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

原创
作者头像
小草飞上天
发布2025-01-15 13:54:10
发布2025-01-15 13:54:10
3061
举报
文章被收录于专栏: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
复制
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
复制
cat cmd.txt | redis-cli -a 111111 --pipe 

java代码执行

代码语言:txt
复制
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
复制
{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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 使用类似set这样单个命令的执行流程
    • 单个命令的执行
    • N条命令的执行
    • 最理想的N条命令的执行
  • 详细说说N次命令往返执行所消耗的资源
  • 实现批处理的方案
    • 原生方法
    • pipeline
      • 命令行执行
      • java代码执行
  • 集群下如何解决批处理key的插槽问题?
    • 第一种:串行执行(最简单的方案)
    • 第二种:串行批量执行(稍复杂,但时间较短)
    • 第三种:并行批量执行(复杂,时间最短)
    • 第四种:使用hash_tag(方案简单且时间较短)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档