作者:陈云龙
MySql的性能瓶颈
虽说关系型数据库,如MySql已经足够满足我们大部分活动开发的需求,然而有些时候你可曾面对产品看似普通且合理的需求,例如:1、能否让网页活动拉取用户的游戏好友关系链从而更精准的推送Tips?2、判断用户是否在所有大区都没有角色这类需求时却感到了深深的蛋疼,Why?
在现有条件下,我们处理需求1时的方法通常是用DC从tlog中拉取用户好友信息并以如下可能的两种方式建库存储:
左边的表结构是将每个用户的一个好友作为一条记录插入库中,通常我们也就是这么做的。然而如果有百万、千万级的用户,每个用户又有着多个好友。且不考虑单台服务器能否承载如此大的数据,从如此庞大的数据中检索出单个用户好友的sql语句的效率可以想象。右边的表结构虽然每个用户只有一条记录,查询效率不错,但是不可行。因为关系型数据库的表结构是固定的,我们无从得知一个用户会有多少个好友,因此无法确定字段数。肿么办?
需求2的通常处理方法同需求1,不仅效率低tlog拉数据还有着一天的数据延迟,用户体验极差。什么?你说可以通过IDIP接口来实时查询用户是否有角色?如果你觉得炫舞39个大区发39条IDIP指令可以不超时的话可以那么做….
HBase简介
为了解决上述问题,我们决定引入HBase,HBase是一个稀疏的,长期存储的,多维度的,排序的映射表,采用Key-Value方式存储数据。这张表的索引是行关键字,列关键字和时间戳。每个值是一个不解释的字符数组,数据都是字符串,没类型,属于非关系型的分布式数据库。
如果采用HBase,存储用户各大区注册时间信息的表结构可以变成这样:
row key:是用来检索记录的主键,其作用类似于Mysql中的主键。访问hbase table中的行,只有三种方式:
1 、通过单个row key访问
2 、通过row key的range
3、 全表扫描
Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
列族
Column Family: HBase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。
TimeStamp:HBase中通过row和columns确定的一个数据存贮单元称为cell。每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
Cell:由{row key, column( =<family> <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。
由于HBase采用Key-Value的存储格式,将用户QQ号作为rowkey就能高效的查询到所对应的信息字符串,加以解析即可得到我们所需要的结果。由于HBase是非关系型数据库,Table在水平方向有一个或者多个Column Family组成,一个Column Family中可以由任意多个Column组成,即Column Family支持动态扩展,无需预先定义Column的数量以及类型。在HBase中,表结构还可以是这样的:
需要注意的是,HBase中所有的行都按照字典序进行排列,字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,如下图:
行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。
HBase不支持条件查询和Order by等查询,读取记录只能按Row key(及其range)或全表扫描,因此Row key需要根据业务来设计以利用其存储排序特性(Table按Row key字典序排序如1,10,100,11,2)提高性能,如果我们希望将行按自然顺序排列,可以在最左边补0。
上述看起来似乎还是没解决问题,如果存放海量用户的关系链数据,单台服务器还是难以负载,HBase又是如何解决数据的存储与检索问题的呢?
HBase的数据存储与检索原理
HBase在行的方向上可以分为多个region,一个region类似于一张mysql中的表.HRegion与表的不同之处在于随着数据不断插入表,region不断增大,当增大到一个阀值的时,Hregion就会等分会两个新的Hregion。当table中的行不断增多,就会有越来越多的Hregion。
Region是HBase分布式存储的最小单位,数据存放在不同服务器上的的region中,而一个region只可能存放于一台服务器上,那么当我们需要从HBase中查询数据的时候,它又是怎样从分布在不同机器上的region中检索数据的呢?
这是由于每个region都记录了Row的Start和Endkey,并由Master交给相应的RegionServer进行管理存储,如下图所示:
那么是如何得知所要查询的数据存放在哪个RegionServer上呢?在HBase中.META.表记录了每个RS上存放数据的Start key和End Key以及RegionServer地址等信息。
现在假设我们要从Table里面查询一条RowKey是100的数据。那么我们应该遵循以下步骤:
- 从.META.表里面查询哪个Region包含这条数据。
- 获取管理这个Region的RegionServer地址。
- 连接这个RegionServer, 查到这条数据。
然而问题也随之而来,.META.自己也是一张表,虽然它记录了数据在RS中的位置信息,如果表的region实在太多导致META表中的数据也多到让其自身分割为多个region存放于不同机器上我们该如何寻址?因此在.META.表之上还有一层-ROOT-表,它用来存放所有.META.表的信息且只有一个region,再由ZooKeeper记录ROOT表的位置。因此假设要检索某条数据,大致流程如下:
HBase存储系统的架构:
HBase分布式数据库,只是分布式存储系统中的一部分,其系统组成包含HDFS、Region Server、Master节点、ZooKeeper集群等四大部分。各部分的具体功能如下描述:
HDFS:
HBase底层存储上依赖于HDFS,HDFS保证了HBase的高可靠性。HDFS为Region Server和Master节点提供分布式存储服务,同时保证数据的可靠性,主要功能如下:
1、提供元数据和表数据的存储
2、数据多副本保存,保证数据的高可靠性和高可用性
Master节点主要负责:
1、为Region Server(在hbase中称之为Region Server)分配region。
2、负责整个集群的负载均衡
3、维护集群中的元数据
4、负责监控整个集群,发现失效的Region Server,并重新分配其上的Region
RegionServer节点主要负责:
代码语言:txt复制 1、管理Master分配的Region,处理来自客户端对Region的读取工作。
代码语言:txt复制 2、切分在运行过程中变的过大的Region。
代码语言:txt复制 3、负责和底层的HDFS交互,存储数据。
Zookeeper节点功能:
代码语言:txt复制 1、保证集群中仅仅存在一个Master能够运行。
代码语言:txt复制 2、监控Region Server的状态,通过回调的形式通知Master RegionServer的上下线的信息。
代码语言:txt复制 3、存储寻址的入口地址。
Hbase存储系统基本上按照google bigtable的论文来实现,基本上的架构如下:
HBase——不仅仅是存储性能的提升
由上图可知,HBase存储系统的底层存储依赖于HDFS(分布式文件系统),分布式的存储方式决定了它优于普通数据库的海量数据存储能力,可是它优秀的仅仅只是存储性能吗?NO!
假如有一个活动,产品想提出所有于某日之后新玩家(所有大区均无角色)名单,并为他们发放新手礼包。按照我们运营开发的传统思想,只可能通过DC从tlog里拉取注册玩家信息,并通过定时脚本遍历该玩家所有大区的角色信息。且不谈这数据量之大mysql是否能够承受,即使能承受,从如此海量的数据找出我们所需要的答案,仅凭脚本所在的一台机器,这个脚本要跑到猴年马月?
既然一台机器的运算能力不够,那多台机器又如何呢?考虑到HBase中的数据存储于多台服务器中,如果能在每台服务器中执行脚本得出结果,再将结果进行合并岂不是能大幅度提高运算效率?
谷歌的MapReduce 编程模型为我们提供了解决方法,它通常把一个问题分成两个子步骤:Map 函数被用来采用大输出并将其分为若干个更小的块,然后将此数据交给其它空闲并且能够用它做一些事情的进程。而Reduce 函数的功能则
是将单一答案从Map所产生的若干个中间输出中化简并带给最终输出。对于上述假设活动,如果运用HBase MapReduce处理该需求的过程如下:
HBase对于活动开发来说不仅仅是提供了大数据的存储能力,还提供了高效的离线并行计算、数据分析能力,对于将来的活动开发有着极大的意义。
上述就是我总结的一些HBase知识小结,希望大家积极分享、拍砖共同学习。