当前位置: 首页 > news >正文

Android调用springboot接口上传大字段,偶现接口超时的优化

介绍

最近有个功能,Android通过okhttp上传实体类,实体类包含一个大字段,上传的字符串长度达到300k,偶现接口超时的情况,大概100次有5次,看日志发现数据并没有到达接口,可能在网络传输中就超时了

优化1

​​直接接收对象:

    @ApiOperation(value = "插入主动测量记录")@PostMapping("/insertMeasureRecord")public Result insertMeasureRecord(@RequestBody LomeRingMeasureRecords lomeRingMeasureRecords ){return iRingAppService.insertMeasureRecord(lomeRingMeasureRecords);}

刚开始是这样接收的,后来优化成流式解析
流式解析:

  @Overridepublic Result insertMeasureRecordRequest(HttpServletRequest request) {// 1. 使用流式读取避免内存爆炸try (InputStream is = request.getInputStream();Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {// 2. 带超时的JSON解析CompletableFuture<LomeRingMeasureRecords> future = CompletableFuture.supplyAsync(() -> {return JSON.parseObject(reader, LomeRingMeasureRecords.class);});LomeRingMeasureRecords records = future.get(15, TimeUnit.SECONDS); // 15秒超时// 3. 继续原有处理逻辑return Result.ok(lomeRingMeasureRecordsService.insertMeasureRecords(records));} catch (TimeoutException e) {return Result.fail(ResultsCode.REQUEST_FAILED);} catch (Exception e) {return Result.fail(ResultsCode.REQUEST_FAILED);}}

优化2

即使修改成这样,还是偶现超时,所以对大字段进行了压缩,然后服务端进行解压
Android部分

public static Observable<JsonResult> insertMeasureRecord(String mac,String value,String testType,String filePath,String fileName,String label,String waveForm) {Log.i("waveForm size",waveForm.length()+"");String compressString="";try {compressString = DataCovertUtils.compressString(waveForm);} catch (IOException e) {e.printStackTrace();}Log.i("compressString size",compressString.length()+"");Map<String, Object> map = new HashMap<>();map.put("mac", mac);map.put("value", value);map.put("testType", testType);map.put("filePath", filePath);map.put("fileName", fileName);map.put("label", label);map.put("compressedWaveForm", compressString);return ServerAPIClient.getApi().insertMeasureRecord(map).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidScheduler.mainThread());}
  // GZIP压缩字符串public  static String compressString(String str) throws IOException {if (str == null || str.isEmpty()) {return str;}ByteArrayOutputStream out = new ByteArrayOutputStream();try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {gzip.write(str.getBytes(StandardCharsets.UTF_8));}return Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP);}

