美文网首页Java
埋头三天才把冷热分离二期实现思路:冷数据存放到HBase给搞懂

埋头三天才把冷热分离二期实现思路:冷数据存放到HBase给搞懂

作者: 愿天堂没有BUG | 来源:发表于2022-08-19 11:04 被阅读0次

    冷热分离二期实现思路:冷数据存放到HBase

    冷热分离一期解决方案的不足

    不得不说,冷热分离一期的解决方案确实能解决写操作慢和热数据慢的问题,但仍然存在诸多不足。

    1)用户查询冷数据的速度依旧很慢,虽然查询冷数据的用户比例很低。

    2)冷数据库偶尔会告警。

    这两点不足体现在用户侧是什么样呢?那就是一旦客服在工单查询表中勾选“查询归档”checkBox,页面就会一直转圈,而后台冷数据库的IO就会飙升。

    如果客服发现页面没反应,可能会多点几次“查询”按钮,那么有可能把后台服务器的请求线程占满,导致整个系统响应都很慢。

    归档的数据库里面,工单表仍然有3000多万的工单数据,工单处理记录表仍然有数亿的数据。这个查询不可能不慢。一期要做冷热分离的时候,项目组只有1周的时间(实际用了10天),但是之后有空闲,就可以好好考虑一下归档数据库的设计了。

    先说一下归档工单的查询场景。

    归档工单的使用场景

    对于归档的工单,与客服沟通后发现,基本只有以下几个查询动作。

    1)根据客户的邮箱查询归档工单。

    2)根据工单ID查出该工单所有的处理记录。而且这些操作一年做不了几次,慢一些完全没有问题。

    这些操作转化成技术需求就是:需要找到一个数据库,它可以满足下面的要求。

    1)可以存放上亿甚至数亿的数据。因为按照一年3000多万的工单来看,3年以后工单表的数据就上亿了,工单处理记录表的数据也会多出几亿。

    2)支持简单的组合关键字查询,查询慢一些可以接受。

    3)存放的数据不再需要变更。基于这个特性,就可以将历史工单的详情数据封装在一个文档中,类似于Key-Value,Key就是工单ID,Value就是工单详情数据。

    最后,项目组决定使用HBase来保存归档工单。

    为什么HBase适合这个场景?下面先简单介绍一下HBase的原理。

    HBase原理介绍

    1.HBase的基本数据结构是什么样子的?

    假设有这样一位大侠的数据(两个JSON对象)

    这样的数据在HBase中应该怎么存储?

    这就要说说HBase的数据结构“列簇存储”了。

    HBase 里 面 有 这 些 概 念 : Table 、 Row 、 Column 、 ColumnFamily 、ColumnQualifier、Cell、TimeStamp。

    其中,Table、Row与关系型数据库中的表、行含义是一样的,较易理解。

    假设对于上面的大侠数据已有一个Table,就是大侠的表。其中,郭靖是这个表中的一行数据(Row),黄蓉也是一行数据。

    那么Column、ColumnFamily、ColumnQualifier、Cell、TimeStamp又分别是什么意思?

    可以发现,大侠有3个一级属性:姓名、武功、关系。从上面的数据可以看到,武功这个一级属性,下面又有很多二级属性,比如掌法、内功、指法;关系这个一级属性,下面也有多种二级属性,比如丈夫、妻子。

    那么,可以根据这些一级属性创建3个ColumnFamily:姓名、武功、关系。ColumnFamily一开始就要定义好,类似于关系型数据库里面的列,属于schema(纲要)的范畴。

    每个ColumnFamily可以灵活增加ColumnQualifier,ColumnQualifier不需要在创建表的时候定义。比如武功的ColumnQualifier有掌法、内功、指法等,以后也可以添加。

    接下来回到HBase的列簇存储。为什么叫列簇存储?因为它们存到HBase中的其实是表1-3和表1-4那样的数据。

    每一个RowKey、TimeStamp以及Key-Value值就是一个Cell。

    Tips

    虽然姓名和武功这两个ColumnFamily属于同一个表,但是它们物理上是

    分开存储的。这也是HBase的一张表可以存放上百亿数据的原因:HBase同一

    行的数据没有存放在一起。但也是因为这个特性,HBase基本实现不了复杂

    的查询,效率也不高。

    2.HBase的物理存储模型

    HBase一个表中的数据会根据RowKey的范围划分成多个Region。

    每 个 RegionServer 是 一 个 服 务 器 节 点 , 会 包 含 多 个 Region 。 一 个RegionServer大概包含1000个Region,这个RegionServer会处理它下面所有Region的读写操作。

    这里再讲一下它们各自的关系。

    1)一个表会被水平切割成多个Region。

    2)一个Region会包含多个Row,包含startkey和endkey之间所有连续的行。每个Region的大小默认会控制在1GB内。

    3)一个Region会包含多个MemStore。

    4)一个MemStore会存储一个ColumnFamily。

    5)一个MemStore会把数据写入多个HFile。

    6)一个RegionServer会服务多个Region。

    接下来讨论一下它对于读和写请求的处理流程。

    3.HBase的写操作

    这一部分直接讲解HBase的写操作流程。

    1)客户端访问ZooKeeper,读取元数据。

    2)根据namespace、表名、RowKey找到数据对应的Region。

    3)访问Region对应的RegionServer。

    4)写入WAL(WriteAheadLog,也叫HLog)。每一个RegionServer都会维护一个WAL文件(也是基于Hadoop分布式文件系统HDFS)。所有的写操作都会先把变动加到WAL文件的末尾。WAL会保存所有未持久化的新数据。它可以用来做数据恢复。

    5)写入MemStore。MemStore相当于一个写缓存。每个Region的每个列簇都有一个MemStore。数据在写入磁盘或持久化之前,会先保存在MemStore。

    6)通知客户端写入完成。

    Tips

    MemStore的数据到达阈值时,数据会被持久化到HFile中。

    4.HBase的读操作

    HBase一次读操作的流程如下。

    1)客户端访问ZooKeeper,读取元数据。

    2)根据namespace、表名、RowKey找到数据对应的Region。

    3)访问Region对应的RegionServer。

    4)查找对应的Region。

    5)查询MemStore。

    6)找到BlockCache。每个RegionServer都有BlockCache,相当于一个读缓存。扫描器会先查询BlockCache。

    7)如果没有找到所有的Cell(单元数据),则会到多个HFile中去查找。

    HBase的表结构设计

    项 目 组 当 时 初 次 使 用 HBase , 认 真 阅 读 了 HBase 的 说 明 文 档 :

    http://HBase.apache.org/book.html。

    文档内容比较丰富,也有很多有趣的地方,比如有一处内容是关于ColumnFamily的数量的:HBase不推荐具有两个以上ColumnFamily的设计。以下是官方文档中的相关说明。

    HBase currently does not do well with anything above two or three column families so keep the number of column families in your schema low.Currently, flushing is done on a per Region basis so if one column family is carrying the bulk of the data bringing on flushes, the adjacent families will also be flushed even though the amount of data they carry is small.When many column families exist the flushing interaction can make for a bunch of needless I/O(To be addressed by changing flushing to work on a per column family basis).In addition, compactions triggered at table/region level will happen per store too.

    Try to make do with one column family if you can in your schemas.Only introduce a second and third column family in the case where data access is usually column scoped; i.e.you query one column family or the other but usually not both at the one time.

    这里简单解释一下。前面提过,每个Region有多个MemStore,每个MemStore 有 一 个 ColumnFamily 的 数 据 , 也 就 是 说 一 个 Region 有 多 个ColumnFamily的数据。MemStore的数据量到达一定的阈值以后,就会保存到HFile。MemStore->HFile这个动作被称为flushing。目前的flushing动作是Region级别的。也就是说,假设MemStore A保存ColumnFamily A的数据,里面的ColumnFamily A数据满了,那么就会触发一次flushing操作,这个MemStore所在的Region下面的所有MemStore都会flushing。但是MemStore B、MemStoreC的数据可能还很空,这种情况下就增加了很多不必要的I/O操作。

    当然,HBase里面还有很多注意事项,仅表结构设计就有很多内容,而且用到线上的业务系统比较难。不过,它还有关于实例的部分(45.SchemaDesign Case Studies)可以参考,能帮助用户快速上手。这部分文档包括以下几个方面。

    • Log Data / Timeseries Data。

    • Log Data / Timeseries on Steroids。

    • Customer/Order。

    • Tall/Wide/Middle Schema Design。

    • List Data。

    项目组从Hbase的说明文档中得出了以下设计要点。

    1)HBase的查询有两种,一种是根据RowKey直接获取记录,一种是以Scan方式扫描所有的Row。前面说过,系统有根据客户邮箱获取工单记录的需求,所以可以将邮箱名放到RowKey中,这样以后查询特定邮箱的工单时只需要扫描RowKey , 而 不 需 要 扫 描 列 的 值 , 速 度 将 大 大 加 快 。 所 以 RowKey 设 计 为[customeremail][ticketID]。

    但是customeremail是不可控的,也可能很长,导致RowKey很长。前面也提过,HBase是KeyValue存储,每个ColumnKey见表1-5。

    如果RowKey很长,就会占用很多存储空间,所以也要控制RowKey的长度。

    最终的RowKey是[MD5(customeremail)][ticketID],前面的邮箱名长度是16字节,后面的工单ID是固定长度。

    而且使用这样的设计后,如果想要根据邮箱查找工单,就可以使用正则过滤器的rowFilter,通过类似于“abc@mail.com****”的过滤字符串找出abc@mail.com的所有工单。

    2)ColumnFamily方面,项目组只会用一个“ColumnFamily:i”(HBase推荐短列名,原因是省空间)。

    3)ColumnKey方面,把这些字段都设计成i列簇下的Key,见表1-6。

    同时,为了应对可能出现的根据最后处理人或者处理小组查找归档工单的需求,还给assignedUserID等以后可能搜索的字段增加了二级索引。

    4)工单处理记录表的设计上,并没有单独为其增加HBase的表,而是将每个工单下面的处理记录全部序列化成一组JSON数据,保存在一个ColumnKey中。

    二期的代码

    改造二期和一期的主要区别就是冷数据库使用了HBase,主要的代码逻辑有一个变化,就是关于事务。

    一期的批量逻辑如下所示。

    1)取出300条工单。

    2)通过单事务包围的BATCHSQL语句插入冷数据库。

    3)通过一个单事务包围的BATCHSQL语句从热数据库中删除数据。

    二期因为HBase不支持类似的事务,所以批量逻辑如下。

    1)取出50条工单,先处理第一个工单。

    2)将当前工单的各个ColumnKey值插入HBase。

    3)通过一个单事务包围的SQL语句删除热数据库中该工单对应的数据。

    4)循环执行第2)步和第3)步,依次处理完成所有50个工单。

    加锁、多线程等其他相关的逻辑并没有变化,从MySQL这个冷数据库将数据迁移到HBase的方案可以参考一期,这里就不再赘述了。

    以上就是冷热分离二期的改造方案。二期花费了3周左右才上线,之后查询归档工单的性能好了很多,特别是单个归档工单的打开操作响应快了不少。

    这个方案还解决了一个隐患,即MySQL这个冷数据库随着归档工单数据量的增加支撑不住的问题。

    小结

    这样,冷热分离这个方案就完成了。

    冷热分离方案只是刚好适用于这个场景,它其实有很多不足。后来笔者又反思了一下,如果不是因为做一期方案的时候只有1周的时间,那么是不是还会使用冷热分离的方案?会不会有更好的方案?

    这个方案有一些明显的不足,如果碰到下面的任何一个场景,这个方案就不适用了。

    1)工单没有“归档”这一特点,经常需要修改。

    2)所有的工单数据都需要支持复杂的查询,并且需要非常快的响应速度。

    3)需要实时地对工单数据进行各种统计。笔者后来做其他项目的时候就碰到了上面列举的场景,那么,这些情况下,又该用什么方案来解决?请看下一场景。

    本文给大家讲解的内容是冷热分离一期实现思路:冷数据存放到HBase

    下篇文章给大家讲解的内容是查询分离:业务场景:千万工单表如何实现快速查询

    觉得文章不错的朋友可以转发此文关注小编;

    感谢大家的支持

    相关文章

      网友评论

        本文标题:埋头三天才把冷热分离二期实现思路:冷数据存放到HBase给搞懂

        本文链接:https://www.haomeiwen.com/subject/mpwpgrtx.html