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,另一部分数据更新不上,不统一