Apache Camel SFTP Consumer 如何按最后修改时间预排序?
可以将 SftpOperations.listFiles() 返回的条目按最后修改时间升序排列,再配合 maxMessagesPerPoll=10 和 eagerMaxMessagesPerPoll=true,让 Consumer 优先选取最旧的候选文件。这样能减少后续为排序创建的对象,但仍需获取完整目录列表,内存占用不会固定在 10 个文件的水平。
现有材料没有提供 Camel 支持 file:modified 内置预排序选项的官方计划或发布承诺,因此无法确认未来哪个版本会支持。
现有配置的取舍
按题述处理流程,这三个参数作用于不同阶段:
| 配置 | 作用 |
|---|---|
maxMessagesPerPoll=10 | 限制单次轮询选取的文件数量 |
sortBy=file:modified | 按最后修改时间对候选文件排序 |
eagerMaxMessagesPerPoll | 决定是否在完成候选文件收集前提前应用数量限制 |
当 eagerMaxMessagesPerPoll=false 时,Camel 可以先收集候选文件并排序,再选取前 10 个。在题述执行路径中,排序涉及的 Exchange、GenericFileMessage 等对象会随候选文件数量增加。这个范围是进入排序的候选文件,未必包含服务器上所有被过滤掉的文件。
设置为 eagerMaxMessagesPerPoll=true 后,数量限制会提前生效。如果目录列表没有预先按修改时间排序,后面的 sortBy 只能调整已选文件的顺序,无法补回修改时间更早却未被选中的文件。要优先选取最旧文件,就需要在数量限制生效前排好目录条目的顺序。
在目录列表返回后排序
可以在 super.listFiles(path) 返回后排序:
public class CustomSortedSftpOperations extends SftpOperations {
public CustomSortedSftpOperations(Proxy proxy) {
super(proxy);
}
@Override
public SftpRemoteFile[] listFiles(String path)
throws GenericFileOperationFailedException {
SftpRemoteFile[] files = super.listFiles(path);
if (files != null && files.length > 1) {
Arrays.sort(
files,
Comparator.comparingLong(SftpRemoteFile::getLastModified)
);
}
return files;
}
}
代码沿用题述 API 签名,省略了 import,能否直接编译需以实际使用的 Camel 依赖为准。
执行到 Arrays.sort() 时,SftpRemoteFile[] 和数组中的条目已经存在,因此不能称为“在创建任何文件对象之前排序”。排序发生在目录条目返回之后、Consumer 后续包装和创建 Exchange 之前。
假设一次目录列表包含 N 个条目:
- 完整目录列表占用的内存仍随
N增长。 - 排序时间复杂度为
O(N log N),对象数组排序还可能使用临时空间。 - 提前限制候选数量,可以减少后续处理大量候选文件时的对象分配。
这项优化不能保证“没有额外内存开销”,也不会让目录列表“只保留 10 个目录条目”。
题述的 preSortComparator 位于 GenericFile 层,与直接排序 SftpRemoteFile[] 的位置不同。Java setter 的存在也不足以证明某种 URI 写法受支持。能否通过参数绑定或注册表引用配置,需要检查目标版本的端点选项和实现。
接入自定义 Operations
按照题述扩展方式,由自定义端点创建 Operations:
public class CustomSortedSftpEndpoint extends SftpEndpoint {
public CustomSortedSftpEndpoint(
String uri,
SftpComponent component,
SftpConfiguration configuration) {
super(uri, component, configuration);
}
@Override
public RemoteFileOperations<SftpRemoteFile>
createRemoteFileOperations() {
CustomSortedSftpOperations operations =
new CustomSortedSftpOperations(getProxy());
operations.setEndpoint(this);
return operations;
}
}
再通过组件返回自定义端点:
@Component("sftp")
public class CustomSortedSftpComponent extends SftpComponent {
@Override
protected GenericFileEndpoint<SftpRemoteFile> buildFileEndpoint(
String uri,
String remaining,
Map<String, Object> parameters) throws Exception {
GenericFileEndpoint<SftpRemoteFile> stockEndpoint =
super.buildFileEndpoint(uri, remaining, parameters);
SftpEndpoint sftp = (SftpEndpoint) stockEndpoint;
return new CustomSortedSftpEndpoint(
uri,
this,
(SftpConfiguration) sftp.getConfiguration()
);
}
}
接入后,轮询配置可以使用:
maxMessagesPerPoll=10&eagerMaxMessagesPerPoll=true
如果 sortBy=file:modified 仅用于相同的升序排序,可以在验证行为后移除,避免重复排序。若还有其他排序规则,则需要确认它们与目录预排序如何配合。
部署前还要确认 Camel 实际解析到的是自定义组件。仅有 @Component("sftp"),不足以确定当前 Spring Boot 自动配置和 Camel 组件注册流程会采用这个实例。
检查端点和 Operations 的初始化
排序覆写本身只调整数组顺序,完整接入代码还替换了端点和 Operations 的创建过程,因此需要检查默认初始化行为是否保留。
组件先创建默认端点,再用它的 SftpConfiguration 创建新端点。复用配置对象并不会复制默认端点的全部状态。应对照 Camel 4.14.5 的实现,确认默认构建过程中是否还设置了其他端点属性,以及 URI 参数最终绑定到了哪个实例。
自定义 createRemoteFileOperations() 只执行了构造和 setEndpoint(),也需要与目标版本的默认方法逐项比较,检查是否遗漏其他初始化步骤。这几行代码不足以证明认证、代理及其他连接配置全部保留。
如果初始化路径一致,过滤、读取和消费后的处理仍可交给 Camel。不过,文件遍历顺序已经改变,提前生效的数量限制也会选出不同的文件。
文件顺序与稳定性
按最后修改时间排序只能决定候选顺序,无法判断文件是否写入完成。仍在上传的文件也可能排在前面。
文件稳定性需要通过目标版本支持的读取锁、文件年龄条件,或上传完成后再重命名等约定处理。还需验证以下情况:
- 前 10 个候选中有未稳定文件时,实际消费数量可能不足 10,不能假定 Camel 会继续补足。
- 递归扫描子目录时,每次
listFiles(path)的局部排序无法保证整个目录树的全局顺序。 - 修改时间相同的文件没有明确的业务先后关系。如果需要确定顺序,应增加文件名或路径等次级比较条件。
- 即使候选选取顺序正确,并发或异步处理仍可能改变完成顺序。
这一方案能保证的范围是:在指定目录列表、过滤条件和稳定性检查规则下,优先选取修改时间较早的候选文件。
升级时需要验证什么
继承实现类会依赖具体实现路径,因此存在升级风险。兼容性需要结合这些路径判断,不能只看是否标有 SPI 注解。
方法签名、构造函数或泛型发生变化,通常会导致编译失败。更隐蔽的情况是代码仍能编译,但 Camel 调整了端点初始化、目录遍历或数量限制的位置,导致覆写失效。这类变化未必会提前给出弃用警告。
升级时至少应验证:目录返回顺序打乱后,是否仍优先选取最旧文件;过滤和稳定性检查是否正常;连接配置是否完整保留;大量文件场景下的对象分配是否确实减少。验证内存时,应分别观察目录条目和 Exchange 的数量。每轮最多消费 10 个文件,并不能说明内存占用已经受控。
备注:内容仅供参考。
