2019 HBase Meetup 演讲者和议题征集

HBase Meetup 会议由 HBase技术社区主办,在全国各大城市举办。在过去的2018年,我们在北京、上海、杭州、深圳以及武汉等城市举办了9场 HBase Meetup 会议,来自各大公司的 HBase PMC、committer 以及 HBase 开发者共聚一堂,为大家分享了 HBase 技术解析及应用实践。 
 
2019年,我们继续在全国举办 HBase 线下交流会。现向大家征集这几次会议的大会演讲者和议题,如果大家有意来分享,可以到 http://hbasegroup.mikecrm.com/zh19LHN 参加调查问卷,欢迎大家踊跃参与。同时愿意主动主办的公司,也可以来联系我。
 
议题范围:HBase、Spark、Phoenix、Solr、时序、时空以及图等使用案例,相关经验等。
投送议题时需要说明议题名称和简介,PPT可后续完成。
 
报名咨询:
微信:iteblog (备注 HBase Meetup)
钉钉:rvix4rb

各地 Meetup 举办时间:
 
继续阅读 »
HBase Meetup 会议由 HBase技术社区主办,在全国各大城市举办。在过去的2018年,我们在北京、上海、杭州、深圳以及武汉等城市举办了9场 HBase Meetup 会议,来自各大公司的 HBase PMC、committer 以及 HBase 开发者共聚一堂,为大家分享了 HBase 技术解析及应用实践。 
 
2019年,我们继续在全国举办 HBase 线下交流会。现向大家征集这几次会议的大会演讲者和议题,如果大家有意来分享,可以到 http://hbasegroup.mikecrm.com/zh19LHN 参加调查问卷,欢迎大家踊跃参与。同时愿意主动主办的公司,也可以来联系我。
 
议题范围:HBase、Spark、Phoenix、Solr、时序、时空以及图等使用案例,相关经验等。
投送议题时需要说明议题名称和简介,PPT可后续完成。
 
报名咨询:
微信:iteblog (备注 HBase Meetup)
钉钉:rvix4rb

各地 Meetup 举办时间:
  收起阅读 »

HBase Meetup 2019 年计划安排

2018年我们在全国各地举办了9场HBase Meetup会议。2019年,我们将继续为大家举办 HBase Meetup 会议,目前计划安排如下:
 
2018年我们在全国各地举办了9场HBase Meetup会议。2019年,我们将继续为大家举办 HBase Meetup 会议,目前计划安排如下:
 

2018年HBase生态社群画像 +最全资料汇总下载

d31742f41fe14a640fcc4b3b527f04985e00dd2c.png

f77a3002db4fae240559d8cc3fe8b92c64ed4937.png

26e49202a039fe1e3eb1adf6470f07d87716769f.png

40cc47db9c4e7f674843066f3ec2633979d75a72.png

388942cd244f470b6f655d62d3f576757dbff8af.png

a65ddc521b8fadf40f57094b44a917a9179ef60f.png

0f1570d0d4240064e6de0815d4441e0c61d988b5.png

27d414dbdcbb2d680461513a5b255b417b3fc70c.png

钉群直播全部资料下载:下载
 
e774dbc14e69ff6895ec38b329b89a30650882ec.png

2200687ad1a586fd54358ae539511f8c0ea8c15c.png

9届Meetup视频和PPT下载:下载

0ae287f151e65e060e273905f916f76805e6f5b4.png


《58HBase平台实践和应用 -平台建设篇》
何良均/张祥 58同城
查看
《HBase Rowkey 设计指南》
吴阳平 阿里巴巴HBase业务架 构师
查看
《HBase 2.0之修复工具HBCK2 运维指南》
田竞云 小米HBase Committer
查看
《从NoSQL到NewSQL,凤 凰涅槃成就Phoenix》
张赟 阿里巴巴 Phoenix Committer
查看
《消灭毛刺!HBase2.0全链路offheap效果拔群》
杨文龙 阿里巴巴技术专家 HBase Committer&HBase PMC
查看
《解读HBase2.0新功能 AssignmentManagerV2》
杨文龙 阿里巴巴技术专家 HBase Committer&Hbase PMC
查看
《深入解读HBase2.0新功能 高可用读Region Replica》
杨文龙 阿里巴巴技术专家 HBase Committer&Hbase PMC
查看
《HBase最佳实践-读性能 优化策略》
范欣欣 网易 HBase 资深开发工程师
查看
《HBase2.0新特性之In- Memory Compaction》
陆豪 阿里巴巴技术专家
查看
《HBase Coprocessor的实现与应用》
叶铿 烽火 大数据平台负责人
查看
《HBase实战之MOB使用指南》
查看
《HBase在新能源汽车监控系统中的应用》
颜禹 重庆博尼施CTO
查看
《HBase在爱奇艺的应用实践》
郑昊南 爱奇艺 资深研发工程师
查看
《HBase在滴滴出行的应用场景和最佳实践》
李扬 滴滴
查看
《HBase在人工智能场景的使用》
吴阳平 阿里HBase业务架构师
查看
《车纷享:基于阿里云 HBase构建车联网平台实践》
查看
《HBase基本知识介绍及典型 案例分析》
吴阳平 阿里HBase业务架构师
查看

9daceab1cd0cfec37d5122c10d424251cddaea94.png

89d798bac2565ae1ea89845a5f2b34f2b4555443.png

4a5346ef14879ecd66262f0f6bfbf9d178a1e6da.png

5a613c85a5a65c4777f339ad0a2a6700c3cced98.png


5d6d269caa4deb5ea85092c36ac5aa461ae8d6b2.png


HBase生态+Spark社区 云栖号:https://yq.aliyun.com/teams/382
中国 HBase 技术社区网站:http://hbase.group
继续阅读 »
d31742f41fe14a640fcc4b3b527f04985e00dd2c.png

f77a3002db4fae240559d8cc3fe8b92c64ed4937.png

26e49202a039fe1e3eb1adf6470f07d87716769f.png

40cc47db9c4e7f674843066f3ec2633979d75a72.png

388942cd244f470b6f655d62d3f576757dbff8af.png

a65ddc521b8fadf40f57094b44a917a9179ef60f.png

0f1570d0d4240064e6de0815d4441e0c61d988b5.png

27d414dbdcbb2d680461513a5b255b417b3fc70c.png

钉群直播全部资料下载:下载
 
e774dbc14e69ff6895ec38b329b89a30650882ec.png

2200687ad1a586fd54358ae539511f8c0ea8c15c.png

9届Meetup视频和PPT下载:下载

0ae287f151e65e060e273905f916f76805e6f5b4.png


《58HBase平台实践和应用 -平台建设篇》
何良均/张祥 58同城
查看
《HBase Rowkey 设计指南》
吴阳平 阿里巴巴HBase业务架 构师
查看
《HBase 2.0之修复工具HBCK2 运维指南》
田竞云 小米HBase Committer
查看
《从NoSQL到NewSQL,凤 凰涅槃成就Phoenix》
张赟 阿里巴巴 Phoenix Committer
查看
《消灭毛刺!HBase2.0全链路offheap效果拔群》
杨文龙 阿里巴巴技术专家 HBase Committer&HBase PMC
查看
《解读HBase2.0新功能 AssignmentManagerV2》
杨文龙 阿里巴巴技术专家 HBase Committer&Hbase PMC
查看
《深入解读HBase2.0新功能 高可用读Region Replica》
杨文龙 阿里巴巴技术专家 HBase Committer&Hbase PMC
查看
《HBase最佳实践-读性能 优化策略》
范欣欣 网易 HBase 资深开发工程师
查看
《HBase2.0新特性之In- Memory Compaction》
陆豪 阿里巴巴技术专家
查看
《HBase Coprocessor的实现与应用》
叶铿 烽火 大数据平台负责人
查看
《HBase实战之MOB使用指南》
查看
《HBase在新能源汽车监控系统中的应用》
颜禹 重庆博尼施CTO
查看
《HBase在爱奇艺的应用实践》
郑昊南 爱奇艺 资深研发工程师
查看
《HBase在滴滴出行的应用场景和最佳实践》
李扬 滴滴
查看
《HBase在人工智能场景的使用》
吴阳平 阿里HBase业务架构师
查看
《车纷享:基于阿里云 HBase构建车联网平台实践》
查看
《HBase基本知识介绍及典型 案例分析》
吴阳平 阿里HBase业务架构师
查看

9daceab1cd0cfec37d5122c10d424251cddaea94.png

89d798bac2565ae1ea89845a5f2b34f2b4555443.png

4a5346ef14879ecd66262f0f6bfbf9d178a1e6da.png

5a613c85a5a65c4777f339ad0a2a6700c3cced98.png


5d6d269caa4deb5ea85092c36ac5aa461ae8d6b2.png


HBase生态+Spark社区 云栖号:https://yq.aliyun.com/teams/382
中国 HBase 技术社区网站:http://hbase.group 收起阅读 »

HBase疑难杂症填坑指南

1.如何避免HBase写入过快引起的各种问题?
首先我们简单回顾下整个写入流程
client api ==> RPC ==> server IPC ==> RPC queue ==> RPC handler ==> write WAL ==> write memstore ==> flush to filesystem
1.png

整个写入流程从客户端调用API开始,数据会通过protobuf编码成一个请求,通过socket实现的IPC模块被送达server的RPC队列中。最后由负责处理RPC的handler取出请求完成写入操作。写入会先写WAL文件,然后再写一份到内存中,也就是memstore模块,当满足条件时,memstore才会被flush到底层文件系统,形成HFile。
当写入过快时会遇见什么问题?
写入过快时,memstore的水位会马上被推高。
你可能会看到以下类似日志:
RegionTooBusyException: Above memstore limit, regionName=xxxxx ...
这个是Region的memstore占用内存大小超过正常的4倍,这时候会抛异常,写入请求会被拒绝,客户端开始重试请求。当达到128M的时候会触发flush memstore,当达到128M * 4还没法触发flush时候会抛异常来拒绝写入。两个相关参数的默认值如下:
hbase.hregion.memstore.flush.size=128M 
hbase.hregion.memstore.block.multiplier=4
或者这样的日志:
regionserver.MemStoreFlusher: Blocking updates on hbase.example.host.com,16020,1522286703886: the global memstore size 1.3 G is >= than blocking 1.3 G size 
regionserver.MemStoreFlusher: Memstore is above high water mark and block 528ms
这是所有region的memstore内存总和开销超过配置上限,默认是配置heap的40%,这会导致写入被阻塞。目的是等待flush的线程把内存里的数据flush下去,否则继续允许写入memestore会把内存写爆
hbase.regionserver.global.memstore.upperLimit=0.4 # 较旧版本,新版本兼容 
hbase.regionserver.global.memstore.size=0.4 # 新版本
当写入请求由于达到memstore上限而被阻塞,队列会开始积压,如果运气不好最后会导致OOM,你可能会发现JVM由于OOM crash或者看到如下类似日志:
ipc.RpcServer: /192.168.x.x:16020 is unable to read call parameter from client 10.47.x.x 
java.lang.OutOfMemoryError: Java heap space
HBase这里我认为有个很不好的设计,捕获了OOM异常却没有终止进程。这时候进程可能已经没法正常运行下去了,你还会在日志里发现很多其它线程也抛OOM异常。比如stop可能根本stop不了,RS可能会处于一种僵死状态。
如何避免RS OOM?
一种是加快flush速度
hbase.hstore.blockingWaitTime = 90000 ms 
hbase.hstore.flusher.count = 2
hbase.hstore.blockingStoreFiles = 10
当达到hbase.hstore.blockingStoreFiles配置上限时,会导致flush阻塞等到compaction工作完成。阻塞时间是hbase.hstore.blockingWaitTime,可以改小这个时间。hbase.hstore.flusher.count可以根据机器型号去配置,可惜这个数量不会根据写压力去动态调整,配多了,非导入数据多场景也没用,改配置还得重启。
同样的道理,如果flush加快,意味这compaction也要跟上,不然文件会越来越多,这样scan性能会下降,开销也会增大。
hbase.regionserver.thread.compaction.small = 1 
hbase.regionserver.thread.compaction.large = 1
增加compaction线程会增加CPU和带宽开销,可能会影响正常的请求。如果不是导入数据,一般而言是够了。好在这个配置在云HBase内是可以动态调整的,不需要重启。
上述配置都需要人工干预,如果干预不及时server可能已经OOM了,这时候有没有更好的控制方法?
hbase.ipc.server.max.callqueue.size = 1024 * 1024 * 1024 # 1G
直接限制队列堆积的大小。当堆积到一定程度后,事实上后面的请求等不到server端处理完,可能客户端先超时了。并且一直堆积下去会导致OOM,1G的默认配置需要相对大内存的型号。当达到queue上限,客户端会收到CallQueueTooBigException 然后自动重试。通过这个可以防止写入过快时候把server端写爆,有一定反压作用。线上使用这个在一些小型号稳定性控制上效果不错。
填坑链接:如何避免HBase写入过快引起的各种问题
继续阅读 »
1.如何避免HBase写入过快引起的各种问题?
首先我们简单回顾下整个写入流程
client api ==> RPC ==> server IPC ==> RPC queue ==> RPC handler ==> write WAL ==> write memstore ==> flush to filesystem
1.png

整个写入流程从客户端调用API开始,数据会通过protobuf编码成一个请求,通过socket实现的IPC模块被送达server的RPC队列中。最后由负责处理RPC的handler取出请求完成写入操作。写入会先写WAL文件,然后再写一份到内存中,也就是memstore模块,当满足条件时,memstore才会被flush到底层文件系统,形成HFile。
当写入过快时会遇见什么问题?
写入过快时,memstore的水位会马上被推高。
你可能会看到以下类似日志:
RegionTooBusyException: Above memstore limit, regionName=xxxxx ...
这个是Region的memstore占用内存大小超过正常的4倍,这时候会抛异常,写入请求会被拒绝,客户端开始重试请求。当达到128M的时候会触发flush memstore,当达到128M * 4还没法触发flush时候会抛异常来拒绝写入。两个相关参数的默认值如下:
hbase.hregion.memstore.flush.size=128M 
hbase.hregion.memstore.block.multiplier=4
或者这样的日志:
regionserver.MemStoreFlusher: Blocking updates on hbase.example.host.com,16020,1522286703886: the global memstore size 1.3 G is >= than blocking 1.3 G size 
regionserver.MemStoreFlusher: Memstore is above high water mark and block 528ms
这是所有region的memstore内存总和开销超过配置上限,默认是配置heap的40%,这会导致写入被阻塞。目的是等待flush的线程把内存里的数据flush下去,否则继续允许写入memestore会把内存写爆
hbase.regionserver.global.memstore.upperLimit=0.4 # 较旧版本,新版本兼容 
hbase.regionserver.global.memstore.size=0.4 # 新版本
当写入请求由于达到memstore上限而被阻塞,队列会开始积压,如果运气不好最后会导致OOM,你可能会发现JVM由于OOM crash或者看到如下类似日志:
ipc.RpcServer: /192.168.x.x:16020 is unable to read call parameter from client 10.47.x.x 
java.lang.OutOfMemoryError: Java heap space
HBase这里我认为有个很不好的设计,捕获了OOM异常却没有终止进程。这时候进程可能已经没法正常运行下去了,你还会在日志里发现很多其它线程也抛OOM异常。比如stop可能根本stop不了,RS可能会处于一种僵死状态。
如何避免RS OOM?
一种是加快flush速度
hbase.hstore.blockingWaitTime = 90000 ms 
hbase.hstore.flusher.count = 2
hbase.hstore.blockingStoreFiles = 10
当达到hbase.hstore.blockingStoreFiles配置上限时,会导致flush阻塞等到compaction工作完成。阻塞时间是hbase.hstore.blockingWaitTime,可以改小这个时间。hbase.hstore.flusher.count可以根据机器型号去配置,可惜这个数量不会根据写压力去动态调整,配多了,非导入数据多场景也没用,改配置还得重启。
同样的道理,如果flush加快,意味这compaction也要跟上,不然文件会越来越多,这样scan性能会下降,开销也会增大。
hbase.regionserver.thread.compaction.small = 1 
hbase.regionserver.thread.compaction.large = 1
增加compaction线程会增加CPU和带宽开销,可能会影响正常的请求。如果不是导入数据,一般而言是够了。好在这个配置在云HBase内是可以动态调整的,不需要重启。
上述配置都需要人工干预,如果干预不及时server可能已经OOM了,这时候有没有更好的控制方法?
hbase.ipc.server.max.callqueue.size = 1024 * 1024 * 1024 # 1G
直接限制队列堆积的大小。当堆积到一定程度后,事实上后面的请求等不到server端处理完,可能客户端先超时了。并且一直堆积下去会导致OOM,1G的默认配置需要相对大内存的型号。当达到queue上限,客户端会收到CallQueueTooBigException 然后自动重试。通过这个可以防止写入过快时候把server端写爆,有一定反压作用。线上使用这个在一些小型号稳定性控制上效果不错。
填坑链接:如何避免HBase写入过快引起的各种问题 收起阅读 »

中国HBase技术社区第九届meetup-HBase典型应用场景与实践

会议时间
2018年12月23日 14:00 ~ 2018年12月23日 18:00  (北京朝阳)360公司 A座一层发布厅,报名地址:http://www.huodongxing.com/event/1469391290300

head_image.jpg

 本期嘉宾介绍

微信图片_20181218193751.jpg

本期活动主题

  13:30-14:00  

   签到

  14:00-14:40 

  HBase 2.0 在360的技术改进与应用实践       

  讲师:王小勇——360系统部分布式存储方向架构师

  介绍内容:HBase在360的使用现状和发展历程,以及在升级HBase2.0的过程中发现的问题与改进。

  14:40-15:20   

   HBase 基本知识介绍及典型案例分析

   讲师:吴阳平——阿里云HBase业务架构师

   介绍内容:HBase基础知识介绍,Rowkey设计技巧,HBase企业级特性及组件介绍,HBase+Spark典型案例分析。

  15:20-15:30   

   抽奖环节 送360、阿里、京东、58专属定制的礼品

  15:30-16:10    

   HBase在无界零售中的应用

   讲师:诸葛子房——京东大数据工程师

   介绍内容:Hbase存储的优势;Hbase案例分享;Hbase数据分析

  16:10-16:50    

   图数据库hgraphdb介绍

   讲师:陈江——阿里高级技术专家

   介绍内容:HGraphDB 是一个使用 HBase 作为底层存储的图数据库, 是 Apache TinkerPop 3 接口的实现。


  16:50-17:30   

   HBase在58的实践和应用

   讲师:何良均——58大数据工程师

   介绍内容:HBase在58的实践和应用,包括平台建设、生态建设、平台监控、平台运营等


主办:中国HBase技术社区

协办:360技术委员会;阿里云飞天八部多模型数据库组;云栖社区;360大学;360系统部,DataFun社区

合作伙伴:开源中国;SegmentFault;掘金;示说网

活动官方报名平台:活动行


技术社群及公众号推荐:


【HBase中国钉钉技术交流社群】:

为了更好的服务HBase开发者,我们启用了钉钉企业号。并每周在群内进行【技术分享直播】和【在线回答技术问题】。

欢迎大家入群,点击link 入群:  https://dwz.cn/Fvqv066s
继续阅读 »
会议时间
2018年12月23日 14:00 ~ 2018年12月23日 18:00  (北京朝阳)360公司 A座一层发布厅,报名地址:http://www.huodongxing.com/event/1469391290300

head_image.jpg

 本期嘉宾介绍

微信图片_20181218193751.jpg

本期活动主题

  13:30-14:00  

   签到

  14:00-14:40 

  HBase 2.0 在360的技术改进与应用实践       

  讲师:王小勇——360系统部分布式存储方向架构师

  介绍内容:HBase在360的使用现状和发展历程,以及在升级HBase2.0的过程中发现的问题与改进。

  14:40-15:20   

   HBase 基本知识介绍及典型案例分析

   讲师:吴阳平——阿里云HBase业务架构师

   介绍内容:HBase基础知识介绍,Rowkey设计技巧,HBase企业级特性及组件介绍,HBase+Spark典型案例分析。

  15:20-15:30   

   抽奖环节 送360、阿里、京东、58专属定制的礼品

  15:30-16:10    

   HBase在无界零售中的应用

   讲师:诸葛子房——京东大数据工程师

   介绍内容:Hbase存储的优势;Hbase案例分享;Hbase数据分析

  16:10-16:50    

   图数据库hgraphdb介绍

   讲师:陈江——阿里高级技术专家

   介绍内容:HGraphDB 是一个使用 HBase 作为底层存储的图数据库, 是 Apache TinkerPop 3 接口的实现。


  16:50-17:30   

   HBase在58的实践和应用

   讲师:何良均——58大数据工程师

   介绍内容:HBase在58的实践和应用,包括平台建设、生态建设、平台监控、平台运营等


主办:中国HBase技术社区

协办:360技术委员会;阿里云飞天八部多模型数据库组;云栖社区;360大学;360系统部,DataFun社区

合作伙伴:开源中国;SegmentFault;掘金;示说网

活动官方报名平台:活动行


技术社群及公众号推荐:


【HBase中国钉钉技术交流社群】:

为了更好的服务HBase开发者,我们启用了钉钉企业号。并每周在群内进行【技术分享直播】和【在线回答技术问题】。

欢迎大家入群,点击link 入群:  https://dwz.cn/Fvqv066s
收起阅读 »

使用 Spark 分析 HBase 的数据

HBase具有很好的在线入库和查询能力,不过在分析上面有比较大的欠缺,这篇文章主要介绍如何使用Spark对HBase中数据做复杂分析。

HBase查询分析的现状
 
  • HBase原生API:HBase原生API适合基于row key做点查,这个是HBase最擅长的查询场景
  • Phoenix:Phoenix作为HBase的SQL层,使用二级索引的技术,擅长多条件组合查询;Phoenix没有自己的计算资源,类似groupby这样的复杂查询需要借助HBase的协处理器来完成,这样一方面性能不好,同时会影响HBase集群的稳定性;
  • Spark:具有丰富的算子支持复杂分析,使用Spark集群的计算资源,通过并发分析的方式可以提高性能,同时不影响HBase集群的稳定性。


Spark分析HBase的方式对比

Spark分析HBase数据有“RDD API”、“SQL API”、“HFILE”三种方式,相关对比如下:

menu.saveimg_.savepath20181128110650_.jpg

 
对于数据动态更新增加的小表推荐使用SQL API的方式,可以有效的优化分析,减少对HBase集群稳定性的影响;对于静态表或者全量静态表的分析推荐使用分析HFILE的方式直读HDFS,这样可以完全不影响HBase集群稳定性;不推荐使用RDD API 的方式,这种方式一方没有优化性能差,同时在高并发以及表数据量大时,会严重影响HBase集群的稳定性,从而影响在线业务。

 三种方式的具体使用

阿里云HBase团队为大家提供了一个github项目供大家参考使用上面的三种方式来开发Spark分析HBase的程序,项目地址:

https://github.com/lw309637554/alicloud-hbase-spark-examples

依赖项:需要下载云HBase及云Phoenix的client包
分析HFILE:
  • 需要先开通云HBase的HDFS访问权限,参考文档
  • 在hbase shell中对表生成snapshot表“snapshot 'sourceTable', ‘snapshotName'”
  • 在项目中配置自己的hdfs-sit.xml文件,然后通过直读HDFS的方式分析snapshot表


具体的example
  • RDD API对应:org.apache.spark.hbase.NativeRDDAnalyze
  • SQL API对应:org.apache.spark.sql.execution.datasources.hbase.SqlAnalyze
  • 分析HFILE对应:org.apache.spark.hfile.SparkAnalyzeHFILE

 
继续阅读 »
HBase具有很好的在线入库和查询能力,不过在分析上面有比较大的欠缺,这篇文章主要介绍如何使用Spark对HBase中数据做复杂分析。

HBase查询分析的现状
 
  • HBase原生API:HBase原生API适合基于row key做点查,这个是HBase最擅长的查询场景
  • Phoenix:Phoenix作为HBase的SQL层,使用二级索引的技术,擅长多条件组合查询;Phoenix没有自己的计算资源,类似groupby这样的复杂查询需要借助HBase的协处理器来完成,这样一方面性能不好,同时会影响HBase集群的稳定性;
  • Spark:具有丰富的算子支持复杂分析,使用Spark集群的计算资源,通过并发分析的方式可以提高性能,同时不影响HBase集群的稳定性。


Spark分析HBase的方式对比

Spark分析HBase数据有“RDD API”、“SQL API”、“HFILE”三种方式,相关对比如下:

menu.saveimg_.savepath20181128110650_.jpg

 
对于数据动态更新增加的小表推荐使用SQL API的方式,可以有效的优化分析,减少对HBase集群稳定性的影响;对于静态表或者全量静态表的分析推荐使用分析HFILE的方式直读HDFS,这样可以完全不影响HBase集群稳定性;不推荐使用RDD API 的方式,这种方式一方没有优化性能差,同时在高并发以及表数据量大时,会严重影响HBase集群的稳定性,从而影响在线业务。

 三种方式的具体使用

阿里云HBase团队为大家提供了一个github项目供大家参考使用上面的三种方式来开发Spark分析HBase的程序,项目地址:

https://github.com/lw309637554/alicloud-hbase-spark-examples

依赖项:需要下载云HBase及云Phoenix的client包
分析HFILE:
  • 需要先开通云HBase的HDFS访问权限,参考文档
  • 在hbase shell中对表生成snapshot表“snapshot 'sourceTable', ‘snapshotName'”
  • 在项目中配置自己的hdfs-sit.xml文件,然后通过直读HDFS的方式分析snapshot表


具体的example
  • RDD API对应:org.apache.spark.hbase.NativeRDDAnalyze
  • SQL API对应:org.apache.spark.sql.execution.datasources.hbase.SqlAnalyze
  • 分析HFILE对应:org.apache.spark.hfile.SparkAnalyzeHFILE

  收起阅读 »

openTSDB源码详解之rowKey生成

openTSDB的一个非常好的设计就是其rowKey。下面笔者主要通过源码的角度来详细介绍一下其rowKey的生成步骤。
1. 相关处理类
openTSDB往hbase中写入数据的处理过程,我之前就已经分析过,主要涉及的类有:- `addPointInternal(...)`
1.png

这里主要讲解的是,一个row key是如何生成的。

2. 具体步骤
  •  2.1 row_size的确定

       2.1.1 `SALT_WIDTH`
首先对于一个rowKey,肯定是有大小的,但是这个rowKey的大小该怎么确定呢?因为这个rowKey是需要写入到HBase中的,也就是需要通过网络的传输才能到达HBase端,所以不可能无限长,即需要精确到一个具体的长度。
openTSDB中的rowKey的大小用变量row_size表示。其具体的生成规则如下图所示:
2.png

其中的 `SALT_WIDTH()`函数返回的是SALT WIDTH,它是一个static修饰的int型变量,初始值为0,如下图所示:
3.png

    2.1.2 `metric_width`,`tag_name_width`,`tag_value_width`
`metric_width` 代表的就是`metric`的长度。在openTSDB系统中,默认的长度是3。该值来自`tsdb.metrics.width()`函数的返回值。
4.png

`width()` 方法如下:
5.png

而id_width的值设置过程如下:
6.png

7.png

调用构造函数`UniqueId(...)`的过程如下:
8.png

最终可以看到这些值默认为3
9.png

针对笔者传输的数据,可以推导出最后生成的row_size的大小就是`row_size = 0 +  3 +  4 + 3*1 + 3*1 = 13`,经过debug验证,也可以看到的确就是13。
  •  2.2 字节数组`row`

10.png

  •  2.3 `pos`变量

11.png

问题1:使用`pos`变量的原因是,控制对字节数组row的复制控制【每将一个值复制到row之后,就需要动态改变row的起始位置,这个位置就是pos定义的】
因为我没有开启 加盐处理功能,所以这里的pos的初始值就是0。
  •  2.4 为字节数组`row`赋值

12.png

上述copyInRowKey(...)的功能就是将metric对应的`uid`值放入到`row`中。而其中的 `getOrCreateId()`方法则是获取metric对应的`uid`值。该方法主要调用的其它方法如下:
- `getIdAsync()` 方法
13.png

- 从缓存中获取uid
14.png

- Deferred 类的构造方法
15.png

- `copyInRowKey()`方法
16.png

注意上面一张图中row和下面一张图的row的变化。这个变化就体现了将metric的id放到了rowKey中。
17.png

  •  2.5 `pos`值的变化

18.png

因为已经处理了metric,所以需要将其pos自增一下。
同时,因为暂时不着急处理timestamp,所以再次将pos自增处理。
  •  2.6 `tag pair`的处理

19.png

最后,需要在row中放入 tag pair,方式是采用for循环处理。
主要调用方法如下:
- `resolveOrCreateAll()`
20.png

- `resolveAllInternal()`方法
21.png

  •  2.7 `row`的大致部分

经过上述几个步骤的处理,则会生成一个row值:
22.png

  •  2.8 `rowKey`的完善

上述的步骤只是生成了一个rowKey的框架,但是还没有完全生成一个rowKey,因为还有最后一个步骤。接着会进入`WriteCB`这个回调类,如下:
23.png

注意这里的offset的取值:`metrics.width() + Const.SALT_WIDTH()`这个是用于将当前value所在小时的时间戳放入到row中。所以需要偏移`metrics.width() + Const.SALT_WIDTH()` 这样的长度。这里使用base_time = 1542096000举例,在未执行Bytes.setInt()方法之前,row的值如下图示:
24.png

使用`Bytes`类的`setInt()`方法修改row的值。`setInt()`主要的是`>>>`运算,这个运算符的意思是指:右移指定的位数,左边的空白位置0。得到的row值如下:
25.png

可能有人会问,为什么使用的是`Bytes.setInt()`这个方法?要知道,openTSDB只是根据timestamp(实质是 `base_time`,这个`base_time`我在后面会补充介绍)去生成rowKey的一部分,所以这一部分是什么样的逻辑不重要了,重要的是这个逻辑需要保持相同的`base_time` 映射到固定的`byte` 值。而openTSDB在`rowKey`的生成过程中采取的方法就是使用`>>>`去处理(timestamp)`base_time` ,从而保证`rowKey`在`base_time`相同时,其`base_time`对应的`rowKey`部分也相同。
到这里,一个完整的rowKey就生成了。

