基于HBase的工业大数据存储实战现场总线技术在物品追踪中的应用实例
随着工业4.0时代的到来,工业互联网和企业的智能化、信息化都将不断推进,传统的工业实时数据库和关系数据库已经难以完全胜任工业大数据的存储,以HBase为代表的NoSQL数据库正在蓬勃发展,其完全分布式特征、高性能、多副本和灵活的动态扩展等特点,使得HBase在工业大数据的存储上拥有强大的优势,打破了流程工业生产中的数据壁垒效应,促进了生产水平和管理水平的大幅提升。本期格物汇,就来给大家介绍HBase数据库及格创东智相关实战案例。
了解HBase
HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统。它利用Hadoop HDFS作为其文件存储系统,将海量数据处理成结构化形式,从而实现快速查询与高效分析。与Google Bigtable相比,虽然两者都是基于Bigtable设计,但有显著差异:前者采用ZooKeeper协调服务,而后者使用Chubby;另外,它们支持不同类型的应用场景。
与传统数据库相比,HBASE具备多重优势:
线性扩展,即使数据量巨大,也能通过增加节点进行支撑。
数据存储在hdfs上,有健全备份机制。
通过zookeeper协作找到数据,加快访问速度。
HBase实战案例
为了更好地展示HBase在人工智能场景下的应用,我们将以某半导体显示企业为案例,再现格创东智如何利用HBase设计出一个快速查找面板特征系统。
该公司业务中涉及大量面板相关特征,每张面板包含3.2k二进制数据,这些面板被分组,其中43%的小组只有一张面板,大约47%的小组包含2至9张面板,而剩余小组内面的数量从10到10000不等。现有业务需求主要包括:
根据组ID查找该组下所有面的数据。
根据组ID加上面的ID查找具体一张面的详细信息。
原方案是MySQL+OSS(对象存储),但由于每个小群含有的玻璃数目极度不均匀,该方案存在两个问题:
面板ID对应关系保存于MySQL中,对应group表;玻璃ID对应特征保存于OSS中,对应face表。这意味着同一群体下的玻璃可能需要多次读取MySQL行才能获取所需信息。
MySQL不支持动态列,因此同一群体内玻璃被拆分成多行保存,这进一步延长了查询时间。在当前解决方案下,如果要根据小群id查询所有玻璃所需时间达到了十秒左右,不足以满足业务增长需求。
HBase解决方案
针对以上问题,由格创东智的大数据团队提出使用HBASE进行优化。此举基于以下考虑:
Hbase提供动态列功能,可以支持万亿行百万列。
支持版本控制,为修改记录提供完整历史追踪能力。
自带MOB(Medium-SizedObject)功能,可用于处理大小介于1k~10MB范围内的小文件,如图片或视频文档等,这种配置能够提供低延迟读写、一致性检索以及易扩展性强等关键能力。
结合应用场景双重查询需求,将Face ID设置为RowKey,并开启MOB功能创建glass表,如下所示:
create 'glass', {NAME=>'c', IS_MOB=>true, MOB_THRESHOLD=>2048}
这里我们创建名为glass 的表,在创建时打开MOB功能并设定MOB_THRESHOLD = 2048,即当文件大小超过2048字节时视作小文件处理。这意味着原始方案中的OSS对象直接转移到了这个新的设计中去,但是这一选择实际上并非最佳,因为它忽略了一些潜在优势,如K-V键值模型搜索能力,以及稀疏表搜索方式。但是,它确实在复杂查询场景下的表现优于标准对象存储,同时成本计费方式更加合理适用于高并发、高吞吐率环境,同时适用范围也更广泛,无论是通用还是专门小型对象,都能得到较好的效果。而且,在操作频率较低的情况下,比起直接使用OSS来访问这些相同大小的事务来说,更具有成本效益。
最后,我们看看具体如何编写Put方法插入新记录:
Put put = new Put(groupId.getBytes());
put.addColumn(CF_DEFAULT.getBytes(), glassId1.getBytes(), feature1.getBytes());
put.addColumn(CF_DEFAULT.getBytes(), glassId2.getBytes(), feature2.GetBytes());
// ...
put.put(put);
对于用户根据groupId获取所有faces情况,可以这样操作Get请求:
Get get = new Get(groupId.getByte())
get.addFamily(Bytes.toBytes("c"));
Result result = table.get(get);
List<ByteBuffer> list = result.getColumnCells(Bytes.toBytes("c"), null);
for (int i=0; i < list.size(); ++i)
{
ByteBuffer cellBuf = list.get(i).getValue();
String faceIdString;
if (cellBuf.hasRemaining()) {
byte[] bytesToParse;
// 这里解析bytesToParse,然后赋值给faceIdString
faceIds.add(faceIdString); // 将faceId加入集合
}
}
总结:通过这种改良后的设计,我们可以一次读取整个group_id所对应的一系列glass_id及其关联feature_data,而不是像之前那样逐个遍历mysql然后再去oss寻找。在这样的条件下,一次完整事务就完成了原本需要多次步骤才能完成的事务,从而极大提高了整体效率。此外,由于没有必要维护那么多单独独立行,所以减少了IO操作次数,与此同时保持了一致性的检索逻辑,使得整个过程变得既迅速又有效果。