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

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

相关推荐
后端AI实验室1 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai
程序员清风3 小时前
小红书二面:Spring Boot的单例模式是如何实现的?
java·后端·面试
belhomme3 小时前
(面试题)Redis实现 IP 维度滑动窗口限流实践
java·面试
Be_Better3 小时前
学会与虚拟机对话---ASM
java
开源之眼5 小时前
《github star 加星 Taimili.com 艾米莉 》为什么Java里面,Service 层不直接返回 Result 对象?
java·后端·github
Maori3166 小时前
放弃 SDKMAN!在 Garuda Linux + Fish 环境下的优雅 Java 管理指南
java
用户908324602737 小时前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
小王和八蛋7 小时前
DecimalFormat 与 BigDecimal
java·后端
beata7 小时前
Java基础-16:Java内置锁的四种状态及其转换机制详解-从无锁到重量级锁的进化与优化指南
java·后端
IT探险家7 小时前
你的第一个 Java 程序就翻车?HelloWorld 的 8 个隐藏陷阱
java