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

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

相关推荐
方也_arkling16 小时前
【Java-Day08】static / final / 枚举
java·开发语言
橙淮16 小时前
Spring Bean作用域与生命周期全解析
java·spring
Chengbei1116 小时前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
llz_11217 小时前
web-第一次课后作业
java·开发语言·idea
秋917 小时前
Java项目运行5天左右自动宕机:系统性定位与解决方案
java·开发语言·python
小江的记录本17 小时前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
DIY源码阁17 小时前
JavaSwing学生成绩管理系统 - MySQL版
java·数据库·mysql·eclipse
basketball61618 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
JAVA面经实录91719 小时前
MyBatis面试题库
java·mybatis
小江的记录本19 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试