记一次Minio ComposeObject无法在ceph上合并文件的异常运维

24 篇文章 1 订阅

问题复现

  1. 直接使用minio java sdk(8.3.3版本)对ceph集群中的compose桶中的已存在的多个文件(test1,test2)进行合并(test),代码如下

     @Test
     void contextLoads() throws Exception{
         MinioClient minioClient =
                    MinioClient.builder()
                            .endpoint("http://172.23.27.119:7480")
                            .credentials("4S897Y9XN9DBR27LAI1L", "WmZ6JRoMNxmtE9WtXM9Jrz8BhEdZnwzzAYcE6b1z")
                            .build();
    
     	composeObject(minioClient,"compose","compose",List.of("test1","test2"),"test");
    
     }
    
    public  boolean composeObject(MinioClient minioClient,String chunkBucKetName, String composeBucketName, List<String> chunkNames, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    
            List<ComposeSource> sourceObjectList = new ArrayList<>(chunkNames.size());
            for (String chunk : chunkNames){
                sourceObjectList.add(
                        ComposeSource.builder()
                                .bucket(chunkBucKetName)
                                .object(chunk)
                                .build()
                );
            }
            minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(composeBucketName)
                            .object(objectName)
                            .sources(sourceObjectList)
                            .build()
            );
            return true;
        }
    
  2. 运行报错

    报错为400,BadDigest

完全一脸懵逼o((⊙﹏⊙))o,说好的兼容S3呢?

问题排查

日志
  1. 第一点想到的就是查看rgw日志,看看究竟出错是什么原因

    日志如下:(在此只截取了部分日志)

    image-20211104143431795

    日志经过分析之后,之前的两次head request(获取test1和test2的元数据信息)以及init multipartUpload都是成功的,但是最终在第一次上传第一块的时候出现了错误(也就是test1),报了400的错误,op状态码为-2005,报了等于没有报错,这不搞笑吗?我咋知道-2005啥意思?

  2. 不行,去看看rgw源码中-2005究竟是啥(唉,笔者C++只知道一些语法,不太会,以下都是随机分析的结果,有点破案的感觉)

    1. 最终在rgw下面的

      好家伙,终于找到2005这个错误码

    image-20211104144053320

    ​ 成功了一半了,哈哈哈哈(现在回过头来看看还是自己太年轻了。。。。)

    1. 继续跟踪ERR_BAD_DIGEST,出现的位置

      最终在rgw_op.cc下面找到了藏身之处,看名字,嗯,应该是处理上传的代码,一共出现了4次,要排查的地方还不多,内心暗自高兴

      image-20211104144411842
    2. 首先看到了第三处和第四处,发现如果是这两处报错的,一定会有对应的日志输出,但是明显之前的rgw日志值没有对应的错误日志,因此排除

      image-20211104144621243
    3. 那么剩下的只剩下第一处和第二处,这两处代码几乎都是一致的,如下

      if (supplied_md5_b64 && strcmp(calc_md5, supplied_md5)) {
        op_ret = -ERR_BAD_DIGEST;
        return;
      }
      

      貌似是在对提供的md5(supplied,命名有点意思)进行校验,但是不太确定

    4. 随后我仔细看了一下两处所在对应的方法

