摘要:不少大公司的一个桶里都是几亿几十亿的对象,那他们都是怎么检索的呢?

本文分享自华为云社区《对象存储只能按文件名搜索? 用 DWR + ElasticSearch 实现文件名、文件内容、图片文字的模糊搜索!》,作者:云存储开发者支持团队 。

众所周知,由于对象存储的架构限制,要想检索对象存储里的文件,只能使用前缀的方式过滤,然后一页一页的列举,限制多,效率低,要是桶内对象实在太多,可能光列举就要列举一两天。你可能会问,不少大公司的一个桶里都是几亿几十亿的对象,那他们都是怎么检索的呢?很简单但很有效的方案——在上传对象时候把对象信息存到其他数据库里,如 Elasticsearch、MongoDB、MySOL 等,然后在数据库里检索。

这种方案虽然见到有效,但修改成本极高,如果在业务设计初期没有考虑到,或系统运行过程中想要添加些新的字段,那就只能修改业务代码并重新部署,要是再碰上有已分发客户端的情况下还要推动客户端升级才能解决。

有没有升级简单,不用改动业务代码的方案呢?还真有,把存数据库的过程转移到对象存储来做就好了,每次上传对象之后,让对象存储帮你把对象信息存一份到你指定的位置。本文我们尝试通过 DWR 平台来进行解决。DWR 是华为云推出的一个近数据计算平台,简单来说,通过 DWR 平台,我们可以在不改动业务系统的情况下实现对对象的处理。如图片上传时把图片转成 JPG 格式并存储在另一个桶里、在获取图片时给图片加上水印等。DWR 将这一个个的能力都封装成了“算子”,除了官方和第三方伙伴提供的算子外,我们也可以编写自定义算子来实现我们的其他定制类要求。

一、架构总览

1.1 数据库选型

对象存储中一个对象(Object)由对象名(Key)、元数据(Metadata)、对象内容(Data)三部分组成。从原始需求出发,为了实现对象的模糊搜索,我们首先要把对象名存起来。进一步的,元数据中也包含了许多可以进行过略、排序用的信息,如对象大小、最后修改时间、上传时间、对象 Content-Type、自定义元数据等。其中自定义元数据中包含的 Key 的数目、value 类型都是可变的。为了方便存储和检索自定义元数据,不在每次想增加一个字段时都去修改数据库,我们首先就排除了传统的关系型数据库。

非关系型数据库(NoSQL)中,比较符合我们要求的是两款文档型数据库——MongoDB 与 Elasticsearch。从定位上来说,MongoDB 更偏向于数据库,可以用作数据管理和数据搜索; CSS 则偏向于数据搜索服务。具体到我们这个场景,从访问便捷度、最小规格价格几个维度对比,最终选择了选择 CSS 服务。下表为华为云上的 DDS、CSS、GaussDB for NoSQL 的对比,大家也可以根据自己具体场景选择合适的服务。

1.2 存储对象信息的整体流程

总的流程分 3 步:

1. 上传文件到对象存储
2. DWR 自动触发,保存需要的信息到 CSS
3. 通过 API、kibana 等方式检索 CSS 中存储的数据

其中第二步还可以进行些进阶的操作,例如上传图片时,检测图片中的文字信息,一并存入数据库;上传视频时,检测把视频大小、码率、清晰度等信息抽取出来存入数据库…

二、购买与配置 CSS

2.1 购买 CSS

1.配置集群 控制台找到 CSS 服务,点击创建集群,集群版本选择了 7.10.2,在此我们先选择最低配的单节点。存储选了超高 IO。

2.配置网络 需要注意,安全组一定要允许 9200 端口,集群在创建后不支持修改安全组,只能删除重新创建。如果只是在 VPC 内网访问可以不开安全模式,要是想开放给公网访问就必须开启完全模式。

3.配置备份 建议开启下数据备份,OBS 本身价格也不贵,还可以通过转冷存储进一步降低成本,数据多一份保护,万一哪天误删了不用从头挨个列举。

4.完成配置 至此就完成了初始的配置,点击立即申请即开始创建集群。

2.2 初始化 Mapping

