1. 背景
我在用minIO sdk(版本号:8.5.17)做开发,需要遍历s3服务的对象列表,希望分页请求。
按照官方API设置了maxKeys,但是发现却返回了全部。
代码简述如下:
java
public static List<String> getObjectsByPage(String bucketName, int pageSize) throws Exception{
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.delimiter("/")
.bucket(bucketName)
.startAfter(null)
.prefix("test/a1")
.maxKeys(5)
.build());
Iterator<Result<Item>> iterator = results.iterator();
int count = 0;
while (iterator.hasNext()) {
Result<Item> next = iterator.next();
count++;
}
return pageItems;
}
结果是count是遍历了全部的值。
2. 分析
github上有人提到:
https://github.com/minio/minio/discussions/18752

再加上,看官方API的例子时,发现有这么个说法:
java
Lazy iterator...
https://docs.min.io/enterprise/aistor-object-store/developers/sdk/java/api/#listObjects

所以,猜想是每次调用hasnext时去请求数据,倘若我在这段代码前没有制止,就会在本次拉去数据结束后,自动去拉取下一批。
因此,实现思路:
-
在hasnext前,制止执行
-
制止执行的方式可采用计数(变量A),计获取到的数据,达到一定数值终止遍历
-
基于2,会出现A的数值大于当前分页的数据总数,导致执行下一次请求被触发,因此设定A为pageSize的值
-
使用日志来查看请求进行验证
3. 验证
将上边的代码进行改造,有如下代码:
java
public static List<String> getObjectsByPage(String bucketName, int pageSize) throws Exception{
List<String> pageItems = new ArrayList<>();
String nextName = null;
while (true) {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.delimiter("/")
.bucket(bucketName)
.startAfter(nextName)
.prefix("test/a1")
.maxKeys(5)
.build());
Iterator<Result<Item>> iterator = results.iterator();
int count = 0;
while (count < pageSize && iterator.hasNext()) {
Result<Item> next = iterator.next();
count++;
nextName = next.get().objectName();
}
if (count <= 0) {
break;
}
}
return pageItems;
}
其中,client创建时指定了httpClient为OKHttpClient,并给httpClient设置了interceptor。
设置client使用OKHttp
java
Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);
// 2. 配置OkHttp的Dispatcher(控制并发请求)
Dispatcher dispatcher = new Dispatcher(executorService);
dispatcher.setMaxRequests(100); // 最大并发请求数
dispatcher.setMaxRequestsPerHost(20); // 每个主机的最大并发请求数
OkHttpClient httpClient = (new OkHttpClient()).newBuilder()
.addInterceptor(new MinioOkHttpLogInterceptor("s3-qos.ttt.com"))
.dispatcher(dispatcher)
.connectTimeout(5, TimeUnit.SECONDS).writeTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).protocols(Arrays.asList(Protocol.HTTP_1_1)).build();
MinioAsyncClient.Builder builder = MinioAsyncClient.builder()
.endpoint(config.getEndpoint())
.httpClient(httpClient)
.credentials(config.getAccessKey(), config.getSecretKey())
.region(config.getRegion());
OKHttp的拦截器
java
/**
* 自定义 OkHttp 日志拦截器,仅打印 MinIO 相关请求
*/
public class MinioOkHttpLogInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(MinioOkHttpLogInterceptor.class);
private String regex;
public MinioOkHttpLogInterceptor(String regex) {
this.regex = regex;
}
@Override
public Response intercept(Chain chain) throws IOException {
// 1. 捕获请求信息(发送前)
Request request = chain.request();
long startTime = System.currentTimeMillis();
// 只打印 MinIO 相关请求(过滤其他无关请求)
if (request.url().host().contains(regex)) {
logger.info("===== MinIO 请求开始 =====");
logger.info("请求方法:{}", request.method());
logger.info("请求URL:{}", request.url());
logger.info("请求头:{}", request.headers());
// 若有请求体(如 POST/PUT),可打印 body(注意:body 只能读取一次)
// logger.info("请求体:{}", request.body() != null ? request.body().toString() : "无");
}
// 2. 执行请求
Response response = chain.proceed(request);
// 3. 捕获响应信息(返回后)
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
ResponseBody responseBody = response.peekBody(1024 * 1024); // 读取响应体(不消耗)
// 只打印 MinIO 相关响应
if (request.url().host().contains(regex)) {
logger.info("响应状态码:{}", response.code());
logger.info("响应耗时:{}ms", duration);
logger.info("响应体:{}", responseBody.string());
logger.info("===== MinIO 请求结束 =====\n");
}
return response;
}
观察


每一次'1'在输出前,都有一次请求发生。
说明验证结果为猜想正确。
4. 解决方案
验证中已经写明,不赘述了。