3.其它
  • 3.1 `base_time`字段的生成

这个`base_time`代表的值就是当前value传递过来所在的timestamp。例如:对于一个timestamp=1542088140,处理过程就是取该timestamp所在小时 0分0秒的timestamp值。这也能解释为什么opentsdb中的存储按照每小时进行行存储,而不是每个时间戳一行。
26.png

这个`Const.MAX_TIMESPAN`定义如下:
  /** Max time delta (in seconds) we can store in a column qualifier.  */
public static final short MAX_TIMESPAN = 3600;
代表的是一个小时的秒跨度。对其`MAX_TIMESPAN` 取余之后,该余数就是秒数,再减去这个秒数就得到hh:00:00的值。上面的值处理之后,得到的base_time值变为了1542085200。如下示:
27_.png

 
继续阅读 »
openTSDB的一个非常好的设计就是其rowKey。下面笔者主要通过源码的角度来详细介绍一下其rowKey的生成步骤。
1. 相关处理类
openTSDB往hbase中写入数据的处理过程,我之前就已经分析过,主要涉及的类有:- `addPointInternal(...)`
1.png

这里主要讲解的是,一个row key是如何生成的。

2. 具体步骤
  •  2.1 row_size的确定

       2.1.1 `SALT_WIDTH`
首先对于一个rowKey,肯定是有大小的,但是这个rowKey的大小该怎么确定呢?因为这个rowKey是需要写入到HBase中的,也就是需要通过网络的传输才能到达HBase端,所以不可能无限长,即需要精确到一个具体的长度。
openTSDB中的rowKey的大小用变量row_size表示。其具体的生成规则如下图所示:
2.png

其中的 `SALT_WIDTH()`函数返回的是SALT WIDTH,它是一个static修饰的int型变量,初始值为0,如下图所示:
3.png

    2.1.2 `metric_width`,`tag_name_width`,`tag_value_width`
`metric_width` 代表的就是`metric`的长度。在openTSDB系统中,默认的长度是3。该值来自`tsdb.metrics.width()`函数的返回值。
4.png

`width()` 方法如下:
5.png

而id_width的值设置过程如下:
6.png

7.png

调用构造函数`UniqueId(...)`的过程如下:
8.png

最终可以看到这些值默认为3
9.png

针对笔者传输的数据,可以推导出最后生成的row_size的大小就是`row_size = 0 +  3 +  4 + 3*1 + 3*1 = 13`,经过debug验证,也可以看到的确就是13。
  •  2.2 字节数组`row`

10.png

  •  2.3 `pos`变量

11.png

问题1:使用`pos`变量的原因是,控制对字节数组row的复制控制【每将一个值复制到row之后,就需要动态改变row的起始位置,这个位置就是pos定义的】
因为我没有开启 加盐处理功能,所以这里的pos的初始值就是0。
  •  2.4 为字节数组`row`赋值

12.png

上述copyInRowKey(...)的功能就是将metric对应的`uid`值放入到`row`中。而其中的 `getOrCreateId()`方法则是获取metric对应的`uid`值。该方法主要调用的其它方法如下:
- `getIdAsync()` 方法
13.png

- 从缓存中获取uid
14.png

- Deferred 类的构造方法
15.png

- `copyInRowKey()`方法
16.png

注意上面一张图中row和下面一张图的row的变化。这个变化就体现了将metric的id放到了rowKey中。
17.png

  •  2.5 `pos`值的变化

18.png

因为已经处理了metric,所以需要将其pos自增一下。
同时,因为暂时不着急处理timestamp,所以再次将pos自增处理。
  •  2.6 `tag pair`的处理

19.png

最后,需要在row中放入 tag pair,方式是采用for循环处理。
主要调用方法如下:
- `resolveOrCreateAll()`
20.png

- `resolveAllInternal()`方法
21.png

  •  2.7 `row`的大致部分

经过上述几个步骤的处理,则会生成一个row值:
22.png

  •  2.8 `rowKey`的完善

上述的步骤只是生成了一个rowKey的框架,但是还没有完全生成一个rowKey,因为还有最后一个步骤。接着会进入`WriteCB`这个回调类,如下:
23.png

注意这里的offset的取值:`metrics.width() + Const.SALT_WIDTH()`这个是用于将当前value所在小时的时间戳放入到row中。所以需要偏移`metrics.width() + Const.SALT_WIDTH()` 这样的长度。这里使用base_time = 1542096000举例,在未执行Bytes.setInt()方法之前,row的值如下图示:
24.png

使用`Bytes`类的`setInt()`方法修改row的值。`setInt()`主要的是`>>>`运算,这个运算符的意思是指:右移指定的位数,左边的空白位置0。得到的row值如下:
25.png

可能有人会问,为什么使用的是`Bytes.setInt()`这个方法?要知道,openTSDB只是根据timestamp(实质是 `base_time`,这个`base_time`我在后面会补充介绍)去生成rowKey的一部分,所以这一部分是什么样的逻辑不重要了,重要的是这个逻辑需要保持相同的`base_time` 映射到固定的`byte` 值。而openTSDB在`rowKey`的生成过程中采取的方法就是使用`>>>`去处理(timestamp)`base_time` ,从而保证`rowKey`在`base_time`相同时,其`base_time`对应的`rowKey`部分也相同。
到这里,一个完整的rowKey就生成了。

3.其它
  • 3.1 `base_time`字段的生成

这个`base_time`代表的值就是当前value传递过来所在的timestamp。例如:对于一个timestamp=1542088140,处理过程就是取该timestamp所在小时 0分0秒的timestamp值。这也能解释为什么opentsdb中的存储按照每小时进行行存储,而不是每个时间戳一行。
26.png

这个`Const.MAX_TIMESPAN`定义如下:
  /** Max time delta (in seconds) we can store in a column qualifier.  */
public static final short MAX_TIMESPAN = 3600;
代表的是一个小时的秒跨度。对其`MAX_TIMESPAN` 取余之后,该余数就是秒数,再减去这个秒数就得到hh:00:00的值。上面的值处理之后,得到的base_time值变为了1542085200。如下示:
27_.png

  收起阅读 »

如何确保多步操作的事务性?HBase基础框架级特性Procedure解读

标题中提及"事务"可能会给大家带来误解,这篇文章不是在讨论HBase如何支持分布式事务能力的,而是介绍HBase用来处理内部事务操作的特性,这个特性被称之为Procedure V2,也是2.0版本的主打特性之一。
本文内容基于HBase 2.1版本,整体内容组织结构为:
1. Procedure特性的设计初衷。
2. Procedure的完整生命周期。
3. Procedure框架关键角色。
4. Procedure重点模块实现细节。
关于HBase基础原理系列文章:
开篇内容
介绍HBase的数据模型、适用场景、集群关键角色、建表流程以及所涉及的HBase基础概念。
Writer全流程
介绍写数据的接口,RowKey定义,数据在客户端的组装,数据路由,打包分发,以及RegionServer侧将数据写入到Region中的全部流程。
Flush与Compaction
阐述Flush与Compaction流程,讲述了Compaction所面临的本质问题,介绍了HBase现有的几种Compaction策略以及各自的适用场景。
Read全流程
首先介绍HBase的两种读取模式(Get与Scan),而后详细介绍Scan的详细实现流程。
HFile详解
详细介绍HBase底层数据文件格式HFile的实现细节。
关于Procedure V2介绍文章:
Procedure V2介绍
回顾·Procedure V2介绍
主要介绍Procedure V2的设计和结构,以及为什么用Procedure V2能比较容易实现出正确的AssignmentManager以及在2.1分支上对一些Procedure实现修正和改进。
以下是正文内容:
Procedure的设计初衷
我们先来看一下Procedure特性的设计初衷。以建表为例: 建表过程,包含如下几个关键的操作:
1.初始化所有的RegionInfos。
2.建表前的必要检查。
3.创建表的文件目录布局。
4.将Region写到Meta表中。
5. ......
从使用者看来,一个建表操作要么是成功的,要么是失败的,并不存在一种中间状态,因此,用"事务"来描述这种操作可以更容易理解。如果曾使用过1.0或更早的版本,相信一定曾被这个问题"折磨"过:“一个表明明没有创建成功,当使用者尝试再次重建的时候,却被告知该表已存在”,究其根因,在于建表遇到异常后,没有进行合理的Rollback操作,导致集群处于一种不一致的状态。
类似的需求,在HBase内部司空见惯:Disable/Enable表,修改表,RegionServer Failover处理,Snapshot, Assign Region, Flush Table...这些操作,都具备两个特点:多步操作,有限状态机(或简单或复杂)。因此,可以实现一个公共能力,这样可避免每个特性各自为营,导致大量难以维护的冗余代码。
最初版本的Procedure,由Online Snapshot特性引入(HBASE-7290),主要用来协调分布式请求。而最新版本的Procedure(称之为Procedure V2),受Accumulo的Fault-Tolerant Executor (FATE)的启发而重新设计,该特性已经在HBase内部得到广泛的应用,因此,可以将其称之为一个基础框架级特性,了解该特性的设计原理是学习2.x源码的基础。本文范畴内所探讨的Procedure,均指Procedure V2。
Procedure的生命周期
一个Procedure由一个或一系列操作构成。一个Procedure的执行结果,要么是成功,要么是失败,失败后不会让集群处于一种不一致的状态。
接下来,我们将以Create Table操作为例,来介绍一个Procedure的完整的生命周期。
创建
Master收到一个Create Table的请求后会创建一个CreateTableProcedure实例。
CreateTableProcedure涉及到一系列的操作,而每一个操作都关联了一个操作状态。在CreateTableState中,定义了与之相关的所有操作状态:
CreateTableProcedure内部定义了自身的初始状态为CREATE_TABLE_PRE_OPERATION,而且定义了每一种状态时对应的处理操作以及当前这个状态完成后应该要切换至哪个状态。如下表列出了当前状态与成功后的退出状态:
Procedure本身还有一个运行状态,这套运行状态的定义如下:
正是为了有所区分,本文将CreateTableProcedure内部定义的私有状态,称之为操作状态,而运行状态则是所有的Procedure都拥有的状态信息,从每一个状态的定义很容易看出它的作用。
提交
Master将创建出来的CreateTableProcedure实例,提交给ProcedureExecutor。
ProcedureExecutor关于一个新提交的Procedure,做如下几步处理:
  1. 必要检查(初始状态,确保无Parent Procedure,确保有Owner信息)。
  2. 设置Nonce Key(如果存在)以及Procedure ID。
  3. 将新的Procedure写入到ProcedureStore中,持久化。
  4. 将新的Procedure添加到ProcedureScheduler的调度队列的尾部。

运行
ProcedureExecutor初始化阶段,启动了若干个WorkerThread,具体数量可配置。
WorkerThread不断从ProcedureScheduler中poll出新的待执行的Procedure,而后:
  1. 获取IdLock: 获取Procedure关联的IdLock,避免同一个Procedure在多个线程中同时处理。
  2. 获取资源锁: 调用Procedure内部定义的Acquire Lock请求,获取Procedure自身所需的资源锁。IdLock是为了确保一个Procedure只被一个线程调用,而这里的Lock是为了确保这一个Table只能被一个Procedure处理,这里需要获取Namespace的共享锁,以及当前这个Table的互斥锁,这里其实是一个分布式锁的需求,容易想到用ZooKeeper实现,事实上,这个Lock也可以用Procedure来实现,在后续章节中将会讲到这一点。
  3. 处理当前操作状态:执行Procedure初始状态所定义的处理逻辑,处理完后会返回当前这个Procedure对象。每一步运行完,都将最新的状态持久化到ProcedureStore中。
  4. 处理下一操作状态:如果返回对象依然是原来的Procedure,而且未失败,则意味着需要继续下一步处理。
  5. 循环处理:循环3,4步即可处理完所有CreateTableProcedure内部定义的所有处理。

       释放锁资源。
完成
处理每一个操作状态时,都产生一个状态返回值(Flow),如果还有下一个待处理的状态,则返回Flow.HAS_MORE_STATE,如果全部执行完成,则返回Flow.NO_MORE_STATE,借此可以判断一个Procedure是否执行完成。一个执行成功后的Procedure,运行状态被设置为SUCCESS。
完成后的Procedure,需要在ProcedureStore中进行标记删除。
Procedure框架关键角色
通过上一章节的内容,我们已经知道了,在一个Procedure执行过程中,涉及到如下几个关键角色:
ProcedureExecutor
负责提交、执行Procedure。Procedure的执行操作,主要由其负责的多个WorkerThread来完成。
Procedure的持久化,由ProcedureExecutor提交给ProcedureStore。后续的每一次状态更新,也由ProcedureExecutor向ProcedureStore发起Update请求。
新的Procedure持久化后将被提交给ProcedureScheduler,由ProcedureScheduler完成调度。ProcedureExecutor中的WorkerThread从ProcedureScheduler中获取待执行的Procedure。
ProcedureStore
用来持久化新提交的Procedure以及后续的每一次状态更新值。
ProcedureStore的默认实现类为WALProcedureStore,基于日志文件来持久化Procedure信息,虽然称之为WAL,但与HBase自身的WAL日志文件的实现完全不同,类似点在于:
  • 当超过一定大小后或者超过一定的时间周期后,需要roll一个新的WAL文件出来,避免一个WAL文件过大。
  • 需要实现一套关于无用WAL日志文件的跟踪清理机制,避免WAL文件占用过大的存储空间。
  • 实现了一套类似于RingBuffer的机制,通过打包sync并发的写入请求,来提升写入吞吐量。

ProcedureScheduler
负责调度一个集群内的各种类型的Procedure请求,支持按优先级调度,相同优先级的Procedure则支持公平调度。
我们先来看看Procedure的几大类型:
  • Meta Procedure:唯一的一种类型为RecoverMetaProcedure,该类型已被废弃。
  • Server Procedure:目前也只有一种类型:ServerCrashProcedure,用来负责RegionServer进程故障后的处理。
  • Peer Procedure:与Replication相关,如AddPeerProcedure, RemovePeerProcedure等等。
  • Table Procedure: Table Procedure的类型最为丰富,如CreateTableProcedure, DisableTableProcedure, EnableTableProcedure, AssignProcedure, SplitTableRegionProcedure,.....涵盖表级别、Region级别的各类操作。

在ProcedureScheduler中,需要同时调度这几种类型的Procedure,调度的优先级顺序(由高到低)为:
    Meta -> Server -> Peer -> Table。
在每一种类型内部,又有内部的优先级定义。以Table Procedure为例,Meta Table的优先级最高,System Table(如acl表)其次,普通用户表的优先级最低。
重点模块实现细节
看到这里,你也许会认为,"Procedure特性原来如此简单!",但如果仔细阅读这部分代码,就会深刻体会到它在实现上的复杂度,导致这种复杂度的客观原因总结起来有如下几点:
  1. 要实现一个统一的状态机管理框架本身就比较复杂,可以说将Assign Region/Create Table/Split Region等流程的复杂度转嫁了过来。
  2. 需要支持优先级调度与公平调度
  3. WALProcedureStore无用WAL文件的跟踪与清理,重启后的回放,均需要严谨的处理。
  4. 涉及复杂的拓扑结构:一个Procedure中间运行过程可能会产生多个Sub-Procedures,这过程需要协调。
  5. 不依赖于ZooKeeper的分布式锁机制。
  6. 跨节点Rpc请求协调。

在实际实现中,还内部维护几个私有的数据结构,如Avl-Tree, FairQueue以及Bitmap,这也是导致实现复杂度过高的一大原因。接下来选择了三点内容来展开讲解,这三点内容也算是Procedure框架里的难点部分,分别为:WAL清理机制,Procedure调度策略以及分布式锁与事件通知机制。
WAL清理机制
WALProcedureStore也存在WAL日志文件的roll机制,这样就会产生多个WAL文件。对于一个旧的WAL文件,如何认定它可以被安全清理了?这就需要在WALProcedureStore中设计一个合理的关于Procedure状态的更新机制。
WALProcedureStore使用ProcedureStoreTracker对象来跟踪Procedure的写入/更新与删除操作,这个对象被称之为storeTracker
在ProcedureStoreTracker中,只需要用ProcID(long类型)来表示一个Procedure。即使只记录大量的ProcIDs,也会占用大量的内存空间,因此,在ProcedureStoreTracker内部实现了一个简单的Bitmap,用一个BIT来表示一个ProcID,这个Bitmap采用了分区、弹性扩展的设计:每一个分区称之为一个BitsetNode,每一个BitsetNode有一个起始值(Start),使用一个long数组来表示这个分区对应的Bitmap,每一个long数值包含64位,因此可以用来表示64个ProcIDs。一个BitsetNode应该可以包含X个long数值,这样就可以表示从Start值开始的X * 64个ProcIDs,但可惜,现在的代码实现还是存在问题的(应该是BUG),导致一个BitsetNode只能包含1个long值。
如果了解过Java Bitset的原理,或者是RoaringBitmap,就会发现这个实现并无任何新颖之处。
在一个BitsetNode内部,其实包含两个Bitmap: 一个Bitmap(modified)用来记录Insert/Update的ProcIDs,另一个Bitmap(Deleted)用来记录已经被Delete的ProcIDs。例如,如果Proc Y在Bitmap(modified)所对应的BIT为1,在Bitmap(Deleted)中所对应的BIT为0,则意味着这个Procedure仍然存活(或许刚刚被写入,或许刚刚被更新)。如果在Bitmap(Modified)中对应的BIT为1,但在Bitmap(Deleted)中所对应的BIT为1,则意味着这个Procedure已被删除了。
如果一个旧的WAL文件所关联的所有的Procedures,都已经被更新过(每一次更新,意味着Procedure的状态已经发生变化,则旧日志记录则已失去意义),或者都已经被删除,则这个WAL文件就可以被删除了。在实现上,同样可以用另外一个ProcedureStoreTracker对象(称之为holdingCleanupTracker)来跟踪最老的WAL中的Procedure的状态,每当有新的Procedure发生更新或者被删除,都同步删除holdingCleanupTracker中对应的ProcID即可。当然,还得考虑另外一种情形,如果有个别Procedure迟迟未更新如何处理? 这时,只要强制触发这些Procedures的更新操作即可。
这样描述起来似乎很简单,但这里却容易出错,而且一旦出错,可能会导致WAL日志被误删,或者堆积大量的日志文件无法被清理,出现这样的问题都是致命的。
Procedure调度策略
关于调度策略的基础需求,可以简单被表述为:
  1. 不同类型的Procedure优先级不同,如Server Procedure要优先于Table Procedure被调度。
  2. 即使同为Table Procedure类型,也需要按照Table的类型进行优先级调度,对于相同的优先级类型,则采用公平调度策略。

MasterProcedureScheduler是默认的ProcedureScheduler实现,接下来,我们看一下它的内部实现。
MasterProcedureScheduler将同一类型的Procedure,放在一个被称之为FairQueue的队列中,这样,共有四种类型的FairQueue队列,这四个队列分别被称之为MetaRunQueue, ServerRunQueue, PeerRunQueue, TableRunQueue),在调度时,按照上述罗列的顺序进行调度,这样,就确保了不同类型间的整体调度顺序。
简单起见,我们假设这四个队列中,仅有TableRunQueue有数据,其它皆空,这样确保会调度到TableRunQueue中的Procedure。在TableRunQueue中,本身会涉及多个Table,而每一个Table也可能会涉及多个Procedures:
TableA -> {ProcA1, ProcA2, ProcA3}
TableB -> {ProcB1, ProcB2, ProcB3, ProcB4, ProcB5}
TableC -> {ProcC1, ProcC2, ProcC3, ProcC4}

每一个Table以及所涉及到的Procedure列表,被封装成了一个TableQueue对象,在TableQueue中,使用了一个双向队列(Deque)来存储Procedures列表,Deque的特点是既可以在队列两端进行插入。在这个Deque中的顺序,直接决定了同一个Table的Procedures之间的调度顺序。
当需要为TableB写入一个新的Procedure时,需要首先快速获取TableB所关联的TableQueue对象,常见的思路是将所有的TableQueue存储在一个ConcurrentHashMap中,以TableName为Key,然而,这里却没有采用ConcurrentHashMap,而是实现了一个Avl-Tree(自动平衡二叉树),这样设计的考虑点为:ConcurrentHashMap中的写入会创建额外的Tree Node对象,当对象的写入与删除非常频繁时对于GC的压力较大(请参考AvlUtil.java)。Avl-Tree利于快速获取检索,但写入性能却慢于红黑树,因为涉及到过多的翻转操作。这样,基于TableName,可以快速从这个Avl-Tree中获取对应的TableQueue对象,而后就可以将这个新的Procedure写入到这个TableQueue的Deque中,写入时还可以指定写入到头部还是尾端。
现在我们已经了解了TableQueue对象,而且知道了多个TableQueue被存储在了一个类似于Map的数据结构中,还有一个关键问题没有解决:如何实现不同Table间的调度?
所有的TableQueue,都存放在TableRunQueue(再强调一下,这是一个FairQueue对象)中,而且按Table的优先级顺序组织。每当有一个新的TableQueue对象产生时,都会按照该TableQueue所关联的Table的优先级,插入到TableRunQueue中的合适位置。
当从这个TableRunQueue中poll一个新的TableQueue时,高优先级的TableQueue先被poll出来。如果被poll出来的TableQueue为普通优先级(priority值为1),为了维持公平调度的原则,在TabelRunQueue中将这个TableQueue从头部移到尾部,这样下一次将会调度到其它的TableQueue。
再简单总结一下:TableQueue对象用来描述一个Table所关联的Procedures队列,TableQueue对象存在于两个数据结构中,一个为Avl-Tree,这样可以基于TableName快速获取对应的TableQueue,以便快速写入;另一个数据结构为FairQueue,这是为了实现多Table间的调度。
分布式锁与事件通知机制
同样围绕Create Table的例子,说明一下关于分布式锁的需求:
  • 两个相同表的CreateTableProcedure不应该被同时执行
  • 同一个Namespace下的多个不同表的CreateTableProcedure允许被同时执行
  • 只要存在未完成的CreateTableProcedure,所关联的Namespace不允许被删除

实现上述需求,Procedure框架采用了共享锁/互斥锁方案:
  • 当CreateTableProcedure执行时,需要获取对应Namespace的共享锁,以及所要创建的Table的互斥锁。
  • 删除一个Namespace则需要获取这个Namespace的互斥锁,这意味着只要有一个Procedure持有该Namespace的共享锁,则无法被删除。

当一个Procedure X试图去获取一个Table的互斥锁时,碰巧该Table的互斥锁被其它Procedure持有,此时,Procedure X需要加到这个Table的锁的等待队列中,一旦该锁被释放,Procedure X需要被唤醒。
回顾一下Procedure的执行过程:
  1. Acquire Lock
  2. Execute

获取锁资源的操作,只需要在"Acquire Lock"步骤完成即可。
MasterProcedureScheduler中,使用一个SchemaLocking的对象来维护所有的锁资源,如Server Lock, Namespace Lock,Table Lock等等。以Table Lock资源为例:一个Table的锁资源,使用一个LockAndQueue对象进行抽象,顾名思义,在这个对象中,既有Lock,又有关于这个锁资源的Procedure等待队列;多个Table的LockAndQueue对象被组织在一个Map中,以TableName为Key。
同时,还可以将获取锁资源的操作封装成一个Procedure,称之为LockProcedure,以供Procedure框架之外的特性使用,如TakeSnapshotHandler,可以利用该机制来获取Table的互斥锁。
Procedure框架就是这样没有依赖于ZooKeeper,实现自身的分布式锁与消息通知机制。
总结
本文先从Procedure的设计初衷着手,而后以Create Table为例介绍了一个Procedure的生命周期,通过这个过程,可以简单了解整个框架所涉及到的几个角色,因此,在接下来的章节中,进一步细化Procedure框架中的几个角色。最后一部分,选择整个框架中比较复杂的几个模块,展开实现细节。受限于篇幅,有几部分内容未涉及到:
  1. WAL数据格式与WAL回放机制
  2. Notification-Bus(当然这部分也只实现了一小部分)
  3. 复杂Procedure拓扑结构情形
  4. Procedure超时处理

作为应用Procedure框架的最典型流程Region Assignment,在此文范畴内几乎未涉及。因为关于Region Assignment的故事太精彩,又太揪心,所以会放在一篇独立的文章中专门讲解。
参考信息
http://hbase.apache.org/book.html#pv2
http://hbase.apache.org/book.html#amv2
HBASE-13439: Procedure Framework(Pv2)
Accumulo FATE
Procedure V2介绍
HBASE-13203 Procedure v2 - master create/delete table
HBASE-14837: Procedure Queue Improvement
HBASE-20828: Finish-up AMv2 Design/List of Tenets/Specification of operation
HBASE-20338: WALProcedureStore#recoverLease() should have fixed sleeps for retrying rollWriter()
HBASE-21354: Procedure may be deleted improperly during master restarts resulting in "corrupt"
HBASE-20973: ArrayIndexOutOfBoundsException when rolling back procedure...
HBASE-19756: Master NPE during completed failed eviction
HBASE-19953: Avoid calling post* hook when procedure fails
HBASE-19996: Some nonce procs might not be cleaned up(follow up HBASE-19756)
继续阅读 »
标题中提及"事务"可能会给大家带来误解,这篇文章不是在讨论HBase如何支持分布式事务能力的,而是介绍HBase用来处理内部事务操作的特性,这个特性被称之为Procedure V2,也是2.0版本的主打特性之一。
本文内容基于HBase 2.1版本,整体内容组织结构为:
1. Procedure特性的设计初衷。
2. Procedure的完整生命周期。
3. Procedure框架关键角色。
4. Procedure重点模块实现细节。
关于HBase基础原理系列文章:
开篇内容
介绍HBase的数据模型、适用场景、集群关键角色、建表流程以及所涉及的HBase基础概念。
Writer全流程
介绍写数据的接口,RowKey定义,数据在客户端的组装,数据路由,打包分发,以及RegionServer侧将数据写入到Region中的全部流程。
Flush与Compaction
阐述Flush与Compaction流程,讲述了Compaction所面临的本质问题,介绍了HBase现有的几种Compaction策略以及各自的适用场景。
Read全流程
首先介绍HBase的两种读取模式(Get与Scan),而后详细介绍Scan的详细实现流程。
HFile详解
详细介绍HBase底层数据文件格式HFile的实现细节。
关于Procedure V2介绍文章:
Procedure V2介绍
回顾·Procedure V2介绍
主要介绍Procedure V2的设计和结构,以及为什么用Procedure V2能比较容易实现出正确的AssignmentManager以及在2.1分支上对一些Procedure实现修正和改进。
以下是正文内容:
Procedure的设计初衷
我们先来看一下Procedure特性的设计初衷。以建表为例: 建表过程,包含如下几个关键的操作:
1.初始化所有的RegionInfos。
2.建表前的必要检查。
3.创建表的文件目录布局。
4.将Region写到Meta表中。
5. ......
从使用者看来,一个建表操作要么是成功的,要么是失败的,并不存在一种中间状态,因此,用"事务"来描述这种操作可以更容易理解。如果曾使用过1.0或更早的版本,相信一定曾被这个问题"折磨"过:“一个表明明没有创建成功,当使用者尝试再次重建的时候,却被告知该表已存在”,究其根因,在于建表遇到异常后,没有进行合理的Rollback操作,导致集群处于一种不一致的状态。
类似的需求,在HBase内部司空见惯:Disable/Enable表,修改表,RegionServer Failover处理,Snapshot, Assign Region, Flush Table...这些操作,都具备两个特点:多步操作,有限状态机(或简单或复杂)。因此,可以实现一个公共能力,这样可避免每个特性各自为营,导致大量难以维护的冗余代码。
最初版本的Procedure,由Online Snapshot特性引入(HBASE-7290),主要用来协调分布式请求。而最新版本的Procedure(称之为Procedure V2),受Accumulo的Fault-Tolerant Executor (FATE)的启发而重新设计,该特性已经在HBase内部得到广泛的应用,因此,可以将其称之为一个基础框架级特性,了解该特性的设计原理是学习2.x源码的基础。本文范畴内所探讨的Procedure,均指Procedure V2。
Procedure的生命周期
一个Procedure由一个或一系列操作构成。一个Procedure的执行结果,要么是成功,要么是失败,失败后不会让集群处于一种不一致的状态。
接下来,我们将以Create Table操作为例,来介绍一个Procedure的完整的生命周期。
创建
Master收到一个Create Table的请求后会创建一个CreateTableProcedure实例。
CreateTableProcedure涉及到一系列的操作,而每一个操作都关联了一个操作状态。在CreateTableState中,定义了与之相关的所有操作状态:
CreateTableProcedure内部定义了自身的初始状态为CREATE_TABLE_PRE_OPERATION,而且定义了每一种状态时对应的处理操作以及当前这个状态完成后应该要切换至哪个状态。如下表列出了当前状态与成功后的退出状态:
Procedure本身还有一个运行状态,这套运行状态的定义如下:
正是为了有所区分,本文将CreateTableProcedure内部定义的私有状态,称之为操作状态,而运行状态则是所有的Procedure都拥有的状态信息,从每一个状态的定义很容易看出它的作用。
提交
Master将创建出来的CreateTableProcedure实例,提交给ProcedureExecutor。
ProcedureExecutor关于一个新提交的Procedure,做如下几步处理:
  1. 必要检查(初始状态,确保无Parent Procedure,确保有Owner信息)。
  2. 设置Nonce Key(如果存在)以及Procedure ID。
  3. 将新的Procedure写入到ProcedureStore中,持久化。
  4. 将新的Procedure添加到ProcedureScheduler的调度队列的尾部。

