HBase 是一个基于 Hadoop 面向列的非关系型分布式数据库(NoSQL), 设计概念来源于谷歌的 BigTable 模型,面向实时读写、随机访问大规模数据集的场景,是一个高可靠性、高性能、高伸缩的分布式存储系统,在大数据相关领域应用广泛. HBase 系统支持对所存储的数据进行透明切分,从而使得系统的存储以及计算具有良好的水平扩展性.
知乎从 2017 年起开始逐渐采用 HBase 系统存储各类在线业务数据,并在 HBase 服务之上构建各类应用模型以及数据计算任务;伴随着知乎这两年的发展,知乎核心架构团队基于开源容器调度平台 Kubernetes 打造了一整套 HBase 服务平台管理系统,经过近两年的研发迭代,目前已经形成了一套较为完整的 HBase 自动化运维服务体系,能够完成 HBase 集群的快捷部署,平滑扩缩容,HBase 组件细粒度监控,故障跟踪等功能.
知乎对 HBase 的使用经验不算太长,在 2017 年初的时候,HBase 服务主要用于离线算法,推荐,反作弊,还有基础数据仓库数据的存储计算,通过 MapReduce 和 Spark 来进行访问. 而在当时知乎的在线存储主要采用 MySQL 和 Redis 系统,其中:
针对以上两种在线存储所存在的一些问题,我们希望建立一套在线存储 NoSQL 服务,对以上两种存储作为一个补充;选型期间我们也考虑过 Cassandra, 早期一些业务曾尝试使用 Cassandra 作为存储,隔壁团队在运维了一段时间的 Cassandra 系统之后,遇到不少的问题,Cassandra 系统可操作性没有达到预期,目前除了 Tracing 相关的系统,其他业务已经放弃使用 Cassandra.
我们从已有的离线存储系统出发,在衡量了稳定性,性能,代码成熟度,上下游系统承接,业界使用场景以及社区活跃度等方面之后,选择了 HBase,作为知乎在线存储的支撑组件之一.
初期知乎只有一套进行离线计算的集群,所有业务都跑在一个集群上,并且 HBase 集群和其他离线计算 yarn 以及 Impala 混合部署,HBase 的日常离线计算和数据读写都严重受到其他系统影响;并且 HBase 的监控都只停留在主机层面的监控,出现运行问题时,进行排查很困难,系统恢复服务时间较长,这种状态下,我们需要重新构建一套适用于在线服务的系统.
在这样的场景下,我们对在线 HBase 服务的需求是明确的:
综合以上需求,参考我们团队之前对基础设施平台化的经验,最终的目标是把 HBase 服务做成基础组件服务平台向提供给上游业务,这个也是知乎技术平台部门工作思路之一,尽可能的把所有的组件对业务都黑盒化,接口化,服务化. 同时在使用和监控的粒度上尽可能的准确,细致,全面. 我们构建在线 HBase 管理运维系统的一个初衷.
前文说到我们希望将整个 HBase 系统平台服务化,那就涉及到如何管理和运维 HBase 系统,知乎在微服务和容器方面的工作积累和经验是相当丰富的,在当时我们所有的在线业务都已经完成了容器化的迁移工作,超万级别的业务容器平稳运行在基于 mesos 的容器管理平台 Bay 上(参见[1]);与此同时,团队也在积极的做着 Infrastructure 容器化的尝试,已经成功将基础消息队列组件 Kafka 容器化运行于 Kubernetes 系统之上 (参见[2]),因此我们决定也将 HBase 通过 Kubernetes 来进行资源的管理调度.
Kubernetes[3] 是谷歌开源的容器集群管理系统,是 Google 多年大规模容器管理技术 Borg 的开源版本. Kubernetes 提供各种维度组件的资源管理和调度方案,隔离容器的资源使用,各个组件的 HA 工作,同时还有较为完善的网络方案. Kubernetes 被设计作为构建组件和工具的生态系统平台,可以轻松地部署、扩展和管理应用程序. 有着 Kubernetes 大法的加持,我们很快有了最初的落地版本([4]).
最初的落地版本架构见下图,平台在共享的物理集群上通过 Kubernetes(以下简称 K8S) API 建立了多套逻辑上隔离的 HBase 集群,每套集群由一组 Master 和若干个 Regionserver (以下简称 RS) 构成, 集群共享一套 HDFS 存储集群,各自依赖的 Zookeeper 集群独立;集群通过一套管理系统 Kubas 服务来进行管理([4]).
第一代架构模块定义
在 K8S 中如何去构建 HBase 集群,首先需要用 K8S 本身的基础组件去描述 HBase 的构成;K8S 的资源组件有以下几种:
结合之前 Kafka on K8S 的经验,出于高可用和扩展性的考虑,我们没有采用一个 Pod 里带多个容器的部署方式,统一用一个 ReplicationController 定义一类 HBase 组件,就是上图中的 Master,Regionserver 还有按需创建的 Thriftserver;通过以上概念,我们在 K8S 上就可以这样定义一套最小 HBase 集群:
作为面向在线业务服务的系统,高可用和故障转移是必需在设计就要考虑的事情,在整体设计中,我们分别考虑组件级别,集群级别和数据存储级别的可用性和故障恢复问题.
组件级别
HBase 本身已经考虑了很多故障切换和恢复的方案:
集群级别
数据级别
初期物理节点统一采用 2*12 核心的 cpu,128G 内存和 4T 的磁盘,其中磁盘用于搭建服务的 HDFS,CPU 和内存则在 K8S 环境中用于建立 HBase 相关服务的节点.
Master 组件的功能主要是管理 HBase 集群,Thriftserver 组件主要承担代理的角色,所以这两个组件资源都按照固定额度分配.
在对 Regionserver 组件进行资源分配设计的时候,考虑两种方式去定义资源:
介于当时考虑接入的在线业务并不多,所以采用了按业务定制的方式去配置 Regionserver, 正式环境同一业务采用统一配置的一组Regionserver,不存在混合配置的 Regionserver 组.
基础镜像基于 cdh5.5.0-hbase1.0.0 构建
# Example for hbase dockerfile# install cdh5.5.0-hbase1.0.0ADDhdfs-site.xml /usr/lib/hbase/conf/ADDcore-site.xml /usr/lib/hbase/conf/ADDenv-init.py /usr/lib/hbase/bin/ENVJAVA_HOME /usr/lib/jvm/java-8-oracleENVHBASE_HOME /usr/lib/hbaseENVHADOOP_PREFIX /usr/lib/hadoopADDenv-init.py /usr/lib/hbase/bin/ADDhadoop_xml_conf.sh /usr/lib/hbase/bin/
REQUEST_DATA={"name":'test-cluster',"rootdir":"hdfs://namenode01:8020/tmp/hbase/test-cluster","zkparent":"/test-cluster","zkhost":"zookeeper01,zookeeper02,zookeeper03","zkport":2181,"regionserver_num":'3',"codecs":"snappy","client_type":"java","cpu":'1',"memory":'30',"status":"running",}
通过上面的参数 Kubas Service 启动 Docker 时,在启动命令中利用 hadoop_xml_conf.sh 和 env-init.py 修改 hbase-site.xml 和 hbase-env.sh 文件来完成最后的配置注入,如下所示:
source/usr/lib/hbase/bin/hadoop_xml_conf.sh&&put_config --file /etc/hbase/conf/hbase-site.xml --property hbase.regionserver.codecs --value snappy&&put_config --file /etc/hbase/conf/hbase-site.xml --property zookeeper.znode.parent --value /test-cluster&&put_config --file /etc/hbase/conf/hbase-site.xml --property hbase.rootdir --value hdfs://namenode01:8020/tmp/hbase/test-cluster&&put_config --file /etc/hbase/conf/hbase-site.xml --property hbase.zookeeper.quorum --value zookeeper01,zookeeper02,zookeeper03&&put_config --file /etc/hbase/conf/hbase-site.xml --property hbase.zookeeper.property.clientPort --value2181&&service hbase-regionserver start&&tail -f /var/log/hbase/hbase-hbase-regionserver.log
网络方面,采用了 Kubernetes 上原生的网络模式,每一个 Pod 都有自己的 IP 地址,容器之间可以直接通信,同时在 Kubernetes 集群中添加了 DNS 自动注册和反注册功能, 以 Pod 的标识名字作为域名,在 Pod 创建和重启和销毁时将相关信息同步全局 DNS.
在这个地方我们遇到过问题,当时我们的 DNS 解析不能在 Docker 网络环境中通过 IP 反解出对应的容器域名,这就使得 Regionserver 在启动之后向 Master 注册和向 Zookeeper 集群注册的服务名字不一致,导致 Master 中对同一个 Regionserver 登记两次,造成 Master 与 Regionserver 无法正常通信,整个集群无法正常提供服务.
经过我们对源码的研究和实验之后,我们在容器启动 Regionserver 服务之前修改 /etc/hosts 文件,将 Kubernetes 对注入的 hostname 信息屏蔽;这样的修改让容器启动的 HBase 集群能够顺利启动并初始化成功,但是也给运维提升了复杂度,因为现在 HBase 提供的 Master 页现在看到的 Regionserver 都是 IP 形式的记录,给监控和故障处理带来了诸多不便.
初代架构顺利落地,在成功接入了近十个集群业务之后,这套架构面临了以下几个问题:
为了进一步解决初版架构存在的问题,优化 HBase 的管控流程,我们重新审视了已有的架构,并结合 Kubernetes 的新特性,对原有的架构进行升级改造,重新用 Golang 重写了整个 Kubas 管理系统的服务 (初版使用了 Python 进行开发) ,并在 Kubas 管理系统的基础上,开发了多个用于监控和运维的基础微服务,提高了在 Kubernetes 上进行 HBase 集群部署的灵活性,架构如下图所示:
二代架构图Deployment & Config Map
在引入了 ConfigMap 功能之后,之前创建集群的请求信息也随之改变.
RequestData
{
"name": "performance-test-rmwl",
"namespace": "online",
"app": "kubas",
"config_template": "online-example-base.v1",
"status": "Ready",
"properties": {
"hbase.regionserver.codecs": "snappy",
"hbase.rootdir": "hdfs://zhihu-example-online:8020/user/online-tsn/performance-test-rmwl",
"hbase.zookeeper.property.clientPort": "2181",
"hbase.zookeeper.quorum": "zookeeper01,zookeeper02,zookeeper03",
"zookeeper.znode.parent": "/performance-test-rmwl"
},
"client_type": "java",
"cluster_uid": "k8s-example-hbase---performance-test-rmwl---example"
}
其中 config_template 指定了该集群使用的配置信息模板,之后所有和该 HBase 集群有关的组件配置都由该配置模板渲染出具体配置.
config_template 中还预先约定了 HBase 组件的基础运行配置信息,如组件类型,使用的启动命令,采用的镜像文件,初始的副本数等.
servers:
{
"master": {
"servertype": "master",
"command": "service hbase-master start && tail -f /var/log/hbase/hbase-hbase-master.log",
"replicas": 1,
"image": "dockerimage.zhihu.example/apps/example-master:v1.1",
"requests": {
"cpu": "500m",
"memory": "5Gi"
},
"limits": {
"cpu": "4000m"
}
},
}
Docker 镜像文件配合 ConfigMap 功能,在预先约定的路径方式存放配置文件信息,同时在真正的 HBase 配置路径中加入软链文件.
RUNmkdir -p /data/hbase/hbase-siteRUNmv /etc/hbase/conf/hbase-site.xml /data/hbase/hbase-site/hbase-site.xmlRUNln -s /data/hbase/hbase-site/hbase-site.xml /etc/hbase/conf/hbase-site.xmlRUNmkdir -p /data/hbase/hbase-envRUNmv /etc/hbase/conf/hbase-env.sh /data/hbase/hbase-env/hbase-env.shRUNln -s /data/hbase/hbase-env/hbase-env.sh /etc/hbase/conf/hbase-env.sh
结合之前对 Deployment 以及 ConfigMap 的引入,以及对 Dockerfile 的修改,整个 HBase 构建流程也有了改进:
HBase on Kubernetes 构建流程通过结合 K8S 的 ConfigMap 功能的配置模板,以及 Kubas API 调用,我们就可以在短时间部署出一套可用的 HBase 最小集群 ( 2Master + 3RegionServer + 2Thriftserver), 在所有宿主机 Host 都已经缓存 Docker 镜像文件的场景下,部署并启动一整套 HBase 集群的时间不超过 15 秒.
同时在缺少专属前端控制台的情况下,可以完全依托 Kubernetes dashboard 完成 HBase 集群组件的扩容缩容,以及组件配置的查询修改更新以及重新部署.
在完成重构之后,HBase 服务面向知乎内部业务进行开放,短期内知乎 HBase 集群上升超过30+ 集群,伴随着 HBase 集群数量的增多,有两个问题逐渐显现:
为了解决如上的两个问题,同时又不能打破资源隔离的需求,我们将 HBase RSGroup 功能加入到了HBase 平台的管理系统中.
优化后的架构如下:
RSGroup 的使用由于平台方对业务 HBase 集群的管理本身就具有隔离性,所以在进行更进一步资源管理的时候,平台方采用的是降级的方式来管理 HBase 集群,通过监听每个单独集群的指标,如果业务集群的负载在上线一段时间后低于阈值,平台方就会配合业务方,将该 HBase 集群迁移到一套 Mixed HBase 集群上.
同时如果在 Mixed HBase 集群中运行的某个 HBase 业务负载增加,并持续一段时间超过阈值后,平台方就会考虑将相关业务提升至单独的集群.
随着知乎业务的发展和扩大,知乎的基础架构逐渐升级至多机房架构,知乎 HBase 平台管理方式也在这个过程中进行了进一步升级,开始构建多机房管理的管理方式;基本架构如下图所示:
多 IDC 访问方式在各类业务场景中,都存在跨 HBase 集群的数据同步的需求,比如数据在离线 HBase 集群和在线集群同步,多 IDC 集群数据同步等;对于 HBase 的数据同步来说,分为全量复制和增量复制两种方式;
HBase 数据同步在知乎 HBase 平台中,我们采用两种方式进行 HBase 集群间的数据同步
全量数据复制我们采用了 HBase Snapshot 的方式进行;主要应用在离线数据同步在线数据的场景;
主要用于 HBase 集群之间的的增量数据同步;增量复制我们没有采用 HBase Replication,相关同步方式我们通过自研的 WALTransfer 组件来对 HBase 数据进行增量同步;
WALTransfer 通过读取源数据 HBase 集群提供 WAL 文件列表,于 HDFS 集群中定位对应的 WAL 文件,将 HBase 的增量数据按序写入到目的集群,相关的细节我们会在以后的文章中详细解析
从之前重构后的架构图上我们可以看到,在 Kubas 服务中我们添加了很多模块,这些模块基本属于 HBase 平台的监控管理模块.
Kubas-Monitor 组件
基本的监控模块,采用轮询的方式发现新增 HBase 集群,通过订阅 Zookeeper 集群发现 HBase 集群 Master 以及 Regionserver 组.
采集 Regionserver Metric 中的数据,主要采集数据包括:
其他维度的指标如容器 CPU 以及 Mem 占用来自 Kubernetes 平台监控,磁盘 IO,磁盘占用等来自主机监控
HBase 部分监控Kubas-Region-Inspector 组件
通过以上模块采集的监控信息,基本可以描述在 Kubernetes 上运行的 HBase 集群的状态信息,并能够辅助运维管理人员对故障进行定位排除.
随着公司业务的快速发展,知乎的 HBase 平台业务同时也在不断的迭代优化,短期内我们会从以下几个方向进一步提升知乎 HBase 平台的管理服务能力:
HBase 在知乎的推广应用从 2017 年开始,平台架构经过了若干个版本的迭代最终稳定,在这里感谢 @bzy 在前期的铺垫,感谢 @高勋为资源隔离化和资源利用率优化所做的工作. 特别感谢 @王政英在使用 HBase 服务期间给我们提供的建议和 downtime.
知乎核心架构团队负责解决知乎业务复杂度和并发规模提升给核心资源调度以及数据存储架构带来的问题以及挑战,随着知乎用户和业务规模的快速增长,以及基础架构复杂度的持续提升,团队面临的技术挑战也越来越多,目前正在持续实施多机房异地多活的架构改造和资源的优化,努力保障和提升知乎核心架构的质量和稳定性,欢迎对技术感兴趣、渴望技术挑战的小伙伴与 [email protected] /[email protected] 联系.
[1] 知乎基于 Kubernetes 的 Kafka 平台的设计和实现
[2] 知乎容器平台演进及与大数据融合实践
[3] Kubernetes
[4] Building online hbase cluster of zhihu based on kubernetes