ES 中的 Mapping 大致可以类比为数据库中的表结构,通过定义 mapping,可以指定字段的存储类型。我们目前需要的字段如下。可以使用 CSS 自带的 kibana 控制台创建 Mapping。

在 CSS 控制台找到 kibana,点击跳转后登录,侧边栏找到 Dev Tools

把下面的代码插入进去,点击运行,记得把 your-bucket-name 替换成你实际要用的桶名,需要自己在 OBS 手动创建桶

PUT your-bucket-name
{"mappings": {"dynamic": true,"properties": {"etag": {"type": "text"},"expiration": {"type": "text"},"content-type": {"type": "text"},"date": {"type": "text"},"content-length": {"type": "integer"},"bucket_name": {"type": "text"},"object_name": {"type": "text"},"create_time": {"type": "integer"}}}
}

三、配置 DWR

数据工坊(Data Workroom,DWR)是一款近数据处理服务,下层调用了函数服务 FunctionGraph 的能力,自定义算子本质上就是 FunctionGraph 的一个函数,为了开发自定义算子,我们首先要在 FunctionGraph 上创建一个自定义函数并测试通过。

3.1 创建 FunctionGraph 函数

创建函数包含上传依赖包、创建函数、创建委托、测试函数几个步骤,都不复杂。

3.1.1 上传依赖包

本地 Python 操作 Elasticsearch 需要通过 pip 安装 Elasticsearch Python 依赖,相应的,我们在函数工作流中调用也需要添加对应的依赖包,我们需要安装7.10.1 版本的 elasticsearch 。

首先需要使用你对应 python 版本创建个新的虚拟环境,如果没有新建,而你本地已经有了部分依赖,会导致依赖包装不出来。

建议使用 Linux 环境打包依赖包,在 windows 环境下打包出的部分包可能不兼容 functionGraph 环境

# 安装虚拟环境包,有的话可以跳过
pip install virtualenv
# 创建 python 3.9 的虚拟环境
virtualenv fgpackage --python=3.9
# Linux 激活虚拟环境
source ./fgpackage/bin/activate
# Windows 激活虚拟环境
# .\fgpackage\Scripts\activate
# 安装指定包到临时目录
pip install elasticsearch==7.10.1 --root \tmp\fgpackage

经过上面的操作,把就elasticsearch 和它们需要的依赖安装到了 \tmp\fgpackage 下了。一层一层进入 \tmp\es_package,一直到 site-packages 一层,全选后添加到一个压缩包内。

在函数列表页点击函数-依赖包管理-添加依赖包

运行语言选 Python3.9,上传刚刚打包的压缩文件点击确定即可。

更多可参考 官方添加依赖说明

3.1.2 创建函数

控制台找到 FuntionGraph 服务,点击创建函数。

选择空白函数,运行时选择为 Python 3.9 (话说把 Runtime 翻译成运行时好奇怪,这种专有词是不是最好别强行翻译?);委托需要具有 VPC Administrator 与 Tenant Administrator 两个权限,用以访问其他云服务和 VPC 内网资源,如果有现成的可以直接选择,没有的话点击创建委托进入创建页,参考下一节进行创建,然后刷新下选择即可。

点击完成创建。

3.1.3 创建委托

委托需要有 VPC Administrator 与 Tenant Administrator 两个权限,如果已有可以直接跳过。上一节中的创建函数位置点击 创建委托 跳转到委托创建页,点击创建委托。

委托类型选择云服务

权限选择 VPC Administrator 与 Tenant Administrator 两个权限

授权范围选择所有资源,或跟你需要自己配置

点击完成即可。

3.1.4 填写代码

创建过函数后,会进入函数编辑页面,将下面的代码写到编辑器里,点击下部署,或键盘按 Ctrl + S 进行部署

