Java Apache Camel REST API首次FTP下载PDF耗时久,二次快,求优化
Apache Camel FTP首次下载耗时过长优化方案
问题背景
用Java+Apache Camel开发的REST API,通过请求参数拼接文件名从FTP目录下载PDF。首次调用耗时超20秒,二次及后续调用仅需5秒内,需要优化首次调用速度至5秒以内。
API示例地址:
http://server3343.erp.local:5464/api/rest/invoices/receipts/pdf?companyNumber=45&accountNumber=000333&documentType=FC&transactionNumber=BG534530SF
当前实现代码:
package ar.com.newsan.esb.route; import ar.com.company.camel.common.route.RestRouteBuilder; import ar.com.company.esb.Constants; import org.apache.camel.LoggingLevel; import org.apache.camel.model.rest.RestParamType; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.ws.rs.core.MediaType; @Component public class GetReceiptsPDFRoute extends RestRouteBuilder { @Value("${timeout.FTP}") private Long timeoutFTP; @Override public void configure() { super.configure(); rest(restApiPath) .get("/receipts/pdf?companyNumber={companyNumber}&accountNumber={accountNumber}&documentType={documentType}&transactionNumber={transactionNumber}").id("receipts PDF") .param().name("companyNumber").type(RestParamType.query).dataType("String").required(true).endParam() .param().name("accountNumber").type(RestParamType.query).dataType("String").required(true).endParam() .param().name("documentType").type(RestParamType.query).dataType("String").required(true).endParam() .param().name("transactionNumber").type(RestParamType.query).dataType("String").required(true).endParam() .consumes(MediaType.APPLICATION_JSON) .produces("application/pdf") .to("direct:receiptsPDF"); /** * set name and location to look for the PDF in FTP DIRECTORY */ from("direct:receiptsPDF").routeId("ReceiptsPDF") .setProperty("currentOperation", constant("ReceiptsPDF")) .log(LoggingLevel.INFO,"ReceiptsPDF", "[Inicia ${property.currentOperation}]") .log(LoggingLevel.INFO, "company Number: ${header.companyNumber}") .log(LoggingLevel.INFO, "account Number: ${header.accountNumber}") .log(LoggingLevel.INFO, "document Type: ${header.documentType}") .log(LoggingLevel.INFO, "transaction Number: ${header.transactionNumber}") .doTry() .choice() .when(header("documentType").isEqualTo(Constants.FILE)) .log(LoggingLevel.INFO, "ReceiptsPDF","[${property.currentOperation}] Find FILE") .setHeader("TARGET_DIR", simple("{{source.ebs.FILEs}}")) .endChoice() .otherwise() .log(LoggingLevel.INFO, "ReceiptsPDF","[${property.currentOperation}] Find FILES") .setHeader("TARGET_DIR", simple("{{source.ebs.FILEs}}")) .end() .setProperty("fileName", simple("${header.companyNumber}${header.accountNumber}${header.documentType}${header.transactionNumber}.pdf")) .to("direct:findPdfInFTP") .log(LoggingLevel.INFO,"ReceiptsPDF", "[Ending ${property.currentOperation}]") .endDoTry() .doCatch(Exception.class) .log(LoggingLevel.ERROR, "ReceiptsPDF", "[ERROR: ${exception.message}") .end(); /** * search in FTP DIRECTORY */ from("direct:findPdfInFTP").routeId("find file in ftp") .doTry() .to("direct:searchInFTP") .choice() .when(simple("${body} == null")) .log(LoggingLevel.INFO, "findPdfInFTP", "Search in processed sub-direcotries") .setHeader("TARGET_DIR", simple("${header.TARGET_DIR}/Procesados")) .to("direct:searchInFTP") .otherwise() .log(LoggingLevel.INFO,"findPdfInFTP","FILE OBTAINED: ${headers.CamelFileName}") .endDoTry() .doCatch(Exception.class) .log(LoggingLevel.ERROR, "findPdfInFTP", "[ERROR: ${exception.message}") .end(); from("direct:searchInFTP").routeId("searchInFTP") .doTry() .log(LoggingLevel.INFO, "searchInFTP","Search: {{ebs.sftp}}/${header.TARGET_DIR}/${property.fileName}") .pollEnrich().simple("{{ebs.sftp}}/${header.TARGET_DIR}?password={{ebs.sftp.password}}&fileName=${property.fileName}&disconnect=true&useList=false&ignoreFileNotFoundOrPermissionError=true&useUserKnownHostsFile=false").timeout(timeoutFTP) .endDoTry() .doCatch(Exception.class) .log(LoggingLevel.ERROR, "searchInFTP", "[ERROR: ${exception.message}") .end(); } }
核心问题分析
首次调用慢的主要原因是FTP连接的初始化开销:
- 当前代码设置了
disconnect=true,每次请求后强制断开连接,导致每次请求都要重新建立TCP连接、完成认证流程,这部分首次操作耗时极长 - 没有启用连接池,无法复用已建立的连接
- 二次调用时连接可能被系统层面缓存复用,所以速度明显提升
优化措施
1. 启用FTP连接池,复用连接
修改searchInFTP路由中的SFTP端点配置,移除disconnect=true,添加连接池相关参数:
from("direct:searchInFTP").routeId("searchInFTP") .doTry() .log(LoggingLevel.INFO, "searchInFTP","Search: {{ebs.sftp}}/${header.TARGET_DIR}/${property.fileName}") // 启用连接池,移除disconnect=true .pollEnrich().simple("{{ebs.sftp}}/${header.TARGET_DIR}?password={{ebs.sftp.password}}&fileName=${property.fileName}&useList=false&ignoreFileNotFoundOrPermissionError=true&useUserKnownHostsFile=false&poolSize=5&maxWait=1000&soTimeout=5000").timeout(timeoutFTP) .endDoTry() .doCatch(Exception.class) .log(LoggingLevel.ERROR, "searchInFTP", "[ERROR: ${exception.message}") .end();
参数说明:
poolSize=5:设置连接池大小,根据业务并发量调整maxWait=1000:获取连接的最大等待时间(毫秒),避免长时间阻塞soTimeout=5000:Socket超时时间,快速终止无响应的连接尝试
2. 预初始化FTP连接
在应用启动时提前建立连接池中的连接,避免首次请求时的连接建立开销。添加如下路由到configure方法中:
// 应用启动时初始化FTP连接池 from("timer:initFTP?repeatCount=1") .routeId("initFTPConnection") .log(LoggingLevel.INFO, "Initializing FTP connection pool") .to("{{ebs.sftp}}/{{source.ebs.FILEs}}?password={{ebs.sftp.password}}&fileName=test.pdf&useList=false&ignoreFileNotFoundOrPermissionError=true&poolSize=5") .log(LoggingLevel.INFO, "FTP connection pool initialized");
这个路由仅在应用启动时执行一次,提前触发连接建立,后续请求直接复用连接池中的连接。
3. 优化超时配置
调整timeout.FTP参数为合理值,比如5000毫秒(5秒),避免首次连接时无意义的等待:
timeout.FTP=5000
4. 可选:优化目录搜索逻辑
当前代码会先后搜索主目录和Procesados子目录,两次FTP操作会增加耗时。如果业务上有明确的文件存放优先级,可以调整搜索顺序,优先搜索文件更可能存在的目录,减少不必要的FTP请求。
5. 网络层面优化
- 尝试使用FTP服务器的IP地址代替域名,减少DNS解析开销
- 如果使用密码认证,可改为密钥认证,降低认证环节的耗时
- 检查防火墙、代理等网络设备是否对首次连接有额外的延迟规则
内容的提问来源于stack exchange,提问作者JustToKnow
相关产品推荐
相关产品推荐