然后springboot解压

 @Overridepublic Result insertMeasureRecordRequest(HttpServletRequest request) {try (InputStream is = request.getInputStream();Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {LomeRingMeasureRecords records = JSON.parseObject(reader, LomeRingMeasureRecords.class);// 解压处理if (records.getCompressedWaveForm() != null ) {try {String decompressed = decompressString(records.getCompressedWaveForm());records.setWaveForm(decompressed);} catch (IOException e) {// 解压失败记录日志,保持原数据log.error("解压waveForm失败", e);return Result.fail(ResultsCode.REQUEST_FAILED);}}return Result.ok(lomeRingMeasureRecordsService.insertMeasureRecords(records));} catch (Exception e) {log.error("处理测量记录请求失败", e);return Result.fail(ResultsCode.REQUEST_FAILED);}}// GZIP解压字符串private String decompressString(String compressedStr) throws IOException {if (compressedStr == null || compressedStr.isEmpty()) {return compressedStr;}byte[] compressed = Base64.getDecoder().decode(compressedStr);ByteArrayInputStream bis = new ByteArrayInputStream(compressed);GZIPInputStream gis = new GZIPInputStream(bis);// 使用ByteArrayOutputStream直接读取所有字节ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = gis.read(buffer)) > 0) {baos.write(buffer, 0, len);}// 转换为字符串,保留原始换行符String result = baos.toString(StandardCharsets.UTF_8.name());gis.close();bis.close();baos.close();return result;}

这样原字段大概是300k,然后压缩完是55k,之前接口返回是3s,修改完是2.5s,因为涉及到python计算,没办法再提高了

疑问

按理说,字段没超过1M,不应该出现超时的情况,根本没有到达接口,可能还是网络问题

场景化建议

  1. ​​推荐使用​直接接收对象的场景​​
    字段大小​​<1MB​​且结构固定(如普通表单数据)。
    需要快速开发,减少手动编码量。
    服务端内存充足(如JVM堆内存>2GB)。
  2. ​​推荐使用流式解析的场景​​
    字段大小​​>10MB​​或包含压缩数据(如音频、视频、科学计算数据)。
    需要支持​​分片传输​​或​​断点续传​​(如大文件上传)。
    服务端内存受限(如云函数环境)

性能关键点分析

  1. ​​数据解析性能​​
    ​​方式1(对象绑定)​​:
    依赖Jackson或Gson等库的优化解析,实测1MB JSON数据解析耗时约​​5-20ms​​(Spring Boot默认配置)。
    ​​瓶颈​​:大字段(如10MB+)可能导致堆内存压力和GC停顿。
    ​​方式2(流式解析)​​:
    使用BufferedReader逐行读取或JSONReader流式解析,1MB数据耗时约​​2-8ms​​(分块处理减少内存峰值)。
    ​​优势​​:避免一次性加载大对象到内存,适合处理​​压缩数据流​​或​​分片传输​​场景。
  2. ​​内存与GC影响​​
    ​​方式1​​:
    若字段包含大文本或二进制数据(如压缩后的波形数据),可能直接导致​​堆内存溢出​​(默认堆大小约128MB-1GB)。
    ​​示例​​:10MB的compressedWaveForm字段会占用约​​20MB堆内存​​(含对象头和引用)。
    ​​方式2​​:
    流式处理可将内存占用控制在​​常量级别​​(如分块读取时每次仅加载64KB),适合处理​​超大数据流​​(如50MB+的压缩文件)
  3. ​​压缩数据处理​​
    ​​方式1的缺陷​​:
    若字段已压缩(如GZIP),直接反序列化会触发​​二次解压​​(框架先解压到临时文件或内存,再解析JSON),导致​​CPU和内存双重消耗​​。
    ​​实测​​:10MB GZIP数据全量解压后约25MB,解析耗时增加50%。
    ​​方式2的优化​​:
    可在流式解析中​​直接处理压缩流​​(如GZIPInputStream),避免中间数据存储,节省50%以上CPU和内存。

性能测试数据参考

在这里插入图片描述

http://www.xdnf.cn/news/78355.html

相关文章:

  • Java容灾架构设计
  • [目标检测] Yolov5模型
  • 开启报名!火山引擎 x PICO-全国大学生物联网设计竞赛赛题发布
  • PDF转换Word深度评测 - ComPDFKit Conversion SDK V3.0
  • DOCA介绍
  • Power BI企业运营分析——数据大屏搭建思路
  • 护眼-科学使用显示器
  • Idea创建项目的搭建方式
  • java面向对象12:static关键字
  • 本地部署DeepSeek大模型
  • Redis—RDB日志持久化
  • 【Spring】单例模式的创建方式(Bean解析)
  • 25.4.22华为--算法真题整理(2025年4月22日)
  • AOSP Android14 Launcher3——RectFSpringAnim窗口动画类详解
  • 前端框架的“快闪“时代:我们该如何应对技术迭代的洪流?
  • strings.ToUpperSpecial 使用详解
  • 屏幕适配常见BUG与兼容性问题
  • 【JavaEE】-- MyBatis操作数据库(1)
  • 从零开始学习MySQL的系统学习大纲
  • vue3新增特性
  • Quantum Algorithms for Compositional Natural Language Processing论文阅读
  • 55、Spring Boot 详细讲义(十一 项目实战)springboot应用的登录功能和权限认证
  • 使用Java对接StockTV全球金融数据API。马来西亚金融数据API
  • FramePack V2版 - 支持首尾帧生成,支持LoRA,支持批量,支持50系显卡,一个强大的AI视频生成软件 本地一键整合包下载
  • Unitest和pytest使用方法
  • 3.1 WPF使用MaterialDesign的介绍1
  • STL常用算法——C++
  • WPF特性分析
  • Java面向对象的三大特性
  • CAD在线查看免费,可以支持DWG/GLB/GLTF/doc/wps/pdf/psd/eml/zip, rar/MP3/MP4/svg/OBJ/FBX格式