# -*- coding:utf-8 -*-
import time
from urllib.parse import unquote_plus
from elasticsearch import Elasticsearch
from obs import ObsClient
def handler(event, context):# 获取桶名与对象名region_id, bucket_name, object_name = get_obs_obj_info(event.get("Records", None)[0])context.getLogger().info(f"bucket name: {bucket_name}, object key: {object_name}")ak = context.getAccessKey()sk = context.getSecretKey()server = 'obs.' + region_id + '.myhuaweicloud.com'context.getLogger().info("before token")context.getLogger().info(context.getToken())context.getLogger().info("finish token")obs_client = ObsClient(access_key_id=ak, secret_access_key=sk, server=server)# 获取对象元数据object_metadata = obs_client.getObjectMetadata(bucket_name, object_name)# 将头域转为字典info_dict = {i[0]: i[1] for i in object_metadata["header"]}info_dict["bucket_name"] = bucket_nameinfo_dict["object_name"] = object_name# 为了不同系统下时区转换导致时间不统一,这里不使用 OBS 里的 last-modified 的 GMT 时间,改用时间戳info_dict["create_time"] = int(time.time())# 把对象大小转为数字格式info_dict["content-length"] = int(info_dict["content-length"])# 去除部分无用的 headerfor i in ["id-2", "request-id", "connection", "last-modified", "uploadid"]:if i in info_dict:info_dict.pop(i)# 把其他算子里包含的信息也一起保存下来if "other_info" in event["dynamic_source"]:info_dict.update(event["dynamic_source"]["other_info"])context.getLogger().info(f"metadata to save: {info_dict}")es_user = event["dynamic_source"]["es_user"]es_password = event["dynamic_source"]["es_password"]es_server_ip = event["dynamic_source"]["es_server"]es_port = event["dynamic_source"]["es_port"]context.getLogger().info(es_port)if es_user != "" and es_password != "":es_server = f"https://{es_user}:{es_password}@{es_server_ip}:{es_port}"context.getLogger().info(es_server.replace(es_password, "xxxxxxx"))else:es_server = f"http://{es_server_ip}:{es_port}"context.getLogger().info(es_server)es = Elasticsearch([es_server], ca_certs=False, verify_certs=False)response = es.index(index=bucket_name, body=info_dict)context.getLogger().info(response)return {"statusCode": 200,"isBase64Encoded": False,"body": response,"headers": {"Content-Type": "application/json"}}
def get_obs_obj_info(record):if 's3' in record:s3 = record['s3']return record["eventRegion"], s3['bucket']['name'], unquote_plus(s3['object']['key'])else:obs_info = record['obs']return record["eventRegion"], obs_info['bucket']['name'], \unquote_plus(obs_info['object']['key'])

3.1.5 配置函数

1.配置依赖 在代码配置页最下找到添加依赖包按钮,分别添加公共依赖中的OBS 3.21.8 与 私有依赖中的fgpackage

2.配置 VPC 还是代码配置页,点击基本信息的编辑按钮,这里要记得与 2.1 节中的 CSS 选择同一个 VPC。

3.1.6 测试函数

在 OBS 里创建一个桶,最好和 CSS、FunctionGraph 都在同一个 Region,我用的上海一节点,region id 为 cn-east-3 桶内随便上传一个对象做备用。然后点击函数代码页中配置测试事件,把下面这段 Json 添加进去,并修改下面的配置为你的配置。其中 es_server 的值为 CSS 集群 IP。

{"Records": [{"eventRegion": "cn-east-3","obs": {"bucket": {"name": "your-bucket-name"},"object": {"key": "your-object-name"}}}],"dynamic_source": {"es_server": "your-CSS-endpoint","es_user": "admin","es_password": "your-CSS-password","es_port": 9200}
}

保存后点击测试,如果一切配置正确,右边会出现这样的结果,如果提示执行失败,就看下下面报错,再找找前面几步哪个写错了。

3.2 配置 DWR 工作流

DWR 现在还在公测中,需要点击申请公测,信息随便填就可以,目测是自动审核的,点完申请就通知申请成功了。

进入页面,点击工作流选项-创建工作流

左侧把自定义算子拖到中间,和 Start、End 连上线,函数选择刚刚创建的函数,再填写下参数。这里的参数就是上一节 Json 文件里 dynamic_source 字段的参数即可。

点击保存,写上名字,会自动跳转出来,创建个触发器再

这里我没有写前缀和后缀,对桶内所有对象生效,如果填了前缀后缀,则会只匹配指定事件。

四、检索