运行
ProcedureExecutor初始化阶段,启动了若干个WorkerThread,具体数量可配置。
WorkerThread不断从ProcedureScheduler中poll出新的待执行的Procedure,而后:
  1. 获取IdLock: 获取Procedure关联的IdLock,避免同一个Procedure在多个线程中同时处理。
  2. 获取资源锁: 调用Procedure内部定义的Acquire Lock请求,获取Procedure自身所需的资源锁。IdLock是为了确保一个Procedure只被一个线程调用,而这里的Lock是为了确保这一个Table只能被一个Procedure处理,这里需要获取Namespace的共享锁,以及当前这个Table的互斥锁,这里其实是一个分布式锁的需求,容易想到用ZooKeeper实现,事实上,这个Lock也可以用Procedure来实现,在后续章节中将会讲到这一点。
  3. 处理当前操作状态:执行Procedure初始状态所定义的处理逻辑,处理完后会返回当前这个Procedure对象。每一步运行完,都将最新的状态持久化到ProcedureStore中。
  4. 处理下一操作状态:如果返回对象依然是原来的Procedure,而且未失败,则意味着需要继续下一步处理。
  5. 循环处理:循环3,4步即可处理完所有CreateTableProcedure内部定义的所有处理。

       释放锁资源。
完成
处理每一个操作状态时,都产生一个状态返回值(Flow),如果还有下一个待处理的状态,则返回Flow.HAS_MORE_STATE,如果全部执行完成,则返回Flow.NO_MORE_STATE,借此可以判断一个Procedure是否执行完成。一个执行成功后的Procedure,运行状态被设置为SUCCESS。
完成后的Procedure,需要在ProcedureStore中进行标记删除。
Procedure框架关键角色
通过上一章节的内容,我们已经知道了,在一个Procedure执行过程中,涉及到如下几个关键角色:
ProcedureExecutor
负责提交、执行Procedure。Procedure的执行操作,主要由其负责的多个WorkerThread来完成。
Procedure的持久化,由ProcedureExecutor提交给ProcedureStore。后续的每一次状态更新,也由ProcedureExecutor向ProcedureStore发起Update请求。
新的Procedure持久化后将被提交给ProcedureScheduler,由ProcedureScheduler完成调度。ProcedureExecutor中的WorkerThread从ProcedureScheduler中获取待执行的Procedure。
ProcedureStore
用来持久化新提交的Procedure以及后续的每一次状态更新值。
ProcedureStore的默认实现类为WALProcedureStore,基于日志文件来持久化Procedure信息,虽然称之为WAL,但与HBase自身的WAL日志文件的实现完全不同,类似点在于:
  • 当超过一定大小后或者超过一定的时间周期后,需要roll一个新的WAL文件出来,避免一个WAL文件过大。
  • 需要实现一套关于无用WAL日志文件的跟踪清理机制,避免WAL文件占用过大的存储空间。
  • 实现了一套类似于RingBuffer的机制,通过打包sync并发的写入请求,来提升写入吞吐量。

ProcedureScheduler
负责调度一个集群内的各种类型的Procedure请求,支持按优先级调度,相同优先级的Procedure则支持公平调度。
我们先来看看Procedure的几大类型:
  • Meta Procedure:唯一的一种类型为RecoverMetaProcedure,该类型已被废弃。
  • Server Procedure:目前也只有一种类型:ServerCrashProcedure,用来负责RegionServer进程故障后的处理。
  • Peer Procedure:与Replication相关,如AddPeerProcedure, RemovePeerProcedure等等。
  • Table Procedure: Table Procedure的类型最为丰富,如CreateTableProcedure, DisableTableProcedure, EnableTableProcedure, AssignProcedure, SplitTableRegionProcedure,.....涵盖表级别、Region级别的各类操作。

在ProcedureScheduler中,需要同时调度这几种类型的Procedure,调度的优先级顺序(由高到低)为:
    Meta -> Server -> Peer -> Table。
在每一种类型内部,又有内部的优先级定义。以Table Procedure为例,Meta Table的优先级最高,System Table(如acl表)其次,普通用户表的优先级最低。
重点模块实现细节
看到这里,你也许会认为,"Procedure特性原来如此简单!",但如果仔细阅读这部分代码,就会深刻体会到它在实现上的复杂度,导致这种复杂度的客观原因总结起来有如下几点:
  1. 要实现一个统一的状态机管理框架本身就比较复杂,可以说将Assign Region/Create Table/Split Region等流程的复杂度转嫁了过来。
  2. 需要支持优先级调度与公平调度
  3. WALProcedureStore无用WAL文件的跟踪与清理,重启后的回放,均需要严谨的处理。
  4. 涉及复杂的拓扑结构:一个Procedure中间运行过程可能会产生多个Sub-Procedures,这过程需要协调。
  5. 不依赖于ZooKeeper的分布式锁机制。
  6. 跨节点Rpc请求协调。

在实际实现中,还内部维护几个私有的数据结构,如Avl-Tree, FairQueue以及Bitmap,这也是导致实现复杂度过高的一大原因。接下来选择了三点内容来展开讲解,这三点内容也算是Procedure框架里的难点部分,分别为:WAL清理机制,Procedure调度策略以及分布式锁与事件通知机制。
WAL清理机制
WALProcedureStore也存在WAL日志文件的roll机制,这样就会产生多个WAL文件。对于一个旧的WAL文件,如何认定它可以被安全清理了?这就需要在WALProcedureStore中设计一个合理的关于Procedure状态的更新机制。
WALProcedureStore使用ProcedureStoreTracker对象来跟踪Procedure的写入/更新与删除操作,这个对象被称之为storeTracker
在ProcedureStoreTracker中,只需要用ProcID(long类型)来表示一个Procedure。即使只记录大量的ProcIDs,也会占用大量的内存空间,因此,在ProcedureStoreTracker内部实现了一个简单的Bitmap,用一个BIT来表示一个ProcID,这个Bitmap采用了分区、弹性扩展的设计:每一个分区称之为一个BitsetNode,每一个BitsetNode有一个起始值(Start),使用一个long数组来表示这个分区对应的Bitmap,每一个long数值包含64位,因此可以用来表示64个ProcIDs。一个BitsetNode应该可以包含X个long数值,这样就可以表示从Start值开始的X * 64个ProcIDs,但可惜,现在的代码实现还是存在问题的(应该是BUG),导致一个BitsetNode只能包含1个long值。
如果了解过Java Bitset的原理,或者是RoaringBitmap,就会发现这个实现并无任何新颖之处。
在一个BitsetNode内部,其实包含两个Bitmap: 一个Bitmap(modified)用来记录Insert/Update的ProcIDs,另一个Bitmap(Deleted)用来记录已经被Delete的ProcIDs。例如,如果Proc Y在Bitmap(modified)所对应的BIT为1,在Bitmap(Deleted)中所对应的BIT为0,则意味着这个Procedure仍然存活(或许刚刚被写入,或许刚刚被更新)。如果在Bitmap(Modified)中对应的BIT为1,但在Bitmap(Deleted)中所对应的BIT为1,则意味着这个Procedure已被删除了。
如果一个旧的WAL文件所关联的所有的Procedures,都已经被更新过(每一次更新,意味着Procedure的状态已经发生变化,则旧日志记录则已失去意义),或者都已经被删除,则这个WAL文件就可以被删除了。在实现上,同样可以用另外一个ProcedureStoreTracker对象(称之为holdingCleanupTracker)来跟踪最老的WAL中的Procedure的状态,每当有新的Procedure发生更新或者被删除,都同步删除holdingCleanupTracker中对应的ProcID即可。当然,还得考虑另外一种情形,如果有个别Procedure迟迟未更新如何处理? 这时,只要强制触发这些Procedures的更新操作即可。
这样描述起来似乎很简单,但这里却容易出错,而且一旦出错,可能会导致WAL日志被误删,或者堆积大量的日志文件无法被清理,出现这样的问题都是致命的。
Procedure调度策略
关于调度策略的基础需求,可以简单被表述为:
  1. 不同类型的Procedure优先级不同,如Server Procedure要优先于Table Procedure被调度。
  2. 即使同为Table Procedure类型,也需要按照Table的类型进行优先级调度,对于相同的优先级类型,则采用公平调度策略。

MasterProcedureScheduler是默认的ProcedureScheduler实现,接下来,我们看一下它的内部实现。
MasterProcedureScheduler将同一类型的Procedure,放在一个被称之为FairQueue的队列中,这样,共有四种类型的FairQueue队列,这四个队列分别被称之为MetaRunQueue, ServerRunQueue, PeerRunQueue, TableRunQueue),在调度时,按照上述罗列的顺序进行调度,这样,就确保了不同类型间的整体调度顺序。
简单起见,我们假设这四个队列中,仅有TableRunQueue有数据,其它皆空,这样确保会调度到TableRunQueue中的Procedure。在TableRunQueue中,本身会涉及多个Table,而每一个Table也可能会涉及多个Procedures:
TableA -> {ProcA1, ProcA2, ProcA3}
TableB -> {ProcB1, ProcB2, ProcB3, ProcB4, ProcB5}
TableC -> {ProcC1, ProcC2, ProcC3, ProcC4}

每一个Table以及所涉及到的Procedure列表,被封装成了一个TableQueue对象,在TableQueue中,使用了一个双向队列(Deque)来存储Procedures列表,Deque的特点是既可以在队列两端进行插入。在这个Deque中的顺序,直接决定了同一个Table的Procedures之间的调度顺序。
当需要为TableB写入一个新的Procedure时,需要首先快速获取TableB所关联的TableQueue对象,常见的思路是将所有的TableQueue存储在一个ConcurrentHashMap中,以TableName为Key,然而,这里却没有采用ConcurrentHashMap,而是实现了一个Avl-Tree(自动平衡二叉树),这样设计的考虑点为:ConcurrentHashMap中的写入会创建额外的Tree Node对象,当对象的写入与删除非常频繁时对于GC的压力较大(请参考AvlUtil.java)。Avl-Tree利于快速获取检索,但写入性能却慢于红黑树,因为涉及到过多的翻转操作。这样,基于TableName,可以快速从这个Avl-Tree中获取对应的TableQueue对象,而后就可以将这个新的Procedure写入到这个TableQueue的Deque中,写入时还可以指定写入到头部还是尾端。
现在我们已经了解了TableQueue对象,而且知道了多个TableQueue被存储在了一个类似于Map的数据结构中,还有一个关键问题没有解决:如何实现不同Table间的调度?
所有的TableQueue,都存放在TableRunQueue(再强调一下,这是一个FairQueue对象)中,而且按Table的优先级顺序组织。每当有一个新的TableQueue对象产生时,都会按照该TableQueue所关联的Table的优先级,插入到TableRunQueue中的合适位置。
当从这个TableRunQueue中poll一个新的TableQueue时,高优先级的TableQueue先被poll出来。如果被poll出来的TableQueue为普通优先级(priority值为1),为了维持公平调度的原则,在TabelRunQueue中将这个TableQueue从头部移到尾部,这样下一次将会调度到其它的TableQueue。
再简单总结一下:TableQueue对象用来描述一个Table所关联的Procedures队列,TableQueue对象存在于两个数据结构中,一个为Avl-Tree,这样可以基于TableName快速获取对应的TableQueue,以便快速写入;另一个数据结构为FairQueue,这是为了实现多Table间的调度。
分布式锁与事件通知机制
同样围绕Create Table的例子,说明一下关于分布式锁的需求:
  • 两个相同表的CreateTableProcedure不应该被同时执行
  • 同一个Namespace下的多个不同表的CreateTableProcedure允许被同时执行
  • 只要存在未完成的CreateTableProcedure,所关联的Namespace不允许被删除

实现上述需求,Procedure框架采用了共享锁/互斥锁方案:
  • 当CreateTableProcedure执行时,需要获取对应Namespace的共享锁,以及所要创建的Table的互斥锁。
  • 删除一个Namespace则需要获取这个Namespace的互斥锁,这意味着只要有一个Procedure持有该Namespace的共享锁,则无法被删除。

当一个Procedure X试图去获取一个Table的互斥锁时,碰巧该Table的互斥锁被其它Procedure持有,此时,Procedure X需要加到这个Table的锁的等待队列中,一旦该锁被释放,Procedure X需要被唤醒。
回顾一下Procedure的执行过程:
  1. Acquire Lock
  2. Execute

获取锁资源的操作,只需要在"Acquire Lock"步骤完成即可。
MasterProcedureScheduler中,使用一个SchemaLocking的对象来维护所有的锁资源,如Server Lock, Namespace Lock,Table Lock等等。以Table Lock资源为例:一个Table的锁资源,使用一个LockAndQueue对象进行抽象,顾名思义,在这个对象中,既有Lock,又有关于这个锁资源的Procedure等待队列;多个Table的LockAndQueue对象被组织在一个Map中,以TableName为Key。
同时,还可以将获取锁资源的操作封装成一个Procedure,称之为LockProcedure,以供Procedure框架之外的特性使用,如TakeSnapshotHandler,可以利用该机制来获取Table的互斥锁。
Procedure框架就是这样没有依赖于ZooKeeper,实现自身的分布式锁与消息通知机制。
总结
本文先从Procedure的设计初衷着手,而后以Create Table为例介绍了一个Procedure的生命周期,通过这个过程,可以简单了解整个框架所涉及到的几个角色,因此,在接下来的章节中,进一步细化Procedure框架中的几个角色。最后一部分,选择整个框架中比较复杂的几个模块,展开实现细节。受限于篇幅,有几部分内容未涉及到:
  1. WAL数据格式与WAL回放机制
  2. Notification-Bus(当然这部分也只实现了一小部分)
  3. 复杂Procedure拓扑结构情形
  4. Procedure超时处理

作为应用Procedure框架的最典型流程Region Assignment,在此文范畴内几乎未涉及。因为关于Region Assignment的故事太精彩,又太揪心,所以会放在一篇独立的文章中专门讲解。
参考信息
http://hbase.apache.org/book.html#pv2
http://hbase.apache.org/book.html#amv2
HBASE-13439: Procedure Framework(Pv2)
Accumulo FATE
Procedure V2介绍
HBASE-13203 Procedure v2 - master create/delete table
HBASE-14837: Procedure Queue Improvement
HBASE-20828: Finish-up AMv2 Design/List of Tenets/Specification of operation
HBASE-20338: WALProcedureStore#recoverLease() should have fixed sleeps for retrying rollWriter()
HBASE-21354: Procedure may be deleted improperly during master restarts resulting in "corrupt"
HBASE-20973: ArrayIndexOutOfBoundsException when rolling back procedure...
HBASE-19756: Master NPE during completed failed eviction
HBASE-19953: Avoid calling post* hook when procedure fails
HBASE-19996: Some nonce procs might not be cleaned up(follow up HBASE-19756) 收起阅读 »

OpenTSDB(HBase)时序数据存储模型

OpenTSDB基于HBase存储时序数据,在HBase层面设计RowKey规则为:metric+timestamp+datasource(tags)。HBase是一个KV数据库,一个时序数据(point)如果以KV的形式表示,那么其中的V必然是point的具体数值,而K就自然而然是唯一确定point数值的datasource+metric+timestamp。这种规律不仅适用于HBase,还适用于其他KV数据库,比如Kudu。

既然HBase中K是由datasource、metric以及timestamp三者构成,现在我们可以简单认为rowkey就为这三者的组合,那问题来了:这三者的组合顺序是怎么样的呢?

首先来看哪个应该排在首位。因为HBase中一张表的数据组织方式是按照rowkey的字典序顺序排列的,为了将同一种指标的所有数据集中放在一起,HBase将将metric放在了rowkey的最前面。假如将timestamp放在最前面,同一时刻的数据必然会写入同一个数据分片,无法起到散列的效果;而如果将datasource(即tags)放在最前面的话,这里有个更大的问题,就是datasource本身由多个标签组成,如果用户指定其中部分标签查找,而且不是前缀标签的话,在HBase里面将会变成大范围的扫描过滤查询,查询效率非常之低。举个上面的例子,如果将datasource放在最前面,那rowkey就可以表示为publisher=ultrarimfast.com&advertiser:google.com&gender:Male&country:USA_impressions_20110101000000,此时用户想查找20110101000000这个时间点所有发布在USA的所有广告的浏览量,即只根据country=USA这样一个维度信息查找指定时间点的某个指标,而且这个维度不是前缀维度,就会扫描大量的记录进行过滤。

确定了metric放在最前面之后,再来看看接下来应该将datasource放在中间呢还是应该将timestamp放在中间?将metric放在前面已经可以解决请求均匀分布(散列)的要求,因此HBase将timestamp放在中间,将datasource放在最后。试想,如果将datasource放在中间,也会遇到上文中说到的后缀维度查找的问题。

因此,OpenTSDB中rowkey的设计为:metric+timestamp+datasource,好了,那HBase就可以只设置一个columnfamily和一个column。那问题来了,OpenTSDB的这种设计有什么问题?在了解设计问题之前需要简单看看HBase在文件中存储KV的方式,即一系列时序数据在文件、内存中的存储方式,如下图所示:

v2-f5949de041cae2639dd7db6d1ca8dc29_r.jpg

上图是HBase中一个存储KeyValue(KV)数据的数据块结构,一个数据块由多个KeyValue数据组成,在我们的事例中KeyValue就是一个时序数据点(point)。其中Value结构很简单,就是一个数值。而Key就比较复杂了,rowkey+columnfamily+column+timestamp+keytype组成,其中rowkey等于metric+timestamp+datasource。

问题一:存在很多无用的字段。一个KeyValue中只有rowkey是有用的,其他字段诸如columnfamily、column、timestamp以及keytype从理论上来讲都没有任何实际意义,但在HBase的存储体系里都必须存在,因而耗费了很大的存储成本。

问题二:数据源和采集指标冗余。KeyValue中rowkey等metric+timestamp+datasource,试想同一个数据源的同一个采集指标,随着时间的流逝不断吐出采集数据,这些数据理论上共用同一个数据源(datasource)和采集指标(metric),但在HBase的这套存储体系下,共用是无法体现的,因此存在大量的数据冗余,主要是数据源冗余以及采集指标冗余。

问题三:无法有效的压缩。HBase提供了块级别的压缩算法-snappy、gzip等,这些通用压缩算法并没有针对时序数据进行设置,压缩效率比较低。HBase同样提供了一些编码算法,比如FastDiff等等,可以起到一定的压缩效果,但是效果并不佳。效果不佳的主要原因是HBase没有数据类型的概念,没有schema的概念,不能针对特定数据类型进行特定编码,只能选择通用的编码,效果可想而知。

问题四:不能完全保证多维查询能力。HBase本身没有schema,目前没有实现倒排索引机制,所有查询必须指定metric、timestamp以及完整的tags或者前缀tags进行查询,对于后缀维度查询也勉为其难。

虽说有这样那样的问题,但是OpenTSDB还是针对存储模型做了两个方面的优化:

优化一:timestamp并不是想象中细粒度到秒级或毫秒级,而是精确到小时级别,然后将小时中每一秒设置到列上。这样一行就会有3600列,每一列表示一小时的一秒。这样设置据说可以有效的取出一小时整的数据。

优化二:所有metrics以及所有标签信息(tags)都使用了全局编码将标签值编码成更短的bit,减少rowkey的存储数据量。上文分析HBase这种存储方式的弊端是说道会存在大量的数据源(tags)冗余以及指标(metric)冗余,有冗余是吧,那我就搞个编码,将string编码成bit,尽最大努力减少冗余。虽说这样的全局编码可以有效降低数据的存储量,但是因为全局编码字典需要存储在内存中,因此在很多时候(海量标签值),字典所需内存都会非常之大。

上述两个优化可以参考OpenTSDB这张经典的示意图:

v2-1e76d8c875d96b6f7b1331997542c44f_hd.jpg

 
继续阅读 »
OpenTSDB基于HBase存储时序数据,在HBase层面设计RowKey规则为:metric+timestamp+datasource(tags)。HBase是一个KV数据库,一个时序数据(point)如果以KV的形式表示,那么其中的V必然是point的具体数值,而K就自然而然是唯一确定point数值的datasource+metric+timestamp。这种规律不仅适用于HBase,还适用于其他KV数据库,比如Kudu。

既然HBase中K是由datasource、metric以及timestamp三者构成,现在我们可以简单认为rowkey就为这三者的组合,那问题来了:这三者的组合顺序是怎么样的呢?

首先来看哪个应该排在首位。因为HBase中一张表的数据组织方式是按照rowkey的字典序顺序排列的,为了将同一种指标的所有数据集中放在一起,HBase将将metric放在了rowkey的最前面。假如将timestamp放在最前面,同一时刻的数据必然会写入同一个数据分片,无法起到散列的效果;而如果将datasource(即tags)放在最前面的话,这里有个更大的问题,就是datasource本身由多个标签组成,如果用户指定其中部分标签查找,而且不是前缀标签的话,在HBase里面将会变成大范围的扫描过滤查询,查询效率非常之低。举个上面的例子,如果将datasource放在最前面,那rowkey就可以表示为publisher=ultrarimfast.com&advertiser:google.com&gender:Male&country:USA_impressions_20110101000000,此时用户想查找20110101000000这个时间点所有发布在USA的所有广告的浏览量,即只根据country=USA这样一个维度信息查找指定时间点的某个指标,而且这个维度不是前缀维度,就会扫描大量的记录进行过滤。

确定了metric放在最前面之后,再来看看接下来应该将datasource放在中间呢还是应该将timestamp放在中间?将metric放在前面已经可以解决请求均匀分布(散列)的要求,因此HBase将timestamp放在中间,将datasource放在最后。试想,如果将datasource放在中间,也会遇到上文中说到的后缀维度查找的问题。

因此,OpenTSDB中rowkey的设计为:metric+timestamp+datasource,好了,那HBase就可以只设置一个columnfamily和一个column。那问题来了,OpenTSDB的这种设计有什么问题?在了解设计问题之前需要简单看看HBase在文件中存储KV的方式,即一系列时序数据在文件、内存中的存储方式,如下图所示:

v2-f5949de041cae2639dd7db6d1ca8dc29_r.jpg

上图是HBase中一个存储KeyValue(KV)数据的数据块结构,一个数据块由多个KeyValue数据组成,在我们的事例中KeyValue就是一个时序数据点(point)。其中Value结构很简单,就是一个数值。而Key就比较复杂了,rowkey+columnfamily+column+timestamp+keytype组成,其中rowkey等于metric+timestamp+datasource。

问题一:存在很多无用的字段。一个KeyValue中只有rowkey是有用的,其他字段诸如columnfamily、column、timestamp以及keytype从理论上来讲都没有任何实际意义,但在HBase的存储体系里都必须存在,因而耗费了很大的存储成本。

问题二:数据源和采集指标冗余。KeyValue中rowkey等metric+timestamp+datasource,试想同一个数据源的同一个采集指标,随着时间的流逝不断吐出采集数据,这些数据理论上共用同一个数据源(datasource)和采集指标(metric),但在HBase的这套存储体系下,共用是无法体现的,因此存在大量的数据冗余,主要是数据源冗余以及采集指标冗余。

问题三:无法有效的压缩。HBase提供了块级别的压缩算法-snappy、gzip等,这些通用压缩算法并没有针对时序数据进行设置,压缩效率比较低。HBase同样提供了一些编码算法,比如FastDiff等等,可以起到一定的压缩效果,但是效果并不佳。效果不佳的主要原因是HBase没有数据类型的概念,没有schema的概念,不能针对特定数据类型进行特定编码,只能选择通用的编码,效果可想而知。

问题四:不能完全保证多维查询能力。HBase本身没有schema,目前没有实现倒排索引机制,所有查询必须指定metric、timestamp以及完整的tags或者前缀tags进行查询,对于后缀维度查询也勉为其难。

虽说有这样那样的问题,但是OpenTSDB还是针对存储模型做了两个方面的优化:

优化一:timestamp并不是想象中细粒度到秒级或毫秒级,而是精确到小时级别,然后将小时中每一秒设置到列上。这样一行就会有3600列,每一列表示一小时的一秒。这样设置据说可以有效的取出一小时整的数据。

优化二:所有metrics以及所有标签信息(tags)都使用了全局编码将标签值编码成更短的bit,减少rowkey的存储数据量。上文分析HBase这种存储方式的弊端是说道会存在大量的数据源(tags)冗余以及指标(metric)冗余,有冗余是吧,那我就搞个编码,将string编码成bit,尽最大努力减少冗余。虽说这样的全局编码可以有效降低数据的存储量,但是因为全局编码字典需要存储在内存中,因此在很多时候(海量标签值),字典所需内存都会非常之大。

上述两个优化可以参考OpenTSDB这张经典的示意图:

v2-1e76d8c875d96b6f7b1331997542c44f_hd.jpg

  收起阅读 »

中国HBase技术社区第八届MeetUp ——HBase典型应用场景与实践(南京站)

HBase—Hadoop Database是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。HBase的特点是高可靠性、高性能、面向列、可伸缩的分布式存储系统,如今HBase已经广泛应用于各互联网行业。那么我们如何熟练掌握HBase技术及应用呢?

2018年11月17号,由中国HBase技术社区、DataFun社区联合氪空间主办的中国第八届HBase Meetup将来到南京,届时来自阿里云、毕马威、苏宁等公司HBase的专家们,将为大家分享HBase的应用实践。

主办方:中国HBase技术社区、DataFun社区
联合主办方:氪空间
合作伙伴:云栖社区、掘金社区
时间:2018.11.17,13:00-18:00
地点:南京市玄武区同仁西街7号南楼三层氪空间(地铁珠江路附近)
注:报名通过审核后,请持有效票二维码参会。
微信图片_20181106142054.jpg

议程安排:
1.jpg

分享介绍:
1.png

玄陵(郭超) 阿里云 工程师
嘉宾介绍:玄陵(郭超),阿里云工程师,了解常见分布式大数据数据库,并有一定实战经验。
分享主题:阿里云HBase备份恢复的原理以及实践
内容概要: 主要介绍阿里云HBase的备份恢复的设计背景,原理以及实现,以及与业内大部分的分布式大数据数据库备份恢复的异同。

2.jpg

蒋晓明  毕马威智能创新空间 技术经理
嘉宾介绍:蒋晓明  现就职于毕马威智能创新空间,主要从事数据平台的建设,热衷于大数据相关技术的研究
分享主题:HBase在审计行业的应用
内容概要:a. 企业财务详情查询,HBase提供多维度快速查询;b. 企业自动化电子对账,HBase提供多维度匹配。

32.jpg


张立明 苏宁高级研发工程师&HBase组件负责人
嘉宾介绍:张立明,苏宁高级研发工程师,HBase组件负责人,负责HBase平台的可用性和稳定性建设,热衷于大数据相关技术。
分享主题: HBase在苏宁的应用和实践
内容概要:介绍HBase在苏宁的主要使用场景,围绕使用场景介绍HBase在苏宁的使用现状及发展历程、功能增强及性能优化、运维监控。

a. 使用现状及发展历程主要包括:集群规模、版本升级、集群部署方式;
b. 功能增强及性能优化主要包括:HA,全局限流,性能优化经验介绍;
c. 运维监控主要包括:系统稳定性和性能方面监控,以及根据监控信息快速定位问题经验介绍,表健康状态监控及自动优化。

主办方介绍:

中国HBase技术社区:为了让众多HBase相关从业人员及爱好者有一个自由交流HBase相关技术的社区,由阿里巴巴、小米、网易、滴滴、知乎等公司的HBase技术研究人员共同发起了组建:中国HBase技术社区(Chinese HBase Technical Community 简称CHTC)。

我们非常欢迎对HBase有技术激情的同学一起加入探讨HBase技术,同时诚邀广大HBase技术爱好者加入。大家在工作学习遇到HBase技术问题,可以把问题发布到中国HBase技术社区论坛http://hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术,关注HBase技术社区公众号(微信号:hbasegroup),邀请进社区的讨论群。
4.jpg

DataFun定位于最“实用”的数据科学社区,主要形式为线下的深度沙龙、线上的内容整理。希望将工业界专家在各自场景下的实践经验,通过DataFun的平台传播和扩散,对即将或已经开始相关尝试的同学有启发和借鉴。DataFun的愿景是:为大数据、人工智能从业者和爱好者打造一个分享、交流、学习、成长的平台,让数据科学领域的知识和经验更好的传播和落地产生价值。

DataFun社区成立至今,已经成功在全国范围内举办数十场线下技术沙龙,有超过一百位的业内专家参与分享,聚集了万余大数据、算法相关领域从业者。
menu.saveimg_.savepath20181106143601_.jpg

联合主办方:
6.png

合作社区:
C80F6D6B-479D-4d5f-97FC-C5FA8B0159AD.png

8.jpg

9.png
继续阅读 »
HBase—Hadoop Database是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。HBase的特点是高可靠性、高性能、面向列、可伸缩的分布式存储系统,如今HBase已经广泛应用于各互联网行业。那么我们如何熟练掌握HBase技术及应用呢?

2018年11月17号,由中国HBase技术社区、DataFun社区联合氪空间主办的中国第八届HBase Meetup将来到南京,届时来自阿里云、毕马威、苏宁等公司HBase的专家们,将为大家分享HBase的应用实践。

主办方:中国HBase技术社区、DataFun社区
联合主办方:氪空间
合作伙伴:云栖社区、掘金社区
时间:2018.11.17,13:00-18:00
地点:南京市玄武区同仁西街7号南楼三层氪空间(地铁珠江路附近)
注:报名通过审核后,请持有效票二维码参会。
微信图片_20181106142054.jpg

议程安排:
1.jpg

分享介绍:
1.png

玄陵(郭超) 阿里云 工程师
嘉宾介绍:玄陵(郭超),阿里云工程师,了解常见分布式大数据数据库,并有一定实战经验。
分享主题:阿里云HBase备份恢复的原理以及实践
内容概要: 主要介绍阿里云HBase的备份恢复的设计背景,原理以及实现,以及与业内大部分的分布式大数据数据库备份恢复的异同。

2.jpg

蒋晓明  毕马威智能创新空间 技术经理
嘉宾介绍:蒋晓明  现就职于毕马威智能创新空间,主要从事数据平台的建设,热衷于大数据相关技术的研究
分享主题:HBase在审计行业的应用
内容概要:a. 企业财务详情查询,HBase提供多维度快速查询;b. 企业自动化电子对账,HBase提供多维度匹配。

32.jpg


