minIO分页请求maxKeys无效的问题

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时去请求数据,倘若我在这段代码前没有制止,就会在本次拉去数据结束后,自动去拉取下一批。

因此,实现思路:

  1. 在hasnext前,制止执行

  2. 制止执行的方式可采用计数(变量A),计获取到的数据,达到一定数值终止遍历

  3. 基于2,会出现A的数值大于当前分页的数据总数,导致执行下一次请求被触发,因此设定A为pageSize的值

  4. 使用日志来查看请求进行验证

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. 解决方案

验证中已经写明,不赘述了。

相关推荐
bbq粉刷匠1 小时前
HashMap 底层原理深度拆解(二):putVal 完整链路解析(懒加载 · 链表遍历 · 尾插法)
java·开发语言·哈希算法
Sam_Deep_Thinking1 小时前
一个靠谱的C端网关服务应该包含什么内容
java·程序员·系统架构
杨运交1 小时前
[055][调度模块]Spring动态任务调度框架的设计与实现
java·后端·spring
白狐_7982 小时前
408 数据结构算法题 01:线性表暴力求解保分指南
java·数据结构·算法
流云鹤2 小时前
03Java学习day(3)
java·学习
tqs_123452 小时前
Agent智能体+Skill插件引擎+Milvus混合检索 技术沉淀
java·python·milvus
严同学正在努力3 小时前
从备份到恢复:我用 30 分钟恢复了误删的核心业务表
android·java·数据库·ai
QQ_21696290964 小时前
Spring Boot 养老院管理系统:从入住、护理到费用结算的全流程实现(源码可领)
java·spring boot·后端
cxoptics4 小时前
冰洲石分束器设计与应用
java
ck-joker4 小时前
M1 Pro跑LLM实测:Ollama+LangChain4j零成本本地大模型开发,比云端API快在哪?
java·语言模型