image-20211104145132572
  可以很明显的看到第一处为Put,第二处为Post,而我的rgw日志是Put请求,因此锁定在第一处(就快要找到真相了)

  ![image-20211104145215146](https://img-blog.csdnimg.cn/img_convert/b5ce8c0abcbfd2853c85c60cadb9249d.png)
  1. 继续分析第一处的代码

    发现这个东西

image-20211104145527793
  由于这几处都是出现在一起的,第一个是supplied_md5_b64,但是还是不太确定是不是`Content-MD5`,但是下面的几个和请求头中的非常像

  ![image-20211104145703581](https://img-blog.csdnimg.cn/img_convert/8c45dff24c0291946829de5962e4696a.png)

  因此我就怀疑是请求头中的MD5了
对比日志
  1. 为了有对比参考的样本,和报错的rgw日志比较,我决定使用aws s3的sdk进行一次实验,原理和minio的一样,也是将compose中原本存在test1,test2进行合并,需要注意点就是partNumber是从1开始的,不是从0开始,从0开始会报错,别问我为啥知道,因为踩过坑了。。。。

    @Test
    public void test1(){
            String bucketName = "compose";
            String keyName = "test";
    
            AWSCredentials awsCredentials = new BasicAWSCredentials("4S897Y9XN9DBR27LAI1L","WmZ6JRoMNxmtE9WtXM9Jrz8BhEdZnwzzAYcE6b1z");
            AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://172.23.27.119:7480",""))
                    .withPathStyleAccessEnabled(true)
                    .build();
    
            InitiateMultipartUploadRequest initRequest  = new InitiateMultipartUploadRequest("compose","test");
            InitiateMultipartUploadResult  initResponse  = s3Client.initiateMultipartUpload(initRequest);
    
            List<PartETag> partETags = new ArrayList<>();
            List<String> list = List.of("test1","test2");
            for (int i = 0; i < list.size(); i++) {
                CopyPartRequest request = new CopyPartRequest()
                        .withDestinationBucketName(bucketName)
                        .withPartNumber(i+1)
                        .withUploadId(initResponse.getUploadId())
                        .withDestinationKey(keyName)
                        .withSourceBucketName(bucketName)
                        .withSourceKey(list.get(i));
                partETags.add(s3Client.copyPart(request).getPartETag());
            }
    
            CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(bucketName, keyName,
                    initResponse.getUploadId(), partETags);
            s3Client.completeMultipartUpload(compRequest);
        }
    
  2. 日志如下

    image-20211104151329411

    可以看到成功了,但是眼尖的我看到了不一样的地方

  3. 不同之处的查找

    minio

    image-20211104150632478

    aws

    image-20211104152559579

    可以看到aws这里没有minio绿色箭头执行的日志,

  4. 再次查看rgw代码

    可以看到在第一处之前确实有这行代码

    image-20211104153529886

    到此为止可以确定一点,那就是minio那边在Put操作的时候携带了一个请求头,可能是md5

抓包进行分析
  1. 对minio的请求进行分析
image-20211104153958551

确实有一个Content-MD5请求,突然间我发现这个md5好像有点眼熟,又经过一次不同的请求测试,发现这家伙没变!!

后来发现,这玩意儿是''的md5之后进行base64的请求结果,难怪不同请求都一样,原来发送的body都是空,因此计算md5的时候才会有这东西

下面这个x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,也是代表发送的body为空,难怪每次都一致

原因分析

minio每次发送Put请求,为了数据安全都会加上md5的请求头,但是composeObject该动作本身上传的body就是空的(本质是复制copyPart操作),因此本地的请求body为空,因此md5值与ceph在接收到该md5与自己计算出来的md5值不一致,那么就导致了合并失败。

验证
minio代码验证
image-20211104154824847

​ 可以看到minio在构造请求的时候,始终会带上md5

修改源码 (对S3Base.java进行修改)
image-20211104155202616

为了将md5值不强制发送,这两处进行注释掉之后再进行composeObject,最终测试结果成功!

总结

说实话,这是第一次排查过程我感觉自己成长了很多,也算是第一次这么仔细地看源码吧,也是真的就是在源码面前,一切bug都能够找到原因。

总之能够找到原因还是非常高兴的。

不过仔细想想也没有错,minio和ceph两者是竞争关系,你用minio的sdk去操作ceph,难免会出现bug,为啥我要贴合你呢?我有我的规则你有你的规则

minio是一种开源的分布式对象存储服务,可以提供对海量数据的存储和访问功能。在minio中,小文件合并是指将多个小文件合并成一个较大的文件,以减少存储和管理的开销。 在minio中,小文件合并可以通过两种方式实现: 1. 手动合并: 用户可以通过编写自定义的程序或脚本来手动合并文件。首先,用户需要获取需要合并的小文件列表,并按照一定的规则确定合并文件的命名和格式。然后,用户可以读取每个小文件的内容,并将其写入合并文件中,直到所有的小文件都被合并。最后,用户需要将合并文件上传到minio中,并删除原小文件,以释放存储空间。 2. 自动合并: 用户可以通过配置minio服务器来实现自动合并功能。首先,用户需要在minio服务器上创建一个触发器,并设置触发条件。可以设置条件为每达到一定数量的小文件时,自动触发合并。然后,当满足触发条件时,minio服务器会自动将小文件合并成一个较大的文件,并将其上传到指定的位置。用户无需手动操作,可以实现自动化的小文件合并。 小文件合并minio中的应用可以大大提高存储和管理效率。通过合并文件,可以减少元数据的存储开销,提高文件读取的性能,并减少对象存储的碎片化。同时,合并后的大文件也更容易进行备份和迁移操作,提高系统的可靠性和可维护性。 总结起来,minio提供了手动和自动两种方式来实现小文件合并,用户可以根据实际需求选择合适的方法来进行操作。小文件合并可以提高存储和管理效率,优化系统性能,并减少存储空间的浪费。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值