如何避免内存不足
介绍
这周,我和我的团队遇到了我在大学时第一次读到的一个问题。 自十月的星期三以来,我完全忘记了它:通过HTTP传输非常大的文件。
需求与设计
我们的客户将其CRM替换为一个云CRM,然后我们将其与整个软件地图集成在一起。
一个集成流程将文档从本地存储发布到CRM,并将其与存储的客户账户关联; 文件大小没有上限,我们假设1 GB为中值。
所有CRM集成均基于REST,不允许共享文件夹,暂存数据库,仅允许受保护的REST API OAUTH1。
我在下图中为您绘制了一个简化的建筑模型。
> IMG 1 — Solution Architecture
我们的应用程序与应用程序映射的其他部分一样,在CRM托管在云租户上的前提下在前提环境中运行。
公开的API接受包含两部分的Multipart主体:一个包含包含元数据(如文件名,客户帐户ID等)的JSON文档,另一个则包含文件的二进制内容。
标准解决方案
该应用程序由两部分组成:第一部分是文件轮询器,该轮询器每次在临时文件夹中看到一个新文件时都会创建一个线程,而第二部分将其与客户账户相关联并发送给CRM。
如果您有兴趣创建文件轮询器,可以将Apache Camel Polling Consumer链接到您,这是轻松实现的绝佳解决方案。
在此,我有兴趣与您讨论我们将文件发送到CRM的方式。
让我们开始编码; 这是pom.xml的摘录:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId></dependency>
这里我们的标准解决方案代码是
RestTemplate remoteService = new RestTemplate();//HTTP has two parts: Header and Body//Here is the header:HttpHeader header = new HttpHeader();//Here is the bodyMultiValueMap<String,Object> bodyMap = new MultiValueMap<String,Object>();bodyMap.add(“customer_file”,new FileSystemResource(fileName));bodyMap.add(“customer_name”, customerJSON);HttpEntity<MultiValueMap> request = new HttpEntity(bodyMap, header);ResponseEntity<String> restResponse = remoteService .exchange(remoteServiceURL, HttpMethod.POST, request, String);
customerJson变量是javax.json.JsonObject; 这样,多部分请求就可以自主选择正确的内容类型,并且在使用
org.springframework.core.io.FileSystemResource实例时,可以期待相同的行为。
我们做了这些测试:
· 发送一个小文件以查找一些格式错误的请求
· 发送一个巨大的文件证明我们的应用程序健壮性
java.lang.OutOfMemoryError: Java heap space
出现此问题的原因不仅是因为我们在开发环境中运行了代码,还因为该应用程序试图将整个文件内容加载到内存中,从而使它比J. Wellington Wimpy占用更多的内存。
很清楚,分析应用程序的内存占用量,简单性和简单性。
回顾一下,从架构的角度来看,这也不是一个很好的解决方案,因为:
· 我们无法假设传入文件的最大大小
· 我们无法按顺序处理文件
我们需要改进它。
分块解决方案
我们需要的是修改我们的代码,而不是将整个文件内容加载到内存中,而是使用HTTP1.1支持的功能,直到我上大学的时候:分块传输编码。
此功能告诉服务器传入请求是由多个HTTP消息组成的,它需要接收所有HTTP消息才能开始处理。
从客户端的角度来看,优点在于您仅将当前正在传输的切片加载到内存中。
如果您想进一步了解HTTP如何实现分块传输编码,请遵循WIKI,W3C。
我们改进了正确配置RestTemplate的类:
RestTemplate remoteService = new RestTemplate();SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();requestFactory.setBufferRequestBody(false); remoteService.setRequestFactory(requestFactory);
我们重复了测试2,这次运行顺利。
对于1.5 GB文件的完整传输,我们发现内存占用不足300 MB! 成功!
结论
我想补充一点,此功能仅与HTTP1.1一起提供,并且HTTP 2不再支持分块传输编码。 我认为您必须寻找某种流API。
在这里,我们选择使用众所周知的RestTemplate类,而不是较新的WebClient:我无法告诉您是否可以适应它。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!