开发者学堂课程【HBase 入门与实战:如何基于 HBase 构建图片、视频数据的统一存储检索方案】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/808/detail/13900
如何基于 HBase 构建图片、视频数据的统一存储检索方案
内容介绍
一、背景介绍
二、图片视频统一存储检索系统设计
三、图片视频统一存储检索系统编码实践
四、总结
一.背景介绍
1、需求情景
老板提出一个紧急需求,下周交付:
.我有非常多的文件需要存储,以图片和短视频为主,以后还会有更多
.每一个图片或者视频都会有很多的标签信息,我需要通过标签能快速的找到我想要的文件,不需要其他的操作,很简单的。
.你需要尽量节省资源
相当于我们能提供的资源不是很多,老板提出需求我们并不知道他背后可能想要的更到。不得不与目标客户交流。
和目标客户交流
.小王最近接收了一个图片模型训练的项目,需要线上批量拉取某种标签的图片进行模型训练,比如涉黄、涉暴,图片大小20k~10M。
.小李是短视频推荐系统的研发,需要一个可以存储和通过标签检索短视屏的服务,短视频文件大小200k~200MB,rt不敏感,吞吐量要求tps100+,qps200+,保存数据2个月以上。
2、需求分析
了解需求后看用户到底想要什么?
图片、视频统一存储检索系统,提供图片、视频文件的存储、检索服务(需要注意的是我们往往会给图片或者视频打上很多的标签,比如:涉黄、涉暴等等,这里提及的检索是根据标签进行检索),数据大多都是小文件(80%大小在数MB以内),数据体量大,增长快速等特点。
根据这些需求可以抽离出系统的功能点
功能需求
1.图片或者视频数据作为单独的对象存储到一个大容器中
2.应用可以通过标签查询来获取每个单独的对象数据(图片或者视频)
3.支持海量数据,高性能,可扩展,高可用
这些需求很急,下周就要交付。
二、图片视频统一存储检索系统设计
基于我们所了解的需求如何构建图片视频统一存储检索系统设计
1、技术选型
HBase,我为你转身
HBase的优势
1.HBase 可以很好的支持非结构化的数据存储,且基 HDFS保证数据的可靠性
2.HBase 吞吐量非常高,足够支撑业务
3.HBase 基于 Hadoop 集群,不需要单独重新维护其他数据存储服务
HBase 有哪些不足之处
1.HBase 对小文件(小于10MB)支持很好,但是对于较大的文件无法满足需求
2.由于 HBase 的设计,HBase 会发生 compact 和 split操作,文件存储会频繁的触发此类的操作导致写放大等不良的影响。
3.HBase 不适合复杂的检索操作,此系统想要标签查询,功能上可能会有限制
那么我们该如何来弥补这些不足呢?
大文件存储方案
1.方案一:比较容易想到的,将大文件分片存储在HBase中。比如100M的文件,按照1M一片,也就是将100M的文件分成100个分片存储在 HBase表里,但对后期的实践查询审核一些复杂的设计来保证整个流程可行。
2.方案二:将大文件支持存储到 HDFS 中,HBase 中只存储图片或者视频的元数据以及标签信息。即避免了将一个文件拆分成多个,而且可以充分用底层的 HDFS 来保证整体的数据可靠性。第二个方案架构如下所示:
用户直接将数据写入统一文件服务,统一文件服务进行判断,如果当前文件大于10M将数据存储在 HDFS 中,其他文件标签信息统一存储在 HBase 中。如果针对小于10M的文件可以直接存储在 HBase 中。
如何解决复查查询支持
1.HBase 本身根据字典排查,我们可以将一些较为固定的标签设计在 Rowkey 中,以便满足我们常规查询的需要
2.如果系统需要支持更加灵活的检索功能,我们也可以引入 phoenix 来构建二级索引或者 ElasticSearch 来满足我们的检索功能
2、技术构架
方案二统一存储检索系统架构图
该项目是一个 SpringBoot 的项目,提供两种接口类型一种Rest 第二种 SDK,接口支持以下:
1. Bucket 的创建,删除,查询等接口
2.上传文件,查询文件内容以及基本的信息
3.根据标签信息查询获取文件信息以及文件的具体内容
在数据存储上,每一个 Bucket 对应 HBase 的一张表,对于小文件(我们将所有小于2MB的文件定义为小文件,而大于2M的文件者称为较大文件)直接存储在 HBase 的表中,而大于2MB文件我们存储在 HDFS 中,并且将文件的 Meta 信息存储在HBase的表中。
额外说明:
1.在下面的实践项目中,我们只是简单的实现其中的部分功能,针对于服务本身的高可用和 SDK 等功能并不加以实现。
2.对于检索功能而言也只是实现一些简单的检索功能。
三、图片视频统一存储检索系统编码实践
1、准备工作
购买HBase集群
·购买云HBase增强版服务
·用户密码在控制台->集群详情->账号管理页面链接串在数据库连接页面
配置集群
.开通公网服务
.获取连接配置
.将本机公网IP添加至白名单
.修改本机hosts配置,绑定 hdfs 的域名
配置开发环境
.下载并安装Idea开发环境
.在Spring模版生成器中生成项目模版,地址
https://start.spring.iol
.将生成好的项目导入Idea
购买 HBase 增强版服务,在用户控制台详情获取链接信息。配置集群公网、连接、本机的白名单。开发环境 Idea、Spring boot,本身此部分用 Spring boot 来构建。
表结构设计
2、HBase 表结构设计
说明
1.设计 HBase 表格为了能够支持检索功能,在 RowKey 的设计上采用 BucketName+标签+key的方式,通过这种方式,可以使用 HBase前缀扫描的方式找到在一个特定 bucket 下标签为tag_1的数据。数据存储,文件的大小、过期时间、创建时间、bucket 名称。最后 content,针对相对小的文件直接将数据本身存储在 HBase 中,针对大文件在 content 里直接 hdfs 路径。
2.虽然这种设计可以满足我们检索的功能,但是这种设计在 RowKey 里,只能应用在一些标签相对比较固定的场景,而且会存在数据的热点问题。Bucket 插入数据较多本身有可能数据热点。
3.该设计对于删除操作不友好,在 RowKey 前面加了其它信息,所以用户删除只需要获取完整的信息包括标签信息,进行数据串的拼接,拼接完成后删除。单考虑到删除操作并不多,所以该设计在一定程度上还是可以满足我们的需求
3、项目目录介绍
common 目录
·公用层,包括工具类和基础的 bean 类
.controller 层,堆外提供的 api 接口
·Service 层,核心处理逻辑,包括对 HBase 和 HDFS 操作的封装类等
4、Service 层private void saveObject(TmobObject object, String bucket, String key, long size) throws I0Exception {
try {
String table = hbaseService.decorateTable(bucket);
HTablehTable=(HTable)hbaseService.getConn().getTable(TableName.valueof(table));
Put p=new Put(Bytes.toBytes(key));
if (size <mobFileThreshold) {
p.addColumn(Bytes.toBytes(TmobUtil.COLUMN_FAMILY_DEFAULT),Bytes.toBytes(TmobUtil.OBJ_CONTENT)
object.getData));
if(lp.isEmpty()){
hTable.put(p);
logger.info("put hbase " + bucket + " " + key);
}
}else {
Pathpath=newPath(String.format(“/%s/%s"hdfsService.getRootPath(),bucket));
FileSystem fileSystem=hdfsService.getFileSystem() ;
if(lfileSystem.exists(path)){
fileSystem.mkdirs(path);
}
FSDataOutputStream fsDataOutputStream=fileSysten
.create(newPath(String.format(“/%s/%s/%s”,hdfsService.getRootPath(), bucket,key)), overwrite: true);
try {
fsDataOutputStream.write(object.getData()); fsDateOutputStream.flush();
} finally {
fsDataOutputStream.close();
}
p.addColumn(Bytes.toBytes(TmobUtil.COLUMN_FAMILY_DEFAULT),Bytes.toBytes(TmobUtil.OBJ_CONTENT)
Bytes.toBytes(String.format(/%s/%s/%s”,hdfsService.getRootPath(),bucket,key)));
if(!p.isEmpty()){
hTable.put(p);
}
}
} catch (Exception e){
.服务端收到图片或者是视频文件的时候,首先会存储meta信息,接着判断文件的大小是否超过长度限制:
.如果没有超过那么我们会将文件的内容直接存储在HBase表中的fc: content
·如果长度超过了限制,那么我们先将文件写入到HDFS的特定的文件夹中,接着将文件的路径存储在fc:content
当用户上传文件,服务端的处理流程,服务端将文件 meta 信息存储在 HBase 表中,数据做以下判断
如果判断数据的长度,长度超过设置的限制,将数据写入 HDFS 中,文件的路径存储在 content 字段。如果长度没有超过限制,数据本身存储到 HBase 中。public List<Tmobobject> queryobject(String bucket, String tag) throws I0Exception {
List<TmobObject> objects =now ArrayList<();
try {
String table=hbaseService.decorateTable(bucket);
HTablehTable=(HTable)hbaseService.getConn().getTableTableName.valueOf(table));
String prefixString = String.format("%s_%s",bucket,tag); Scan scan=new Scan();
scan.setRowPrefixFilter(Bytes.toBytes(prefixString)); scan.setCaching(18);
ResultScannerrs=hTable.getScanner(scan)
for(Resultr:rs){
TmobObjectMeta objectMeta=getObjectMeta(r);
Tmobobject tmobObject=newTmobobject();
tmobObject.setMeta(objectMeta);
objectMeta.setStoreType(TmobType.HBASE.name()); tmobObject.setMeta(objectMeta);
tmobObject.setKey(Bytes.toHex(r.getRow());
if(objectMeta.getSize()<mobFileThreshold){
tmobObject.setData(r.getValue(Bytes.toBytes(TmobUtil.COLUMN_FAMILY DEFAULT),
Bytes.toBytes(TnobUtil.OBJ_CONTENT)));
} else {
FileSystem fileSystem=hdfsServicegetFileSystem():
String filePath=Bytes.toString(r.getValue(Bytes.toBytes(TmobUtil.COLUMN_FAMILY_DEFAULT)
Bytes.toBytos(TnobUtil.OBJ_CONTENT)));
if (filePath ==null I I!fileSystem.exists(new Path(filePath))) {
continue;
}
objectMeta.setStoreType(TmobType.HDFS.name(());
FSDataInputStreamInputStream=fileSystem.open(newPath(filePath));
byte[]content=newbyte[inputStream.available()]:
inputStream.readFully(content);
tmobObiect.setData(content);
数据存储后用户通过某个标签查询时具体流程
服务端根据容器名称和标签组合进行模糊查询,获取文件的 Mate 信息,接着根据文件的长度进行判断:
.如果长度小于存放 HBase 的阈值,者说明文件是存储在 HBase 中的,直接从 HBase 中获取,整合数据之后返回给用
.如果长度大于存储 HBase 的阈值,者说明文件是存储在 HDFS 中,需要根据cf: content 中存储的文件路径从 HDFS 中获取文件信息,组装完成之后返回给用户。
5、效果显示
整个编码核心逻辑,最主要的核心在
HbaseObjectServiceimpl
在保存时进行判断,如果值小于在系统中配置的阈值
If(size<mobFileThreshold{
那么数据会存储在 HBase 中
p.addColumn(Bytes.toBytes(TmobUtil.COLUMN_FAMILY_DEFAULT),Bytes.toBytes(TmobUtil.OBJ_CONTENT)
object.getData());
if(lp.isEmpty()){
hTable.put(p);
logger.info("put hbase "+ bucket +""+ key);
如果大于在系统中配置的阈值,通过 HTTP 访问方式,创建容器,取名为 test_2
代表 bucket 创建成功
先上传一个较小的图片,如果小于10M的数据直接存储在 HBase。按照系统的设计256KB照片应该存储在 HBase,可以通过查询检查。
标记存储类型存储在 HBase 中,下面的数据是具体文件的数据。
再上传一个较大的文件,文件大约20多M,将大文件进行上传,用 bucket tag 进行查询
可以看到文件本身存储到 HDFS 中。
之前演示的效果图如下:
四、总结
虽然我们基本已经完成了老板交给待的任务,项目也正常上线了,但是从设计上我们依旧遗留两个问题
.RowKey 的热点问题,因为我们是将容器名称和标签作为前缀,那么势必当存储的文件达到一定程度之后会导致热点,影响 HBase 的稳定性。
.无法支持复杂的查询,并且由于用于查询条件的标签是开始就固定的 RowKey 当中的,所以对于之后如果想扩展标签的维度势必会照常困难。
那么正对上面的两个问题,我们应该如何解决呢?
.其实上面的两个问题,都是由于我们将 RowKey 作为查询的依据而产生的问题,那么我们是否可以将查询所使用到的字段不存储在RowKey 上呢?这样我们就可以使用一个随机的 RowKey 避免热点问题。答案是特定的。
.方案一:使用 HBase+phoenix 的方案,创建容器名称和标签的二级缩影,查询通过 phoenix 二级索引进行加速,支持更多字段的查询,后期扩展时只需要添加更多的二级索引,可以通过更多标签查询。
方案二:使用 HBase+ElasticSearch 方案,索引信息通过 hbase-indexer 服务同步到 ES 中,同步相当于标签信息,查询的时候先查询 ES 获取 RowKey 再从 HBase 中获取明细数据,通过 ES 可以支持更多复杂的查询条件。
是否有更好的方案?
阿里巴巴引动多能引擎,集成了宽表、时序、搜索、文件、键值,完全可以支持统一的存储,通过收纳的功能提供更复杂的查询来满足需求。