到现在所有配置都完成了,使用 OBS Browser+ 向桶里上传几个测试文件,然后用自己熟悉 Elasticsearch 调用方法尝试下检索,我这里使用 CSS 自带的 kibana 控制台。

4.1 检索名字里包含测试 两个字的对象

测试代码:

POST _search
{"query": {"match": {"object_name": "测试"}}
}

4.2 检索为 .mp4 结尾的对象

POST _search
{"query": {"regexp": {"object_name": ".*mp4"}}
}

4.3 检索大小介于 100k 到 1M 的对象

POST _search
{"query": {"range": {"content-length": {"gt":"102400","lt":"1048576"}}}
}

4.4 检索创建时间在 2022 年 8 月 5 日与 2022 年 8 月 6 日之间的对象(时间戳)

POST _search
{"query": {"range": {"create_time": {"gt":"1659628800","lt":"1659715200"}}}
}

五、再进一步

有的同学可能注意到了,流程介绍时我提到可以进行进阶的操作,存入 CSS 的算子上头可以拼接很多其他算子,算子的代码包里也留了一个小扩展,可以从上一个算子中读取 other_info 并一起保存,这个能干点啥嘞。咱们举几个场景:

  1. 把对象名缩写存起来,如一个文件叫我的文件.txt,只用输入 wdwj 就能找到该文件的全称
  2. 图片文件上传后,调用 AI 给图片打个标签,把标签存到数据库,可以通过风景、美食、猫咪 等关键词检索到图片,现在华为鸿蒙、苹果 iOS 都有这样的功能
  3. 图片包含文字的话,把文字识别出来存入数据库,可以通过文字搜索图片,在某些业务系统里还可以用专用的算子,如发票识别、身份证识别等。

这里先把最简单的对象名缩写给个示例,抛砖引玉,大家可以自行尝试更多功能。

用了xpinyin 这个库,上传依赖包步骤参考前面的介绍。代码很简单:

# coding:utf-8
from urllib.parse import unquote_plus
from xpinyin import Pinyin
def handler(event, context):# 获取桶名与对象名_, _, object_name = get_obs_obj_info(event.get("Records", None)[0])context.getLogger().info(f"Object name is {object_name}")pinyin = Pinyin()pinyin = pinyin.get_pinyin(object_name, '-')short_pinyin = "".join([i[0] for i in pinyin.split("-") if i[0].isalpha()])if "other_info" in event["dynamic_source"]:event["dynamic_source"]["other_info"]["short_pinyin"] = short_pinyinelse:event["dynamic_source"]["other_info"] = {"short_pinyin": short_pinyin}context.getLogger().info(f"Object short name is {short_pinyin}")context.getLogger().info(event)return event
def get_obs_obj_info(record):if 's3' in record:s3 = record['s3']return record["eventRegion"], s3['bucket']['name'], unquote_plus(s3['object']['key'])else:obs_info = record['obs']return record["eventRegion"], obs_info['bucket']['name'], \unquote_plus(obs_info['object']['key'])

这就配置完了,只用去 DWR 工作流页面创建个工作流,把这个函数加载前面:

给工作流配置个触发器,然后把之前创建的工作流先删除掉,以免重复触发。再上传几个文件。

搜索下包含 cs 的对象

POST _search
{"query": {"regexp": {"short_pinyin": ".*cs.*"}}
}

点击关注,第一时间了解华为云新鲜技术~