张立明 苏宁高级研发工程师&HBase组件负责人
嘉宾介绍:张立明,苏宁高级研发工程师,HBase组件负责人,负责HBase平台的可用性和稳定性建设,热衷于大数据相关技术。
分享主题: HBase在苏宁的应用和实践
内容概要:介绍HBase在苏宁的主要使用场景,围绕使用场景介绍HBase在苏宁的使用现状及发展历程、功能增强及性能优化、运维监控。

a. 使用现状及发展历程主要包括:集群规模、版本升级、集群部署方式;
b. 功能增强及性能优化主要包括:HA,全局限流,性能优化经验介绍;
c. 运维监控主要包括:系统稳定性和性能方面监控,以及根据监控信息快速定位问题经验介绍,表健康状态监控及自动优化。

主办方介绍:

中国HBase技术社区:为了让众多HBase相关从业人员及爱好者有一个自由交流HBase相关技术的社区,由阿里巴巴、小米、网易、滴滴、知乎等公司的HBase技术研究人员共同发起了组建:中国HBase技术社区(Chinese HBase Technical Community 简称CHTC)。

我们非常欢迎对HBase有技术激情的同学一起加入探讨HBase技术,同时诚邀广大HBase技术爱好者加入。大家在工作学习遇到HBase技术问题,可以把问题发布到中国HBase技术社区论坛http://hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术,关注HBase技术社区公众号(微信号:hbasegroup),邀请进社区的讨论群。
4.jpg

DataFun定位于最“实用”的数据科学社区,主要形式为线下的深度沙龙、线上的内容整理。希望将工业界专家在各自场景下的实践经验,通过DataFun的平台传播和扩散,对即将或已经开始相关尝试的同学有启发和借鉴。DataFun的愿景是:为大数据、人工智能从业者和爱好者打造一个分享、交流、学习、成长的平台,让数据科学领域的知识和经验更好的传播和落地产生价值。

DataFun社区成立至今,已经成功在全国范围内举办数十场线下技术沙龙,有超过一百位的业内专家参与分享,聚集了万余大数据、算法相关领域从业者。
menu.saveimg_.savepath20181106143601_.jpg

联合主办方:
6.png

合作社区:
C80F6D6B-479D-4d5f-97FC-C5FA8B0159AD.png

8.jpg

9.png
收起阅读 »

HBase Meetup 成都站 PPT下载

2018年11月3号,由中国HBase技术社区、DataFun社区、爱奇艺主办的中国第七届HBase Meetup来到成都。各位嘉宾分享了 HBase 相关的知识。
 
本次会议的分享议题如下:
 
  1. 分享主题:HBase2.0重新定义小对象实时存取,PPT下载:http://hbase.group/slides/167

 
嘉宾介绍:天引,专注在大数据领域,拥有多年分布式、高并发、大规模系统的研发与实践经验,先后参与hbase、phoenix、lindorm等产品的内核引擎研发,目前负责阿里上万节点的HBase As a Service的发展与落地。
内容概要:小对象,特别指1K~10MB范围的数据,比如图片,短视频,文档等广泛的存在于人工智能,医疗,教育,生活分享,电子商务等领域。HBase2.0在MOB技术的加持下重新定义小对象实时存取,具有低延迟,读写强一致,检索能力强,水平易扩展等关键能力。本文介绍了MOB特性的原理与实现,以及与经典对象存储相比,MOB带来的差异性与优势。

       2. 分享主题:HBase在爱奇艺的应用实践,PPT下载:http://hbase.group/slides/168 

嘉宾介绍:郑浩南,爱奇艺资深研发工程师,专注于大数据领域,负责Hadoop服务的运维研究以及DevOps平台开发。
内容概要:随着大数据存储计算对延时吞吐要求越来越高,需求日益复杂化,HBase在爱奇艺中被广泛应用和实践以应对多样化的业务场景。本次演讲将介绍HBase在爱奇艺的部署模式和使用场景,以及在爱奇艺私有云环境下的运维策略。
 
      3. 分享主题:Hbase在车联网的应用与实践,PPT下载:http://hbase.group/slides/169 

嘉宾介绍:巨鹏,g7高级数据运维工程师,负责g7的数据维护以及中间件的稳定性建设。
内容概要:为大家分享在大数据量的IOT车联网环境中,是如何发挥HBase优势解决实际问题以及他稳定性如何保障。
继续阅读 »
2018年11月3号,由中国HBase技术社区、DataFun社区、爱奇艺主办的中国第七届HBase Meetup来到成都。各位嘉宾分享了 HBase 相关的知识。
 
本次会议的分享议题如下:
 
  1. 分享主题:HBase2.0重新定义小对象实时存取,PPT下载:http://hbase.group/slides/167

 
嘉宾介绍:天引,专注在大数据领域,拥有多年分布式、高并发、大规模系统的研发与实践经验,先后参与hbase、phoenix、lindorm等产品的内核引擎研发,目前负责阿里上万节点的HBase As a Service的发展与落地。
内容概要:小对象,特别指1K~10MB范围的数据,比如图片,短视频,文档等广泛的存在于人工智能,医疗,教育,生活分享,电子商务等领域。HBase2.0在MOB技术的加持下重新定义小对象实时存取,具有低延迟,读写强一致,检索能力强,水平易扩展等关键能力。本文介绍了MOB特性的原理与实现,以及与经典对象存储相比,MOB带来的差异性与优势。

       2. 分享主题:HBase在爱奇艺的应用实践,PPT下载:http://hbase.group/slides/168 

嘉宾介绍:郑浩南,爱奇艺资深研发工程师,专注于大数据领域,负责Hadoop服务的运维研究以及DevOps平台开发。
内容概要:随着大数据存储计算对延时吞吐要求越来越高,需求日益复杂化,HBase在爱奇艺中被广泛应用和实践以应对多样化的业务场景。本次演讲将介绍HBase在爱奇艺的部署模式和使用场景,以及在爱奇艺私有云环境下的运维策略。
 
      3. 分享主题:Hbase在车联网的应用与实践,PPT下载:http://hbase.group/slides/169 

嘉宾介绍:巨鹏,g7高级数据运维工程师,负责g7的数据维护以及中间件的稳定性建设。
内容概要:为大家分享在大数据量的IOT车联网环境中,是如何发挥HBase优势解决实际问题以及他稳定性如何保障。 收起阅读 »

HBase Coprocessor的实现与应用

本文来自于中国HBase技术社区武汉站HBase MeetUp线下交流会的烽火大数据平台研发负责人叶铿(云端浪子)。
HBase Coprocessor的实现与应用PPT下载:http://hbase.group/slides/159

搜狗截图20181031141018.png

本次分享的内容主要分为以下五点:
 
  1. Coprocessor简介
  2. Endpoint服务端实现
  3. Endpoint客户端实现
  4. Observer实现二级索引
  5. Coprocessor应用场景


1.Coprocessor简介

HBase协处理器的灵感来自于Jeff Dean 09年的演讲,根据该演讲实现类似于Bigtable的协处理器,包括以下特性:每个表服务器的任意子表都可以运行代码客户端的高层调用接口(客户端能够直接访问数据表的行地址,多行读写会自动分片成多个并行的RPC调用),提供一个非常灵活的、可用于建立分布式服务的数据模型,能够自动化扩展、负载均衡、应用请求路由。HBase的协处理器灵感来自Bigtable,但是实现细节不尽相同。HBase建立框架为用户提供类库和运行时环境,使得代码能够在HBase Region Server和Master上面进行处理。

menu.saveimg_.savepath20181031141144_.jpg

(1)实现目的
 
  1. HBase无法轻易建立“二级索引”;
  2. 执行求和、计数、排序等操作比较困难,必须通过MapReduce/Spark实现,对于简单的统计或聚合计算时,可能会因为网络与IO开销大而带来性能问题。


(2)灵感来源

灵感来源于Bigtable的协处理器,包含如下特性:
  1. 每个表服务器的任意子表都可以运行代码;
  2. 客户端能够直接访问数据表的行,多行读写会自动分片成多个并行的RPC调用。


(3)提供接口
 
  1. RegionObserver:提供客户端的数据操纵事件钩子:Get、Put、Delete、Scan等;
  2. WALObserver:提供WAL相关操作钩子;
  3. MasterObserver:提供DDL-类型的操作钩子。如创建、删除、修改数据表等;
  4. Endpoint:终端是动态RPC插件的接口,它的实现代码被安装在服务器端,能够通过HBase RPC调用唤醒。


(4)应用范围
 
  1. 通过使用RegionObserver接口可以实现二级索引的创建和维护;
  2. 通过使用Endpoint接口,在对数据进行简单排序和sum,count等统计操作时,能够极大提高性能。


本文将通过具体实例来演示两种协处理器的开发方法的详细实现过程。

2.Endpoint服务端实现

在传统关系型数据库里面,可以随时的对某列进行求和sum,但是目前HBase目前所提供的接口,直接求和是比较困难的,所以先编写好服务端代码,并加载到对应的Table上,加载协处理器有几种方法,可以通过HTableDescriptor的addCoprocessor方法直接加载,同理也可以通过removeCoprocessor方法卸载协处理器。

Endpoint协处理器类似传统数据库的存储过程,客户端调用Endpoint协处理器执行一段Server端代码,并将Server端代码的结果返回给Client进一步处理,最常见的用法就是进行聚合操作。举个例子说明:如果没有协处理器,当用户需要找出一张表中的最大数据即max聚合操作,必须进行全表扫描,客户端代码遍历扫描结果并执行求max操作,这样的方法无法利用底层集群的并发能力,而将所有计算都集中到Client端统一执行, 效率非常低。但是使用Coprocessor,用户将求max的代码部署到HBase Server端,HBase将利用底层Cluster的多个节点并行执行求max的操作即在每个Region范围内执行求最大值逻辑,将每个Region的最大值在Region Server端计算出,仅仅将该max值返回给客户端。客户端进一步将多个Region的max进一步处理而找到其中的max,这样整体执行效率提高很多。但是一定要注意的是Coprocessor一定要写正确,否则导致RegionServer宕机。

menu.saveimg_.savepath20181031141342_.jpg

 
Protobuf定义

如前所述,客户端和服务端之间需要进行RPC通信,所以两者间需要确定接口,当前版本的HBase的协处理器是通过Google Protobuf协议来实现数据交换的,所以需要通过Protobuf来定义接口。

如下所示:
option java_package = "com.my.hbase.protobuf.generated";
option java_outer_classname = "AggregateProtos";
option java_generic_services = true;
option java_generate_equals_and_hash = true;
option optimize_for = SPEED;

import "Client.proto";

message AggregateRequest {
required string interpreter_class_name = 1;
required Scan scan = 2;
optional bytes interpreter_specific_bytes = 3;
}

message AggregateResponse {
repeated bytes first_part = 1;
optional bytes second_part = 2;
}

service AggregateService {
rpc GetMax (AggregateRequest) returns (AggregateResponse);
rpc GetMin (AggregateRequest) returns (AggregateResponse);
rpc GetSum (AggregateRequest) returns (AggregateResponse);
rpc GetRowNum (AggregateRequest) returns (AggregateResponse);
rpc GetAvg (AggregateRequest) returns (AggregateResponse);
rpc GetStd (AggregateRequest) returns (AggregateResponse);
rpc GetMedian (AggregateRequest) returns (AggregateResponse);
}
可以看到这里定义7个聚合服务RPC,名字分别叫做GetMax、GetMin、GetSum等,本文通过GetSum进行举例,其他的聚合RPC也是类似的内部实现。RPC有一个入口参数,用消息AggregateRequest表示;RPC的返回值用消息AggregateResponse表示。Service是一个抽象概念,RPC的Server端可以看作一个用来提供服务的Service。在HBase Coprocessor中Service就是Server端需要提供的Endpoint Coprocessor服务,主要用来给HBase的Client提供服务。AggregateService.java是由Protobuf软件通过终端命令“protoc filename.proto--java_out=OUT_DIR”自动生成的,其作用是将.proto文件定义的消息结构以及服务转换成对应接口的RPC实现,其中包括如何构建request消息和response响应以及消息包含的内容的处理方式,并且将AggregateService包装成一个抽象类,具体的服务以类的方法的形式提供。AggregateService.java定义Client端与Server端通信的协议,代码中包含请求信息结构AggregateRequest、响应信息结构AggregateResponse、提供的服务种类AggregateService,其中AggregateRequest中的interpreter_class_name指的是column interpreter的类名,此类的作用在于将数据格式从存储类型解析成所需类型。AggregateService.java由于代码太长,在这里就不贴出来了。

下面我们来讲一下服务端的架构,

首先,Endpoint Coprocessor是一个Protobuf Service的实现,因此需要它必须继承某个ProtobufService。我们在前面已经通过proto文件定义Service,命名为AggregateService,因此Server端代码需要重载该类,其次作为HBase的协处理器,Endpoint 还必须实现HBase定义的协处理器协议,用Java的接口来定义。具体来说就是CoprocessorService和Coprocessor,这些HBase接口负责将协处理器和HBase 的RegionServer等实例联系起来以便协同工作。Coprocessor接口定义两个接口函数:start和stop。

加载Coprocessor之后Region打开的时候被RegionServer自动加载,并会调用器start 接口完成初始化工作。一般情况该接口函数仅仅需要将协处理器的运行上下文环境变量CoprocessorEnvironment保存到本地即可。

CoprocessorEnvironment保存协处理器的运行环境,每个协处理器都是在一个RegionServer进程内运行并隶属于某个Region。通过该变量获取Region的实例等 HBase运行时环境对象。

Coprocessor接口还定义stop()接口函数,该函数在Region被关闭时调用,用来进行协处理器的清理工作。本文里我们没有进行任何清理工作,因此该函数什么也不干。

我们的协处理器还需要实现CoprocessorService接口。该接口仅仅定义一个接口函数 getService()。我们仅需要将本实例返回即可。HBase的Region Server在接收到客户端的调用请求时,将调用该接口获取实现RPCService的实例,因此本函数一般情况下就是返回自身实例即可。

完成以上三个接口函数之后,Endpoint的框架代码就已完成。每个Endpoint协处理器都必须实现这些框架代码而且写法雷同。

menu.saveimg_.savepath20181031141452_.jpg

Server端的代码就是一个Protobuf RPC的Service实现,即通过Protobuf提供的某种服务。其开发内容主要包括:
 
  1. 实现Coprocessor的基本框架代码
  2. 实现服务的RPC具体代码


Endpoint 协处理的基本框架

Endpoint 是一个Server端Service的具体实现,其实现有一些框架代码,这些框架代码与具体的业务需求逻辑无关。仅仅是为了和HBase运行时环境协同工作而必须遵循和完成的一些粘合代码。因此多数情况下仅仅需要从一个例子程序拷贝过来并进行命名修改即可。不过我们还是完整地对这些粘合代码进行粗略的讲解以便更好地理解代码。
public Service getService() {
return this;
}

public void start(CoprocessorEnvironment env) throws IOException {
if(env instanceof RegionCoprocessorEnvironment) {
this.env = (RegionCoprocessorEnvironment)env;
} else {
throw new CoprocessorException("Must be loaded on a table region!");
}
}

public void stop(CoprocessorEnvironment env) throws IOException {
}
Endpoint协处理器真正的业务代码都在每一个RPC函数的具体实现中。


在本文中,我们的Endpoint协处理器仅提供一个RPC函数即getSUM。我将分别介绍编写该函数的几个主要工作:了解函数的定义,参数列表;处理入口参数;实现业务逻辑;设置返回参数。
public void getSum(RpcController controller, AggregateRequest request, RpcCallbackdone) {
AggregateResponse response = null;
RegionScanner scanner = null;
long sum = 0L;
try {
ColumnInterpreter ignored = this.constructColumnInterpreterFromRequest(request);
Object sumVal = null;
Scan scan = ProtobufUtil.toScan(request.getScan());
scanner = this.env.getRegion().getScanner(scan);
byte[] colFamily = scan.getFamilies()[0];
NavigableSet qualifiers = (NavigableSet) scan.getFamilyMap().get(colFamily);
byte[] qualifier = null;
if (qualifiers != null && !qualifiers.isEmpty()) {
qualifier = (byte[]) qualifiers.pollFirst();
}

ArrayList results = new ArrayList();
boolean hasMoreRows = false;

do {
hasMoreRows = scanner.next(results);
int listSize = results.size();

for (int i = 0; i < listSize; ++i) {
//取出列值
Object temp = ignored.getValue(colFamily, qualifier,
(Cell) results.get(i));
if (temp != null) {
sumVal = ignored.add(sumVal, ignored.castToReturnType(temp));
}
}

results.clear();
} while (hasMoreRows);

if (sumVal != null) {
response = AggregateResponse.newBuilder().addFirstPart(
ignored.getProtoForPromotedType(sumVal).toByteString()).build();
}
} catch (IOException var27) {
ResponseConverter.setControllerException(controller, var27);
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (IOException var26) {
;
}
}

}

log.debug("Sum from this region is " +
this.env.getRegion().getRegionInfo().getRegionNameAsString() + ": " + sum);
done.run(response);
}
Endpoint类比于数据库的存储过程,其触发服务端的基于Region的同步运行再将各个结果在客户端搜集后归并计算。特点类似于传统的MapReduce框架,服务端Map客户端Reduce。

3.Endpoint客户端实现

HBase提供客户端Java包org.apache.hadoop.hbase.client.HTable,提供以下三种方法来调用协处理器提供的服务:
 
  1. coprocessorService(byte[])
  2. coprocessorService(Class, byte[], byte[],Batch.Call),
  3. coprocessorService(Class, byte[], byte[],Batch.Call, Batch.Callback)

 

menu.saveimg_.savepath20181031141634_.jpg

该方法采用rowkey指定Region。这是因为HBase客户端很少会直接操作Region,一般不需要知道Region的名字;况且在HBase中Region名会随时改变,所以用rowkey来指定Region是最合理的方式。使用rowkey可以指定唯一的一个Region,如果给定的Rowkey并不存在,只要在某个Region的rowkey范围内依然用来指定该Region。比如Region 1处理[row1, row100]这个区间内的数据,则rowkey=row1就由Region 1来负责处理,换句话说我们可以用row1来指定Region 1,无论rowkey等于”row1”的记录是否存在。CoprocessorService方法返回类型为CoprocessorRpcChannel的对象,该 RPC通道连接到由rowkey指定的Region上面,通过此通道可以调用该Region上面部署的协处理器RPC。

menu.saveimg_.savepath20181031141702_.jpg

有时候客户端需要调用多个 Region上的同一个协处理器,比如需要统计整个Table的sum,在这种情况下,需要所有的Region都参与进来,分别统计自身Region内部的sum并返回客户端,最终客户端将所有Region的返回结果汇总,就可以得到整张表的sum。


这意味着该客户端同时和多个Region进行批处理交互。一个可行的方法是,收集每个 Region的startkey,然后循环调用第一种coprocessorService方法:用每一个Region的startkey 作为入口参数,获得RPC通道创建stub对象,进而逐一调用每个Region上的协处理器RPC。这种做法需要写很多的代码,为此HBase提供两种更加简单的 coprocessorService方法来处理多个Region的协处理器调用。先来看第一种方法 coprocessorService(Class, byte[],byte[],Batch.Call),

该方法有 4 个入口参数。第一个参数是实现RPC的Service 类,即前文中的AggregateService类。通过它,HBase就可以找到相应的部署在Region上的协处理器,一个Region上可以部署多个协处理器,客户端必须通过指定Service类来区分究竟需要调用哪个协处理器提供的服务。

要调用哪些Region上的服务则由startkey和endkey来确定,通过 rowkey范围即可确定多个 Region。为此,coprocessorService方法的第二个和第三个参数分别是 startkey和endkey,凡是落在[startkey,endkey]区间内的Region都会参与本次调用。

第四个参数是接口类Batch.Call。它定义了如何调用协处理器,用户通过重载该接口的call()方法来实现客户端的逻辑。在call()方法内,可以调用RPC,并对返回值进行任意处理。即前文代码清单1中所做的事情。coprocessorService将负责对每个 Region调用这个call()方法。

coprocessorService方法的返回值是一个Map类型的集合。该集合的key是Region名字,value是Batch.Call.call方法的返回值。该集合可以看作是所有Region的协处理器 RPC 返回的结果集。客户端代码可以遍历该集合对所有的结果进行汇总处理。

这种coprocessorService方法的大体工作流程如下。首先它分析startkey和 endkey,找到该区间内的所有Region,假设存放在regionList中。然后,遍历regionList,为每一个Region调用Batch.Call,在该接口内,用户定义具体的RPC调用逻辑。最后coprocessorService将所有Batch.Call.call()的返回值加入结果集合并返回。

menu.saveimg_.savepath20181031141730_.jpg

coprocessorService的第三种方法比第二个方法多了一个参数callback。coprocessorService第二个方法内部使用HBase自带的缺省callback,该缺省 callback将每个Region的返回结果都添加到一个Map类型的结果集中,并将该集合作为coprocessorService方法的返回值。

HBase 提供第三种coprocessorService方法允许用户定义callback行为,coprocessorService 会为每一个RPC返回结果调用该callback,用户可以在callback 中执行需要的逻辑,比如执行sum累加。用第二种方法的情况下,每个Region协处理器RPC的返回结果先放入一个列表,所有的 Region 都返回后,用户代码再从该列表中取出每一个结果进行累加;用第三种方法,直接在callback中进行累加,省掉了创建结果集合和遍历该集合的开销,效率会更高一些。

因此我们只需要额外定义一个callback即可,callback是一个Batch.Callback接口类,用户需要重载其update方法。
 
public S sum(final HTable table, final ColumnInterpreter<R, S, P, Q, T> ci,final Scan scan)throws Throwable {

final AggregateRequest requestArg = validateArgAndGetPB(scan, ci, false);

class SumCallBack implements Batch.Callback {

S sumVal = null;

public S getSumResult() {
return sumVal;
}

@Override
public synchronized void update(byte[] region, byte[] row, S result) {
sumVal = ci.add(sumVal, result);
}}

SumCallBack sumCallBack = new SumCallBack();
table.coprocessorService(AggregateService.class, scan.getStartRow(), scan.getStopRow(),
new Batch.Call<AggregateService, S>() {
@Override
public S call(AggregateService instance) throws IOException {
ServerRpcController controller = new ServerRpcController();
BlockingRpcCallback<AggregateResponse> rpcCallback =
new BlockingRpcCallback<AggregateResponse>();
//RPC 调用
instance.getSum(controller, requestArg, rpcCallback);
AggregateResponse response = rpcCallback.get();
if (controller.failedOnException()) {
throw controller.getFailedOn();
}
if (response.getFirstPartCount() == 0) {
return null;
}
ByteString b = response.getFirstPart(0);
T t = ProtobufUtil.getParsedGenericInstance(ci.getClass(), 4, b);
S s = ci.getPromotedValueFromProto(t);
return s;
}
}, sumCallBack);
return sumCallBack.getSumResult();
4.Observer实现二级索引

Observer类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。Observer Coprocessor是一些散布在HBase Server端代码的 hook钩子, 在固定的事件发生时被调用。比如:put操作之前有钩子函数prePut,该函数在pu 操作执 行前会被Region Server调用;在put操作之后则有postPut 钩子函数。
menu.saveimg_.savepath20181031141817_.jpg

 
RegionObserver工作原理

RegionObserver提供客户端的数据操纵事件钩子,Get、Put、Delete、Scan,使用此功能能够解决主表以及多个索引表之间数据一致性的问题

menu.saveimg_.savepath20181031141844_.jpg

 
  1. 客户端发出put请求;
  2. 该请求被分派给合适的RegionServer和Region;
  3. coprocessorHost拦截该请求,然后在该表上登记的每个 RegionObserver 上调用prePut();
  4. 如果没有被preGet()拦截,该请求继续送到 region,然后进行处理;
  5. Region产生的结果再次被CoprocessorHost拦截,调用postGet();
  6. 假如没有postGet()拦截该响应,最终结果被返回给客户端;

 

menu.saveimg_.savepath20181031141924_.jpg

如上图所示,HBase可以根据rowkey很快的检索到数据,但是如果根据column检索数据,首先要根据rowkey减小范围,再通过列过滤器去过滤出数据,如果使用二级索引,可以先查基于column的索引表,获取到rowkey后再快速的检索到数据。

menu.saveimg_.savepath20181031141955_.jpg

如图所示首先继承BaseRegionObserver类,重写postPut,postDelete方法,在postPut方法体内中写Put索引表数据的代码,在postDelete方法里面写Delete索引表数据,这样可以保持数据的一致性。

在Scan表的时候首先判断是否先查索引表,如果不查索引直接scan主表,如果走索引表通过索引表获取主表的rowkey再去查主表。

使用Elastic Search建立二级索引也是一样。

我们在同一个主机集群上同时建立了HBase集群和Elastic Search集群,存储到HBase的数据必须实时地同步到Elastic Search。而恰好HBase和Elastic Search都没有更新的概念,我们的需求可以简化为两步:
 
  1. 当一个新的Put操作产生时,将Put数据转化为json,索引到ElasticSearch,并把RowKey作为新文档的ID;
  2. 当一个新的Delete操作产生时获取Delete数据的rowkey删除Elastic Search中对应的ID。


5.协处理的主要应用场景 
  1. Observer允许集群在正常的客户端操作过程中可以有不同的行为表现;
  2. Endpoint允许扩展集群的能力,对客户端应用开放新的运算命令;
  3. Observer类似于RDBMS的触发器,主要在服务端工作;
  4. Endpoint类似于RDBMS的存储过程,主要在服务端工作;
  5. Observer可以实现权限管理、优先级设置、监控、ddl控制、二级索引等功能;
  6. Endpoint可以实现min、max、avg、sum、distinct、group by等功能。


例如HBase源码org.apache.hadoop.hbase.security.access.AccessController利用Observer实现对HBase进行了权限控制,有兴趣的读者可以看看相关代码。
继续阅读 »
本文来自于中国HBase技术社区武汉站HBase MeetUp线下交流会的烽火大数据平台研发负责人叶铿(云端浪子)。
HBase Coprocessor的实现与应用PPT下载:http://hbase.group/slides/159

搜狗截图20181031141018.png

本次分享的内容主要分为以下五点:
 
  1. Coprocessor简介
  2. Endpoint服务端实现
  3. Endpoint客户端实现
  4. Observer实现二级索引
  5. Coprocessor应用场景


1.Coprocessor简介

HBase协处理器的灵感来自于Jeff Dean 09年的演讲,根据该演讲实现类似于Bigtable的协处理器,包括以下特性:每个表服务器的任意子表都可以运行代码客户端的高层调用接口(客户端能够直接访问数据表的行地址,多行读写会自动分片成多个并行的RPC调用),提供一个非常灵活的、可用于建立分布式服务的数据模型,能够自动化扩展、负载均衡、应用请求路由。HBase的协处理器灵感来自Bigtable,但是实现细节不尽相同。HBase建立框架为用户提供类库和运行时环境,使得代码能够在HBase Region Server和Master上面进行处理。

menu.saveimg_.savepath20181031141144_.jpg

(1)实现目的
 
  1. HBase无法轻易建立“二级索引”;
  2. 执行求和、计数、排序等操作比较困难,必须通过MapReduce/Spark实现,对于简单的统计或聚合计算时,可能会因为网络与IO开销大而带来性能问题。


(2)灵感来源

灵感来源于Bigtable的协处理器,包含如下特性:
  1. 每个表服务器的任意子表都可以运行代码;
  2. 客户端能够直接访问数据表的行,多行读写会自动分片成多个并行的RPC调用。


(3)提供接口
 
  1. RegionObserver:提供客户端的数据操纵事件钩子:Get、Put、Delete、Scan等;
  2. WALObserver:提供WAL相关操作钩子;
  3. MasterObserver:提供DDL-类型的操作钩子。如创建、删除、修改数据表等;
  4. Endpoint:终端是动态RPC插件的接口,它的实现代码被安装在服务器端,能够通过HBase RPC调用唤醒。


(4)应用范围
 
  1. 通过使用RegionObserver接口可以实现二级索引的创建和维护;
  2. 通过使用Endpoint接口,在对数据进行简单排序和sum,count等统计操作时,能够极大提高性能。


本文将通过具体实例来演示两种协处理器的开发方法的详细实现过程。

2.Endpoint服务端实现

在传统关系型数据库里面,可以随时的对某列进行求和sum,但是目前HBase目前所提供的接口,直接求和是比较困难的,所以先编写好服务端代码,并加载到对应的Table上,加载协处理器有几种方法,可以通过HTableDescriptor的addCoprocessor方法直接加载,同理也可以通过removeCoprocessor方法卸载协处理器。

Endpoint协处理器类似传统数据库的存储过程,客户端调用Endpoint协处理器执行一段Server端代码,并将Server端代码的结果返回给Client进一步处理,最常见的用法就是进行聚合操作。举个例子说明:如果没有协处理器,当用户需要找出一张表中的最大数据即max聚合操作,必须进行全表扫描,客户端代码遍历扫描结果并执行求max操作,这样的方法无法利用底层集群的并发能力,而将所有计算都集中到Client端统一执行, 效率非常低。但是使用Coprocessor,用户将求max的代码部署到HBase Server端,HBase将利用底层Cluster的多个节点并行执行求max的操作即在每个Region范围内执行求最大值逻辑,将每个Region的最大值在Region Server端计算出,仅仅将该max值返回给客户端。客户端进一步将多个Region的max进一步处理而找到其中的max,这样整体执行效率提高很多。但是一定要注意的是Coprocessor一定要写正确,否则导致RegionServer宕机。

menu.saveimg_.savepath20181031141342_.jpg

 
Protobuf定义

如前所述,客户端和服务端之间需要进行RPC通信,所以两者间需要确定接口,当前版本的HBase的协处理器是通过Google Protobuf协议来实现数据交换的,所以需要通过Protobuf来定义接口。

如下所示:
option java_package = "com.my.hbase.protobuf.generated";
option java_outer_classname = "AggregateProtos";
option java_generic_services = true;
option java_generate_equals_and_hash = true;
option optimize_for = SPEED;

import "Client.proto";

message AggregateRequest {
required string interpreter_class_name = 1;
required Scan scan = 2;
optional bytes interpreter_specific_bytes = 3;
}

message AggregateResponse {
repeated bytes first_part = 1;
optional bytes second_part = 2;
}

service AggregateService {
rpc GetMax (AggregateRequest) returns (AggregateResponse);
rpc GetMin (AggregateRequest) returns (AggregateResponse);
rpc GetSum (AggregateRequest) returns (AggregateResponse);
rpc GetRowNum (AggregateRequest) returns (AggregateResponse);
rpc GetAvg (AggregateRequest) returns (AggregateResponse);
rpc GetStd (AggregateRequest) returns (AggregateResponse);
rpc GetMedian (AggregateRequest) returns (AggregateResponse);
}
可以看到这里定义7个聚合服务RPC,名字分别叫做GetMax、GetMin、GetSum等,本文通过GetSum进行举例,其他的聚合RPC也是类似的内部实现。RPC有一个入口参数,用消息AggregateRequest表示;RPC的返回值用消息AggregateResponse表示。Service是一个抽象概念,RPC的Server端可以看作一个用来提供服务的Service。在HBase Coprocessor中Service就是Server端需要提供的Endpoint Coprocessor服务,主要用来给HBase的Client提供服务。AggregateService.java是由Protobuf软件通过终端命令“protoc filename.proto--java_out=OUT_DIR”自动生成的,其作用是将.proto文件定义的消息结构以及服务转换成对应接口的RPC实现,其中包括如何构建request消息和response响应以及消息包含的内容的处理方式,并且将AggregateService包装成一个抽象类,具体的服务以类的方法的形式提供。AggregateService.java定义Client端与Server端通信的协议,代码中包含请求信息结构AggregateRequest、响应信息结构AggregateResponse、提供的服务种类AggregateService,其中AggregateRequest中的interpreter_class_name指的是column interpreter的类名,此类的作用在于将数据格式从存储类型解析成所需类型。AggregateService.java由于代码太长,在这里就不贴出来了。

下面我们来讲一下服务端的架构,

首先,Endpoint Coprocessor是一个Protobuf Service的实现,因此需要它必须继承某个ProtobufService。我们在前面已经通过proto文件定义Service,命名为AggregateService,因此Server端代码需要重载该类,其次作为HBase的协处理器,Endpoint 还必须实现HBase定义的协处理器协议,用Java的接口来定义。具体来说就是CoprocessorService和Coprocessor,这些HBase接口负责将协处理器和HBase 的RegionServer等实例联系起来以便协同工作。Coprocessor接口定义两个接口函数:start和stop。

加载Coprocessor之后Region打开的时候被RegionServer自动加载,并会调用器start 接口完成初始化工作。一般情况该接口函数仅仅需要将协处理器的运行上下文环境变量CoprocessorEnvironment保存到本地即可。

CoprocessorEnvironment保存协处理器的运行环境,每个协处理器都是在一个RegionServer进程内运行并隶属于某个Region。通过该变量获取Region的实例等 HBase运行时环境对象。

Coprocessor接口还定义stop()接口函数,该函数在Region被关闭时调用,用来进行协处理器的清理工作。本文里我们没有进行任何清理工作,因此该函数什么也不干。

我们的协处理器还需要实现CoprocessorService接口。该接口仅仅定义一个接口函数 getService()。我们仅需要将本实例返回即可。HBase的Region Server在接收到客户端的调用请求时,将调用该接口获取实现RPCService的实例,因此本函数一般情况下就是返回自身实例即可。

完成以上三个接口函数之后,Endpoint的框架代码就已完成。每个Endpoint协处理器都必须实现这些框架代码而且写法雷同。

menu.saveimg_.savepath20181031141452_.jpg

Server端的代码就是一个Protobuf RPC的Service实现,即通过Protobuf提供的某种服务。其开发内容主要包括:
 
  1. 实现Coprocessor的基本框架代码
  2. 实现服务的RPC具体代码


Endpoint 协处理的基本框架

Endpoint 是一个Server端Service的具体实现,其实现有一些框架代码,这些框架代码与具体的业务需求逻辑无关。仅仅是为了和HBase运行时环境协同工作而必须遵循和完成的一些粘合代码。因此多数情况下仅仅需要从一个例子程序拷贝过来并进行命名修改即可。不过我们还是完整地对这些粘合代码进行粗略的讲解以便更好地理解代码。
public Service getService() {
return this;
}

public void start(CoprocessorEnvironment env) throws IOException {
if(env instanceof RegionCoprocessorEnvironment) {
this.env = (RegionCoprocessorEnvironment)env;
} else {
throw new CoprocessorException("Must be loaded on a table region!");
}
}

public void stop(CoprocessorEnvironment env) throws IOException {
}
Endpoint协处理器真正的业务代码都在每一个RPC函数的具体实现中。


在本文中,我们的Endpoint协处理器仅提供一个RPC函数即getSUM。我将分别介绍编写该函数的几个主要工作:了解函数的定义,参数列表;处理入口参数;实现业务逻辑;设置返回参数。
public void getSum(RpcController controller, AggregateRequest request, RpcCallbackdone) {
AggregateResponse response = null;
RegionScanner scanner = null;
long sum = 0L;
try {
ColumnInterpreter ignored = this.constructColumnInterpreterFromRequest(request);
Object sumVal = null;
Scan scan = ProtobufUtil.toScan(request.getScan());
scanner = this.env.getRegion().getScanner(scan);
byte[] colFamily = scan.getFamilies()[0];
NavigableSet qualifiers = (NavigableSet) scan.getFamilyMap().get(colFamily);
byte[] qualifier = null;
if (qualifiers != null && !qualifiers.isEmpty()) {
qualifier = (byte[]) qualifiers.pollFirst();
}

ArrayList results = new ArrayList();
boolean hasMoreRows = false;

do {
hasMoreRows = scanner.next(results);
int listSize = results.size();

for (int i = 0; i < listSize; ++i) {
//取出列值
Object temp = ignored.getValue(colFamily, qualifier,
(Cell) results.get(i));
if (temp != null) {
sumVal = ignored.add(sumVal, ignored.castToReturnType(temp));
}
}

results.clear();
} while (hasMoreRows);

if (sumVal != null) {
response = AggregateResponse.newBuilder().addFirstPart(
ignored.getProtoForPromotedType(sumVal).toByteString()).build();
}
} catch (IOException var27) {
ResponseConverter.setControllerException(controller, var27);
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (IOException var26) {
;
}
}

}

log.debug("Sum from this region is " +
this.env.getRegion().getRegionInfo().getRegionNameAsString() + ": " + sum);
done.run(response);
}
Endpoint类比于数据库的存储过程,其触发服务端的基于Region的同步运行再将各个结果在客户端搜集后归并计算。特点类似于传统的MapReduce框架,服务端Map客户端Reduce。

