minio update meta info 更新minio S3对象存储meta的实现方法

minio update meta info 更新minio S3对象存储meta的实现方法

应用场景

对象存储就不多作介绍了

亚马逊,阿里云,金山云,都有相应的对象存储服务

常见的方案有ceph,minio等

amazon S3 是事实上的标准,金山云,minio都支持S3协议(但支持的完整度不一样,亚马逊的S3可以直接通过安装s3 驱动包,mount直接挂载,也能挂k8s上,但minio就mount不了)

通常对象存储,存一些小文件,又以互联网的媒体文件为主,例如图片,视频等等

现将大数据中的部分媒体文件迁至minio,该工作基本已全部做完

要对媒体文件,附加深度学习/机器学习算法应用文本识别,语音识别,特征抽取,向量信息等算法结果

通过sql/nosql外部存储,保存映射关系的方案就不提了,各有优缺点

缺点主要是又引入了一重依赖,复杂度相对较高,考虑在直接在minio的对象meta上附加信息

因为要应用各种算法,所以为了方便对接,直接相关项目,也部分采用了python实现,以下示例全部为python,其他语言也类似

首先 minio上传文件时可以附加meta信息

https://docs.min.io/cn/python-client-api-reference.html#put_object


# Put an object 'myobject' with contents from '/tmp/otherobject', upon success prints the etag identifier computed by server.
try:
    print(minioClient.fput_object('mybucket', 'myobject', '/tmp/otherobject'))
except ResponseError as err:
    print(err)

# Put on object 'myobject.csv' with contents from
# '/tmp/otherobject.csv' as 'application/csv'.
try:
    print(minioClient.fput_object('mybucket', 'myobject.csv',
                             '/tmp/otherobject.csv',
                             content_type='application/csv'))
except ResponseError as err:
    print(err)
    

在metadata中存储附加信息,但是这只是文件上传时可以附加,而大数据和各种数据挖掘算法的应用较为耗时,通常是异步,延后更新的,并不适用

需要找到update object metadata的方法

遗憾的是 https://docs.min.io/cn/python-client-api-reference.html 查官方各语言的api文档,都不提供update方法

所以就只能用外部存储了?看官方文档没有update方法,通常就放弃了,有精力去官方提issue,或改代码提merget,精力有限,这个先跳过

只是总觉得不应该这样,个人评判一个技术产品和一个功能需求时,大概会考虑这个需求是否普遍,再考虑技术产品的生态成熟度

update object metadata 是较为普遍的功能需求,minio是较为成熟优秀的对象存储方案,这种功能,在未看文档前,我预期的是会支持才对


总之没有找到原生的update object metadata sdk/api 看能不能在其他sdk/api上想办法实现

put_res = client.put_object(bucket_name, object_name, BytesIO(
        msg), len(msg), content_type=content_type, metadata={})
('f124631fb276876f23e0c8d81f3649ae', None)

调用put_object的返回值put_res 包含上传对象的md5值

minio 实现put_object会不会检查客户端和服务器同名object的md5?如果完全一致,就免去该次put_object的网络io呢?(百度云的秒传,就是这种思路)

来作测试

def test_bif_file_multi_put_same_file():
    MINIO_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
    MINIO_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    ENDPOINT = "192.168.5.196:9000"
    start_time = time.time()
    fileBytes = readFileBytes("./NVIDIA-Linux-x86_64-450.51.06.run")
    minioClient = Minio(ENDPOINT, access_key=MINIO_ACCESS_KEY,
                        secret_key=MINIO_SECRET_KEY, secure=False)
    put_res = putObject(minioClient, "testbucketname",
                        "objectest2.jpeg", fileBytes, 'image/jpeg', None)
    first_finish_time = time.time()
    print("第一次上传时间时间开销", first_finish_time-start_time)
    put_res = putObject(minioClient, "testbucketname",
                        "objectest2.jpeg", fileBytes, 'image/jpeg', None)
    secend_finish_time = time.time()
    print("第二次上传时间时间开销", secend_finish_time-first_finish_time)

第一次上传时间时间开销 19.879034519195557
第二次上传时间时间开销 22.13285183906555
重复上传了一次,minio垃圾

