当前位置:首页 > 技术文章 > 正文内容

Spring Boot 请求处理超大JSON数据 3种 方式性能对比

zonemu2个月前 (08-24)技术文章23

1. 简介

在 Web 项目开发中,超大 JSON 数据请求的情况愈发常见,比如物联网设备批量上传海量监测数据、大数据分析系统接收大规模用户行为信息等场景。

超大 JSON 数据不仅数据量庞大,可能达到几十兆甚至上百兆,而且内部结构复杂,嵌套层级深、字段繁多。若处理不当,会引发诸多问题,如内存溢出,导致应用崩溃;数据解析耗时过长,降低系统响应速度,影响用户体验;还可能因数据传输不稳定,造成数据丢失或损坏。

因此,如何让 Spring Boot 应用高效、稳定地处理超大 JSON 数据请求,成为保障系统性能与可靠性的关键。

运用三种不同的方法,对处理超大 JSON 数据的性能展开对比分析。

2.实战案例

准备数据

public record User(Long id, String name, Integer age, String sex, String email, String address) {
}

准备了50W条json数据

接下来的测试都是基于此数据进行测试。

2.1 普通处理方式

@PostMapping("/normal")
public ResponseEntity<String> normal(@RequestBody List<User> datas) {
  datas.forEach(this::processItem) ;
  try {
    TimeUnit.MILLISECONDS.sleep(2000) ;
  } catch (InterruptedException e) {}
  return ResponseEntity.ok("normal success") ;
}
private void processItem(User item) {
  // todo
}

这是一个普通 POST 请求处理方法。使用 @RequestBody 接收客户端传来的 List<User> 类型 JSON 数据。这种方式在数据量大时可能因同步处理和内存占用导致性能瓶颈。

如下,通过JConsole查看,在处理整个请求过程中内存的变化情况:

只要进入到该方法中内存就已经暴增到最高点。

2.2 使用流式解析

使用 Jackson 的 JsonParser 逐块读取数据,不加载整个 JSON 到内存。

@PostMapping("/stream")
public ResponseEntity<String> stream(InputStream inputStream) throws IOException {
  ObjectMapper mapper = new ObjectMapper();
  try (JsonParser parser = mapper.getFactory().createParser(inputStream)) {
    // 遍历 JSON 结构
    JsonToken token;
    while ((token = parser.nextToken()) != null) {
      if (token == JsonToken.START_ARRAY) {
        while (parser.nextToken() != JsonToken.END_ARRAY) {
          // 逐条处理数组中的对象
          User item = parser.readValueAs(User.class) ;
          processItem(item);
        }
      }
    }
  }
  try {
    TimeUnit.MILLISECONDS.sleep(2000) ;
  } catch (InterruptedException e) {}
  return ResponseEntity.ok("stream success");
}

通过流式处理超大 JSON 数据。利用 InputStream 读取数据,借助 JsonParser 逐个解析 JSON 令牌。当遇到数组起始时,逐条读取并处理 User 对象,避免一次性加载全部数据到内存,有效降低内存占用。

如下,通过JConsole查看,在处理整个请求过程中内存的变化情况:

整体内存变化非常的小。当我们没有进入while循环处理的时候,内存基本是没有变化的,只有再真正处理数据的时候,内存才会递增。

2.3 使用响应式WebFlux

使用 Spring WebFlux 实现非阻塞流式处理。

@PostMapping(value = "/reactive")
public Mono<String> fluxJson(@RequestBody Flux<User> items) {
  return items.doOnNext(this::processItem)
      .doOnComplete(() -> {
        System.err.println("处理完成...") ;
      })
      .then(Mono.just("flux success"))
      .delayElement(Duration.ofSeconds(2)) ;
}

访问该接口,最终内存情况如下:

与上面的InputStream处理方式一样,只要没有开始处理数据,内存基本不会变化,只有开始处理后内存才开始递增。

相关文章

2024前端面试真题之—VUE篇(前端面试题vuex)

添加图片注释,不超过 140 字(可选)1.vue的生命周期有哪些及每个生命周期做了什么? beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、com...

学习ES6- 入门Vue(大量源代码及笔记,带你起飞)

ES6学习网站: https://es6.ruanyifeng.com/箭头函数普通函数//普通函数 this 指向调用时所在的对象(可变) let fn = function fn(a, b) {...

程序员开发必会之git常用命令,git配置、拉取、提交、分支管理

整理日常开发过程中经常使用的git命令!git配置SSH刚进入项目开发中,我们首先需要配置git的config、配置SSH方式拉取代码,以后就免输入账号密码了!# 按顺序执行 git config -...

(在线编辑DWG)网页CAD二开实现焊接符号绘制

前言在工程制图和制造领域,焊接符号(Welding Symbols)是用于表示焊缝类型、尺寸、位置以及工艺要求的标准化图形语言。广泛应用于机械设计、钢结构、船舶制造、压力容器等行业中,帮助技术人员理解...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockers...

15款测试html5响应式的在线工具(测试类h5)

手机、平板灯手持设备的增多,网站要顺应变化,就必须要做响应式开发,响应式网站最大的特点在于可以在不同设备下呈现不同的布局,是基于html5+css3技术,目前越来越多的网站开始采用了响应式设计,而下面...