3.Endpoint客户端实现

HBase提供客户端Java包org.apache.hadoop.hbase.client.HTable,提供以下三种方法来调用协处理器提供的服务:
 
  1. coprocessorService(byte[])
  2. coprocessorService(Class, byte[], byte[],Batch.Call),
  3. coprocessorService(Class, byte[], byte[],Batch.Call, Batch.Callback)

 

menu.saveimg_.savepath20181031141634_.jpg

该方法采用rowkey指定Region。这是因为HBase客户端很少会直接操作Region,一般不需要知道Region的名字;况且在HBase中Region名会随时改变,所以用rowkey来指定Region是最合理的方式。使用rowkey可以指定唯一的一个Region,如果给定的Rowkey并不存在,只要在某个Region的rowkey范围内依然用来指定该Region。比如Region 1处理[row1, row100]这个区间内的数据,则rowkey=row1就由Region 1来负责处理,换句话说我们可以用row1来指定Region 1,无论rowkey等于”row1”的记录是否存在。CoprocessorService方法返回类型为CoprocessorRpcChannel的对象,该 RPC通道连接到由rowkey指定的Region上面,通过此通道可以调用该Region上面部署的协处理器RPC。

menu.saveimg_.savepath20181031141702_.jpg

有时候客户端需要调用多个 Region上的同一个协处理器,比如需要统计整个Table的sum,在这种情况下,需要所有的Region都参与进来,分别统计自身Region内部的sum并返回客户端,最终客户端将所有Region的返回结果汇总,就可以得到整张表的sum。


这意味着该客户端同时和多个Region进行批处理交互。一个可行的方法是,收集每个 Region的startkey,然后循环调用第一种coprocessorService方法:用每一个Region的startkey 作为入口参数,获得RPC通道创建stub对象,进而逐一调用每个Region上的协处理器RPC。这种做法需要写很多的代码,为此HBase提供两种更加简单的 coprocessorService方法来处理多个Region的协处理器调用。先来看第一种方法 coprocessorService(Class, byte[],byte[],Batch.Call),

该方法有 4 个入口参数。第一个参数是实现RPC的Service 类,即前文中的AggregateService类。通过它,HBase就可以找到相应的部署在Region上的协处理器,一个Region上可以部署多个协处理器,客户端必须通过指定Service类来区分究竟需要调用哪个协处理器提供的服务。

要调用哪些Region上的服务则由startkey和endkey来确定,通过 rowkey范围即可确定多个 Region。为此,coprocessorService方法的第二个和第三个参数分别是 startkey和endkey,凡是落在[startkey,endkey]区间内的Region都会参与本次调用。

第四个参数是接口类Batch.Call。它定义了如何调用协处理器,用户通过重载该接口的call()方法来实现客户端的逻辑。在call()方法内,可以调用RPC,并对返回值进行任意处理。即前文代码清单1中所做的事情。coprocessorService将负责对每个 Region调用这个call()方法。

coprocessorService方法的返回值是一个Map类型的集合。该集合的key是Region名字,value是Batch.Call.call方法的返回值。该集合可以看作是所有Region的协处理器 RPC 返回的结果集。客户端代码可以遍历该集合对所有的结果进行汇总处理。

这种coprocessorService方法的大体工作流程如下。首先它分析startkey和 endkey,找到该区间内的所有Region,假设存放在regionList中。然后,遍历regionList,为每一个Region调用Batch.Call,在该接口内,用户定义具体的RPC调用逻辑。最后coprocessorService将所有Batch.Call.call()的返回值加入结果集合并返回。

menu.saveimg_.savepath20181031141730_.jpg

coprocessorService的第三种方法比第二个方法多了一个参数callback。coprocessorService第二个方法内部使用HBase自带的缺省callback,该缺省 callback将每个Region的返回结果都添加到一个Map类型的结果集中,并将该集合作为coprocessorService方法的返回值。

HBase 提供第三种coprocessorService方法允许用户定义callback行为,coprocessorService 会为每一个RPC返回结果调用该callback,用户可以在callback 中执行需要的逻辑,比如执行sum累加。用第二种方法的情况下,每个Region协处理器RPC的返回结果先放入一个列表,所有的 Region 都返回后,用户代码再从该列表中取出每一个结果进行累加;用第三种方法,直接在callback中进行累加,省掉了创建结果集合和遍历该集合的开销,效率会更高一些。

因此我们只需要额外定义一个callback即可,callback是一个Batch.Callback接口类,用户需要重载其update方法。
 
