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

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

相关推荐
无心水9 小时前
Java时间处理封神篇:java.time全解析
java·开发语言·python·架构·localdate·java.time·java时间处理
wuyikeer9 小时前
Spring BOOT 启动参数
java·spring boot·后端
多看书少吃饭10 小时前
Vue + Java + Python 打造企业级 AI 知识库与任务分发系统(RAG架构全解析)
java·vue.js·笔记
博傅10 小时前
Kubernetes (K8s) 入门到实战教程
java
奋斗的老史10 小时前
Stream-流式操作
java·windows
清风徐来QCQ11 小时前
八股文(1)
java·开发语言
zdl68611 小时前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
摇滚侠11 小时前
你是一名 java 程序员,总结定义数组的方式
java·开发语言·python
架构师沉默11 小时前
AI 让程序员更轻松了吗?
java·后端·架构
MrSYJ12 小时前
有没有人懂socketChannel中的write,read方法啊,给我讲讲
java·程序员·netty