对象存储只能按文件名搜索,你out了吧相关推荐

  1. 对象存储与块存储、文件存储等对比

    看到 一篇文档, 讲 对象存储, 好奇,搜索文章,摘抄,学习记录 ! 背景: 传统存储在面对海量非结构化数据时,在存储.分享与容灾上面临很大的挑战,主要表现在以下几个方面:传统存储并非为非结构化内容设 ...

  2. 使用腾讯云cos对象存储服务托管静态网站

    1.开通cos对象存储服务 在腾讯云服务官网的菜单栏中找到对象存储,或者服务搜索框中搜索对象存储,都可以快捷地找到对象存储产品页面. 按提示开通相关服务即可. 开通服务之后,进入对象存储管理控制台,如 ...

  3. 在ThinkPHP框架(5.0.24)下引入Ueditor并实现向七牛云对象存储上传图片同时将图片信息保存到MySQL数据库,同时实现lazyload懒加载...

    这是我花了很多天的时间才得以真正实现的一组需求. 文章后面有完整Demo的GitHub链接. 一. 需求描述 1. 应用是基于ThinkPHP5开发的: 2. 服务器环境是LNMP,PHP版本是7.2 ...

  4. 6、阿里云OSS对象存储手把手学习及企业使用方案

    文章目录 1.什么是分布式文件系统 2.图片存储解决方案 3.阿里云OSS对象存储 3.1.开通oss访问 3.2.购买服务 3.3.基本概念 3.4.创建一个bucket 3.5.创建AccessK ...

  5. Minio分布式对象存储的部署与使用

    一.部署 下载Minio直接Github搜索即可,minio/minio: Multi-Cloud Object Storage 1.1 单机部署 单机部署命令,指定静态端口9001. 一般会占用两个 ...

  6. 阿里云OSS(对象存储服务)简介

    最近公司想要使用阿里云OSS来存储精准客流图片,所以提前熟悉一下,做一个记录 注:阿里云官方文档已经很详细的阐述了OSS.以及开发流程,本文大多都是参考官方文档 OSS官方介绍地址:https://h ...

  7. ceph的数据存储之路(10) -----ceph对象存储的ls命令实现及思考

    2019独角兽企业重金招聘Python工程师标准>>> 更新:2016-10-19--------------------------------------- 前面更新的内容可能略 ...

  8. 对象存储,为什么那么火?

    作者|小枣君 来源|鲜枣课堂 引言 上期文章(链接:关于存储技术的最强入门科普),小枣君给大家详细介绍了数据存储技术的基本知识,其中重点对DAS.SAN和NAS技术进行了对比分析. 我们知道,在很长的 ...

  9. 史上最完整Java中将File转化为MultipartFile的方法(附阿里云腾讯云对象存储API对照)

    业务中需要调用别人提供的接口进行文件上传,但别人的接口只能上传MultipartFile类型的文件(吐槽一下,也不知道是哪个二货设计的这种接口).所以需要在我们的业务代码中将File转化为Multip ...

最新文章

  1. Javascript基础知识篇(5): 面向对象之链式调用
  2. pytorch笔记:policy gradient
  3. 途家网获3亿美元融资,PMCAFF放送100元途家优惠券(途家内部员工专用)
  4. ./4.sh: No such file or directory
  5. 深入浅出PE文件格式---自己动手打造PE Show
  6. 阿里云服务器和mysql的原理_mysql 数据库同步原理
  7. 计算机智能测试与机电工程学,机电工程学院智能技术创新团队论文被国际计算机视觉顶级会议ICCV录用...
  8. poj 2502 Subway dijkstra基础 !!!!入门题
  9. 线程中消费者生产者的实例代码(synchronized关键字)
  10. php赛车,php调用接口及三分赛车平台搭建出租编写接口
  11. 统计学 常用的数据分析方法大总结,推荐收藏
  12. 后缀树 c++代码实现
  13. 淘宝x-sign签名算法
  14. Intel IPP密码库 IPPCP 2018——第二部分 对称密码算法开发说明与示例代码
  15. 谨慎程序员创业的思维障碍
  16. idea spell checking(拼写检查)
  17. 蓝桥杯 基础练习 数列排序 Java
  18. 计算机应用专业需求ppt,计算机应用专业建设报告.ppt
  19. CAT ----分布式实时监控系统
  20. 缠论工具(笔, 线段)

热门文章

  1. 《2022年Java开发者生产力报告》出炉啦
  2. 外贸网络营销搜索引擎和B2B模式比较
  3. 回射程序改进3——消息的群发
  4. Python——requests模块详解
  5. oracle sys_context()函数
  6. 兼容低版本IE浏览器的一些技巧分享
  7. 苹果/安卓兼容性页面显示问题
  8. 0206.BFPRT在一大堆数中求其前k大或前k小的问题,简称TOP-K问题(左神算法基础班源码)
  9. UserAgent 解析
  10. 什么是沟通能力?如何提升沟通能力?