很遗憾并不是期望中的那样,大文件的第二次上传,依然会花费同样的时间,很失望

另外又看到一个api

import os
# Put a file with default content-type, upon success prints the etag identifier computed by server.
try:
    with open('my-testfile', 'rb') as file_data:
        file_stat = os.stat('my-testfile')
        print(minioClient.put_object('mybucket', 'myobject',
                               file_data, file_stat.st_size))
except ResponseError as err:
    print(err)

https://docs.min.io/cn/javascript-client-api-reference.html#copyObject

var conds = new Minio.CopyConditions()
conds.setMatchETag('bd891862ea3e22c93ed53a098218791d')
minioClient.copyObject('mybucket', 'newobject', '/mybucket/srcobject', conds, function(e, data) {
  if (e) {
    return console.log(e)
  }
  console.log("Successfully copied the object:")
  console.log("etag = " + data.etag + ", lastModified = " + data.lastModified)
})

上传从客户端到服务端,要上传完整的文件流

但copy总不会也这样,对存储服务,底层二进制文件,上层都只是一个引用,copy一般是加一个引用信息,不会把文件io再跑一次

而copy_object(bucket_name, object_name, object_source, copy_conditions=None, metadata=None)

copy_object 又是可以附加文件信息的

stat_object(bucket_name, object_name)

而旧文档的metadata是可以拿到的

已知这些信息,就有了一个实现方案,是否可执行还需验证

有经验的开发人员应该都也想到了

  • 1 stat_object 获取old object meta
  • 2 old object meta 附加新的值,生成new object meta
  • 3 copy old object -> other ojbect

其实这一步对一些场景已经可以满足了
原始文件放在 /old/object1
附加meta的文件放在 /new/object1 新地址的object1已经有附加的meta信息

但还不够好,因为路径变了,我们要的变更原文件的信息,这个也简单,再copy回去就行

  • 4 对要修改old object meta值的 再执行 copy other ojbect -> old ojbect

  • 5 完成更改后 remove other ojbect

已验证可行,具体代码如下,代码比较粗略,主要是提供一种思路和方案

def updateObjectMeta(client=minioClient, bucket_name=None, object_name=None, metadata=None, is_merge=True):
    """
    更新meta
    !注意,官方不提供该功能,目前通过多次copy实现,网络io较多
    """
    if not metadata:
        raise Exception("未定义待更新meta")
    new_metadata = metadata
    # merge meta
    if is_merge:
        exist_res = client.stat_object(bucket_name, object_name)
        old_metadata = exist_res.metadata
        for key in metadata:
            old_metadata[key] = metadata[key]
        new_metadata = old_metadata
    tmp_object_name = object_name+"_update_metadata_tmp"
    # source obj -> tmp-obj
    tmp_obj_res = client.copy_object(
        bucket_name, tmp_object_name, bucket_name+"/"+object_name)
    print("copy to tmp", bucket_name, tmp_object_name,
          bucket_name+"/"+object_name, tmp_obj_res)
    # tmp-obj -> source obj with new metadata
    print("new_metadata", new_metadata)
    org_obj_res = client.copy_object(
        bucket_name, object_name, bucket_name+"/"+tmp_object_name, metadata=new_metadata)
    print("copy to back", bucket_name, object_name,
          bucket_name+"/"+tmp_object_name, org_obj_res)
    # remove tmp-obj
    client.remove_object(bucket_name, tmp_object_name)

缺点也很明显,每次api调用,都是一次网络io,一共几次我懒得数了,单机本地测试,每次io在300ms左右,需根据项目评估性能,但对我个人而言还可以接受

经个人实际使用最大的缺点是object meta的空间很小,具体多少我忘记了,大概2000个中文?

这主要是因为http协议的问题meta放在http response header中间,除过其他必须的header项,剩下的空间可以放meta,但总体空间很少

也因此最好只能放一些定长meta,不然会一部分数据能更新上meta,另一部分数据更新不上,不统一

posted @ 2020-08-24 01:46  cclient  阅读(2674)  评论(0编辑  收藏  举报