当前位置: 首页 > 科技观察

HBase在人工智能场景中的使用

时间:2023-03-14 15:44:33 科技观察

近年来,人工智能逐渐流行起来,尤其是与大数据结合的时候。人工智能的主要场景包括图像能力、语音能力、自然语言处理能力、用户画像能力。在这些场景中我们都需要处理海量的数据,处理后的数据一般都需要存储。这些数据的主要特点如下:大:数据量越大,越有利于我们后续建模;sparse:每一行数据都可能有不同的属性,比如用户画像数据,每个人的属性差异很大,可能用户A有这个属性,而用户B没有;那么我们希望存储系统能够处理这种情况,没有属性不占用底层空间,这样可以节省大量的空间使用;列动态变化:每行数据都有不同数量的列。为了更好的介绍HBase在人工智能场景中的使用,下面通过一个人工智能行业的客户案例来分析如何使用HBase设计一个人脸特征快速查找系统。目前公司业务场景中与人脸相关的特征数据非常多,总数超过3400万条,每张人脸数据约3.2k条。这些人脸数据被分成很多组,每个人脸特征都属于某一组。目前总共有近62W个人脸组,每个组的人脸数量从1到1W不等。每组都会包含同一个人不同形式的人脸数据。组和人脸分布如下:约43%的组包含1张人脸数据;大约47%的组包含2到9个人脸数据;其余组的范围从10到10000张面孔。目前的业务需求主要分为以下两类:根据人脸分组id查找分组下的所有人脸;根据facegroupid+faceid查找某张脸的具体数据。在MySQL+OSS方案之前,业务数据量比较小的时候,使用的存储主要是MySQL和OSS(对象存储)。相关表主要包括人脸组表组和人脸表人脸。表的格式如下:组表:人脸表:特征大小为3.2k,存储在二进制数据库base64之后。这是真实的人脸特征数据。现在mysql中存储了facegroupid和faceid的对应关系,对应上面的group表;人脸id和人脸相关的特征数据存储在OSS中,对应上面的人脸表。由于每个人脸组包含的人脸特征数量相差很大(1~1W),基于上面的表设计,我们需要在每一行存储人脸组和每个人脸特征id,所以属于同一个人脸组MySQL中的数据实际上存储了很多行。比如一个人脸分组id对应的人脸特征数量为1W,那么MySQL中需要存储1W行。如果我们需要根据人脸组id查找组下的所有人脸,需要从mysql中读取多行数据,获取人脸组与人脸的对应关系,然后去OSS中查找所有人脸根据人脸id获取所有人脸相关的特征数据,如下图左侧部分所示。我们从上图中的查询路径可以看出,这样的查询导致了一个很长的链接。从上面的设计可以看出,如果查询组中包含大量的人脸,那么我们需要从MySQL中扫描很多行,然后从OSS中获取这些人脸的特征数据。整个查询时间在10s左右,远远不能满足现有业务快速发展的需要。上述HBase方案的设计方案存在两个问题:由于数据本身的大小,原本属于同一条数据的内容无法存储在一行中,导致后续需要访问两个存储系统查询;因为MySQL不支持动态列的特性,所以将属于同一个面组的数据拆分成多行存储。针对以上两个问题,我们进行了分析,得出的结论是这是一个典型的HBase场景。原因如下:HBase具有动态列的特性,支持万亿行、百万列;HBase支持多版本,所有修改都会记录在HBase中;HBase2.0引入了MOB(Medium-SizedObject)特性来支持小文件存储。HBase的MOB特性针对1k到10MB大小的文件,如图片、短视频、文档等,具有低延迟、读写强一致性、检索能力强、易于水平扩展。我们可以利用这三个功能重新设计上面的MySQL+OSS方案。结合以上应用场景的两个查询需求,我们可以将人脸分组id作为HBase的Rowkey。系统设计如上图右侧所示。创建表时,开启MOB函数,如下:create'face',{NAME=>'c',IS_MOB=>true,MOB_THRESHOLD=>2048}上面我们创建了一个名为face的表,IS_MOB属性表示columnclusterc会开启MOB特性,MOB_THRESHOLD是MOB文件大小的阈值,单位是字节,这里的设置表示文件大于2k的列作为小文件存储。大家可能已经注意到,上面的原始方案中使用了OSS对象存储,那我们为什么不直接使用OSS来存储人脸特征数据呢?如果你有这样的疑问,可以看看下表的性能测试:根据上面的对比,使用HBaseMOB特性存储小于10MB的对象比直接使用对象存储有一些优势。下面我们来看一下具体的表设计,如下图所示:上面HBase表的列簇名是c,我们使用faceid作为列名。我们只用了HBase的一张表来代替之前的三张表!虽然我们启用了MOB,但是具体的插入方式和正常使用是一样的。代码片段如下:StringCF_DEFAULT="c";Putput=newPut(groupId.getBytes());put.addColumn(CF_DEFAULT.getBytes(),faceId1.getBytes(),feature1.getBytes());put.addColumn(CF_DEFAULT.getBytes(),faceId2.getBytes(),feature2.getBytes());......put.addColumn(CF_DEFAULT.getBytes(),faceIdn.getBytes(),featuren.getBytes());表.put(put);如果用户需要根据人脸组id获取所有的人脸数据,可以使用如下方法:Getget=newGet(groupId.getBytes());resultre=table.get(get);这样我们就可以得到某个人脸组id对应的所有人脸数据。如果需要根据人脸组id+人脸id查找某个人脸的具体数据,可以使用如下方法:getget=newGet(groupId.getBytes());get.addColumn(CF_DEFAULT.getBytes(),faceId1.getBytes())Resultre=table.get(get);经过以上改造,两个HBaseworker节点的内存为32GB,核数为8,每个节点挂载4块SSD盘,大小为250GB,写入100W行。每行有1W列,读取一行的时间大概在100ms-500ms。当每行有1000张人脸时,读取一行的时间基本在20-50ms左右,比之前的10s提升了200-500倍。下面是各个程序的性能比较对比。使用Spark加速数据分析。我们在阿里云HBase中存储了人脸特征数据。这只是数据应用的第一步。如何挖掘出这些数据背后隐藏的价值?这就需要借助数据分析了。在这种场景下,就需要使用机器学习的方法进行聚类等操作。我们可以使用Spark来分析存储在HBase中的数据,Spark本身就支持机器学习。但是如果直接使用开源的Spark读取HBase中的数据,会对HBase本身的读写造成影响。针对这些问题,阿里云HBase团队对Spark进行了优化,比如直接读取HFiles、下沉算子等;它还提供完全托管的Spark产品,并通过SQL服务ThriftServer和作业服务LivyServer简化Spark的使用。目前的Spark技术栈如下图所示。通过Spark服务,我们可以很好的与HBase集成,实现实时流式处理和人脸特征挖掘的集成。整个架构图如下:我们可以从各种人脸数据源采集实时数据,通过SparkStreamingOperation进行简单的ETL;其次,我们使用SparkMLib类库对刚才采集的数据进行人脸特征挖掘,最后将挖掘的结果存储在HBase中。***,用户可以访问HBase中挖掘出的人脸特征数据,供其他应用使用。