public S sum(final HTable table, final ColumnInterpreter<R, S, P, Q, T> ci,final Scan scan)throws Throwable {

final AggregateRequest requestArg = validateArgAndGetPB(scan, ci, false);

class SumCallBack implements Batch.Callback {

S sumVal = null;

public S getSumResult() {
return sumVal;
}

@Override
public synchronized void update(byte[] region, byte[] row, S result) {
sumVal = ci.add(sumVal, result);
}}

SumCallBack sumCallBack = new SumCallBack();
table.coprocessorService(AggregateService.class, scan.getStartRow(), scan.getStopRow(),
new Batch.Call<AggregateService, S>() {
@Override
public S call(AggregateService instance) throws IOException {
ServerRpcController controller = new ServerRpcController();
BlockingRpcCallback<AggregateResponse> rpcCallback =
new BlockingRpcCallback<AggregateResponse>();
//RPC 调用
instance.getSum(controller, requestArg, rpcCallback);
AggregateResponse response = rpcCallback.get();
if (controller.failedOnException()) {
throw controller.getFailedOn();
}
if (response.getFirstPartCount() == 0) {
return null;
}
ByteString b = response.getFirstPart(0);
T t = ProtobufUtil.getParsedGenericInstance(ci.getClass(), 4, b);
S s = ci.getPromotedValueFromProto(t);
return s;
}
}, sumCallBack);
return sumCallBack.getSumResult();
4.Observer实现二级索引

Observer类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。Observer Coprocessor是一些散布在HBase Server端代码的 hook钩子, 在固定的事件发生时被调用。比如:put操作之前有钩子函数prePut,该函数在pu 操作执 行前会被Region Server调用;在put操作之后则有postPut 钩子函数。
menu.saveimg_.savepath20181031141817_.jpg

 
RegionObserver工作原理

RegionObserver提供客户端的数据操纵事件钩子,Get、Put、Delete、Scan,使用此功能能够解决主表以及多个索引表之间数据一致性的问题

menu.saveimg_.savepath20181031141844_.jpg

 
  1. 客户端发出put请求;
  2. 该请求被分派给合适的RegionServer和Region;
  3. coprocessorHost拦截该请求,然后在该表上登记的每个 RegionObserver 上调用prePut();
  4. 如果没有被preGet()拦截,该请求继续送到 region,然后进行处理;
  5. Region产生的结果再次被CoprocessorHost拦截,调用postGet();
  6. 假如没有postGet()拦截该响应,最终结果被返回给客户端;

 

menu.saveimg_.savepath20181031141924_.jpg

如上图所示,HBase可以根据rowkey很快的检索到数据,但是如果根据column检索数据,首先要根据rowkey减小范围,再通过列过滤器去过滤出数据,如果使用二级索引,可以先查基于column的索引表,获取到rowkey后再快速的检索到数据。

menu.saveimg_.savepath20181031141955_.jpg

如图所示首先继承BaseRegionObserver类,重写postPut,postDelete方法,在postPut方法体内中写Put索引表数据的代码,在postDelete方法里面写Delete索引表数据,这样可以保持数据的一致性。

在Scan表的时候首先判断是否先查索引表,如果不查索引直接scan主表,如果走索引表通过索引表获取主表的rowkey再去查主表。

使用Elastic Search建立二级索引也是一样。

我们在同一个主机集群上同时建立了HBase集群和Elastic Search集群,存储到HBase的数据必须实时地同步到Elastic Search。而恰好HBase和Elastic Search都没有更新的概念,我们的需求可以简化为两步:
 
  1. 当一个新的Put操作产生时,将Put数据转化为json,索引到ElasticSearch,并把RowKey作为新文档的ID;
  2. 当一个新的Delete操作产生时获取Delete数据的rowkey删除Elastic Search中对应的ID。


5.协处理的主要应用场景 
  1. Observer允许集群在正常的客户端操作过程中可以有不同的行为表现;
  2. Endpoint允许扩展集群的能力,对客户端应用开放新的运算命令;
  3. Observer类似于RDBMS的触发器,主要在服务端工作;
  4. Endpoint类似于RDBMS的存储过程,主要在服务端工作;
  5. Observer可以实现权限管理、优先级设置、监控、ddl控制、二级索引等功能;
  6. Endpoint可以实现min、max、avg、sum、distinct、group by等功能。


例如HBase源码org.apache.hadoop.hbase.security.access.AccessController利用Observer实现对HBase进行了权限控制,有兴趣的读者可以看看相关代码。 收起阅读 »

HBase Region Balance实践

HBase是一种支持自动负载均衡的分布式KV数据库,在开启balance的开关(balance_switch)后,HBase的HMaster进程会自动根据 指定策略 挑选出一些Region,并将这些Region分配给负载比较低的RegionServer上。官方目前支持两种挑选Region的策略,一种叫做DefaultLoadBalancer,另一种叫做StochasticLoadBalancer,这两种策略后面会具体讲到。由于HBase的所有数据(包括HLog/Meta/HStoreFile等)都是写入到HDFS文件系统中的, 因此HBase的Region移动其实非常轻量级。在做Region移动的时候,保持这个Region对应的HDFS文件位置不变,只需要将Region的Meta数据分配到相关的RegionServer即可,整个Region移动的过程取决于RegionClose以及RegionOpen的耗时,这个时间一般都很短。
本文来讲讲hbase的balance实现。balance的流程
  • 首先通过LoadBalancer找出所有需要移动的region plan,一个region plan包括region/原始RegionServer/目的RegionServer三个属性。
  • unassign region , 将region从原来的RegionServer上解除绑定;
  • assign region ,将region绑定到目标RegionServer上;

其中, unassign region的具体流程为:
  • create zk closing node . 该节点在/unassigned路径下, 包含(znode状态,region名字,原始RS名,payload)这些数据。
  • hmaster 调用rpc服务关闭region server。region-close的流程大致为先获取region的writeLock , 然后flush memstore, 再并发关闭该region下的所有的store file文件(注意一个region有多个store,每个store又有多个store file , 所以可以实现并发close store file) 。最后释放region的writeLock.
  • 设置zk closing node的znode状态为closed.

assgin region的具体流程为:
  • 获取到对应的Region Plan.
  • HMaster调用rpc服务去Region Plan对应的RegionServer上open region. 这里会先更新/unassigned节点为opening. 然后并发Load HStore,再更行zk/ROOT/META表信息,这里是为了client下次能获取到正确的路由信息, 最后更新region状态为OPEN.

DefaultLoadBalancer策略
这种策略能够保证每个RS的regions个数基本上都相等,确切来说,假设一共有n个RS,第i个RS有Ai个region,记average=sigma(Ai)/n , 那么这种策略能够保证所有的RS的region个数都在[floor(average), ceil(average)]之间。这种策略的实现简单,应用广泛。
但是,这种策略考虑的因素比较单一, 没有考虑到每台region server的读写qps/负载压力等等,这样就可能导致出现一种情况:虽然每个region server的regions都非常接近,但是90%的请求还是落在了一台RS上,因为这台RS上的region全部都是热点数据,这样还是没有达到负载均衡的目的。 但我觉得balance的首要目的是保证数据均衡,如果在数据均衡的情况下,负载还是集中,这时候就要考虑下rowKey的选择是否有问题了。因此, 我个人还是比较推荐采用DefaultLoadBalancer的。StochasticLoadBalancer策略
StochasticLoadBalancer 这种策略真的是非常复杂,简单来讲,是一种综合权衡一下6个因素的均衡策略:
  • 每台RegionServer读请求数(ReadRequestCostFunction)
  • 每台RegionServer写请求数(WriteRequestCostFunction)
  • 每台RegionServer的Region个数(RegionCountSkewCostFunction)
  • 移动代价(MoveCostFunction)
  • 数据locality(TableSkewCostFunction)
  • 每张表占据RegionServer中region个数上限(LocalityCostFunction)

对于cluster的每一种region分布, 采用6个因素加权的方式算出一个代价值,这个代价值就用来评估当前region分布是否均衡,越均衡则代价值越低。然后通过成千上万次随机迭代来找到一组RegionMove的序列,使得最终的代价值严格递减。 得到的这一组RegionMove就是HMaster最终执行的region迁移方案。
这里用一段伪代码来描述这个迭代的过程:
[code]currentCost = MAX ; 
plans = []
for(step = 0 ; step < 1000000; step ++ ){
action = cluster.generateMove()
doAction( action );
newCost = computeCost(action) ;
if (newCost < currentCost){
currentCost = newCost;
plans.add( action );
}else{
undoAction(action);
}
}
[/code]
其中generateMove()每次随机选择以下3种策略中的一种来生成RegionMove:
  1. 随机选择两个RS, 从每个RS中随机选择两个Region,然后生成一个Action, 这个Action有一半概率做RegionMove(从Region多的RS迁移到Region少的RS), 另一半概率做RegionSwap(两个RS之间做Region的交换)。
  2. 选择Region最多的RS和Region最少的RS,然后生成一个Action, 这个Action一半概率做RegionMove, 一半概率做RegionSwap。
  3. 随机找一个RS,然后找到该RS上数据locality最差的Region,再找到Region大部分数据落在的RS,然后生成一个Action,该Action用来把Region迁移到它应该所在的RS,用来提高locality.

对于这种策略,JavaDoc上说效果比较好,但其中的合理性个人觉得有待测试数据的证明(官方基本没有给出这方面的测试结果)。如果6个因素每个参数占据的权重如果没有调好的话,会导致线上的Region大量不均衡。按照我的一次线上经历,采用如下blance配置,出现过每次balance都只选择60个左右的plan去移动, 但真实的情况是145个RS,其中region数量最多的有700+个, 最少的region数量有2个,然后其他RS的region数量在2~700不等,这时候按理来讲应该需要进行大量的balance,但HMaster每隔一个period只生成60个plan左右去移动,这样balance太慢导致很长一段时间内负载不均,有的RS非常清闲,有的RS非常繁忙经常超时。
[code]hbase.master.loadbalancer.class=\
org.apache.hadoop.hbase.master.StochasticLoadBalancer
hbase.master.balancer.stochastic.regionCountCost=10
hbase.master.balancer.stochastic.tableSkewCost=5
hbase.master.balancer.stochastic.readRequestCost=5
hbase.master.balancer.stochastic.writeRequestCost=5
hbase.master.balancer.stochastic.localityCost=10
hbase.master.balancer.stochastic.moveCost=4
hbase.master.balancer.stochastic.maxMovePercent=1
[/code]
后面对比了下了官方的默认配置,应该是regionCountCost一项权重太低, 但是,我想说的是除非线下有一个测试结果支撑具体的权重配置下 balance是符合预期的,否则线上操作时一般对权重很难有一个准确的把握,所以像这么复杂的策略还是要比较谨慎的选择,最好有过历史测试数据来评估balance的效果。
继续阅读 »
HBase是一种支持自动负载均衡的分布式KV数据库,在开启balance的开关(balance_switch)后,HBase的HMaster进程会自动根据 指定策略 挑选出一些Region,并将这些Region分配给负载比较低的RegionServer上。官方目前支持两种挑选Region的策略,一种叫做DefaultLoadBalancer,另一种叫做StochasticLoadBalancer,这两种策略后面会具体讲到。由于HBase的所有数据(包括HLog/Meta/HStoreFile等)都是写入到HDFS文件系统中的, 因此HBase的Region移动其实非常轻量级。在做Region移动的时候,保持这个Region对应的HDFS文件位置不变,只需要将Region的Meta数据分配到相关的RegionServer即可,整个Region移动的过程取决于RegionClose以及RegionOpen的耗时,这个时间一般都很短。
本文来讲讲hbase的balance实现。balance的流程
  • 首先通过LoadBalancer找出所有需要移动的region plan,一个region plan包括region/原始RegionServer/目的RegionServer三个属性。
  • unassign region , 将region从原来的RegionServer上解除绑定;
  • assign region ,将region绑定到目标RegionServer上;

其中, unassign region的具体流程为:
  • create zk closing node . 该节点在/unassigned路径下, 包含(znode状态,region名字,原始RS名,payload)这些数据。
  • hmaster 调用rpc服务关闭region server。region-close的流程大致为先获取region的writeLock , 然后flush memstore, 再并发关闭该region下的所有的store file文件(注意一个region有多个store,每个store又有多个store file , 所以可以实现并发close store file) 。最后释放region的writeLock.
  • 设置zk closing node的znode状态为closed.

assgin region的具体流程为:
  • 获取到对应的Region Plan.
  • HMaster调用rpc服务去Region Plan对应的RegionServer上open region. 这里会先更新/unassigned节点为opening. 然后并发Load HStore,再更行zk/ROOT/META表信息,这里是为了client下次能获取到正确的路由信息, 最后更新region状态为OPEN.

DefaultLoadBalancer策略
这种策略能够保证每个RS的regions个数基本上都相等,确切来说,假设一共有n个RS,第i个RS有Ai个region,记average=sigma(Ai)/n , 那么这种策略能够保证所有的RS的region个数都在[floor(average), ceil(average)]之间。这种策略的实现简单,应用广泛。
但是,这种策略考虑的因素比较单一, 没有考虑到每台region server的读写qps/负载压力等等,这样就可能导致出现一种情况:虽然每个region server的regions都非常接近,但是90%的请求还是落在了一台RS上,因为这台RS上的region全部都是热点数据,这样还是没有达到负载均衡的目的。 但我觉得balance的首要目的是保证数据均衡,如果在数据均衡的情况下,负载还是集中,这时候就要考虑下rowKey的选择是否有问题了。因此, 我个人还是比较推荐采用DefaultLoadBalancer的。StochasticLoadBalancer策略
StochasticLoadBalancer 这种策略真的是非常复杂,简单来讲,是一种综合权衡一下6个因素的均衡策略:
  • 每台RegionServer读请求数(ReadRequestCostFunction)
  • 每台RegionServer写请求数(WriteRequestCostFunction)
  • 每台RegionServer的Region个数(RegionCountSkewCostFunction)
  • 移动代价(MoveCostFunction)
  • 数据locality(TableSkewCostFunction)
  • 每张表占据RegionServer中region个数上限(LocalityCostFunction)

对于cluster的每一种region分布, 采用6个因素加权的方式算出一个代价值,这个代价值就用来评估当前region分布是否均衡,越均衡则代价值越低。然后通过成千上万次随机迭代来找到一组RegionMove的序列,使得最终的代价值严格递减。 得到的这一组RegionMove就是HMaster最终执行的region迁移方案。
这里用一段伪代码来描述这个迭代的过程:
[code]currentCost = MAX ; 
plans = []
for(step = 0 ; step < 1000000; step ++ ){
action = cluster.generateMove()
doAction( action );
newCost = computeCost(action) ;
if (newCost < currentCost){
currentCost = newCost;
plans.add( action );
}else{
undoAction(action);
}
}
[/code]
其中generateMove()每次随机选择以下3种策略中的一种来生成RegionMove:
  1. 随机选择两个RS, 从每个RS中随机选择两个Region,然后生成一个Action, 这个Action有一半概率做RegionMove(从Region多的RS迁移到Region少的RS), 另一半概率做RegionSwap(两个RS之间做Region的交换)。
  2. 选择Region最多的RS和Region最少的RS,然后生成一个Action, 这个Action一半概率做RegionMove, 一半概率做RegionSwap。
  3. 随机找一个RS,然后找到该RS上数据locality最差的Region,再找到Region大部分数据落在的RS,然后生成一个Action,该Action用来把Region迁移到它应该所在的RS,用来提高locality.

对于这种策略,JavaDoc上说效果比较好,但其中的合理性个人觉得有待测试数据的证明(官方基本没有给出这方面的测试结果)。如果6个因素每个参数占据的权重如果没有调好的话,会导致线上的Region大量不均衡。按照我的一次线上经历,采用如下blance配置,出现过每次balance都只选择60个左右的plan去移动, 但真实的情况是145个RS,其中region数量最多的有700+个, 最少的region数量有2个,然后其他RS的region数量在2~700不等,这时候按理来讲应该需要进行大量的balance,但HMaster每隔一个period只生成60个plan左右去移动,这样balance太慢导致很长一段时间内负载不均,有的RS非常清闲,有的RS非常繁忙经常超时。
[code]hbase.master.loadbalancer.class=\
org.apache.hadoop.hbase.master.StochasticLoadBalancer
hbase.master.balancer.stochastic.regionCountCost=10
hbase.master.balancer.stochastic.tableSkewCost=5
hbase.master.balancer.stochastic.readRequestCost=5
hbase.master.balancer.stochastic.writeRequestCost=5
hbase.master.balancer.stochastic.localityCost=10
hbase.master.balancer.stochastic.moveCost=4
hbase.master.balancer.stochastic.maxMovePercent=1
[/code]
后面对比了下了官方的默认配置,应该是regionCountCost一项权重太低, 但是,我想说的是除非线下有一个测试结果支撑具体的权重配置下 balance是符合预期的,否则线上操作时一般对权重很难有一个准确的把握,所以像这么复杂的策略还是要比较谨慎的选择,最好有过历史测试数据来评估balance的效果。 收起阅读 »

中国HBase技术社区第七届MeetUp ——HBase技术与应用实践(成都站)

HBase—Hadoop Database是一个分布式的、面向列的开源数据库,该技术来源于Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。HBase的特点是高可靠性、高性能、面向列、可伸缩的分布式存储系统,如今HBase已经广泛应用于各互联网行业。那么我们如何熟练掌握HBase技术及应用呢?

2018年11月3号,由中国HBase技术社区、DataFun社区、爱奇艺主办的中国第七届HBase Meetup将来到成都,届时来自阿里、爱奇艺、G7等公司HBase的专家们,将为大家分享HBase技术的相关应用与发展情况。
主办方:中国HBase技术社区、DataFun社区、爱奇艺
联合主办方:泰智会
合作伙伴:极客邦科技、掘金社区
时间:2018.11.3,13:00-18:00
地点:成都市武侯区世外桃源广场 B座8层(磨子桥A出口,沿科华北路向南860m)
注:报名通过审核后,请持有效票二维码参会。

微信图片_20181029104745.png

议程安排
E7BCAF95-7769-49d5-9729-9234BE724B45.png

分享介绍

1.png


天引 阿里巴巴 技术专家
嘉宾介绍
天引,专注在大数据领域,拥有多年分布式、高并发、大规模系统的研发与实践经验,先后参与hbase、phoenix、lindorm等产品的内核引擎研发,目前负责阿里上万节点的HBase As a Service的发展与落地
分享主题:HBase2.0重新定义小对象实时存取
内容概要:小对象,特别指1K~10MB范围的数据,比如图片,短视频,文档等广泛的存在于人工智能,医疗,教育,生活分享,电子商务等领域。HBase2.0在MOB技术的加持下重新定义小对象实时存取,具有低延迟,读写强一致,检索能力强,水平易扩展等关键能力。本文介绍了MOB特性的原理与实现,以及与经典对象存储相比,MOB带来的差异性与优势。


2.jpg


郑浩南 爱奇艺 资深研发工程师
嘉宾介绍:郑浩南,爱奇艺资深研发工程师,专注于大数据领域,负责Hadoop服务的运维研究以及DevOps平台开发。
分享主题:HBase在爱奇艺的应用实践
内容概要:随着大数据存储计算对延时吞吐要求越来越高,需求日益复杂化,HBase在爱奇艺中被广泛应用和实践以应对多样化的业务场景。本次演讲将介绍HBase在爱奇艺的部署模式和使用场景,以及在爱奇艺私有云环境下的运维策略。
3.jpg

巨鹏 g7 高级数据运维工程师
嘉宾介绍:巨鹏,g7高级数据运维工程师,负责g7的数据维护以及中间件的稳定性建设。
分享主题:Hbase在车联网的应用与实践
内容概要:为大家分享在大数据量的IOT车联网环境中,是如何发挥HBase优势解决实际问题以及他稳定性如何保障。
 
主办方介绍

中国HBase技术社区:为了让众多HBase相关从业人员及爱好者有一个自由交流HBase相关技术的社区,由阿里巴巴、小米、网易、滴滴、知乎等公司的HBase技术研究人员共同发起了组建:中国HBase技术社区(Chinese HBase Technical Community 简称CHTC)。

我们非常欢迎对HBase有技术激情的同学一起加入探讨HBase技术,同时诚邀广大HBase技术爱好者加入。大家在工作学习遇到HBase技术问题,可以把问题发布到中国HBase技术社区论坛http://hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术,关注HBase技术社区公众号(微信号:hbasegroup),邀请进社区的讨论群。
4.jpg

DataFun定位于最“实用”的数据科学社区,主要形式为线下的深度沙龙、线上的内容整理。希望将工业界专家在各自场景下的实践经验,通过DataFun的平台传播和扩散,对即将或已经开始相关尝试的同学有启发和借鉴。DataFun的愿景是:为大数据、人工智能从业者和爱好者打造一个分享、交流、学习、成长的平台,让数据科学领域的知识和经验更好的传播和落地产生价值。

DataFun社区成立至今,已经成功在全国范围内举办数十场线下技术沙龙,有超过一百位的业内专家参与分享,聚集了万余大数据、算法相关领域从业者。
5.jpg

爱奇艺成都研发中心成立于2018年初,以算法和数据研发为主,团队初期成员来自于谷歌、微软、百度、腾讯等公司。爱奇艺成都将和开源社区一同努力,为广大成都技术爱好者持续打造更好的技术环境和学习交流氛围。
6.jpg


联合主办方

泰智会是国内首家产业加速器,致力于整合产业链上下游资源,聚焦细分产业领域,运营创新创业载体,打造原始创新的产业生态系统。

泰智会成立于2015年10月,已在北京、深圳、上海、成都、漳州等地布局专业孵化器和垂直产业加速体系;完成了国内首家产业促进组织加速器、园区协同创新中心,运营了人工智能、虚拟现实、车联网等主题的众创空间,形成了高新技术企业+产业联盟+产业园区的三位一体产业生态培育体系。
7.png

 
继续阅读 »
HBase—Hadoop Database是一个分布式的、面向列的开源数据库,该技术来源于Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。HBase的特点是高可靠性、高性能、面向列、可伸缩的分布式存储系统,如今HBase已经广泛应用于各互联网行业。那么我们如何熟练掌握HBase技术及应用呢?

2018年11月3号,由中国HBase技术社区、DataFun社区、爱奇艺主办的中国第七届HBase Meetup将来到成都,届时来自阿里、爱奇艺、G7等公司HBase的专家们,将为大家分享HBase技术的相关应用与发展情况。
主办方:中国HBase技术社区、DataFun社区、爱奇艺
联合主办方:泰智会
合作伙伴:极客邦科技、掘金社区
时间:2018.11.3,13:00-18:00
地点:成都市武侯区世外桃源广场 B座8层(磨子桥A出口,沿科华北路向南860m)
注:报名通过审核后,请持有效票二维码参会。

微信图片_20181029104745.png

议程安排
E7BCAF95-7769-49d5-9729-9234BE724B45.png

分享介绍

1.png


天引 阿里巴巴 技术专家
嘉宾介绍
天引,专注在大数据领域,拥有多年分布式、高并发、大规模系统的研发与实践经验,先后参与hbase、phoenix、lindorm等产品的内核引擎研发,目前负责阿里上万节点的HBase As a Service的发展与落地
分享主题:HBase2.0重新定义小对象实时存取
内容概要:小对象,特别指1K~10MB范围的数据,比如图片,短视频,文档等广泛的存在于人工智能,医疗,教育,生活分享,电子商务等领域。HBase2.0在MOB技术的加持下重新定义小对象实时存取,具有低延迟,读写强一致,检索能力强,水平易扩展等关键能力。本文介绍了MOB特性的原理与实现,以及与经典对象存储相比,MOB带来的差异性与优势。


2.jpg


郑浩南 爱奇艺 资深研发工程师
嘉宾介绍:郑浩南,爱奇艺资深研发工程师,专注于大数据领域,负责Hadoop服务的运维研究以及DevOps平台开发。
分享主题:HBase在爱奇艺的应用实践
内容概要:随着大数据存储计算对延时吞吐要求越来越高,需求日益复杂化,HBase在爱奇艺中被广泛应用和实践以应对多样化的业务场景。本次演讲将介绍HBase在爱奇艺的部署模式和使用场景,以及在爱奇艺私有云环境下的运维策略。
3.jpg

巨鹏 g7 高级数据运维工程师
嘉宾介绍:巨鹏,g7高级数据运维工程师,负责g7的数据维护以及中间件的稳定性建设。
分享主题:Hbase在车联网的应用与实践
内容概要:为大家分享在大数据量的IOT车联网环境中,是如何发挥HBase优势解决实际问题以及他稳定性如何保障。
 
主办方介绍

中国HBase技术社区:为了让众多HBase相关从业人员及爱好者有一个自由交流HBase相关技术的社区,由阿里巴巴、小米、网易、滴滴、知乎等公司的HBase技术研究人员共同发起了组建:中国HBase技术社区(Chinese HBase Technical Community 简称CHTC)。

我们非常欢迎对HBase有技术激情的同学一起加入探讨HBase技术,同时诚邀广大HBase技术爱好者加入。大家在工作学习遇到HBase技术问题,可以把问题发布到中国HBase技术社区论坛http://hbase.group,欢迎大家论坛上面提问留言讨论。想了解更多HBase技术,关注HBase技术社区公众号(微信号:hbasegroup),邀请进社区的讨论群。
4.jpg

DataFun定位于最“实用”的数据科学社区,主要形式为线下的深度沙龙、线上的内容整理。希望将工业界专家在各自场景下的实践经验,通过DataFun的平台传播和扩散,对即将或已经开始相关尝试的同学有启发和借鉴。DataFun的愿景是:为大数据、人工智能从业者和爱好者打造一个分享、交流、学习、成长的平台,让数据科学领域的知识和经验更好的传播和落地产生价值。

DataFun社区成立至今,已经成功在全国范围内举办数十场线下技术沙龙,有超过一百位的业内专家参与分享,聚集了万余大数据、算法相关领域从业者。
5.jpg

爱奇艺成都研发中心成立于2018年初,以算法和数据研发为主,团队初期成员来自于谷歌、微软、百度、腾讯等公司。爱奇艺成都将和开源社区一同努力,为广大成都技术爱好者持续打造更好的技术环境和学习交流氛围。
6.jpg


联合主办方

泰智会是国内首家产业加速器,致力于整合产业链上下游资源,聚焦细分产业领域,运营创新创业载体,打造原始创新的产业生态系统。

泰智会成立于2015年10月,已在北京、深圳、上海、成都、漳州等地布局专业孵化器和垂直产业加速体系;完成了国内首家产业促进组织加速器、园区协同创新中心,运营了人工智能、虚拟现实、车联网等主题的众创空间,形成了高新技术企业+产业联盟+产业园区的三位一体产业生态培育体系。
7.png

  收起阅读 »

HBase基本知识介绍及典型案例分析

本文来自于2018年10月20日由中国 HBase 技术社区在武汉举办的中国 HBase Meetup 第六次线下交流会。分享者为过往记忆。
本文ppt下载地址:http://hbase.group/slides/162

HBase2-iteblog.jpg

 
本次分享的内容主要分为以下五点:
 
  • HBase基本知识;
  • HBase读写流程;
  • RowKey设计要点;
  • HBase生态介绍;
  • HBase典型案例分析。


首先我们简单介绍一下 HBase 是什么。

HBase4-iteblog.jpg

HBase 最开始是受 Google 的 BigTable 启发而开发的分布式、多版本、面向列的开源数据库。其主要特点是支持上亿行、百万列,支持强一致性、并且具有高扩展、高可用等特点。

既然 HBase 是一种分布式的数据库,那么其和传统的 RMDB 有什么区别的呢?我们先来看看HBase表核心概念,理解这些基本的核心概念对后面我理解 HBase 的读写以及如何设计 HBase 表有着重要的联系。

HBase5-iteblog.jpg

HBase 表主要由以下几个元素组成:
 
  • RowKey:表中每条记录的主键;
  • Column Family:列族,将表进行横向切割,后面简称CF;
  • Column:属于某一个列族,可动态添加列;
  • Version Number:类型为Long,默认值是系统时间戳,可由用户自定义;
  • Value:真实的数据。


大家可以从上面的图看出:一行(Row)数据是可以包含一个或多个 Column Family,但是我们并不推荐一张 HBase 表的 Column Family 超过三个。Column 是属于 Column Family 的,一个 Column Family 包含一个或多个 Column。

在物理层面上,所有的数据其实是存放在 Region 里面的,而 Region 又由 RegionServer 管理,其对于的关系如下:
HBase6-iteblog.jpg

 
  • Region:一段数据的集合;
  • RegionServer:用于存放Region的服务。


从上面的图也可以清晰看到,一个 RegionServer 管理多个 Region;而一个 Region 管理一个或多个 Column Family。
到这里我们已经了解了 HBase 表的组成,但是 HBase 表里面的数据到底是怎么存储的呢?

HBase7-iteblog.jpg

上面是一张从逻辑上看 HBase 表形式,这个和关系型数据库很类似。那么如果我们再深入看,可以看出,这张表的划分可以如下图表示。
HBase8-iteblog.jpg

从上图大家可以明显看出,这张表有两个 Column Family ,分别为 personal 和 office。而 personal 又有三列name、city 以及 phone;office 有两列 tel 以及 address。由于存储在 HBase 里面的表一般有上亿行,所以 HBase 表会对整个数据按照 RowKey 进行字典排序,然后再对这张表进行横向切割。切割出来的数据是存储在 Region 里面,而不同的 Column Family 虽然属于一行,但是其在底层存储是放在不同的 Region 里。所以这张表我用了六种颜色表示,也就是说,这张表的数据会被放在六个 Region 里面的,这就可以把数据尽可能的分散到整个集群。

在前面我们介绍了 HBase 其实是面向列的数据库,所以说一行 HBase 的数据其实是分了好几行存储,一个列对应一行,HBase 的 KV 结构如下:
HBase9-iteblog.jpg

为了简便期间,在后面的表示我们删除了类似于 Key Length 的属性,只保留 Row Key、Column Family、Column Qualifier等信息。所以 RowKey 为 Row1 的数据第一列表示为上图最后一行的形式。以此类推,整个表的存储就可以如下表示:
HBase10-iteblog.jpg

大家可以从上面的 kv 表现形式看出,Row11 的 phone 这列其实是没有数据的,在 HBase 的底层存储里面也就没有存储这列了,这点和我们传统的关系型数据库有很大的区别,有了这个特点, HBase 特别适合存储稀疏表。

我们前面也将了 HBase 其实是多版本的,那如果我们修改了 HBase 表的一列,HBase 又是如何存储的呢?
HBase11-iteblog.jpg

比如上如中我们将 Row1 的 city 列从北京修改为上海了,如果使用 KV 表示的话,我们可以看出其实底层存储了两条数据,这两条数据的版本是不一样的,最新的一条数据版本比之前的新。总结起来就是:
 
  • HBase支持数据多版本特性,通过带有不同时间戳的多个KeyValue版本来实现的;
  • 每次put,delete都会产生一个新的Cell,都拥有一个版本;
  • 默认只存放数据的三个版本,可以配置;
  • 查询默认返回最新版本的数据,可以通过制定版本号或版本数获取旧数据。


到这里我们已经了解了 HBase 表及其底层的 KV 存储了,现在让我们来了解一下 HBase 是如何读写数据的。首先我们来看看 HBase 的架构设计,这种图来自于社区:

HBase13-iteblog.jpg

HBase 的写过程如下:
  • 先将数据写到WAL中;
  • WAL 存放在HDFS之上;
  • 每次Put、Delete操作的数据均追加到WAL末端;
  • 持久化到WAL之后,再写到MemStore中;
  • 两者写完返回ACK到客户端。


HBase14-iteblog.jpg

MemStore 其实是一种内存结构,一个Column Family 对应一个MemStore,MemStore 里面的数据也是对 Rowkey 进行字典排序的,如下:
HBase15-iteblog.jpg

既然我们写数都是先写 WAL,再写 MemStore ,而 MemStore 是内存结构,所以 MemStore 总会写满的,将 MemStore 的数据从内存刷写到磁盘的操作成为 flush:
HBase16-iteblog.jpg

以下几种行为会导致 flush 操作
  • 全局内存控制;
  • MemStore使用达到上限;
  • RegionServer的Hlog数量达到上限;
  • 手动触发;
  • 关闭RegionServer触发。


每次 flush 操作都是将一个 MemStore 的数据写到一个 HFile 里面的,所以上图中 HDFS 上有许多个 HFile 文件。文件多了会对后面的读操作有影响,所以 HBase 会隔一定的时间将 HFile 进行合并。根据合并的范围不同分为 Minor Compaction 和 Major Compaction:

HBase17-iteblog.jpg

Minor Compaction: 指选取一些小的、相邻的HFile将他们合并成一个更大的Hfile。
Major Compaction:

将一个column family下所有的 Hfiles 合并成更大的;
删除那些被标记为删除的数据、超过TTL(time-to-live)时限的数据,以及超过了版本数量限制的数据。

HBase 读操作相对于写操作更为复杂,其需要读取 BlockCache、MemStore 以及 HFile。
HBase18-iteblog.jpg

上图只是简单的表示 HBase 读的操作,实际上读的操作比这个还要复杂,我这里就不深入介绍了。

到这里,有些人可能就想到了,前面我们说 HBase 表按照 Rowkey 分布到集群的不同机器上,那么我们如何去确定我们该读写哪些 RegionServer 呢?这就是 HBase Region 查找的问题,
HBase19-iteblog.jpg

客户端按照上面的流程查找需要读写的 RegionServer 。这个过程一般是第一次读写的时候进行的,在第一次读取到元数据之后客户端一般会把这些信息缓存到自己内存中,后面操作直接从内存拿就行。当然,后面元数据信息可能还会变动,这时候客户端会再次按照上面流程获取元数据。

到这里整个读写流程得基本知识就讲完了。现在我们来看看 HBase RowKey 的设计要点。我们一般都会说,看 HBase 设计的好不好,就看其 RowKey 设计的好不好,所以RowKey 的设计在后面的写操作至关重要。我们先来看看 Rowkey 的作用
HBase21-iteblog.jpg

HBase 中的 Rowkey 主要有以下的作用:

读写数据时通过Row Key找到对应的Region
MemStore 中的数据按RowKey字典顺序排序
HFile中的数据按RowKey字典顺序排序

从下图可以看到,底层的 HFile 最终是按照 Rowkey 进行切分的,所以我们的设计原则是结合业务的特点,并考虑高频查询,尽可能的将数据打散到整个集群。
HBase22-iteblog.jpg

一定要充分分析清楚后面我们的表需要怎么查询。下面我们来看看三种比较场景的 Rowkey 设计方案。

HBase23-iteblog.jpg

HBase24-iteblog.jpg


HBase25-iteblog.jpg

这三种 Rowkey 的设计非常常见,具体的内容图片上也有了,我就不打文字了。

数据如果只是存储在哪里其实并没有什么用,我们还需要有办法能够使用到里面的数据。幸好的是,当前 HBase 有许多的组件可以满足我们各种需求。如下图是 HBase 比较常用的组件:
HBase27-iteblog.jpg

HBase 的生态主要有:
  • Phoenix:主要提供使用 SQL 的方式来查询 HBase 里面的数据。一般能够在毫秒级别返回,比较适合 OLTP 场景。
  • Spark:我们可以使用 Spark 进行 OLAP 分析;也可以使用 Spark SQL 来满足比较复杂的 SQL 查询场景;使用 Spark Streaming 来进行实时流分析。
  • Solr:原生的 HBase 只提供了 Rowkey 单主键,如果我们需要对 Rowkey 之外的列进行查找,这时候就会有问题。幸好我们可以使用 Solr 来建立二级索引/全文索引充分满足我们的查询需求。
  • HGraphDB:HGraphDB是分布式图数据库。依托图关联技术,帮助金融机构有效识别隐藏在网络中的黑色信息,在团伙欺诈、黑中介识别等。
  • GeoMesa:目前基于NoSQL数据库的时空数据引擎中功能最丰富、社区贡献人数最多的开源系统。
  • OpenTSDB:基于HBase的分布式的,可伸缩的时间序列数据库。适合做监控系统;譬如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储,查询。


下面简单介绍一下这些组件。

HBase28-iteblog.jpg


HBase29-iteblog.jpg


HBase30-iteblog.jpg


HBase31-iteblog.jpg


HBase32-iteblog.jpg


HBase33-iteblog.jpg

有了这么多组件,我们都可以干什么呢?来看看 HBase 的典型案例。

HBase35-iteblog.jpg
HBase 在风控场景、车联网/物联网、广告推荐、电子商务等行业有这广泛的使用。下面是四个典型案例的架构,由于图片里有详细的文字,我就不再打出来了。
HBase36-iteblog.jpg


HBase37-iteblog.jpg


HBase38-iteblog.jpg


HBase39-iteblog.jpg

 
继续阅读 »
本文来自于2018年10月20日由中国 HBase 技术社区在武汉举办的中国 HBase Meetup 第六次线下交流会。分享者为过往记忆。
本文ppt下载地址:http://hbase.group/slides/162

HBase2-iteblog.jpg

 
本次分享的内容主要分为以下五点:
 
  • HBase基本知识;
  • HBase读写流程;
  • RowKey设计要点;
  • HBase生态介绍;
  • HBase典型案例分析。


首先我们简单介绍一下 HBase 是什么。

HBase4-iteblog.jpg

HBase 最开始是受 Google 的 BigTable 启发而开发的分布式、多版本、面向列的开源数据库。其主要特点是支持上亿行、百万列,支持强一致性、并且具有高扩展、高可用等特点。

既然 HBase 是一种分布式的数据库,那么其和传统的 RMDB 有什么区别的呢?我们先来看看HBase表核心概念,理解这些基本的核心概念对后面我理解 HBase 的读写以及如何设计 HBase 表有着重要的联系。

HBase5-iteblog.jpg

HBase 表主要由以下几个元素组成:
 
  • RowKey:表中每条记录的主键;
  • Column Family:列族,将表进行横向切割,后面简称CF;
  • Column:属于某一个列族,可动态添加列;
  • Version Number:类型为Long,默认值是系统时间戳,可由用户自定义;
  • Value:真实的数据。


大家可以从上面的图看出:一行(Row)数据是可以包含一个或多个 Column Family,但是我们并不推荐一张 HBase 表的 Column Family 超过三个。Column 是属于 Column Family 的,一个 Column Family 包含一个或多个 Column。

在物理层面上,所有的数据其实是存放在 Region 里面的,而 Region 又由 RegionServer 管理,其对于的关系如下:
HBase6-iteblog.jpg

 
  • Region:一段数据的集合;
  • RegionServer:用于存放Region的服务。


从上面的图也可以清晰看到,一个 RegionServer 管理多个 Region;而一个 Region 管理一个或多个 Column Family。
到这里我们已经了解了 HBase 表的组成,但是 HBase 表里面的数据到底是怎么存储的呢?

HBase7-iteblog.jpg

上面是一张从逻辑上看 HBase 表形式,这个和关系型数据库很类似。那么如果我们再深入看,可以看出,这张表的划分可以如下图表示。
HBase8-iteblog.jpg

从上图大家可以明显看出,这张表有两个 Column Family ,分别为 personal 和 office。而 personal 又有三列name、city 以及 phone;office 有两列 tel 以及 address。由于存储在 HBase 里面的表一般有上亿行,所以 HBase 表会对整个数据按照 RowKey 进行字典排序,然后再对这张表进行横向切割。切割出来的数据是存储在 Region 里面,而不同的 Column Family 虽然属于一行,但是其在底层存储是放在不同的 Region 里。所以这张表我用了六种颜色表示,也就是说,这张表的数据会被放在六个 Region 里面的,这就可以把数据尽可能的分散到整个集群。

在前面我们介绍了 HBase 其实是面向列的数据库,所以说一行 HBase 的数据其实是分了好几行存储,一个列对应一行,HBase 的 KV 结构如下:
HBase9-iteblog.jpg

为了简便期间,在后面的表示我们删除了类似于 Key Length 的属性,只保留 Row Key、Column Family、Column Qualifier等信息。所以 RowKey 为 Row1 的数据第一列表示为上图最后一行的形式。以此类推,整个表的存储就可以如下表示:
HBase10-iteblog.jpg

大家可以从上面的 kv 表现形式看出,Row11 的 phone 这列其实是没有数据的,在 HBase 的底层存储里面也就没有存储这列了,这点和我们传统的关系型数据库有很大的区别,有了这个特点, HBase 特别适合存储稀疏表。

我们前面也将了 HBase 其实是多版本的,那如果我们修改了 HBase 表的一列,HBase 又是如何存储的呢?
HBase11-iteblog.jpg

比如上如中我们将 Row1 的 city 列从北京修改为上海了,如果使用 KV 表示的话,我们可以看出其实底层存储了两条数据,这两条数据的版本是不一样的,最新的一条数据版本比之前的新。总结起来就是:
 
  • HBase支持数据多版本特性,通过带有不同时间戳的多个KeyValue版本来实现的;
  • 每次put,delete都会产生一个新的Cell,都拥有一个版本;
  • 默认只存放数据的三个版本,可以配置;
  • 查询默认返回最新版本的数据,可以通过制定版本号或版本数获取旧数据。


到这里我们已经了解了 HBase 表及其底层的 KV 存储了,现在让我们来了解一下 HBase 是如何读写数据的。首先我们来看看 HBase 的架构设计,这种图来自于社区:

HBase13-iteblog.jpg

HBase 的写过程如下:
  • 先将数据写到WAL中;
  • WAL 存放在HDFS之上;
  • 每次Put、Delete操作的数据均追加到WAL末端;
  • 持久化到WAL之后,再写到MemStore中;
  • 两者写完返回ACK到客户端。


HBase14-iteblog.jpg

MemStore 其实是一种内存结构,一个Column Family 对应一个MemStore,MemStore 里面的数据也是对 Rowkey 进行字典排序的,如下:
HBase15-iteblog.jpg

既然我们写数都是先写 WAL,再写 MemStore ,而 MemStore 是内存结构,所以 MemStore 总会写满的,将 MemStore 的数据从内存刷写到磁盘的操作成为 flush:
HBase16-iteblog.jpg

以下几种行为会导致 flush 操作
  • 全局内存控制;
  • MemStore使用达到上限;
  • RegionServer的Hlog数量达到上限;
  • 手动触发;
  • 关闭RegionServer触发。


每次 flush 操作都是将一个 MemStore 的数据写到一个 HFile 里面的,所以上图中 HDFS 上有许多个 HFile 文件。文件多了会对后面的读操作有影响,所以 HBase 会隔一定的时间将 HFile 进行合并。根据合并的范围不同分为 Minor Compaction 和 Major Compaction:

HBase17-iteblog.jpg

Minor Compaction: 指选取一些小的、相邻的HFile将他们合并成一个更大的Hfile。
Major Compaction:

将一个column family下所有的 Hfiles 合并成更大的;
删除那些被标记为删除的数据、超过TTL(time-to-live)时限的数据,以及超过了版本数量限制的数据。

HBase 读操作相对于写操作更为复杂,其需要读取 BlockCache、MemStore 以及 HFile。
HBase18-iteblog.jpg

上图只是简单的表示 HBase 读的操作,实际上读的操作比这个还要复杂,我这里就不深入介绍了。

到这里,有些人可能就想到了,前面我们说 HBase 表按照 Rowkey 分布到集群的不同机器上,那么我们如何去确定我们该读写哪些 RegionServer 呢?这就是 HBase Region 查找的问题,
HBase19-iteblog.jpg

客户端按照上面的流程查找需要读写的 RegionServer 。这个过程一般是第一次读写的时候进行的,在第一次读取到元数据之后客户端一般会把这些信息缓存到自己内存中,后面操作直接从内存拿就行。当然,后面元数据信息可能还会变动,这时候客户端会再次按照上面流程获取元数据。

到这里整个读写流程得基本知识就讲完了。现在我们来看看 HBase RowKey 的设计要点。我们一般都会说,看 HBase 设计的好不好,就看其 RowKey 设计的好不好,所以RowKey 的设计在后面的写操作至关重要。我们先来看看 Rowkey 的作用
HBase21-iteblog.jpg

HBase 中的 Rowkey 主要有以下的作用:

读写数据时通过Row Key找到对应的Region
MemStore 中的数据按RowKey字典顺序排序
HFile中的数据按RowKey字典顺序排序

从下图可以看到,底层的 HFile 最终是按照 Rowkey 进行切分的,所以我们的设计原则是结合业务的特点,并考虑高频查询,尽可能的将数据打散到整个集群。
HBase22-iteblog.jpg

一定要充分分析清楚后面我们的表需要怎么查询。下面我们来看看三种比较场景的 Rowkey 设计方案。

HBase23-iteblog.jpg

HBase24-iteblog.jpg


HBase25-iteblog.jpg

这三种 Rowkey 的设计非常常见,具体的内容图片上也有了,我就不打文字了。

数据如果只是存储在哪里其实并没有什么用,我们还需要有办法能够使用到里面的数据。幸好的是,当前 HBase 有许多的组件可以满足我们各种需求。如下图是 HBase 比较常用的组件:
HBase27-iteblog.jpg

HBase 的生态主要有:
  • Phoenix:主要提供使用 SQL 的方式来查询 HBase 里面的数据。一般能够在毫秒级别返回,比较适合 OLTP 场景。
  • Spark:我们可以使用 Spark 进行 OLAP 分析;也可以使用 Spark SQL 来满足比较复杂的 SQL 查询场景;使用 Spark Streaming 来进行实时流分析。
  • Solr:原生的 HBase 只提供了 Rowkey 单主键,如果我们需要对 Rowkey 之外的列进行查找,这时候就会有问题。幸好我们可以使用 Solr 来建立二级索引/全文索引充分满足我们的查询需求。
  • HGraphDB:HGraphDB是分布式图数据库。依托图关联技术,帮助金融机构有效识别隐藏在网络中的黑色信息,在团伙欺诈、黑中介识别等。
  • GeoMesa:目前基于NoSQL数据库的时空数据引擎中功能最丰富、社区贡献人数最多的开源系统。
  • OpenTSDB:基于HBase的分布式的,可伸缩的时间序列数据库。适合做监控系统;譬如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储,查询。


下面简单介绍一下这些组件。

HBase28-iteblog.jpg


HBase29-iteblog.jpg


HBase30-iteblog.jpg


HBase31-iteblog.jpg


HBase32-iteblog.jpg


HBase33-iteblog.jpg

有了这么多组件,我们都可以干什么呢?来看看 HBase 的典型案例。

HBase35-iteblog.jpg
HBase 在风控场景、车联网/物联网、广告推荐、电子商务等行业有这广泛的使用。下面是四个典型案例的架构,由于图片里有详细的文字,我就不再打出来了。
HBase36-iteblog.jpg


HBase37-iteblog.jpg


HBase38-iteblog.jpg


HBase39-iteblog.jpg

  收起阅读 »

Apache HBase Operator Tools

推荐Apache HBase Operator Tools
Apache HBase HBCK2 Tool

HBCK2 is the successor to hbck, the hbase-1.x fixup tool (A.K.A hbck1). Use it in place of hbck1 making repairs against hbase-2.x installs.

hbck1

The hbck tool that ships with hbase-1.x (A.K.A hbck1) should not be run against an hbase-2.x cluster. It may do damage. While hbck1 is still bundled inside hbase-2.x -- to minimize surprise (it has a fat pointer to HBCK2 at the head of its help output) -- it's write-facility (-fix) has been removed. It can report on the state of an hbase-2.x cluster but its assessments are likely inaccurate since it does not understand the internal workings of an hbase-2.x.

HBCK2 does much less than hbck1 because many of the class of problems hbck1 addressed are either no longer issues in hbase-2.x, or we've made (or will make) a dedicated tool to do what hbck1 used incorporate. HBCK2 also works in a manner that differs from how hbck1 operated, asking the HBase Master to do its bidding, rather than replicate functionality outside of the Master inside the hbck1 tool.

Building HBCK2

Run:mvn installThe built HBCK2 fat jar will be in the target sub-directory.

Running HBCK2

org.apache.hbase.HBCK2 is the name of the HBCK2 main class. After building HBCK2 to generate the HBCK2 jar file, running the below will dump out the HBCK2 usage:$ HBASE_CLASSPATH_PREFIX=./hbase-hbck2-1.0.0-SNAPSHOT.jar ./bin/hbase org.apache.hbase.HBCK2
usage: HBCK2 [OPTIONS] COMMAND <ARGS> Options: -d,--debug run with debug output -h,--help output this help message -p,--hbase.zookeeper.property.clientPort port of target hbase ensemble -q,--hbase.zookeeper.quorum <arg> ensemble of target hbase -v,--version this hbck2 version -z,--zookeeper.znode.parent parent znode of target hbase Commands: assigns [OPTIONS] <ENCODED_REGIONNAME>... Options: -o,--override override ownership by another procedure A 'raw' assign that can be used even during Master initialization. Skirts Coprocessors. Pass one or more encoded RegionNames. 1588230740 is the hard-coded name for the hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example of what a user-space encoded Region name looks like. For example: $ HBCK2 assign 1588230740 de00010733901a05f5a2a3a382e27dd4 Returns the pid(s) of the created AssignProcedure(s) or -1 if none. bypass [OPTIONS] <PID>... Options: -o,--override override if procedure is running/stuck -r,--recursive bypass parent and its children. SLOW! EXPENSIVE! -w,--lockWait milliseconds to wait on lock before giving up; default=1 Pass one (or more) procedure 'pid's to skip to procedure finish. Parent of bypassed procedure will also be skipped to the finish. Entities will be left in an inconsistent state and will require manual fixup. May need Master restart to clear locks still held. Bypass fails if procedure has children. Add 'recursive' if all you have is a parent pid to finish parent and children. This is SLOW, and dangerous so use selectively. Does not always work. unassigns <ENCODED_REGIONNAME>... Options: -o,--override override ownership by another procedure A 'raw' unassign that can be used even during Master initialization. Skirts Coprocessors. Pass one or more encoded RegionNames: 1588230740 is the hard-coded name for the hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example of what a user-space encoded Region name looks like. For example: $ HBCK2 unassign 1588230740 de00010733901a05f5a2a3a382e27dd4 Returns the pid(s) of the created UnassignProcedure(s) or -1 if none. setTableState <TABLENAME> <STATE> Possible table states: ENABLED, DISABLED, DISABLING, ENABLING To read current table state, in the hbase shell run: hbase> get 'hbase:meta', '<TABLENAME>', 'table:state' A value of \x08\x00 == ENABLED, \x08\x01 == DISABLED, etc. An example making table name 'user' ENABLED: $ HBCK2 setTableState users ENABLED Returns whatever the previous table state was.HBCK2 Overview

HBCK2 is currently a simple tool that does one thing at a time only.

HBCK2 does not do diagnosis, leaving that function to other tooling, described below.

In hbase-2.x, the Master is the final arbiter of all state, so a general principal of HBCK2 is that it asks the Master to effect all repair. This means a Master must be up before you can run an HBCK2 command.

HBCK2 works by making use of an intentionally obscured HbckService hosted on the Master. The Service publishes a few methods for the HBCK2 tool to pull on. The first thing HBCK2 does is poke the cluster to ensure the service is available. It will fail if it is not or if the HbckService is lacking a wanted facility. HBCK2 versions should be able to work across multiple hbase-2 releases. It will fail with a complaint if it is unable to run. There is no HbckService in versions of hbase before 2.0.3 and 2.1.1. HBCK2 will not work against these versions.

Finding Problems

While hbck1 performed analysis reporting your cluster GOOD or BAD, HBCK2 is less presumptious. In hbase-2.x, the operator figures what needs fixing and then uses tooling including HBCK2 to do fixup.

To figure issues in assignment, make use of the following utilities.

Diagnosis Tooling
Master Logs

The Master runs all assignments, server crash handling, cluster start and stop, etc. In hbase-2.x, all that the Master does has been cast as Procedures run on a state machine engine. See Procedure Framework and Assignment Manager for detail on how this new infrastructure works. Each Procedure has a unique Procedure id, its pid, that it lists on each logging. Following the pid, you can trace the lifecycle of a Procedure in the Master logs as Procedures transition from start, through each of the Procedure's various stages to finish. Some Procedures spawn sub-procedures, wait on their Children, and then themselves finish. Each child logs its pid but also its ppid; its parent's pid.

Generally all runs problem free but if some unforeseen circumstance arises, the assignment framework may sustain damage requiring operator intervention. Below we will discuss some such scenarios but they can manifest in the Master log as a Region being STUCK or a Procedure transitioning an entity -- a Region or a Table -- may be blocked because another Procedure holds the exclusive lock and is not letting go.

STUCK Procedures look like this:2018-09-12 15:29:06,558 WARN org.apache.hadoop.hbase.master.assignment.AssignmentManager: STUCK Region-In-Transition rit=OPENING, location=va1001.example.org,22101,1536173230599, table=IntegrationTestBigLinkedList_20180626110336, region=dbdb56242f17610c46ea044f7a42895b/master-status#tables

This section about midway down in Master UI home-page shows a list of tables with columns for whether the table is ENABLED, ENABLING, DISABLING, or DISABLED among other attributes. Also listed are columns with counts of Regions in their various transition states: OPEN, CLOSED, etc. A read of this table is good for figuring if the Regions of this table have a proper disposition. For example if a table is ENABLED and there are Regions that are not in the OPEN state and the Master Log is silent about any ongoing assigns, then something is amiss.

Procedures & Locks

This page off the Master UI home page under the Procedures & Locks menu item in the page heading lists all ongoing Procedures and Locks as well as the current set of Master Procedure WALs (named pv2-0000000000000000###.log under the MasterProcWALs directory in your hbase install). On startup, on a large cluster when furious assigning is afoot, this page is filled with lists of Procedures and Locks. The count of MasterProcWALs will bloat too. If after the cluster settles, there is a stuck Lock or Procedure or the count of WALs doesn't ever come down but only grows, then operator intervention is needed to alieve the blockage.

Lists of locks and procedures can also be obtained via the hbase shell:$ echo "list_locks"| hbase shell &> /tmp/locks.txt $ echo "list_procedures"| hbase shell &> /tmp/procedures.txtThe HBase Canary Tool

The Canary tool is useful verifying the state of assign. It can be run with a table focus or against the whole cluster.

For example, to check cluster assigns:$ hbase canary -f false -t 6000000 &>/tmp/canary.logThe -f false tells the Canary to keep going across failed Region fetches and the -t 6000000 tells the Canary run for ~two hours maximum. When done, check out /tmp/canary.log. Grep for ERROR lines to find problematic Region assigns.

You can do a probe like the Canary's in the hbase shell. For example, given a Region that has a start row of d1dddd0cbelonging to the table testtable, do as follows:hbase> scan 'testtable', {STARTROW => 'd1dddd0c', LIMIT => 10}For an overview on parsing a Region name into its constituent parts, see RegionInfo API.

Other Tools

To figure the list of Regions that are not OPEN on an ENABLED or ENABLING table, read the hbase:meta table info:state column. For example, to find the state of all Regions in the table IntegrationTestBigLinkedList_20180626064758, do the following:$ echo " scan 'hbase:meta', {ROWPREFIXFILTER => 'IntegrationTestBigLinkedList_20180626064758,', COLUMN => 'info:state'}"| hbase shell > /tmp/t.txt...then grep for OPENING or CLOSING Regions.

To move an OPENING issue to OPEN so it agrees with a table's ENABLED state, use the assign command in the hbase shell to queue a new Assign Procedure (watch the Master logs to see the Assign run). If many Regions to assign, use the HBCK2 tool. It can do bulk assigning.

Fixing

General principals include a Region can not be assigned if it is in CLOSING state (or the inverse, unassigned if in OPENINGstate) without first transitioning via CLOSED: Regions must always move from CLOSED, to OPENING, to OPEN, and then to CLOSING, CLOSED.

When making repair, do fixup a table at a time.

Also, if a table is DISABLED, you cannot assign a Region. In the Master logs, you will see that the Master will report that the assign has been skipped because the table is DISABLED. You may want to assign a Region because it is currently in the OPENING state and you want it in the CLOSED state so it agrees with the table's DISABLED state. In this situation, you may have to temporarily set the table status to ENABLED, just so you can do the assign, and then set it back again after the unassign.HBCK2 has facility to allow you do this. See the HBCK2 usage output.

Start-over

At an extreme, if the Master is distraught and all attempts at fixup only turn up undoable locks or Procedures that won't finish, and/or the set of MasterProcWALs is growing without bound, it is possible to wipe the Master state clean. Just move aside the/hbase/MasterProcWALs/ directory under your hbase install and restart the Master process. It will come back as a tabula rasawithout memory of the bad times past.

If at the time of the erasure, all Regions were happily assigned or offlined, then on Master restart, the Master should pick up and continue as though nothing happened. But if there were Regions-In-Transition at the time, then the operator may have to intervene to bring outstanding assigns/unassigns to their terminal point. Read the hbase:meta info:state columns as described above to figure what needs assigning/unassigning. Having erased all history moving aside the MasterProcWALs, none of the entities should be locked so you are free to bulk assign/unassign.

Assigning/Unassigning

Generally, on assign, the Master will persist until successful. An assign takes an exclusive lock on the Region. This precludes a concurrent assign or unassign from running. An assign against a locked Region will wait until the lock is released before making progress. See the [Procedures & Locks] section above for current list of outstanding Locks.

Master startup cannot progress, in holding-pattern until region onlined

This should never happen. If it does, here is what it looks like:2018-10-01 22:07:42,792 WARN org.apache.hadoop.hbase.master.HMaster: hbase:meta,,1.1588230740 is NOT online; state={1588230740 state=CLOSING, ts=1538456302300, server=ve1017.example.org,22101,1538449648131}; ServerCrashProcedures=true. Master startup cannot progress, in holding-pattern until region onlined.The Master is unable to continue startup because there is no Procedure to assign hbase:meta (or hbase:namespace). To inject one, use the HBCK2 tool:HBASE_CLASSPATH_PREFIX=./hbase-hbck2-1.0.0-SNAPSHOT.jar hbase org.apache.hbase.HBCK2 assigns 1588230740...where 1588230740 is the encoded name of the hbase:meta Region.

The same may happen to the hbase:namespace system table. Look for the encoded Region name of the hbase:namespaceRegion and do similar to what we did for hbase:meta.
 
继续阅读 »
推荐Apache HBase Operator Tools
Apache HBase HBCK2 Tool

HBCK2 is the successor to hbck, the hbase-1.x fixup tool (A.K.A hbck1). Use it in place of hbck1 making repairs against hbase-2.x installs.

hbck1

The hbck tool that ships with hbase-1.x (A.K.A hbck1) should not be run against an hbase-2.x cluster. It may do damage. While hbck1 is still bundled inside hbase-2.x -- to minimize surprise (it has a fat pointer to HBCK2 at the head of its help output) -- it's write-facility (-fix) has been removed. It can report on the state of an hbase-2.x cluster but its assessments are likely inaccurate since it does not understand the internal workings of an hbase-2.x.

HBCK2 does much less than hbck1 because many of the class of problems hbck1 addressed are either no longer issues in hbase-2.x, or we've made (or will make) a dedicated tool to do what hbck1 used incorporate. HBCK2 also works in a manner that differs from how hbck1 operated, asking the HBase Master to do its bidding, rather than replicate functionality outside of the Master inside the hbck1 tool.

Building HBCK2

Run:mvn installThe built HBCK2 fat jar will be in the target sub-directory.

Running HBCK2

org.apache.hbase.HBCK2 is the name of the HBCK2 main class. After building HBCK2 to generate the HBCK2 jar file, running the below will dump out the HBCK2 usage:$ HBASE_CLASSPATH_PREFIX=./hbase-hbck2-1.0.0-SNAPSHOT.jar ./bin/hbase org.apache.hbase.HBCK2
usage: HBCK2 [OPTIONS] COMMAND <ARGS> Options: -d,--debug run with debug output -h,--help output this help message -p,--hbase.zookeeper.property.clientPort port of target hbase ensemble -q,--hbase.zookeeper.quorum <arg> ensemble of target hbase -v,--version this hbck2 version -z,--zookeeper.znode.parent parent znode of target hbase Commands: assigns [OPTIONS] <ENCODED_REGIONNAME>... Options: -o,--override override ownership by another procedure A 'raw' assign that can be used even during Master initialization. Skirts Coprocessors. Pass one or more encoded RegionNames. 1588230740 is the hard-coded name for the hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example of what a user-space encoded Region name looks like. For example: $ HBCK2 assign 1588230740 de00010733901a05f5a2a3a382e27dd4 Returns the pid(s) of the created AssignProcedure(s) or -1 if none. bypass [OPTIONS] <PID>... Options: -o,--override override if procedure is running/stuck -r,--recursive bypass parent and its children. SLOW! EXPENSIVE! -w,--lockWait milliseconds to wait on lock before giving up; default=1 Pass one (or more) procedure 'pid's to skip to procedure finish. Parent of bypassed procedure will also be skipped to the finish. Entities will be left in an inconsistent state and will require manual fixup. May need Master restart to clear locks still held. Bypass fails if procedure has children. Add 'recursive' if all you have is a parent pid to finish parent and children. This is SLOW, and dangerous so use selectively. Does not always work. unassigns <ENCODED_REGIONNAME>... Options: -o,--override override ownership by another procedure A 'raw' unassign that can be used even during Master initialization. Skirts Coprocessors. Pass one or more encoded RegionNames: 1588230740 is the hard-coded name for the hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example of what a user-space encoded Region name looks like. For example: $ HBCK2 unassign 1588230740 de00010733901a05f5a2a3a382e27dd4 Returns the pid(s) of the created UnassignProcedure(s) or -1 if none. setTableState <TABLENAME> <STATE> Possible table states: ENABLED, DISABLED, DISABLING, ENABLING To read current table state, in the hbase shell run: hbase> get 'hbase:meta', '<TABLENAME>', 'table:state' A value of \x08\x00 == ENABLED, \x08\x01 == DISABLED, etc. An example making table name 'user' ENABLED: $ HBCK2 setTableState users ENABLED Returns whatever the previous table state was.HBCK2 Overview

HBCK2 is currently a simple tool that does one thing at a time only.

HBCK2 does not do diagnosis, leaving that function to other tooling, described below.

In hbase-2.x, the Master is the final arbiter of all state, so a general principal of HBCK2 is that it asks the Master to effect all repair. This means a Master must be up before you can run an HBCK2 command.

HBCK2 works by making use of an intentionally obscured HbckService hosted on the Master. The Service publishes a few methods for the HBCK2 tool to pull on. The first thing HBCK2 does is poke the cluster to ensure the service is available. It will fail if it is not or if the HbckService is lacking a wanted facility. HBCK2 versions should be able to work across multiple hbase-2 releases. It will fail with a complaint if it is unable to run. There is no HbckService in versions of hbase before 2.0.3 and 2.1.1. HBCK2 will not work against these versions.

Finding Problems

While hbck1 performed analysis reporting your cluster GOOD or BAD, HBCK2 is less presumptious. In hbase-2.x, the operator figures what needs fixing and then uses tooling including HBCK2 to do fixup.

To figure issues in assignment, make use of the following utilities.

Diagnosis Tooling
Master Logs

The Master runs all assignments, server crash handling, cluster start and stop, etc. In hbase-2.x, all that the Master does has been cast as Procedures run on a state machine engine. See Procedure Framework and Assignment Manager for detail on how this new infrastructure works. Each Procedure has a unique Procedure id, its pid, that it lists on each logging. Following the pid, you can trace the lifecycle of a Procedure in the Master logs as Procedures transition from start, through each of the Procedure's various stages to finish. Some Procedures spawn sub-procedures, wait on their Children, and then themselves finish. Each child logs its pid but also its ppid; its parent's pid.

Generally all runs problem free but if some unforeseen circumstance arises, the assignment framework may sustain damage requiring operator intervention. Below we will discuss some such scenarios but they can manifest in the Master log as a Region being STUCK or a Procedure transitioning an entity -- a Region or a Table -- may be blocked because another Procedure holds the exclusive lock and is not letting go.

STUCK Procedures look like this:2018-09-12 15:29:06,558 WARN org.apache.hadoop.hbase.master.assignment.AssignmentManager: STUCK Region-In-Transition rit=OPENING, location=va1001.example.org,22101,1536173230599, table=IntegrationTestBigLinkedList_20180626110336, region=dbdb56242f17610c46ea044f7a42895b/master-status#tables

This section about midway down in Master UI home-page shows a list of tables with columns for whether the table is ENABLED, ENABLING, DISABLING, or DISABLED among other attributes. Also listed are columns with counts of Regions in their various transition states: OPEN, CLOSED, etc. A read of this table is good for figuring if the Regions of this table have a proper disposition. For example if a table is ENABLED and there are Regions that are not in the OPEN state and the Master Log is silent about any ongoing assigns, then something is amiss.

Procedures & Locks

This page off the Master UI home page under the Procedures & Locks menu item in the page heading lists all ongoing Procedures and Locks as well as the current set of Master Procedure WALs (named pv2-0000000000000000###.log under the MasterProcWALs directory in your hbase install). On startup, on a large cluster when furious assigning is afoot, this page is filled with lists of Procedures and Locks. The count of MasterProcWALs will bloat too. If after the cluster settles, there is a stuck Lock or Procedure or the count of WALs doesn't ever come down but only grows, then operator intervention is needed to alieve the blockage.

Lists of locks and procedures can also be obtained via the hbase shell:$ echo "list_locks"| hbase shell &> /tmp/locks.txt $ echo "list_procedures"| hbase shell &> /tmp/procedures.txtThe HBase Canary Tool

The Canary tool is useful verifying the state of assign. It can be run with a table focus or against the whole cluster.

For example, to check cluster assigns:$ hbase canary -f false -t 6000000 &>/tmp/canary.logThe -f false tells the Canary to keep going across failed Region fetches and the -t 6000000 tells the Canary run for ~two hours maximum. When done, check out /tmp/canary.log. Grep for ERROR lines to find problematic Region assigns.

You can do a probe like the Canary's in the hbase shell. For example, given a Region that has a start row of d1dddd0cbelonging to the table testtable, do as follows:hbase> scan 'testtable', {STARTROW => 'd1dddd0c', LIMIT => 10}For an overview on parsing a Region name into its constituent parts, see RegionInfo API.

Other Tools

To figure the list of Regions that are not OPEN on an ENABLED or ENABLING table, read the hbase:meta table info:state column. For example, to find the state of all Regions in the table IntegrationTestBigLinkedList_20180626064758, do the following:$ echo " scan 'hbase:meta', {ROWPREFIXFILTER => 'IntegrationTestBigLinkedList_20180626064758,', COLUMN => 'info:state'}"| hbase shell > /tmp/t.txt...then grep for OPENING or CLOSING Regions.

To move an OPENING issue to OPEN so it agrees with a table's ENABLED state, use the assign command in the hbase shell to queue a new Assign Procedure (watch the Master logs to see the Assign run). If many Regions to assign, use the HBCK2 tool. It can do bulk assigning.

Fixing

General principals include a Region can not be assigned if it is in CLOSING state (or the inverse, unassigned if in OPENINGstate) without first transitioning via CLOSED: Regions must always move from CLOSED, to OPENING, to OPEN, and then to CLOSING, CLOSED.

When making repair, do fixup a table at a time.

Also, if a table is DISABLED, you cannot assign a Region. In the Master logs, you will see that the Master will report that the assign has been skipped because the table is DISABLED. You may want to assign a Region because it is currently in the OPENING state and you want it in the CLOSED state so it agrees with the table's DISABLED state. In this situation, you may have to temporarily set the table status to ENABLED, just so you can do the assign, and then set it back again after the unassign.HBCK2 has facility to allow you do this. See the HBCK2 usage output.

Start-over

At an extreme, if the Master is distraught and all attempts at fixup only turn up undoable locks or Procedures that won't finish, and/or the set of MasterProcWALs is growing without bound, it is possible to wipe the Master state clean. Just move aside the/hbase/MasterProcWALs/ directory under your hbase install and restart the Master process. It will come back as a tabula rasawithout memory of the bad times past.

If at the time of the erasure, all Regions were happily assigned or offlined, then on Master restart, the Master should pick up and continue as though nothing happened. But if there were Regions-In-Transition at the time, then the operator may have to intervene to bring outstanding assigns/unassigns to their terminal point. Read the hbase:meta info:state columns as described above to figure what needs assigning/unassigning. Having erased all history moving aside the MasterProcWALs, none of the entities should be locked so you are free to bulk assign/unassign.

Assigning/Unassigning

Generally, on assign, the Master will persist until successful. An assign takes an exclusive lock on the Region. This precludes a concurrent assign or unassign from running. An assign against a locked Region will wait until the lock is released before making progress. See the [Procedures & Locks] section above for current list of outstanding Locks.

Master startup cannot progress, in holding-pattern until region onlined

This should never happen. If it does, here is what it looks like:2018-10-01 22:07:42,792 WARN org.apache.hadoop.hbase.master.HMaster: hbase:meta,,1.1588230740 is NOT online; state={1588230740 state=CLOSING, ts=1538456302300, server=ve1017.example.org,22101,1538449648131}; ServerCrashProcedures=true. Master startup cannot progress, in holding-pattern until region onlined.The Master is unable to continue startup because there is no Procedure to assign hbase:meta (or hbase:namespace). To inject one, use the HBCK2 tool:HBASE_CLASSPATH_PREFIX=./hbase-hbck2-1.0.0-SNAPSHOT.jar hbase org.apache.hbase.HBCK2 assigns 1588230740...where 1588230740 is the encoded name of the hbase:meta Region.

The same may happen to the hbase:namespace system table. Look for the encoded Region name of the hbase:namespaceRegion and do similar to what we did for hbase:meta.
  收起阅读 »

HBase在滴滴出行的应用场景和最佳实践

作者简介:李扬,滴滴出行资深软件开发工程师。2015年加入滴滴出行基础平台部,主要负责HBase和Phoenix以及相关分布式存储技术。在滴滴之前,曾在新浪担任数据工程师,专注于分布式计算和存储。 
责编:郭芮(guorui@csdn.net),关注大数据领域。 
 背景
对接业务类型

HBase是建立在Hadoop生态之上的Database,源生对离线任务支持友好,又因为LSM树是一个优秀的高吞吐数据库结构,所以同时也对接了很多线上业务。在线业务对访问延迟敏感,并且访问趋向于随机,如订单、客服轨迹查询。离线业务通常是数仓的定时大批量处理任务,对一段时间内的数据进行处理并产出结果,对任务完成的时间要求不是非常敏感,并且处理逻辑复杂,如天级别报表、安全和用户行为分析、模型训练等。

多语言支持

HBase提供了多语言解决方案,并且由于滴滴各业务线RD所使用的开发语言各有偏好,所以多语言支持对于HBase在滴滴内部的发展是至关重要的一部分。我们对用户提供了多种语言的访问方式:HBase Java native API、Thrift Server(主要应用于C++、PHP、Python)、JAVA JDBC(Phoenix JDBC)、Phoenix QueryServer(Phoenix对外提供的多语言解决方案)、MapReduce Job(Htable/Hfile Input)、Spark Job、Streaming等。

数据类型

HBase在滴滴主要存放了以下四种数据类型:
 
  • 统计结果、报表类数据:主要是运营、运力情况、收入等结果,通常需要配合Phoenix进行SQL查询。数据量较小,对查询的灵活性要求高,延迟要求一般。
  • 原始事实类数据:如订单、司机乘客的GPS轨迹、日志等,主要用作在线和离线的数据供给。数据量大,对一致性和可用性要求高,延迟敏感,实时写入,单点或批量查询。
  • 中间结果数据:指模型训练所需要的数据等。数据量大,可用性和一致性要求一般,对批量查询时的吞吐量要求高。
  • 线上系统的备份数据:用户把原始数据存在了其他关系数据库或文件服务,把HBase作为一个异地容灾的方案。

 使用场景介绍
场景一:订单事件

这份数据使用过滴滴产品的用户应该都接触过,就是App上的历史订单。近期订单的查询会落在Redis,超过一定时间范围,或者当Redis不可用时,查询会落在HBase上。业务方的需求如下:
 
  • 在线查询订单生命周期的各个状态,包括status、event_type、order_detail等信息。主要的查询来自于客服系统。
  • 在线历史订单详情查询。上层会有Redis来存储近期的订单,当Redis不可用或者查询范围超出Redis,查询会直接落到HBase。
  • 离线对订单的状态进行分析。
  • 写入满足每秒10K的事件,读取满足每秒1K的事件,数据要求在5s内可用。


592fd58ff297e.png

按照这些要求,我们对Rowkey做出了下面的设计,都是很典型的scan场景。

订单状态表

Rowkey:reverse(order_id) + (MAX_LONG - TS) 
Columns:该订单各种状态

订单历史表

Rowkey:reverse(passenger_id | driver_id) + (MAX_LONG - TS) 
Columns:用户在时间范围内的订单及其他信息

场景二:司机乘客轨迹

这也是一份滴滴用户关系密切的数据,线上用户、滴滴的各个业务线和分析人员都会使用。举几个使用场景上的例子:用户查看历史订单时,地图上显示所经过的路线;发生司乘纠纷,客服调用订单轨迹复现场景;地图部门用户分析道路拥堵情况。

592fd62ec3aa9.png

用户们提出的需求:
 
  • 满足App用户或者后端分析人员的实时或准实时轨迹坐标查询;
  • 满足离线大规模的轨迹分析;
  • 满足给出一个指定的地理范围,取出范围内所有用户的轨迹或范围内出现过的用户。

 其中,关于第三个需求,地理位置查询,我们知道MongoDB对于这种地理索引有源生的支持,但是在滴滴这种量级的情况下可能会发生存储瓶颈,HBase存储和扩展性上没有压力但是没有内置类似MongoDB地理位置索引的功能,没有就需要我们自己实现。通过调研,了解到关于地理索引有一套比较通用的GeohHash算法 。

GeoHash是将二维的经纬度转换成字符串,每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,比如说我在悠唐酒店,我的一个朋友在旁边的悠唐购物广场,我们的经纬度点会得到相同的GeoHash串。这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存。

592fd6715b13f.png

但是我们要查询的范围和GeohHash块可能不会完全重合。以圆形为例,查询时会出现如图4所示的一半在GeoHash块内,一半在外面的情况(如A、B、C、D、E、F、G等点)。这种情况就需要对GeoHash块内每个真实的GPS点进行第二次的过滤,通过原始的GPS点和圆心之间的距离,过滤掉不符合查询条件的数据。

592fd6ba65517.png

最后依据这个原理,把GeoHash和其他一些需要被索引的维度拼装成Rowkey,真实的GPS点为Value,在这个基础上封装成客户端,并且在客户端内部对查询逻辑和查询策略做出速度上的大幅优化,这样就把HBase变成了一个MongoDB一样支持地理位置索引的数据库。如果查询范围非常大(比如进行省级别的分析),还额外提供了MR的获取数据的入口。

两种查询场景的Rowkey设计如下:
 
  • 单个用户按订单或时间段查询: reverse(user_id) + (Integer.MAX_LONG-TS/1000)
  • 给定范围内的轨迹查询:reverse(geohash) + ts/1000 + user_id

 场景三:ETA

ETA是指每次选好起始和目的地后,提示出的预估时间和价格。提示的预估到达时间和价格,最初版本是离线方式运行,后来改版通过HBase实现实时效果,把HBase当成一个KeyValue缓存,带来了减少训练时间、可多城市并行、减少人工干预的好处。 
整个ETA的过程如下:
 
  • 模型训练通过Spark Job,每30分钟对各个城市训练一次;
  • 模型训练第一阶段,在5分钟内,按照设定条件从HBase读取所有城市数据;
  • 模型训练第二阶段在25分钟内完成ETA的计算;
  • HBase中的数据每隔一段时间会持久化至HDFS中,供新模型测试和新的特征提取。

 Rowkey:salting+cited+type0+type1+type2+TS 
Column:order, feature

592fd708c4246.png

场景四:监控工具DCM

用于监控Hadoop集群的资源使用(Namenode,Yarn container使用等),关系数据库在时间维度过程以后会产生各种性能问题,同时我们又希望可以通过SQL做一些分析查询,所以使用Phoenix,使用采集程序定时录入数据,生产成报表,存入HBase,可以在秒级别返回查询结果,最后在前端做展示。

592fd7375c265.png

图7、图8、图9是几张监控工具的用户UI,数字相关的部分做了模糊处理。

592fd7610405d.png


592fd77d8ebbe.png


592fd7997641f.png

滴滴在HBase对多租户的管理
我们认为单集群多租户是最高效和节省精力的方案,但是由于HBase对多租户基本没有管理,使用上会遇到很多问题:在用户方面比如对资源使用情况不做分析、存储总量发生变化后不做调整和通知、项目上线下线没有计划、想要最多的资源和权限等;我们平台管理者也会遇到比如线上沟通难以理解用户的业务、对每个接入HBase的项目状态不清楚、不能判断出用户的需求是否合理、多租户在集群上发生资源竞争、问题定位和排查时间长等。

针对这些问题,我们开发了DHS系统(Didi HBase Service)进行项目管理,并且在HBase上通过Namespace、RS Group等技术来分割用户的资源、数据和权限。通过计算开销并计费的方法来管控资源分配。

592fd7f8d4da8.png

DHS主要有下面几个模块和功能:
 
  • 项目生命周期管理:包括立项、资源预估和申请、项目需求调整、需求讨论;
  • 用户管理:权限管理,项目审批;
  • 集群资源管理;
  • 表级别的使用情况监控:主要是读写监控、memstore、blockcache、locality。

 当用户有使用HBase存储的需求,我们会让用户在DHS上注册项目。介绍业务的场景和产品相关的细节,以及是否有高SLA要求。

之后是新建表以及对表性能需求预估,我们要求用户对自己要使用的资源有一个准确的预估。如果用户难以估计,我们会以线上或者线下讨论的方式与用户讨论帮助确定这些信息。 
然后会生成项目概览页面,方便管理员和用户进行项目进展的跟踪。

HBase自带的jxm信息会汇总到Region和RegionServer级别的数据,管理员会经常用到,但是用户却很少关注这个级别。根据这种情况我们开发了HBase表级别的监控,并且会有权限控制,让业务RD只能看到和自己相关的表,清楚自己项目表的吞吐及存储占用情况。

通过DHS让用户明确自己使用资源情况的基础之上,我们使用了RS Group技术,把一个集群分成多个逻辑子集群,可以让用户选择独占或者共享资源。共享和独占各有自己的优缺点,如表1。

592fd853ebaac.png

根据以上的情况,我们在资源分配上会根据业务的特性来选择不同方案:
 
  • 对于访问延迟要求低、访问量小、可用性要求低、备份或者测试阶段的数据:使用共享资源池;
  • 对于延迟敏感、吞吐要求高、高峰时段访问量大、可用性要求高、在线业务:让其独占一定机器数量构成的RegionServer Group资源,并且按用户预估的资源量,额外给出20%~30%的余量。

 最后我们会根据用户对资源的使用,定期计算开销并向用户发出账单。

RS Group
RegionServer Group,实现细节可以参照HBase HBASE-6721这个Patch。滴滴在这个基础上作了一些分配策略上的优化,以便适合滴滴业务场景的修改。RS Group简单概括是指通过分配一批指定的RegionServer列表,成为一个RS Group,每个Group可以按需挂载不同的表,并且当Group内的表发生异常后,Region不会迁移到其他的Group。这样,每个Group就相当于一个逻辑上的子集群,通过这种方式达到资源隔离的效果,降低管理成本,不必为每个高SLA的业务线单独搭集群。

592fd8a900ab5.png

总结
在滴滴推广和实践HBase的工作中,我们认为至关重要的两点是帮助用户做出良好的表结构设计和资源的控制。有了这两个前提之后,后续出现问题的概率会大大降低。良好的表结构设计需要用户对HBase的实现有一个清晰的认识,大多数业务用户把更多精力放在了业务逻辑上,对架构实现知之甚少,这就需要平台管理者去不断帮助和引导,有了好的开端和成功案例后,通过这些用户再去向其他的业务方推广。资源隔离控制则帮助我们有效减少集群的数量,降低运维成本,让平台管理者从多集群无止尽的管理工作中解放出来,将更多精力投入到组件社区跟进和平台管理系统的研发工作中,使业务和平台都进入一个良性循环,提升用户的使用体验,更好地支持公司业务的发展。
 
继续阅读 »
作者简介:李扬,滴滴出行资深软件开发工程师。2015年加入滴滴出行基础平台部,主要负责HBase和Phoenix以及相关分布式存储技术。在滴滴之前,曾在新浪担任数据工程师,专注于分布式计算和存储。 
责编:郭芮(guorui@csdn.net),关注大数据领域。 
 背景
对接业务类型

HBase是建立在Hadoop生态之上的Database,源生对离线任务支持友好,又因为LSM树是一个优秀的高吞吐数据库结构,所以同时也对接了很多线上业务。在线业务对访问延迟敏感,并且访问趋向于随机,如订单、客服轨迹查询。离线业务通常是数仓的定时大批量处理任务,对一段时间内的数据进行处理并产出结果,对任务完成的时间要求不是非常敏感,并且处理逻辑复杂,如天级别报表、安全和用户行为分析、模型训练等。

多语言支持

HBase提供了多语言解决方案,并且由于滴滴各业务线RD所使用的开发语言各有偏好,所以多语言支持对于HBase在滴滴内部的发展是至关重要的一部分。我们对用户提供了多种语言的访问方式:HBase Java native API、Thrift Server(主要应用于C++、PHP、Python)、JAVA JDBC(Phoenix JDBC)、Phoenix QueryServer(Phoenix对外提供的多语言解决方案)、MapReduce Job(Htable/Hfile Input)、Spark Job、Streaming等。

数据类型

HBase在滴滴主要存放了以下四种数据类型:
 
  • 统计结果、报表类数据:主要是运营、运力情况、收入等结果,通常需要配合Phoenix进行SQL查询。数据量较小,对查询的灵活性要求高,延迟要求一般。
  • 原始事实类数据:如订单、司机乘客的GPS轨迹、日志等,主要用作在线和离线的数据供给。数据量大,对一致性和可用性要求高,延迟敏感,实时写入,单点或批量查询。
  • 中间结果数据:指模型训练所需要的数据等。数据量大,可用性和一致性要求一般,对批量查询时的吞吐量要求高。
  • 线上系统的备份数据:用户把原始数据存在了其他关系数据库或文件服务,把HBase作为一个异地容灾的方案。

 使用场景介绍
场景一:订单事件

这份数据使用过滴滴产品的用户应该都接触过,就是App上的历史订单。近期订单的查询会落在Redis,超过一定时间范围,或者当Redis不可用时,查询会落在HBase上。业务方的需求如下:
 
  • 在线查询订单生命周期的各个状态,包括status、event_type、order_detail等信息。主要的查询来自于客服系统。
  • 在线历史订单详情查询。上层会有Redis来存储近期的订单,当Redis不可用或者查询范围超出Redis,查询会直接落到HBase。
  • 离线对订单的状态进行分析。
  • 写入满足每秒10K的事件,读取满足每秒1K的事件,数据要求在5s内可用。


592fd58ff297e.png

按照这些要求,我们对Rowkey做出了下面的设计,都是很典型的scan场景。

订单状态表

Rowkey:reverse(order_id) + (MAX_LONG - TS) 
Columns:该订单各种状态

订单历史表

Rowkey:reverse(passenger_id | driver_id) + (MAX_LONG - TS) 
Columns:用户在时间范围内的订单及其他信息

场景二:司机乘客轨迹

这也是一份滴滴用户关系密切的数据,线上用户、滴滴的各个业务线和分析人员都会使用。举几个使用场景上的例子:用户查看历史订单时,地图上显示所经过的路线;发生司乘纠纷,客服调用订单轨迹复现场景;地图部门用户分析道路拥堵情况。

592fd62ec3aa9.png

用户们提出的需求:
 
  • 满足App用户或者后端分析人员的实时或准实时轨迹坐标查询;
  • 满足离线大规模的轨迹分析;
  • 满足给出一个指定的地理范围,取出范围内所有用户的轨迹或范围内出现过的用户。

 其中,关于第三个需求,地理位置查询,我们知道MongoDB对于这种地理索引有源生的支持,但是在滴滴这种量级的情况下可能会发生存储瓶颈,HBase存储和扩展性上没有压力但是没有内置类似MongoDB地理位置索引的功能,没有就需要我们自己实现。通过调研,了解到关于地理索引有一套比较通用的GeohHash算法 。

GeoHash是将二维的经纬度转换成字符串,每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,比如说我在悠唐酒店,我的一个朋友在旁边的悠唐购物广场,我们的经纬度点会得到相同的GeoHash串。这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存。

592fd6715b13f.png

但是我们要查询的范围和GeohHash块可能不会完全重合。以圆形为例,查询时会出现如图4所示的一半在GeoHash块内,一半在外面的情况(如A、B、C、D、E、F、G等点)。这种情况就需要对GeoHash块内每个真实的GPS点进行第二次的过滤,通过原始的GPS点和圆心之间的距离,过滤掉不符合查询条件的数据。

592fd6ba65517.png

最后依据这个原理,把GeoHash和其他一些需要被索引的维度拼装成Rowkey,真实的GPS点为Value,在这个基础上封装成客户端,并且在客户端内部对查询逻辑和查询策略做出速度上的大幅优化,这样就把HBase变成了一个MongoDB一样支持地理位置索引的数据库。如果查询范围非常大(比如进行省级别的分析),还额外提供了MR的获取数据的入口。

两种查询场景的Rowkey设计如下:
 
  • 单个用户按订单或时间段查询: reverse(user_id) + (Integer.MAX_LONG-TS/1000)
  • 给定范围内的轨迹查询:reverse(geohash) + ts/1000 + user_id

 场景三:ETA

ETA是指每次选好起始和目的地后,提示出的预估时间和价格。提示的预估到达时间和价格,最初版本是离线方式运行,后来改版通过HBase实现实时效果,把HBase当成一个KeyValue缓存,带来了减少训练时间、可多城市并行、减少人工干预的好处。 
整个ETA的过程如下:
 
  • 模型训练通过Spark Job,每30分钟对各个城市训练一次;
  • 模型训练第一阶段,在5分钟内,按照设定条件从HBase读取所有城市数据;
  • 模型训练第二阶段在25分钟内完成ETA的计算;
  • HBase中的数据每隔一段时间会持久化至HDFS中,供新模型测试和新的特征提取。

 Rowkey:salting+cited+type0+type1+type2+TS 
Column:order, feature

592fd708c4246.png

场景四:监控工具DCM

用于监控Hadoop集群的资源使用(Namenode,Yarn container使用等),关系数据库在时间维度过程以后会产生各种性能问题,同时我们又希望可以通过SQL做一些分析查询,所以使用Phoenix,使用采集程序定时录入数据,生产成报表,存入HBase,可以在秒级别返回查询结果,最后在前端做展示。

592fd7375c265.png

图7、图8、图9是几张监控工具的用户UI,数字相关的部分做了模糊处理。

592fd7610405d.png


592fd77d8ebbe.png


592fd7997641f.png

滴滴在HBase对多租户的管理
我们认为单集群多租户是最高效和节省精力的方案,但是由于HBase对多租户基本没有管理,使用上会遇到很多问题:在用户方面比如对资源使用情况不做分析、存储总量发生变化后不做调整和通知、项目上线下线没有计划、想要最多的资源和权限等;我们平台管理者也会遇到比如线上沟通难以理解用户的业务、对每个接入HBase的项目状态不清楚、不能判断出用户的需求是否合理、多租户在集群上发生资源竞争、问题定位和排查时间长等。

针对这些问题,我们开发了DHS系统(Didi HBase Service)进行项目管理,并且在HBase上通过Namespace、RS Group等技术来分割用户的资源、数据和权限。通过计算开销并计费的方法来管控资源分配。

592fd7f8d4da8.png

DHS主要有下面几个模块和功能:
 
  • 项目生命周期管理:包括立项、资源预估和申请、项目需求调整、需求讨论;
  • 用户管理:权限管理,项目审批;
  • 集群资源管理;
  • 表级别的使用情况监控:主要是读写监控、memstore、blockcache、locality。

 当用户有使用HBase存储的需求,我们会让用户在DHS上注册项目。介绍业务的场景和产品相关的细节,以及是否有高SLA要求。

之后是新建表以及对表性能需求预估,我们要求用户对自己要使用的资源有一个准确的预估。如果用户难以估计,我们会以线上或者线下讨论的方式与用户讨论帮助确定这些信息。 
然后会生成项目概览页面,方便管理员和用户进行项目进展的跟踪。

HBase自带的jxm信息会汇总到Region和RegionServer级别的数据,管理员会经常用到,但是用户却很少关注这个级别。根据这种情况我们开发了HBase表级别的监控,并且会有权限控制,让业务RD只能看到和自己相关的表,清楚自己项目表的吞吐及存储占用情况。

通过DHS让用户明确自己使用资源情况的基础之上,我们使用了RS Group技术,把一个集群分成多个逻辑子集群,可以让用户选择独占或者共享资源。共享和独占各有自己的优缺点,如表1。

592fd853ebaac.png

根据以上的情况,我们在资源分配上会根据业务的特性来选择不同方案:
 
  • 对于访问延迟要求低、访问量小、可用性要求低、备份或者测试阶段的数据:使用共享资源池;
  • 对于延迟敏感、吞吐要求高、高峰时段访问量大、可用性要求高、在线业务:让其独占一定机器数量构成的RegionServer Group资源,并且按用户预估的资源量,额外给出20%~30%的余量。

 最后我们会根据用户对资源的使用,定期计算开销并向用户发出账单。

RS Group
RegionServer Group,实现细节可以参照HBase HBASE-6721这个Patch。滴滴在这个基础上作了一些分配策略上的优化,以便适合滴滴业务场景的修改。RS Group简单概括是指通过分配一批指定的RegionServer列表,成为一个RS Group,每个Group可以按需挂载不同的表,并且当Group内的表发生异常后,Region不会迁移到其他的Group。这样,每个Group就相当于一个逻辑上的子集群,通过这种方式达到资源隔离的效果,降低管理成本,不必为每个高SLA的业务线单独搭集群。

592fd8a900ab5.png

总结
在滴滴推广和实践HBase的工作中,我们认为至关重要的两点是帮助用户做出良好的表结构设计和资源的控制。有了这两个前提之后,后续出现问题的概率会大大降低。良好的表结构设计需要用户对HBase的实现有一个清晰的认识,大多数业务用户把更多精力放在了业务逻辑上,对架构实现知之甚少,这就需要平台管理者去不断帮助和引导,有了好的开端和成功案例后,通过这些用户再去向其他的业务方推广。资源隔离控制则帮助我们有效减少集群的数量,降低运维成本,让平台管理者从多集群无止尽的管理工作中解放出来,将更多精力投入到组件社区跟进和平台管理系统的研发工作中,使业务和平台都进入一个良性循环,提升用户的使用体验,更好地支持公司业务的发展。
  收起阅读 »

OpenTSDB 入门教程

架构简介

这里我们简单看一下它的架构,如下图所示:

7bzsydidhh.jpeg

其最主要的部件就是TSD了,这是接收数据并存储到HBase处理的核心所在。而带有C(collector)标志的Server,则是数据采集源,将数据发给 TSD服务。

安装 OpenTSDB

为了安装 OpenTSDB ,都需要以下条件和软件:
  • Linux操作系统
  • JRE 1.6 or later
  • HBase 0.92 or later
  • 安装GnuPlot


如果你还想使用自带的界面,则需要安装GnuPlot 4.2及以后版本,以及gd和gd-devel等。这里我们选择了GnuPlot 5.0.1的版本。

根据情况执行(没有就装),安装所需软件
$ sudo yum install -y gd gd-devel libpng libpng-devel
之后安装GnuPlot:
$ tar zxvf gnuplot-5.0.1.tar.gz
$ cd gnuplot-5.0.1
$ ./configure
$ make
$ sudo make install
安装HBase

首先,确保设置了JAVA_HOME:
$ echo $JAVA_HOME
/usr
这个不多说了,非常简单,只需要按照 https://hbase.apache.org/book.html#quickstart 这里所说,下载、解压、修改配置文件、启动即可。

这时候,再设置HBASE_HOME:
$ echo $HBASE_HOME
/opt/hbase-1.0.1.1
之后便可启动hbase:
$ /opt/hbase-1.0.1.1/bin/start-hbase.sh
starting master, logging to /opt/hbase-1.0.1.1/logs/hbase-vagrant-master-localhost.localdomain.out
安装 OpenTSDB

这个也很简单,如果build失败,那肯定是缺少Make或者Autotools等东西,用包管理器安装即可。
$ git clone git://github.com/OpenTSDB/opentsdb.git
$ cd opentsdb
$ ./build.sh
创建表OpenTSDB所需要的表结构:
$ env COMPRESSION=NONE ./src/create_table.sh
2016-01-08 06:17:58,045 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
HBase Shell; enter ‘help‘ for list of supported commands.
Type “exit” to leave the HBase Shell
Version 1.0.1.1, re1dbf4df30d214fca14908df71d038081577ea46, Sun May 17 12:34:26 PDT 2015


create ‘tsdb-uid’,
{NAME => ‘id’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’},
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 1.3180 seconds


Hbase::Table – tsdb-uid


create ‘tsdb’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2400 seconds


Hbase::Table – tsdb


create ‘tsdb-tree’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2160 seconds


Hbase::Table – tsdb-tree


create ‘tsdb-meta’,
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.4480 seconds


Hbase::Table – tsdb-meta
在habse shell里,可以看到表已经创建成功。
> list
TABLE
tsdb
tsdb-meta
tsdb-tree
tsdb-uid
4 row(s) in 0.0160 seconds
表创建之后,即可启动tsd服务,只需要运行如下命令:
$ build/tsdb tsd
如果看到输出:
2016-01-09 05:51:10,875 INFO [main] TSDMain: Ready to serve on /0.0.0.0:4242  
即可认为启动成功。

保存数据到OpenTSDB

在安装并启动所有服务之后,我们就来尝试发送1条数据吧。

最简单的保存数据方式就是使用telnet。
$ telnet localhost 4242
put sys.cpu.user 1436333416 23 host=web01 user=10001
这时,从 OpenTSDB 自带界面都可以看到这些数据。 由于sys.cpu.sys的数据只有一条,所以 OpenTSDB 只能看到一个点。

下图为 OpenTSDB 自带的查询界面,访问http://localhost:4242即可。

bvqbhf0v5g.jpeg

OpenTSDB中的数据存储结构

我们来看看 OpenTSDB 的重要概念uid,先从HBase中存储的数据开始吧,我们来看一下它都有哪些表,以及这些表都是干什么的。

tsdb:存储数据点
hbase(main):003:0> scan 'tsdb'  
ROW COLUMN+CELL
\x00\x00\x01U\x9C\xAEP\x00\x column=t:q\x80, timestamp=1436350142588, value=\x17
00\x01\x00\x00\x01\x00\x00\x
02\x00\x00\x02
1 row(s) in 0.2800 seconds
可以看出,该表只有一条数据,我们先不管rowid,只来看看列,只有一列,值为0x17,即十进制23,即该metric的值。

左面的row key则是 OpenTSDB 的特点之一,其规则为:
metric + timestamp + tagk1 + tagv1… + tagkN + tagvN  
以上属性值均为对应名称的uid。

我们上面添加的metric为:
sys.cpu.user 1436333416 23 host=web01 user=10001  
一共涉及到5个uid,即名为sys.cpu.user的metric,以及host和user两个tagk及其值web01和10001。

上面数据的row key为:
\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02
具体这个row key是怎么算出来的,我们来看看tsdb-uid表。

tsdb-uid:存储name和uid的映射关系

下面tsdb-uid表的数据,各行之间人为加了空行,为方便显示。
kxb9h2rpw2.jpeg

tsdb-uid用来保存名字和UID(metric,tagk,tagv)之间互相映射的关系,都是成组出现的,即给定一个name和uid,会保存(name,uid)和(uid,name)两条记录。

我们一共看到了8行数据。

前面我们在tsdb表中已经看到,metric数据的row key为\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02,我们将其分解下,用+号连起来(从name到uid的映射为最后5行):
\x00\x00\x01 + U + \x9C\xAE + P + \x00\x00\x01 + \x00\x00\x01 + \x00\x00\x02  + \x00\x00\x02
sys.cpu.user 1436333416 host = web01 user = 10001
可以看出,这和我们前面说到的row key的构成方式是吻合的。

需要着重说明的是时间戳的存储方式

虽然我们指定的时间是以秒为单位的,但是,row key中用到的却是以一小时为单位的,即:1436333416 – 1436333416 % 3600 = 1436331600。

1436331600转换为16进制,即0x55 0x9c 0xae 0x50,而0x55即大写字母U,0x50为大写字母P,这就是4个字节的时间戳存储方式。相信下面这张图能帮助各位更好理解这个意思,即一小时只有一个row key,每秒钟的数据都会存为一列,大大提高查询的速度。
8an6jpc5it.jpeg

反过来,从uid到name也一样,比如找uid为\x00\x00\x02的tagk,我们从上面结果可以看到,该row key(\x00\x00\x02)有4列,而column=name:tagk的value就是user,非常简单直观。

重要:我们看到,上面的metric也好,tagk或者tagv也好,uid只有3个字节,这是 OpenTSDB 的默认配置,三个字节,应该能表示1600多万的不同数据,这对metric名或者tagk来说足够长了,对tagv来说就不一定了,比如tagv是ip地址的话,或者电话号码,那么这个字段就不够长了,这时可以通过修改源代码来重新编译 OpenTSDB 就可以了,同时要注意的是,重编以后,老数据就不能直接使用了,需要导出后重新导入。

tsdb-meta:元数据表

我们再看下第三个表tsdb-meta,这是用来存储时间序列索引和元数据的表。这也是一个可选特性,默认是不开启的,可以通过配置文件来启用该特性,这里不做特殊介绍了。

rqjvnt039d.jpeg

tsdb-tree:树形表

第4个表是tsdb-tree,用来以树状层次关系来表示metric的结构,只有在配置文件开启该特性后,才会使用此表,这里我们不介绍了,可以自己尝试。

通过HTTP接口保存数据

保存数据除了我们前面用到的telnet方式,也可以选择HTTP API或者批量导入工具“` import( http://opentsdb.net/docs/build ... .html

假设我们有如下数据,保存为文件mysql.json:
[ { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716527, "value": 1234, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716529, "value": 2345, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716627, "value": 3456, "tags": { "host": "web02", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716727, "value": 6789, "tags": { "host": "web01", "dc": "tianjin" } } ]
之后执行如下命令:
$ curl -X POST -H “Content-Type: application/json” http://localhost:4242/api/put -d @mysql.json

即可将数据保存到 OpenTSDB 了。

查询数据

看完了如何保存数据,我们再来看看如何查询数据。

查询数据可以使用query接口,它既可以使用get的query string方式,也可以使用post方式以JSON格式指定查询条件,这里我们以后者为例,对刚才保存的数据进行说明。

首先,保存如下内容为search.json:
{ "start": 1435716527, "queries": [ { "metric": "mysql.innodb.rowlocktime", "aggregator": "avg", "tags": { "host": "*", "dc": "beijing" } } ] }
执行如下命令进行查询:
$ curl -s -X POST -H "Content-Type: application/json" http://localhost:4242/api/query -d @search.json | jq . [ { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web01", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716527": 1234, "1435716529": 2345 } }, { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web02", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716627": 3456 } } ] 

 
可以看出,我们保存了dc=tianjin的数据,但是并没有在此查询中返回,这是因为,我们指定了dc=beijing这一条件。

值得注意的是,tags参数在新版本2.2中,将不被推荐,取而代之的是filters参数。

总结

可以看出来, OpenTSDB 还是非常容易上手的,尤其是单机版,安装也很简单。有HBase作为后盾,查询起来也非常快,很多大公司,类似雅虎等,也都在用此软件。

但是,大规模用起来,多个TDB以及多存储节点等,应该都需要专业、细心的运维工作了。
继续阅读 »
架构简介

这里我们简单看一下它的架构,如下图所示:

7bzsydidhh.jpeg

其最主要的部件就是TSD了,这是接收数据并存储到HBase处理的核心所在。而带有C(collector)标志的Server,则是数据采集源,将数据发给 TSD服务。

安装 OpenTSDB

为了安装 OpenTSDB ,都需要以下条件和软件:
  • Linux操作系统
  • JRE 1.6 or later
  • HBase 0.92 or later
  • 安装GnuPlot


如果你还想使用自带的界面,则需要安装GnuPlot 4.2及以后版本,以及gd和gd-devel等。这里我们选择了GnuPlot 5.0.1的版本。

根据情况执行(没有就装),安装所需软件
$ sudo yum install -y gd gd-devel libpng libpng-devel
之后安装GnuPlot:
$ tar zxvf gnuplot-5.0.1.tar.gz
$ cd gnuplot-5.0.1
$ ./configure
$ make
$ sudo make install
安装HBase

首先,确保设置了JAVA_HOME:
$ echo $JAVA_HOME
/usr
这个不多说了,非常简单,只需要按照 https://hbase.apache.org/book.html#quickstart 这里所说,下载、解压、修改配置文件、启动即可。

这时候,再设置HBASE_HOME:
$ echo $HBASE_HOME
/opt/hbase-1.0.1.1
之后便可启动hbase:
$ /opt/hbase-1.0.1.1/bin/start-hbase.sh
starting master, logging to /opt/hbase-1.0.1.1/logs/hbase-vagrant-master-localhost.localdomain.out
安装 OpenTSDB

这个也很简单,如果build失败,那肯定是缺少Make或者Autotools等东西,用包管理器安装即可。
$ git clone git://github.com/OpenTSDB/opentsdb.git
$ cd opentsdb
$ ./build.sh
创建表OpenTSDB所需要的表结构:
$ env COMPRESSION=NONE ./src/create_table.sh
2016-01-08 06:17:58,045 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
HBase Shell; enter ‘help‘ for list of supported commands.
Type “exit” to leave the HBase Shell
Version 1.0.1.1, re1dbf4df30d214fca14908df71d038081577ea46, Sun May 17 12:34:26 PDT 2015


create ‘tsdb-uid’,
{NAME => ‘id’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’},
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 1.3180 seconds


Hbase::Table – tsdb-uid


create ‘tsdb’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2400 seconds


Hbase::Table – tsdb


create ‘tsdb-tree’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2160 seconds


Hbase::Table – tsdb-tree


create ‘tsdb-meta’,
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.4480 seconds


Hbase::Table – tsdb-meta
在habse shell里,可以看到表已经创建成功。
> list
TABLE
tsdb
tsdb-meta
tsdb-tree
tsdb-uid
4 row(s) in 0.0160 seconds
表创建之后,即可启动tsd服务,只需要运行如下命令:
$ build/tsdb tsd
如果看到输出:
2016-01-09 05:51:10,875 INFO [main] TSDMain: Ready to serve on /0.0.0.0:4242  
即可认为启动成功。

保存数据到OpenTSDB

在安装并启动所有服务之后,我们就来尝试发送1条数据吧。

最简单的保存数据方式就是使用telnet。
$ telnet localhost 4242
put sys.cpu.user 1436333416 23 host=web01 user=10001
这时,从 OpenTSDB 自带界面都可以看到这些数据。 由于sys.cpu.sys的数据只有一条,所以 OpenTSDB 只能看到一个点。

下图为 OpenTSDB 自带的查询界面,访问http://localhost:4242即可。

bvqbhf0v5g.jpeg

OpenTSDB中的数据存储结构

我们来看看 OpenTSDB 的重要概念uid,先从HBase中存储的数据开始吧,我们来看一下它都有哪些表,以及这些表都是干什么的。

tsdb:存储数据点
hbase(main):003:0> scan 'tsdb'  
ROW COLUMN+CELL
\x00\x00\x01U\x9C\xAEP\x00\x column=t:q\x80, timestamp=1436350142588, value=\x17
00\x01\x00\x00\x01\x00\x00\x
02\x00\x00\x02
1 row(s) in 0.2800 seconds
可以看出,该表只有一条数据,我们先不管rowid,只来看看列,只有一列,值为0x17,即十进制23,即该metric的值。

左面的row key则是 OpenTSDB 的特点之一,其规则为:
metric + timestamp + tagk1 + tagv1… + tagkN + tagvN  
以上属性值均为对应名称的uid。

我们上面添加的metric为:
sys.cpu.user 1436333416 23 host=web01 user=10001  
一共涉及到5个uid,即名为sys.cpu.user的metric,以及host和user两个tagk及其值web01和10001。

上面数据的row key为:
\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02
具体这个row key是怎么算出来的,我们来看看tsdb-uid表。

tsdb-uid:存储name和uid的映射关系

下面tsdb-uid表的数据,各行之间人为加了空行,为方便显示。
kxb9h2rpw2.jpeg

tsdb-uid用来保存名字和UID(metric,tagk,tagv)之间互相映射的关系,都是成组出现的,即给定一个name和uid,会保存(name,uid)和(uid,name)两条记录。

我们一共看到了8行数据。

前面我们在tsdb表中已经看到,metric数据的row key为\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02,我们将其分解下,用+号连起来(从name到uid的映射为最后5行):
\x00\x00\x01 + U + \x9C\xAE + P + \x00\x00\x01 + \x00\x00\x01 + \x00\x00\x02  + \x00\x00\x02
sys.cpu.user 1436333416 host = web01 user = 10001
可以看出,这和我们前面说到的row key的构成方式是吻合的。

需要着重说明的是时间戳的存储方式

虽然我们指定的时间是以秒为单位的,但是,row key中用到的却是以一小时为单位的,即:1436333416 – 1436333416 % 3600 = 1436331600。

1436331600转换为16进制,即0x55 0x9c 0xae 0x50,而0x55即大写字母U,0x50为大写字母P,这就是4个字节的时间戳存储方式。相信下面这张图能帮助各位更好理解这个意思,即一小时只有一个row key,每秒钟的数据都会存为一列,大大提高查询的速度。
8an6jpc5it.jpeg

反过来,从uid到name也一样,比如找uid为\x00\x00\x02的tagk,我们从上面结果可以看到,该row key(\x00\x00\x02)有4列,而column=name:tagk的value就是user,非常简单直观。

重要:我们看到,上面的metric也好,tagk或者tagv也好,uid只有3个字节,这是 OpenTSDB 的默认配置,三个字节,应该能表示1600多万的不同数据,这对metric名或者tagk来说足够长了,对tagv来说就不一定了,比如tagv是ip地址的话,或者电话号码,那么这个字段就不够长了,这时可以通过修改源代码来重新编译 OpenTSDB 就可以了,同时要注意的是,重编以后,老数据就不能直接使用了,需要导出后重新导入。

tsdb-meta:元数据表

我们再看下第三个表tsdb-meta,这是用来存储时间序列索引和元数据的表。这也是一个可选特性,默认是不开启的,可以通过配置文件来启用该特性,这里不做特殊介绍了。

rqjvnt039d.jpeg

tsdb-tree:树形表

第4个表是tsdb-tree,用来以树状层次关系来表示metric的结构,只有在配置文件开启该特性后,才会使用此表,这里我们不介绍了,可以自己尝试。

通过HTTP接口保存数据

保存数据除了我们前面用到的telnet方式,也可以选择HTTP API或者批量导入工具“` import( http://opentsdb.net/docs/build ... .html

假设我们有如下数据,保存为文件mysql.json:
[ { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716527, "value": 1234, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716529, "value": 2345, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716627, "value": 3456, "tags": { "host": "web02", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716727, "value": 6789, "tags": { "host": "web01", "dc": "tianjin" } } ]
之后执行如下命令:
$ curl -X POST -H “Content-Type: application/json” http://localhost:4242/api/put -d @mysql.json

即可将数据保存到 OpenTSDB 了。

查询数据

看完了如何保存数据,我们再来看看如何查询数据。

查询数据可以使用query接口,它既可以使用get的query string方式,也可以使用post方式以JSON格式指定查询条件,这里我们以后者为例,对刚才保存的数据进行说明。

首先,保存如下内容为search.json:
{ "start": 1435716527, "queries": [ { "metric": "mysql.innodb.rowlocktime", "aggregator": "avg", "tags": { "host": "*", "dc": "beijing" } } ] }
执行如下命令进行查询:
$ curl -s -X POST -H "Content-Type: application/json" http://localhost:4242/api/query -d @search.json | jq . [ { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web01", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716527": 1234, "1435716529": 2345 } }, { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web02", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716627": 3456 } } ] 

 
可以看出,我们保存了dc=tianjin的数据,但是并没有在此查询中返回,这是因为,我们指定了dc=beijing这一条件。

值得注意的是,tags参数在新版本2.2中,将不被推荐,取而代之的是filters参数。

总结

可以看出来, OpenTSDB 还是非常容易上手的,尤其是单机版,安装也很简单。有HBase作为后盾,查询起来也非常快,很多大公司,类似雅虎等,也都在用此软件。

但是,大规模用起来,多个TDB以及多存储节点等,应该都需要专业、细心的运维工作了。 收起阅读 »

中国HBase技术社区微信公众号:
hbasegroup

欢迎加入HBase生态+Spark社区钉钉大群