
笔名:哥不是小萝莉 博客园地址:http://www.cnblogs.com/smartloli/
1.概述 HBase中表的基本单位是Region,日常在调用HBase API操作一个表时,交互的数据也会以Region的形式进行呈现。一个表可以有若干个Region,今天笔者就来和大家分享一下Region合并的一些问题和解决方法。 2.内容 在分析合并Region之前,我们先来了解一下Region的体系结构,如下图所示: 从图中可知,能够总结以下知识点: HRegion:一个Region可以包含多个Store; Store:每个Store包含一个Memstore和若干个StoreFile; StoreFile:表数据真实存储的地方,HFile是表数据在HDFS上的文件格式。 如果要查看HFile文件,HBase有提供命令,命令如下: hbase hfile -p -f /hbase/data/default/ip_login/d0d7d881bb802592c09d305e47ae70a5/_d/7ec738167e9f4d4386316e5e702c8d3d 执行输出结果,如下图所示: 2.1 为什么需要合并Region 那为什么需要合并Region呢?这个需要从Region的Split来说。当一个Region被不断的写数据,达到Region的Split的阀值时(由属性hbase.hregion.max.filesize来决定,默认是10GB),该Region就会被Split成2个新的Region。随着业务数据量的不断增加,Region不断的执行Split,那么Region的个数也会越来越多。 一个业务表的Region越多,在进行读写操作时,或是对该表执行Compaction操作时,此时集群的压力是很大的。这里笔者做过一个线上统计,在一个业务表的Region个数达到9000+时,每次对该表进行Compaction操作时,集群的负载便会加重。而间接的也会影响应用程序的读写,一个表的Region过大,势必整个集群的Region个数也会增加,负载均衡后,每个RegionServer承担的Region个数也会增加。 因此,这种情况是很有必要的进行Region合并的。比如,当前Region进行Split的阀值设置为30GB,那么我们可以对小于等于10GB的Region进行一次合并,减少每个业务表的Region,从而降低整个集群的Region,减缓每个RegionServer上的Region压力。 2.2 如何进行Region合并 那么我们如何进行Region合并呢?HBase有提供一个合并Region的命令,具体操作如下: # 合并相邻的两个Region hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME' # 强制合并两个Region hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true 但是,这种方式会有一个问题,就是只能一次合并2个Region,如果这里有几千个Region需要合并,这种方式是不可取的。 2.2.1 批量合并 这里有一种批量合并的方式,就是通过编写脚本(merge_small_regions.rb)来实现,实现代码如下: # Test Mode: # # hbase org.jruby.Main merge_empty_regions.rb namespace.tablename <skip_size> <batch_regions> <merge?> # # Non Test - ie actually do the merge: # # hbase org.jruby.Main merge_empty_regions.rb namespace.tablename <skip_size> <batch_regions> merge # # Note: Please replace namespace.tablename with your namespace and table, eg NS1.MyTable. This value is case sensitive. require 'digest' require 'java' java_import org.apache.hadoop.hbase.HBaseConfiguration java_import org.apache.hadoop.hbase.client.HBaseAdmin java_import org.apache.hadoop.hbase.TableName java_import org.apache.hadoop.hbase.HRegionInfo; java_import org.apache.hadoop.hbase.client.Connection java_import org.apache.hadoop.hbase.client.ConnectionFactory java_import org.apache.hadoop.hbase.client.Table java_import org.apache.hadoop.hbase.util.Bytes def list_bigger_regions(admin, table, low_size) cluster_status = admin.getClusterStatus() master = cluster_status.getMaster() biggers = [] cluster_status.getServers.each do |s| cluster_status.getLoad(s).getRegionsLoad.each do |r| # getRegionsLoad returns an array of arrays, where each array # is 2 elements # Filter out any regions that don't match the requested # tablename next unless r[1].get_name_as_string =~ /#{table}\,/ if r[1].getStorefileSizeMB() > low_size if r[1].get_name_as_string =~ /\.([^\.]+)\.$/ biggers.push $1 else raise "Failed to get the encoded name for #{r[1].get_name_as_string}" end end end end biggers end # Handle command line parameters table_name = ARGV[0] low_size = 1024 if ARGV[1].to_i >= low_size low_size=ARGV[1].to_i end limit_batch = 1000 if ARGV[2].to_i <= limit_batch limit_batch = ARGV[2].to_i end do_merge = false if ARGV[3] == 'merge' do_merge = true end config = HBaseConfiguration.create(); connection = ConnectionFactory.createConnection(config); admin = HBaseAdmin.new(connection); bigger_regions = list_bigger_regions(admin, table_name, low_size) regions = admin.getTableRegions(Bytes.toBytes(table_name)); puts "Total Table Regions: #{regions.length}" puts "Total bigger regions: #{bigger_regions.length}" filtered_regions = regions.reject do |r| bigger_regions.include?(r.get_encoded_name) end puts "Total regions to consider for Merge: #{filtered_regions.length}" filtered_regions_limit = filtered_regions if filtered_regions.length < 2 puts "There are not enough regions to merge" filtered_regions_limit = filtered_regions end if filtered_regions.length > limit_batch filtered_regions_limit = filtered_regions[0,limit_batch] puts "But we will merge : #{filtered_regions_limit.length} regions because limit in parameter!" end r1, r2 = nil filtered_regions_limit.each do |r| if r1.nil? r1 = r next end if r2.nil? r2 = r end # Skip any region that is a split region if r1.is_split() r1 = r2 r2 = nil puts "Skip #{r1.get_encoded_name} bcause it in spliting!" next end if r2.is_split() r2 = nil puts "Skip #{r2.get_encoded_name} bcause it in spliting!" next end if HRegionInfo.are_adjacent(r1, r2) # only merge regions that are adjacent puts "#{r1.get_encoded_name} is adjacent to #{r2.get_encoded_name}" if do_merge admin.mergeRegions(r1.getEncodedNameAsBytes, r2.getEncodedNameAsBytes, false) puts "Successfully Merged #{r1.get_encoded_name} with #{r2.get_encoded_name}" sleep 2 end r1, r2 = nil else puts "Regions are not adjacent, so drop the first one and with the #{r2.get_encoded_name} to iterate again" r1 = r2 r2 = nil end end admin.close 该脚本默认是合并1GB以内的Region,个数为1000个。如果我们要合并小于10GB,个数在4000以内,脚本(merging-region.sh)如下: #! /bin/bash num=$1 echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : RegionServer Start Merging..." if [ ! -n "$num" ]; then echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Default Merging 10 Times." num=10 elif [[ $num == *[!0-9]* ]]; then echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Input [$num] Times Must Be Number." exit 1 else echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : User-Defined Merging [$num] Times." fi for (( i=1; i<=$num; i++ )) do echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Merging [$i] Times,Total [$num] Times." hbase org.jruby.Main merge_small_regions.rb namespace.tablename 10240 4000 merge sleep 5 done 在merging-region.sh脚本中,做了参数控制,可以循环来执行批量合并脚本。可能在实际操作过程中,批量执行一次Region合并,合并后的结果Region还是有很多(可能此时又有新的Region生成),这是我们可以使用merging-region.sh这个脚本多次执行批量合并Region操作,具体操作命令如下: # 默认循环10次,例如本次循环执行5次 sh merging-region.sh 5 2.3 如果在合并Region的过程中出现永久RIT怎么办 在合并Region的过程中出现永久RIT怎么办?笔者在生产环境中就遇到过这种情况,在批量合并Region的过程中,出现了永久MERGING_NEW的情况,虽然这种情况不会影响现有集群的正常的服务能力,但是如果集群有某个节点发生重启,那么可能此时该RegionServer上的Region是没法均衡的。因为在RIT状态时,HBase是不会执行Region负载均衡的,即使手动执行balancer命令也是无效的。 如果不解决这种RIT情况,那么后续有HBase节点相继重启,这样会导致整个集群的Region验证不均衡,这是很致命的,对集群的性能将会影响很大。经过查询HBase JIRA单,发现这种MERGING_NEW永久RIT的情况是触发了HBASE-17682的BUG,需要打上该Patch来修复这个BUG,其实就是HBase源代码在判断业务逻辑时,没有对MERGING_NEW这种状态进行判断,直接进入到else流程中了。源代码如下: for (RegionState state : regionsInTransition.values()) { HRegionInfo hri = state.getRegion(); if (assignedRegions.contains(hri)) { // Region is open on this region server, but in transition. // This region must be moving away from this server, or splitting/merging. // SSH will handle it, either skip assigning, or re-assign. LOG.info("Transitioning " + state + " will be handled by ServerCrashProcedure for " + sn); } else if (sn.equals(state.getServerName())) { // Region is in transition on this region server, and this // region is not open on this server. So the region must be // moving to this server from another one (i.e. opening or // pending open on this server, was open on another one. // Offline state is also kind of pending open if the region is in // transition. The region could be in failed_close state too if we have // tried several times to open it while this region server is not reachable) if (state.isPendingOpenOrOpening() || state.isFailedClose() || state.isOffline()) { LOG.info("Found region in " + state + " to be reassigned by ServerCrashProcedure for " + sn); rits.add(hri); } else if(state.isSplittingNew()) { regionsToCleanIfNoMetaEntry.add(state.getRegion()); } else { LOG.warn("THIS SHOULD NOT HAPPEN: unexpected " + state); } } } 修复之后的代码如下: for (RegionState state : regionsInTransition.values()) { HRegionInfo hri = state.getRegion(); if (assignedRegions.contains(hri)) { // Region is open on this region server, but in transition. // This region must be moving away from this server, or splitting/merging. // SSH will handle it, either skip assigning, or re-assign. LOG.info("Transitioning " + state + " will be handled by ServerCrashProcedure for " + sn); } else if (sn.equals(state.getServerName())) { // Region is in transition on this region server, and this // region is not open on this server. So the region must be // moving to this server from another one (i.e. opening or // pending open on this server, was open on another one. // Offline state is also kind of pending open if the region is in // transition. The region could be in failed_close state too if we have // tried several times to open it while this region server is not reachable) if (state.isPendingOpenOrOpening() || state.isFailedClose() || state.isOffline()) { LOG.info("Found region in " + state + " to be reassigned by ServerCrashProcedure for " + sn); rits.add(hri); } else if(state.isSplittingNew()) { regionsToCleanIfNoMetaEntry.add(state.getRegion()); } else if (isOneOfStates(state, State.SPLITTING_NEW, State.MERGING_NEW)) { regionsToCleanIfNoMetaEntry.add(state.getRegion()); }else { LOG.warn("THIS SHOULD NOT HAPPEN: unexpected " + state); } } } 但是,这里有一个问题,目前该JIRA单只是说了需要去修复BUG,打Patch。但是,实际生产情况下,面对这种RIT情况,是不可能长时间停止集群,影响应用程序读写的。那么,有没有临时的解决办法,先临时解决当前的MERGING_NEW这种永久RIT,之后在进行HBase版本升级操作。 办法是有的,在分析了MERGE合并的流程之后,发现HBase在执行Region合并时,会先生成一个初始状态的MERGING_NEW。整个Region合并流程如下: 从流程图中可以看到,MERGING_NEW是一个初始化状态,在Master的内存中,而处于Backup状态的Master内存中是没有这个新Region的MERGING_NEW状态的,那么可以通过对HBase的Master进行一个主备切换,来临时消除这个永久RIT状态。而HBase是一个高可用的集群,进行主备切换时对用户应用来说是无感操作。因此,面对MERGING_NEW状态的永久RIT可以使用对HBase进行主备切换的方式来做一个临时处理方案。之后,我们在对HBase进行修复BUG,打Patch进行版本升级。 3.总结 HBase的RIT问题,是一个比较常见的问题,在遇到这种问题时,可以先冷静的分析原因,例如查看Master的日志、仔细阅读HBase Web页面RIT异常的描述、使用hbck命令查看Region、使用fsck查看HDFS的block等。分析出具体的原因后,我们在对症下药,做到大胆猜想,小心求证。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在进行数据传输中,批量加载数据到HBase集群有多种方式,比如通过HBase API进行批量写入数据、使用Sqoop工具批量导数到HBase集群、使用MapReduce批量导入等。这些方式,在导入数据的过程中,如果数据量过大,可能耗时会比较严重或者占用HBase集群资源较多(如磁盘IO、HBase Handler数等)。今天这篇博客笔者将为大家分享使用HBase BulkLoad的方式来进行海量数据批量写入到HBase集群。 2.内容 在使用BulkLoad之前,我们先来了解一下HBase的存储机制。HBase存储数据其底层使用的是HDFS来作为存储介质,HBase的每一张表对应的HDFS目录上的一个文件夹,文件夹名以HBase表进行命名(如果没有使用命名空间,则默认在default目录下),在表文件夹下存放在若干个Region命名的文件夹,Region文件夹中的每个列簇也是用文件夹进行存储的,每个列簇中存储就是实际的数据,以HFile的形式存在。路径格式如下: /hbase/data/default/<tbl_name>/<region_id>/<cf>/<hfile_id> 2.1 实现原理 按照HBase存储数据按照HFile格式存储在HDFS的原理,使用MapReduce直接生成HFile格式的数据文件,然后在通过RegionServer将HFile数据文件移动到相应的Region上去。流程如下图所示: 2.2. 生成HFile文件 HFile文件的生成,可以使用MapReduce来进行实现,将数据源准备好,上传到HDFS进行存储,然后在程序中读取HDFS上的数据源,进行自定义封装,组装RowKey,然后将封装后的数据在回写到HDFS上,以HFile的形式存储到HDFS指定的目录中。实现代码如下: /** * Read DataSource from hdfs & Gemerator hfile. * * @author smartloli. * * Created by Aug 19, 2018 */ public class GemeratorHFile2 { static class HFileImportMapper2 extends Mapper<LongWritable, Text, ImmutableBytesWritable, KeyValue> { protected final String CF_KQ = "cf"; @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); System.out.println("line : " + line); String[] datas = line.split(" "); String row = new Date().getTime() + "_" + datas[1]; ImmutableBytesWritable rowkey = new ImmutableBytesWritable(Bytes.toBytes(row)); KeyValue kv = new KeyValue(Bytes.toBytes(row), this.CF_KQ.getBytes(), datas[1].getBytes(), datas[2].getBytes()); context.write(rowkey, kv); } } public static void main(String[] args) { if (args.length != 1) { System.out.println("<Usage>Please input hbase-site.xml path.</Usage>"); return; } Configuration conf = new Configuration(); conf.addResource(new Path(args[0])); conf.set("hbase.fs.tmp.dir", "partitions_" + UUID.randomUUID()); String tableName = "person"; String input = "hdfs://nna:9000/tmp/person.txt"; String output = "hdfs://nna:9000/tmp/pres"; System.out.println("table : " + tableName); HTable table; try { try { FileSystem fs = FileSystem.get(URI.create(output), conf); fs.delete(new Path(output), true); fs.close(); } catch (IOException e1) { e1.printStackTrace(); } Connection conn = ConnectionFactory.createConnection(conf); table = (HTable) conn.getTable(TableName.valueOf(tableName)); Job job = Job.getInstance(conf); job.setJobName("Generate HFile"); job.setJarByClass(GemeratorHFile2.class); job.setInputFormatClass(TextInputFormat.class); job.setMapperClass(HFileImportMapper2.class); FileInputFormat.setInputPaths(job, input); FileOutputFormat.setOutputPath(job, new Path(output)); HFileOutputFormat2.configureIncrementalLoad(job, table); try { job.waitForCompletion(true); } catch (InterruptedException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } } 在HDFS目录/tmp/person.txt中,准备数据源如下: 1 smartloli 100 2 smartloli2 101 3 smartloli3 102 然后,将上述代码编译打包成jar,上传到Hadoop集群进行执行,执行命令如下: hadoop jar GemeratorHFile2.jar /data/soft/new/apps/hbaseapp/hbase-site.xml 如果在执行命令的过程中,出现找不到类的异常信息,可能是本地没有加载HBase依赖JAR包,在当前用户中配置如下环境变量信息: export HADOOP_CLASSPATH=$HBASE_HOME/lib/*:classpath 然后,执行source命令使配置的内容立即生生效。 2.3. 执行预览 在成功提交任务后,Linux控制台会打印执行任务进度,也可以到YARN的资源监控界面查看执行进度,结果如下所示: 等待任务的执行,执行完成后,在对应HDFS路径上会生成相应的HFile数据文件,如下图所示: 2.4 使用BulkLoad导入到HBase 然后,在使用BulkLoad的方式将生成的HFile文件导入到HBase集群中,这里有2种方式。一种是写代码实现导入,另一种是使用HBase命令进行导入。 2.4.1 代码实现导入 通过LoadIncrementalHFiles类来实现导入,具体代码如下: /** * Use BulkLoad inport hfile from hdfs to hbase. * * @author smartloli. * * Created by Aug 19, 2018 */ public class BulkLoad2HBase { public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("<Usage>Please input hbase-site.xml path.</Usage>"); return; } String output = "hdfs://cluster1/tmp/pres"; Configuration conf = new Configuration(); conf.addResource(new Path(args[0])); HTable table = new HTable(conf, "person"); LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf); loader.doBulkLoad(new Path(output), table); } } 执行上述代码,运行结果如下: 2.4.2 使用HBase命令进行导入 先将生成好的HFile文件迁移到目标集群(即HBase集群所在的HDFS上),然后在使用HBase命令进行导入,执行命令如下: # 先使用distcp迁移hfile hadoop distcp -Dmapreduce.job.queuename=queue_1024_01 -update -skipcrccheck -m 10 /tmp/pres hdfs://nns:9000/tmp/pres # 使用bulkload方式导入数据 hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /tmp/pres person 最后,我们可以到指定的RegionServer节点上查看导入的日志信息,如下所示为导入成功的日志信息: 2018-08-19 16:30:34,969 INFO [B.defaultRpcServer.handler=7,queue=1,port=16020] regionserver.HStore: Successfully loaded store file hdfs://cluster1/tmp/pres/cf/7b455535f660444695589edf509935e9 into store cf (new location: hdfs://cluster1/hbase/data/default/person/2d7483d4abd6d20acdf16533a3fdf18f/cf/d72c8846327d42e2a00780ac2facf95b_SeqId_4_) 2.5 验证 使用BulkLoad方式导入数据后,可以进入到HBase集群,使用HBase Shell来查看数据是否导入成功,预览结果如下: 3.总结 本篇博客为了演示实战效果,将生成HFile文件和使用BulkLoad方式导入HFile到HBase集群的步骤进行了分解,实际情况中,可以将这两个步骤合并为一个,实现自动化生成与HFile自动导入。如果在执行的过程中出现RpcRetryingCaller的异常,可以到对应RegionServer节点查看日志信息,这里面记录了出现这种异常的详细原因。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在《HBase查询优化》一文中,介绍了基于HBase层面的读取优化。由于HBase的实际数据是以HFile的形式,存储在HDFS上。那么,HDFS层面也有它自己的优化点,即:Short-Circuit Local Reads。本篇博客笔者将从HDFS层面来进行优化,从而间接的提升HBase的查询性能。 2.内容 Hadoop系统在设计之初,遵循一个原则,那就是移动计算的代价比移动数据要小。故Hadoop在做计算的时候,通常是在本地节点上的数据中进行计算。即计算和数据本地化。流程如下图所示: 在最开始的时候,短回路本地化读取和跨节点的读取的处理方式是一样的,流程都是先从DataNode读取数据,然后通过RPC服务把数据传输给DFSClient,这样处理虽然流程比较简单,但是读取性能会受到影响,因为跨节点读取数据,需要经过网络将一个DataNode的数据传输到另外一个DataNode节点(一般来说,HDFS有3个副本,所以,本地取不到数据,会到其他DataNode节点去取数据)。 2.1 方案一:客户端直接读取DataNode文件 短回路本地化读取的核心思想是,由于客户端和数据在同一个节点上,所以DataNode不需要在数据路径中。相反,客户端本身可以简单地读取来自本地磁盘的数据。这种性能优化集成在CDH的Hadoop相关项目中,实现如下图所示: 这种短回路本地化读取的思路虽然很好,但是配置问题比较麻烦。系统管理员必须更改DataNode数据目录的权限,以便客户端有权限能够打开相关文件。这样就不得不专门为那些能够使用短回路本地化读取的用户提供白名单,不允许其他用户使用。通常,这些用也必须被放置在一个特殊的UNIX组中。 另外,这种本地化短回路读取的思路还存在另外一个安全问题,客户端在读取DataNode数据目录时打开了一些权限,这样意味着,拥有这个目录的权限,那么其目录下的子目录中的数据也可以被访问,比如HBase用户。由于存在这种安全风险,所以这个实现思路已经不建议使用了。 2.2 方案二:短回路本地化安全读取 为了解决上述问题,在实际读取中需要非常小心的选择文件。在UNIX中有这样一种机制,叫做“文件描述符传递”。使用这种机制来实现安全的短回路本地读取,而不是通过目录名称的客户端,DataNode打开Block文件和元数据文件,将它们直接给客户端。因为文件描述符是只读的,用户不能修改文件。由于它没有进入Block目录本身,它无法读取任何不应该访问的目录。 举个例子: 现有两个用户hbase1和hbase2,hbase1拥有访问HDFS目录上/appdata/hbase1文件的权限,而hbase2用户没有改权限,但是hbase2用户又需要访问这个文件,那么可以借助这种“文件描述符传递”的机制,可以让hbase1用户打开文件得到一个文件描述符,然后把文件描述符传递给hbase2用户,那么hbase2用户就可以读取文件里面的内容了,即使hbase2用户没有权限。这种关系映射到HDFS中,可以把DataNode看作hbase1用户,客户端DFSClient看作hbase2用户,需要读取的文件就是DataNode目录中的/appdata/hbase1文件。实现如下图所示: 2.3 缓存文件描述 HDFS客户端可能会有经常读取相同Block文件的场景,为了提升这种读取性能,旧的短回路本地读取实现具有Block路径的高速缓存。该缓存允许客户端重新打开其最近已读取的Block文件,而不需要再去访问DataNode路径读取。 新的短回路本地读取实现不是一个路径缓存,而是一个名为FileInputStreamCache的文件描述符缓存。这样比路径缓存要更好一些,因为它不需要客户端重新打开文件来重新读取Block,这种读取方式比就的短回路本地读取方式在读性能上有更好的表现。 缓存的大小可以通过dfs.client.read.shortcircuit.streams.cache.size属性来进行调整,默认是256,缓存超时可以通过dfs.client.read.shortcircuit.streams.cache.expiry.ms属性来进行控制,默认是300000,也可以将其设置为0来将其进行关闭,这两个属性均在hdfs-site.xml文件中可以配置。 2.4 如何配置 为了配置短回路本地化读取,需要启用libhadoop.so,一般来说所使用Hadoop通常都是包含这些包的,可以通过以下命令来检测是否有安装: $ hadoop checknative -a Native library checking: hadoop: true /home/ozawa/hadoop/lib/native/libhadoop.so.1.0.0 zlib: true /lib/x86_64-linux-gnu/libz.so.1 snappy: true /usr/lib/libsnappy.so.1 lz4: true revision:99 bzip2: false 短回路本地化读取利用UNIX的域套接字(UNIX domain socket),它在文件系统中有一个特定的路径,允许客户端和DataNode进行通信。在使用的时候需要设置这个路径到Socket中,同时DataNode需要能够创建这个路径。另外,这个路径应该不可能被除了hdfs用户或root用户之外的任何用户创建。因此,在实际创建时,通常会使用/var/run或者/var/lib路径。 短回路本地化读取在DataNode和客户端都需要配置,配置如下: <configuration> <property> <name>dfs.client.read.shortcircuit</name> <value>true</value> </property> <property> <name>dfs.domain.socket.path</name> <value>/var/lib/hadoop-hdfs/dn_socket</value> </property> </configuration> 其中,配置dfs.client.read.shortcircuit属性是打开这个功能的开关,dfs.domain.socket.path属性是DataNode和客户端之间进行通信的Socket路径地址,核心指标配置参数如下: 属性 描述 dfs.client.read.shortcircuit 打开短回路本地化读取,默认false dfs.client.read.shortcircuit.skip.checksum 如果配置这个参数,短回路本地化读取将会跳过checksum,默认false dfs.client.read.shortcircuit.streams.cache.size 客户端维护一个最近打开文件的描述符缓存,默认256 dfs.domain.socket.path DataNode和客户端DFSClient通信的Socket地址 dfs.client.read.shortcircuit.streams.cache.expiry.ms 设置超时时间,用来设置文件描述符可以被放进FileInputStreamCache的最小时间 dfs.client.domain.socket.data.traffic 通过UNIX域套接字传输正常的数据流量,默认false 3.总结 短回路本地化读取能够从HDFS层面来提升读取性能,如果HBase场景中,有涉及到读多写少的场景,在除了从HBase服务端和客户端层面优化外,还可以尝试从HDFS层面来进行优化。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 HBase是一个实时的非关系型数据库,用来存储海量数据。但是,在实际使用场景中,在使用HBase API查询HBase中的数据时,有时会发现数据查询会很慢。本篇博客将从客户端优化和服务端优化两个方面来介绍,如何提高查询HBase的效率。 2.内容 这里,我们先给大家介绍如何从客户端优化查询速度。 2.1 客户端优化 客户端查询HBase,均通过HBase API的来获取数据,如果在实现代码逻辑时使用API不当,也会造成读取耗时严重的情况。 2.1.1 Scan优化 在使用HBase的Scan接口时,一次Scan会返回大量数据。客户端向HBase发送一次Scan请求,实际上并不会将所有数据加载到本地,而是通过多次RPC请求进行加载。这样设计的好处在于避免大量数据请求会导致网络带宽负载过高影响其他业务使用HBase,另外从客户端的角度来说可以避免数据量太大,从而本地机器发送OOM(内存溢出)。 默认情况下,HBase每次Scan会缓存100条,可以通过属性hbase.client.scanner.caching来设置。另外,最大值默认为-1,表示没有限制,具体实现见源代码: /** * @return the maximum result size in bytes. See {@link #setMaxResultSize(long)} */ public long getMaxResultSize() { return maxResultSize; } /** * Set the maximum result size. The default is -1; this means that no specific * maximum result size will be set for this scan, and the global configured * value will be used instead. (Defaults to unlimited). * * @param maxResultSize The maximum result size in bytes. */ public Scan setMaxResultSize(long maxResultSize) { this.maxResultSize = maxResultSize; return this; } 一般情况下,默认缓存100就可以满足,如果数据量过大,可以适当增大缓存值,来减少RPC次数,从而降低Scan的总体耗时。另外,在做报表呈现时,建议使用HBase分页来返回Scan的数据。 2.1.2 Get优化 HBase系统提供了单条get数据和批量get数据,单条get通常是通过请求表名+rowkey,批量get通常是通过请求表名+rowkey集合来实现。客户端在读取HBase的数据时,实际是与RegionServer进行数据交互。在使用批量get时可以有效的较少客户端到各个RegionServer之间RPC连接数,从而来间接的提高读取性能。批量get实现代码见org.apache.hadoop.hbase.client.HTable类: public Result[] get(List<Get> gets) throws IOException { if (gets.size() == 1) { return new Result[]{get(gets.get(0))}; } try { Object[] r1 = new Object[gets.size()]; batch((List<? extends Row>)gets, r1, readRpcTimeoutMs); // Translate. Result [] results = new Result[r1.length]; int i = 0; for (Object obj: r1) { // Batch ensures if there is a failure we get an exception instead results[i++] = (Result)obj; } return results; } catch (InterruptedException e) { throw (InterruptedIOException)new InterruptedIOException().initCause(e); } } 从实现的源代码分析可知,批量get请求的结果,要么全部返回,要么抛出异常。 2.1.3 列簇和列优化 通常情况下,HBase表设计我们一个指定一个列簇就可以满足需求,但也不排除特殊情况,需要指定多个列簇(官方建议最多不超过3个),其实官方这样建议也是有原因的,HBase是基于列簇的非关系型数据库,意味着相同的列簇数据会存放在一起,而不同的列簇的数据会分开存储在不同的目录下。如果一个表设计多个列簇,在使用rowkey查询而不限制列簇,这样在检索不同列簇的数据时,需要独立进行检索,查询效率固然是比指定列簇查询要低的,列簇越多,这样影响越大。 而同一列簇下,可能涉及到多个列,在实际查询数据时,如果一个表的列簇有上1000+的列,这样一个大表,如果不指定列,这样查询效率也是会很低。通常情况下,在查询的时候,可以查询指定我们需要返回结果的列,对于不需要的列,可以不需要指定,这样能够有效地的提高查询效率,降低延时。 2.1.4 禁止缓存优化 批量读取数据时会全表扫描一次业务表,这种提现在Scan操作场景。在Scan时,客户端与RegionServer进行数据交互(RegionServer的实际数据时存储在HDFS上),将数据加载到缓存,如果加载很大的数据到缓存时,会对缓存中的实时业务热数据有影响,由于缓存大小有限,加载的数据量过大,会将这些热数据“挤压”出去,这样当其他业务从缓存请求这些数据时,会从HDFS上重新加载数据,导致耗时严重。 在批量读取(T+1)场景时,建议客户端在请求是,在业务代码中调用setCacheBlocks(false)函数来禁止缓存,默认情况下,HBase是开启这部分缓存的。源代码实现为: /** * Set whether blocks should be cached for this Get. * <p> * This is true by default. When true, default settings of the table and * family are used (this will never override caching blocks if the block * cache is disabled for that family or entirely). * * @param cacheBlocks if false, default settings are overridden and blocks * will not be cached */ public Get setCacheBlocks(boolean cacheBlocks) { this.cacheBlocks = cacheBlocks; return this; } /** * Get whether blocks should be cached for this Get. * @return true if default caching should be used, false if blocks should not * be cached */ public boolean getCacheBlocks() { return cacheBlocks; } 2.2 服务端优化 HBase服务端配置或集群有问题,也会导致客户端读取耗时较大,集群出现问题,影响的是整个集群的业务应用。 2.2.1 负载均衡优化 客户端的请求实际上是与HBase集群的每个RegionServer进行数据交互,在细分一下,就是与每个RegionServer上的某些Region进行数据交互,每个RegionServer上的Region个数上的情况下,可能这种耗时情况影响不大,体现不够明显。但是,如果每个RegionServer上的Region个数较大的话,这种影响就会很严重。笔者这里做过统计的数据统计,当每个RegionServer上的Region个数超过800+,如果发生负载不均衡,这样的影响就会很严重。 可能有同学会有疑问,为什么会发送负载不均衡?负载不均衡为什么会造成这样耗时严重的影响? 1.为什么会发生负载不均衡? 负载不均衡的影响通常由以下几个因素造成: 没有开启自动负载均衡 集群维护,扩容或者缩减RegionServer节点 集群有RegionServer节点发生宕机或者进程停止,随后守护进程又自动拉起宕机的RegionServer进程 针对这些因素,可以通过以下解决方案来解决: 开启自动负载均衡,执行命令:echo "balance_switch true" | hbase shell 在维护集群,或者守护进程拉起停止的RegionServer进程时,定时调度执行负载均衡命令:echo "balancer" | hbase shell 2.负载不均衡为什么会造成这样耗时严重的影响? 这里笔者用一个例子来说,集群每个RegionServer包含由800+的Region数,但是,由于集群维护,有几台RegionServer节点的Region全部集中到一台RegionServer,分布如下图所示: 这样之前请求在RegionServer2和RegionServer3上的,都会集中到RegionServer1上去请求。这样就不能发挥整个集群的并发处理能力,另外,RegionServer1上的资源使用将会翻倍(比如网络、磁盘IO、HBase RPC的Handle数等)。而原先其他正常业务到RegionServer1的请求也会因此受到很大的影响。因此,读取请求不均衡不仅会造成本身业务性能很长,还会严重影响其他正常业务的查询。同理,写请求不均衡,也会造成类似的影响。故HBase负载均衡是HBase集群性能的重要体现。 2.2.2 BlockCache优化 BlockCache作为读缓存,合理设置对于提高读性能非常重要。默认情况下,BlockCache和Memstore的配置各站40%,可以通过在hbase-site.xml配置以下属性来实现: hfile.block.cache.size,默认0.4,用来提高读性能 hbase.regionserver.global.memstore.size,默认0.4,用来提高写性能 本篇博客主要介绍提高读性能,这里我们可以将BlockCache的占比设置大一些,Memstore的占比设置小一些(总占比保持在0.8即可)。另外,BlockCache的策略选择也是很重要的,不同的策略对于读性能来说影响不大,但是对于GC的影响却比较明显,在设置hbase.bucketcache.ioengine属性为offheap时,GC表现的很优秀。缓存结构如下图所示: 设置BlockCache可以在hbase-site.xml文件中,配置如下属性: <!-- 分配的内存大小尽可能的多些,前提是不能超过 (机器实际物理内存-JVM内存) --> <property> <name>hbase.bucketcache.size</name> <value>16384</value> </property> <property> <name>hbase.bucketcache.ioengine</name> <value>offheap</value> </property> 设置块内存大小,可以参考入下表格: 标号 描述 计算公式或值 结果 A 物理内存选择:on-heap(JVM)+off-heap(Direct) 单台物理节点内存值,单位MB 262144 B HBASE_HEAPSIZE('-Xmx) 单位MB 20480 C -XX:MaxDirectMemorySize,off-heap允许的最大内存值 A-B 241664 Dp hfile.block.cache.size和hbase.regionserver.global.memstore.size总和不要超过0.8 读取比例占比*0.8 0.5*0.8=0.4 Dm JVM Heap允许的最大BlockCache(MB) B*Dp 20480*0.4=8192 Ep hbase.regionserver.global.memstore.size设置的最大JVM值 0.8-Dp 0.8-0.4=0.4 F 用于其他用途的off-heap内存,例如DFSClient 推荐1024到2048 2048 G BucketCache允许的off-heap内存 C-F 241664-2048=239616 另外,BlockCache策略,能够有效的提高缓存命中率,这样能够间接的提高热数据覆盖率,从而提升读取性能。 2.2.3 HFile优化 HBase读取数据时会先从BlockCache中进行检索(热数据),如果查询不到,才会到HDFS上去检索。而HBase存储在HDFS上的数据以HFile的形式存在的,文件如果越多,检索所花费的IO次数也就必然增加,对应的读取耗时也就增加了。文件数量取决于Compaction的执行策略,有以下2个属性有关系: hbase.hstore.compactionThreshold,默认为3,表示store中文件数超过3个就开始进行合并操作 hbase.hstore.compaction.max.size,默认为9223372036854775807,合并的文件最大阀值,超过这个阀值的文件不能进行合并 另外,hbase.hstore.compaction.max.size值可以通过实际的Region总数来计算,公式如下: hbase.hstore.compaction.max.size = RegionTotal / hbase.hstore.compactionThreshold 2.2.4 Compaction优化 Compaction操作是将小文件合并为大文件,提高后续业务随机读取的性能,但是在执行Compaction操作期间,节点IO、网络带宽等资源会占用较多,那么什么时候执行Compaction才最好?什么时候需要执行Compaction操作? 1.什么时候执行Compaction才最好? 实际应用场景中,会关闭Compaction自动执行策略,通过属性hbase.hregion.majorcompaction来控制,将hbase.hregion.majorcompaction=0,就可以禁止HBase自动执行Compaction操作。一般情况下,选择集群负载较低,资源空闲的时间段来定时调度执行Compaction。 如果合并的文件较多,可以通过设置如下属性来提生Compaction的执行速度,配置如下: <property> <name>hbase.regionserver.thread.compaction.large</name> <value>8</value> <description></description> </property> <property> <name>hbase.regionserver.thread.compaction.small</name> <value>5</value> <description></description> </property> 2.什么时候需要执行Compaction操作? 一般维护HBase集群后,由于集群发生过重启,HBase数据本地性较低,通过HBase页面可以观察,此时如果不执行Compaction操作,那么客户端查询的时候,需要跨副本节点去查询,这样来回需要经过网络带宽,对比正常情况下,从本地节点读取数据,耗时是比较大的。在执行Compaction操作后,HBase数据本地性为1,这样能够有效的提高查询效率。 3.总结 本篇博客HBase查询优化从客户端和服务端角度,列举一些常见有效地优化手段。当然,优化还需要从自己实际应用场景出发,例如代码实现逻辑、物理机的实际配置等方面来设置相关参数。大家可以根据实际情况来参考本篇博客进行优化。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 最近有同学留言反馈了使用Kafka监控工具Kafka Eagle的一些问题,这里笔者特意整理了这些问题。并且希望通过这篇博客来解答这些同学的在使用Kafka Eagle的时候遇到的一些困惑,同时也给出一些定位分析Kafka Eagle异常的时的解决办法。 2.内容 2.1 背景 在使用Kafka Eagle监控系统之前,笔者简单的介绍一下这款工具的用途。Kafka Eagle监控系统是一款用来监控Kafka集群的工具,目前更新的版本是v1.2.3,支持管理多个Kafka集群、管理Kafka主题(包含查看、删除、创建等)、消费者组合消费者实例监控、消息阻塞告警、Kafka集群健康状态查看等。目前Kafka Eagle v1.2.3整个系统所包含的功能,这里笔者给绘制成了一个图,结果如下图所示: 2.2 安装 接下来,我们开始安装Kafka Eagle系统,安装之前,我们需要准备好Kafka Eagle安装包。这里有2种方式: 下载编译好的安装包 下载源代码,然后自行编译安装 下面分别介绍这2种方式。 2.2.1 直接下载安装包 可以直接访问Kafka Eagle安装包下载地址:http://download.smartloli.org/,然后点击下载按钮,等待下载完成即可。下载界面如下图所示: 2.2.2 下载源代码,自行编译安装 Kafka Eagle系统的源代码托管在Github上,大家可以访问https://github.com/smartloli/kafka-eagle来获取源代码。Kafka Eagle源代码是由Maven工程来管理的,所以,在编译Kafka Eagle源代码之前,需要在本地开发环境中准备好你的Maven环境。 Maven安装比较简单,这里给大家介绍安装Maven的步骤: # 步骤1:下载Maven安装包 wget http://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz # 步骤2:解压Maven安装包 tar -zxvf apache-maven-3.5.4-bin.tar.gz # 步骤3:重命名并移动到指定位置 mv apache-maven-3.5.4 /usr/local/maven # 步骤4:配置Maven环境编辑 vi ~/.bash_profile export M2_HOME=/usr/local/maven export PATH=$PATH:$M2_HOME/bin # 步骤5:执行source命令让环境变量立即生效 source ~/.bash_profile # 步骤6:验证Maven是否安装成功 mvn -v # 如果能打印Maven版本信息,则安装成功 然后,进入到Kafka Eagle源代码根目录,在根目录中有一个build.sh的脚本,执行该脚本进行源代码编译。编译成功后,控制台会打印相关信息,如下图所示: 2.2.3 配置Kafka Eagle 准备好Kafka Eagle安装包后,接下来我们就可以进行安装了。其实,Kafka Eagle的安装是很简单的,当初设计这个系统就是遵循简单、易用的原则来的。但是,很多同学在安装的过程当中却遇到了很多各式各样的问题。其实,在官方使用手册的安装一节中,介绍的也很详细。官方使用手册地址:http://ke.smartloli.org/ 文档托管在Gitbook,这里需要注意的是,可能有些同学反馈说访问不了,如果网络有波动,偶尔可能需要使用代理来访问。 接下来,我们就开始配置Kafka Eagle系统,步骤如下: 1. 配置JAVA_HOME和KE_HOME 由于源代码核心实现采用的是Java语言,所以需要配置JDK环境,建议采用JDK8以上。配置内容如下: vi ~/.bash_profile export JAVA_HOME=/usr/local/jdk8 export KE_HOME=/data/soft/new/kafka-eagle export PATH=$PATH:$JAVA_HOME/bin:$KE_HOME/bin 然后,执行source ~/.bash_profile命令让命令立即生效。如果不配置环境变量,可能在启动Kafka Eagle脚本ke.sh时抛出如下异常,异常信息如下: [2018-07-26 18:41:51] Error: The KE_HOME environment variable is not defined correctly. [2018-07-26 18:41:51] Error: This environment variable is needed to run this program. [2018-07-26 18:41:51] Error: The JAVA_HOME environment variable is not defined correctly. [2018-07-26 18:41:51] Error: This environment variable is needed to run this program. 2. 配置system-config.properties文件 该文件在$KE_HOME/conf/目录,配置内容如下: ###################################### # 配置多个Kafka集群所对应的Zookeeper ###################################### kafka.eagle.zk.cluster.alias=cluster1,cluster2 cluster1.zk.list=dn1:2181,dn2:2181,dn3:2181 cluster2.zk.list=tdn1:2181,tdn2:2181,tdn3:2181 ###################################### # 设置Zookeeper线程数 ###################################### kafka.zk.limit.size=25 ###################################### # 设置Kafka Eagle浏览器访问端口 ###################################### kafka.eagle.webui.port=8048 ###################################### # 如果你的offsets存储在Kafka中,这里就配置 # 属性值为kafka,如果是在Zookeeper中,可以 # 注释该属性。一般情况下,Offsets的也和你消 # 费者API有关系,如果你使用的Kafka版本为0.10.x # 以后的版本,但是,你的消费API使用的是0.8.2.x # 时的API,此时消费者依然是在Zookeeper中 ###################################### cluster1.kafka.eagle.offset.storage=kafka####################################### 如果你的集群一个是新版本(0.10.x以上),# 一个是老版本(0.8或0.9),可以这样设置,# 如果都是新版本,那么可以将值都设置成kafka######################################cluster2.kafka.eagle.offset.storage=zookeeper ###################################### # 是否启动监控图表,默认是不启动的 ###################################### kafka.eagle.metrics.charts=false ###################################### # 在使用Kafka SQL查询主题时,如果遇到错误, # 可以尝试开启这个属性,默认情况下,不开启 ###################################### kafka.eagle.sql.fix.error=false ###################################### # 邮件服务器设置,用来告警 ###################################### kafka.eagle.mail.enable=false kafka.eagle.mail.sa= kafka.eagle.mail.username= kafka.eagle.mail.password= kafka.eagle.mail.server.host= kafka.eagle.mail.server.port= ###################################### # 设置告警用户,多个用户以英文逗号分隔 ###################################### kafka.eagle.alert.users=smartloli.org@gmail.com ###################################### # 超级管理员删除主题的Token ###################################### kafka.eagle.topic.token=keadmin ###################################### # 如果启动Kafka SASL协议,开启该属性 ###################################### kafka.eagle.sasl.enable=false kafka.eagle.sasl.protocol=SASL_PLAINTEXT kafka.eagle.sasl.mechanism=PLAIN ###################################### # Kafka Eagle默认存储在Sqlite中,如果要使用 # MySQL可以替换驱动、用户名、密码、连接地址 ###################################### #kafka.eagle.driver=com.mysql.jdbc.Driver #kafka.eagle.url=jdbc:mysql://127.0.0.1:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull #kafka.eagle.username=root #kafka.eagle.password=123456 kafka.eagle.driver=org.sqlite.JDBC kafka.eagle.url=jdbc:sqlite:/Users/dengjie/workspace/kafka-egale/db/ke.db kafka.eagle.username=root kafka.eagle.password=root 3. 启动Kafka Eagle 配置完成后,可以执行Kafka Eagle脚本ke.sh。如果首次执行,需要给该脚本赋予执行权限,命令如下: chmod +x $KE_HOME/bin/ke.sh 在ke.sh脚本中,支持以下命令: 命令 说明 ke.sh start 启动Kafka Eagle系统 ke.sh stop 停止Kafka Eagle系统 ke.sh restart 重启Kafka Eagle系统 ke.sh status 查看Kafka Eagle系统运行状态 ke.sh stats 统计Kafka Eagle系统占用Linux资源情况 ke.sh find [ClassName] 查看Kafka Eagle系统中的类是否存在 3.预览 在反馈出现频率最多的问题,就是Consumer模块没有数据展示、趋势监控图没有数据、Kafka SQL查询Topic没有数据。下面围绕这3个问题来给大家演示,以及解释什么情况下出现这种情况。 3.1 Consumer模块展示 启动一个消费者程序,然后进入到Consumer模块,截图如下: 这里需要注意的时,Kafka在0.10.x之后的版本和之前的版本底层设计有了变化,在之前的版本消费者信息是存储在Zookeeper中的,在0.10.x版本之后,默认存储到了Kafka内部主题中,只保留了元数据信息存储在Zookeeper中,例如:Kafka Broker地址、Topic名称、分区等信息。 是不是我使用的是Kafka 0.10.x之后的版本(如0.10.0、1.0.x、1.x等),然后配置属性kafka.eagle.offset.storage=kafka,启动消费者,就可以看到消费者信息呢?不一定的,还有一个关键因素决定Kafka Eagle系统是否可以展示你消费者程序信息,那就是消费者API的使用。 如果你使用的Kafka 0.10.x之后的版本,然后消费者API也是使用的最新的写法,那么自然你的消费者信息会被记录到Kafka内部主题中,那么此时你设置kafka.eagle.offset.storage=kafka这个属性,Kafka Eagle系统可以完美展示你的消费者使用情况。 但是,如果你虽然使用的是Kafka 0.10.x之后的版本,但是你使用的消费者API还是0.8.2.x或是0.9.x时的写法,此时的消费者信息是会被记录到Zookeeper中进行存储,那么此时你需要设置kafka.eagle.offset.storage=zookeeper或者注释掉该属性,在访问Kafka Eagle系统就可以查看到你的消费者详情了。 3.2. 监控趋势图 Kafka系统默认是没有开启JMX端口的,所以Kafka Eagle的监控趋势图默认采用不启用的方式,即kafka.eagle.metrics.charts=false。如果需要查看监控趋势图,需要开启Kafka系统的JMX端口,设置该端口在$KAFKA_HOME/bin/kafka-server-start.sh脚本中,设置内容如下: vi kafka-server-start.sh if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then export KAFKA_HEAP_OPTS="-server -Xms2G -Xmx2G -XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70" export JMX_PORT="9999" #export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" fi 需要注意的时,这里的端口不一定非要设置成9999,端口只有可用,均可。Kafka Eagle系统会自动获取这个JMX端口,采集结果如下: 3.3 Kafka SQL查询Topic 还有一部分同学在Kafka Eagle系统的SQL查询Topic时,会出现查询不到数据的情况。这里查询不到数据可能情况有多种,首先需要排除Kafka集群因素,确保Kafka集群运行正常,Topic能够正常访问,并且Topic中是有数据的。 在排除一些主观因素后,回到Kafka Eagle系统应用层面,如果出现这种问题,可以尝试开启属性kafka.eagle.sql.fix.error=true,这个属性默认是不开启的。正常情况下使用Kafka SQL查询Topic,返回结果如下图所示: 4.总结 另外,如果在使用Kafka Eagle系统中遇到其他问题,可以查看$KE_HOME/logs/ke_console.out日志来分析具体的异常信息,一般都会提示具体的错误,大家可以根据错误提示来进行解决。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在分布式系统中,负载均衡是一个非常重要的功能,在HBase中通过Region的数量来实现负载均衡,HBase中可以通过hbase.master.loadbalancer.class来实现自定义负载均衡算法。下面将为大家剖析HBase负载均衡的相关内容以及性能指标。 2.内容 在HBase系统中,负载均衡是一个周期性的操作,通过负载均衡来均匀分配Region到各个RegionServer上,通过hbase.balancer.period属性来控制负载均衡的时间间隔,默认是5分钟。触发负载均衡操作是有条件的,但是如果发生如下情况,则不会触发负载均衡操作: 负载均衡自动操作balance_switch关闭,即:balance_switch false HBase Master节点正在初始化操作 HBase集群中正在执行RIT,即Region正在迁移中 HBase集群正在处理离线的RegionServer 2.1 负载均衡算法 HBase在执行负载均衡操作时,如何判断各个RegionServer节点上的Region个数是否均衡,这里通过以下步骤来判断: 计算均衡值的区间范围,通过总Region个数以及RegionServer节点个数,算出平均Region个数,然后在此基础上计算最小值和最大值 遍历超过Region最大值的RegionServer节点,将该节点上的Region值迁移出去,直到该节点的Region个数小于等于最大值的Region 遍历低于Region最小值的RegionServer节点,分配集群中的Region到这些RegionServer上,直到大于等于最小值的Region 负责上述操作,直到集群中所有的RegionServer上的Region个数在最小值与最大值之间,集群才算到达负载均衡,之后,即使再次手动执行均衡命令,HBase底层逻辑判断会执行忽略操作 2.2 实例分析 下面笔者通过一个实际的应用场景来给大家剖析HBase负载均衡算法的实现流程。举个例子,假如我们当前有一个5台节点规模的HBase集群(包含Master和RegionServer),其中2台Master和3台RegionServer组成,每台RegionServer上的Region个数,如下图所示。 在执行负载均衡操作之前,会计算集群中总的Region个数,当前实例中集群中的Region总个数为175+56+99=330。然后计算每个RegionServer需要容纳的Region平均值。计算结果如下: 平均值(110) = 总Region个数(330) / RegionServers总数(3) 计算最小值和最大值来判断HBase集群是否需要进行负载均衡操作,计算公式如下: # hbase.regions.slop 权重值,默认为0.2 最小值 = Math.floor(平均值 * (1-0.2)) 最大值 = Math.ceil(平均值 * (1+0.2)) HBase集群如果判断各个RegionServer中的最小Region个数大于计算后的最小值,并且最大Region个数小于最大值,这是直接返回不会触发负载均衡操作。根据实例中给出的Region数,计算得出最小值Region为88,最大值Region为120。 由于实例中RegionServer2的Region个数为56,小于最小值Region数88,而RegionServer1的Region个数为175,大于了最大值Region数120,所以需要负载均衡操作。 HBase系统有提供管理员命令,来操作负载均衡,具体操作如下: # 使用hbase shell命令进入到HBase控制台,然后开启自动执行负载均衡 hbase(main):001:0> balance_switch true 这样HBase负载均衡自动操作就开启了,但是,如果我们需要立即均衡集群中的Region个数怎么办?这里HBase也提供了管理命令,通过balancer命令来实现,操作如下: hbase(main):001:0> balancer 但是,这样每次手动执行,每次均衡的个数不一定能满足要求,那么我们可以通过封装该命令,用脚本来调度执行,具体实现代码如下: #! /bin/bash num=$1 echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : RegionServer Start Balancer..." if [ ! -n "$num" ]; then echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Default Balancer 20 Times." num=20 elif [[ $num == *[!0-9]* ]]; then echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Input [$num] Times Must Be Number." exit 1 else echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : User-Defined Balancer [$num] Times." fi for (( i=1; i<=$num; i++ )) do echo "[`date "+%Y-%m-%d %H:%M:%S"`] INFO : Balancer [$i] Times,Total [$num] Times." echo "balancer"|hbase shell sleep 5 done 脚本默认执行20次,可以通过输入一个整型参数来自定义执行次数。 当HBase集群检查完所有的RegionServer上的Region个数已打要求,那么此时集群的负载均衡操作就已经完成了。如果没有达到要求,可以再次执行上述脚本,直到所有的Region个数在最小值和最大值之间为止。当HBase集群中所有的RegionServer完成负载均衡后,实例中的各个RegionServer上的Region个数分布,如下图所示。 此时,各个RegionServer节点上的Region个数均在最小值和最大值范围内,HBase集群各个RegionServer节点上的Region处理均衡状态。 3.性能指标 在HBase系统中,有一个非常重要的性能指标,那就是集群处理请求的延时。HBase系统为了反应集群内部处理请求所耗费的时间,提供了一个工具类,即:org.apache.hadoop.hbase.tool.Canary,这个类主要用户检查HBase系统的耗时状态。如果不知道使用方法,可以通过help命令来查看具体的用法,命令如下: hbase org.apache.hadoop.hbase.tool.Canary -help (1)查看集群中每个表中每个Region的耗时情况 hbase org.apache.hadoop.hbase.tool.Canary (2)查看money表中每个Region的耗时情况,多个表之间使用空格分割 # 查看money表和person表 hbase org.apache.hadoop.hbase.tool.Canary money person (3)查看每个RegionServer的耗时情况 hbase org.apache.hadoop.hbase.tool.Canary -regionserver dn1 通常情况下,我们比较关注每个RegionServer节点的耗时情况,将该命令封装一下,然后打印集群中每个RegionServer的耗时情况,脚本实现如下所示: ######################################################### # 将捕获的RS耗时,写入到InfluxDB中进行存储,用于绘制历史趋势图 ######################################################### #!/bin/bash post_influxdb_write='http://influxdb:8086/write?db=telegraf_rs' source /home/hadoop/.bash_profile for i in `cat rs.list` do timespanStr=`(hbase org.apache.hadoop.hbase.tool.Canary -regionserver $i 2>&1) | grep tool.Canary` timespanMs=`echo $timespanStr|awk -F ' ' '{print $NF}'` timespan=`echo $timespanMs|awk -F "ms" '{print $1}'` echo `date +'%Y-%m-%d %H:%M:%S'` INFO : RegionServer $i delay $timespanMs . currentTime=`date "+%Y-%m-%d %H:%M:%S"` currentTimeStamp=`date -d "$currentTime" +%s` insert_sql="regionsever,host=$i value=$timespan ${currentTimeStamp}000000000" #echo $insert_sql curl -i -X POST "$post_influxdb_write" --data-binary "$insert_sql" done exit 4.总结 在维护HBase集群时,比如重启某几个RegionServer节点后,可能会发送Region不均衡的情况,这时如果开启自动均衡后,需要立即使当前集群上其他RegionServer上的Region处于均衡状态,那么就可以使用手动均衡操作。另外,HBase集群中各个RegionServer的耗时情况,能够反映当前集群的健康状态。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 另外,博主出书了《Hadoop大数据挖掘从入门到进阶实战》,喜欢的朋友或同学, 可以在公告栏那里点击购买链接购买博主的书进行学习,在此感谢大家的支持。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 大数据时代,数据的存储与挖掘至关重要。企业在追求高可用性、高扩展性及高容错性的大数据处理平台的同时还希望能够降低成本,而Hadoop为实现这些需求提供了解决方案。面对Hadoop的普及和学习热潮,笔者愿意分享自己多年的开发经验,带领读者比较轻松地掌握Hadoop数据挖掘的相关知识。这边是笔者编写本书的原因。本书使用通俗易懂的语言进行讲解,从基础部署到集群管理,再到底层设计等内容均由涉及。通过阅读本书,读者可以较为轻松地掌握Hadoop大数据挖掘与分析的相关技术。 本书目前已在网上商城上架,可以通过京东自营,当当自营,亚马逊自营等网上商城进行购买。书籍封面如下: 2.本书内容 本书采用“理论+实战”的形式编写,通过大量的实例,结合作者多年一线开发实战经验,全面的介绍了Hadoop的使用方法。全书设计秉承方便学习、易于理解、便于查询的理念,无论是刚入门的初学者系统的学习Hadoop的基础知识,还是拥有多年开发经验的开发者想学习Hadoop,都能通过本书迅速掌握Hadoop的各种基础语法和实战技巧。本书作者曾经与极客学院合作,拥有丰富的教学视频制作经验,为读者精心录制了详细的视频介绍。本书还免费提供所有案例的源码,为读者的学习和工作提供更多的便利。 本书分为13章,分别介绍Hadoop平台管理与维护、异常处理解决方案以及Hadoop的分布式文件系统等内容。最后一章对Hadoop进行了拓展,剖析了Kafka消息系统并介绍了笔者的开源监控系统Kafka Eagle。 本书结构清晰、案例丰富、通俗易懂、实用性强。特别适合初学者自学和进阶读者查询及参考。另外,本书也适合社会培训学校作为培训教材使用,还适合大中专院校的相关专业作为教学参考书。 3.本书特色 3.1 提供专业的配套教学视频,高效、直观 笔者曾接受过极客学院的专业视频制作指导,并在极客学院录制过多期Hadoop和Kafka实战教学视频课程,得到了众多学习者的青睐及好评。为了便于读者更加高效、直观地学习本书内容,笔者特意为本书实战部分的内容录制了配套教学视频,读者可以在教学视频的辅助下学习,从而更加轻松地掌握Hadoop。 3.2 来自一线的开发经验及实战例子 本书给出的代码讲解和实例大多数来自于笔者多年的教学积累和技术分享,几乎都是得到了学习者一致好评的干活。另外,笔者还是一名开源爱好者,编写了业内著名的Kafka Eagle监控系统。本书第13章介绍了该系统的使用,以帮助读者掌握如何监控大数据集群的相关知识。 3.3 浅显易懂的语言、触类旁通的对比、循序渐进的知识体系 本书在文字及目录编排上,尽量做到通俗易懂。在讲解一些常见的知识点时,将Hadoop命令与Linux命令做对比,掌握Linux命令的开发者能够迅速掌握Hadoop的操作命令。无论是初学者,还是久经沙场的老程序员都能快速通过本书学习Hadoop的精华之处。 3.4 内容全面,实用性强 本书精心挑选了多个实用性很强的例子,例如:Hadoop套件实战、Hive 编程、Hadoop平台管理与维护、ELK实战、Kafka实战等。读者既可以从例子中学习并理解Hadoop及其套件知识点,还可以将这些例子用于开发中。 4. 示例代码 本书的所有示例都封装在该项目中,读者可以下载该工程的源代码来对照书中的内容进行学习。由于本工程采用的是Maven来进行管理,所以在需要编译打包时,可以直接只用mvn命令,或者执行./build.sh脚本来实现打包。 5. 书籍目录部分预览 6. 读者对象 学习Hadoop没有想象中的那么困难,本书通过将一些Hadoop难懂的知识点,通过通俗易懂的语言进行概述,来减少读者的学习成本,让读者轻轻松松地掌握Hadoop的相关知识。适用范围但不仅仅包含以下: Hadoop初学者 Hadoop进阶人员 后端程序初学者 前端转后端的开发人员 熟悉Linux操作系统以及有编程语言基础的 学习Hadoop的编程爱好者 7. 总结 最后,衷心希望笔者编写的这本书能够帮助到对Hadoop感兴趣、学习Hadoop的同学。希望阅读过本书的同学能够掌握Hadoop相关知识,希望笔者书中的经验和总结能够帮助读者少走弯路,在Hadoop学习之路上游刃有余。 8.结束语 感兴趣的同学可以购买本书,如果在学习本书的内容中遇到任何疑问,可以通过下面的联系方式进行邮件留言或者加入Hadoop学习讨论群,笔者会尽我所能,帮您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在Kafka0.9版本之前,Kafka集群时没有安全机制的。Kafka Client应用可以通过连接Zookeeper地址,例如zk1:2181:zk2:2181,zk3:2181等。来获取存储在Zookeeper中的Kafka元数据信息。拿到Kafka Broker地址后,连接到Kafka集群,就可以操作集群上的所有主题了。由于没有权限控制,集群核心的业务主题时存在风险的。 2.内容 2.2 身份认证 Kafka的认证范围包含如下: Client与Broker之间 Broker与Broker之间 Broker与Zookeeper之间 当前Kafka系统支持多种认证机制,如SSL、SASL(Kerberos、PLAIN、SCRAM)。 2.3 SSL认证流程 在Kafka系统中,SSL协议作为认证机制默认是禁止的,如果需要使用,可以手动启动SSL机制。安装和配置SSL协议的步骤,如下所示: 在每个Broker中Create一个Tmp密钥库 创建CA 给证书签名 配置Server和Client 执行脚本如下所示: #! /bin/bash # 1.Create rsa keytool -keystore server.keystore.jks -alias dn1 -validity 365 -genkey -keyalg RSA # 2.Create CA openssl req -new -x509 -keyout ca-key -out ca-cert -days 365 # 3.Import client keytool -keystore client.truststore.jks -alias CAROOT -import -file ca-cert # 4.Import server keytool -keystore server.truststore.jks -alias CAROOT -import -file ca-cert # 5.Export keytool -keystore server.keystore.jks -alias dn1 -certreq -file cert-file # 6.Signed openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file -out cert-signed -days 365 -CAcreateserial -passin pass:123456 # 7.Import ca-cert keytool -keystore server.keystore.jks -alias CARoot -import -file ca-cert # 8.Import cert-signed keytool -keystore server.keystore.jks -alias dn1 -import -file cert-signed 2.4 SASL认证流程 在Kafka系统中,SASL机制包含三种,它们分别是Kerberos、PLAIN、SCRAM。以PLAIN认证为示例,下面给大家介绍PLAIN认证流程。 2.4.1 配置Server 首先,在$KAFKA_HOME/config目录中新建一个文件,名为kafka_server_jaas.conf,配置内容如下: KafkaServer { org.apache.kafka.common.security.plain.PlainLoginModule required username="smartloli" password="smartloli-secret" user_admin="smartloli-secret"; }; Client { org.apache.kafka.common.security.plain.PlainLoginModule required username="smartloli" password="smartloli-secret"; }; 然后在Kafka启动脚本(kafka-server-start.sh)中添加配置文件路径,设置内容如下: [hadoop@dn1 bin]$ vi kafka-server-start.sh # Add jaas file export KAFKA_OPTS="-Djava.security.auth.login.config=/data/soft/new/kafka/config/kafka_server_jaas.conf" 接下来,配置server.properties文件,内容如下: # Set ip & port listeners=SASL_PLAINTEXT://dn1:9092 advertised.listeners=SASL_PLAINTEXT://dn1:9092 # Set protocol security.inter.broker.protocol=SASL_PLAINTEXT sasl.enabled.mechanisms=PLAIN sasl.mechanism.inter.broker.protocol=PLAIN # Add acl allow.everyone.if.no.acl.found=true auto.create.topics.enable=false delete.topic.enable=true advertised.host.name=dn1 super.users=User:admin # Add class authorizer.class.name=kafka.security.auth.SimpleAclAuthorizer 2.4.2 配置Client 当Kafka Server端配置启用了SASL/PLAIN,那么Client连接的时候需要配置认证信息,Client配置一个kafka_client_jaas.conf文件,内容如下: KafkaClient { org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin-secret"; }; 然后,在producer.properties和consumer.properties文件中设置认证协议,内容如下: security.protocol=SASL_PLAINTEXT sasl.mechanism=PLAIN 最后,在kafka-console-producer.sh脚本和kafka-console-producer.sh脚本中添加JAAS文件的路径,内容如下: # For example: kafka-console-producer.sh hadoop@dn1 bin]$ vi kafka-console-producer.sh # Add jaas file export KAFKA_OPTS="-Djava.security.auth.login.config=/data/soft/new/kafka\ /config/kafka_client_jaas.conf" 2.5 ACL操作 在配置好SASL后,启动Zookeeper集群和Kafka集群之后,就可以使用kafka-acls.sh脚本来操作ACL机制。 (1)查看:在kafka-acls.sh脚本中传入list参数来查看ACL授权新 [hadoop@dn1 bin]$ kafka-acls.sh --list --authorizer-properties zookeeper.connect=dn1:2181 (2)创建:创建待授权主题之前,在kafka-acls.sh脚本中指定JAAS文件路径,然后在执行创建操作 [hadoop@dn1 bin]$ kafka-topics.sh --create --zookeeper dn1:2181 --replication-factor 1 --partitions 1 --topic kafka_acl_topic (3)生产者授权:对生产者执行授权操作 [hadoop@dn1 ~]$ kafka-acls.sh --authorizer kafka.security.auth.SimpleAclAuthorizer --authorizer-properties zookeeper.connect=dn1:2181 --add --allow-principalUser:producer --operation Write --topic kafka_acl_topic (4)消费者授权:对生产者执行授权后,通过消费者来进行验证 [hadoop@dn1 ~]$ kafka-acls.sh --authorizer kafka.security.auth.SimpleAclAuthorizer --authorizer-properties zookeeper.connect=dn1:2181 --add --allow-principalUser:consumer --operation Read --topic kafka_acl_topic (5)删除:通过remove参数来回收相关权限 [hadoop@dn1 bin]$ kafka-acls.sh --authorizer-properties zookeeper.connect=dn1:2181 --remove --allow-principal User:producer --operation Write --topic kafka_acl_topic3 3.总结 在处理一些核心的业务数据时,Kafka的ACL机制还是非常重要的,对核心业务主题进行权限管控,能够避免不必要的风险。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 接着上一篇博客的内容,继续介绍Hadoop3的其他新特性。其内容包含:优化Hadoop Shell脚本、重构Hadoop Client Jar包、支持等待Container、MapReduce任务级别本地优化、支持多个NameNode、部分默认服务端口被改变、支持文件系统连接器、DataNode内部添加负载均衡、重构后台程序和任务堆管理。 2.内容 2.2.1 优化Hadoop Shell脚本 Hadoop Shell脚本已经被重写,用来修复已知的BUG,解决兼容性问题和一些现有安装的更改。它还包含了一些新的特性,内容如下所示: 所有Hadoop Shell脚本子系统现在都会执行hadoop-env.sh这个脚本,它允许所有环节变量位于一个位置; 守护进程已通过*-daemon.sh选项从*-daemon.sh移动到了bin命令中,在Hadoop3中,我们可以简单的使用守护进程来启动、停止对应的Hadoop系统进程; 触发SSH连接操作现在可以在安装时使用PDSH; ${HADOOP_CONF_DIR}现在可以任意配置到任何地方; 脚本现在测试并报告守护进程启动时日志和进程ID的各种状态; 2.2.2 重构Hadoop Client Jar包 Hadoop2 中可用的Hadoop客户端将Hadoop的传递依赖性拉到Hadoop应用程序的类路径上。如果这些传递依赖项的版本与应用程序使用的版本发送冲突,这可能会产生问题。 因此,在Hadoop3中有新的Hadoop客户端API和Hadoop客户端运行时工件,它们将Hadoop的依赖性遮蔽到单个JAR中,Hadoop客户端API是编译范围,Hadoop客户端运行时是运行时范围,它包含从Hadoop客户端重新定位的第三方依赖关系。因此,你可以将依赖项绑定到JAR中,并测试整个JAR以解决版本冲突。这样避免了将Hadoop的依赖性泄露到应用程序的类路径上。例如,HBase可以用来与Hadoop集群进行数据交互,而不需要看到任何实现依赖。 2.2.3 支持等待容器和分布式调度 在Hadoop3 中引入了一种新型执行类型,即等待容器,即使在调度时集群没有可用的资源,它也可以在NodeManager中被调度执行。在这种情况下,这些容器将在NM中排队等待资源启动,等待荣容器比默认容器优先级低,因此,如果需要,可以抢占默认容器的空间,这样可以提供机器的利用率。如下图所示: 默认容器对于现有的YARN容器,它们由容量调度分配,一旦被调度到节点,就保证有可用的资源使它们执行立即开始。此外,只要没有故障发生,这些容器就可以允许完毕。 等待容器默认由中心RM分配,但还增加了支持以允许等待容器被分布式调度,该调度群被实现于AM和RM协议的拦截器。 2.2.4 MapReduce任务级别本地化优化 在Hadoop3中,本地的Java实现已加入MapReduce地图输出器,对于Shuffle密集的作业,这样可以提高30%或者更高的性能。 它们添加了映射输出收集器的本机实现,让MapTask基于JNI来本机优化。基本思想是添加一个NativeMapOutputCollector收集器来处理映射器发出的键值对,因此Sort、Spill、文件序列化都可以在本机代码中完成。 2.2.5 支持多个NameNode节点 在Hadoop2中,HDFS NameNode高可用体系结构有一个Active和Standby NameNode,通过JournalNodes,该体系结构能够容忍任何一个NameNode失败。 然而,业务关键部署需要更高程度的容错性。因此,在Hadoop3中允许用户运行多个备用的NameNode。例如,通过配置三个NameNode(1个Active NameNode和2个Standby NameNode)和5个JournalNodes节点,集群可以容忍2个NameNode节点故障。如下图所示: 2.2.6 默认的服务端口被修改 早些时候,多个Hadoop服务的默认端口位于Linux端口范围以内。除非客户端程序明确的请求特定的端口号,否则使用的端口号是临时的,因此,在启动时,服务有时会因为与其他另一个应用程序冲突而无法绑定到端口。 因此,具有临时范围冲突端口已经被移除该范围,影响多个服务的端口号,即NameNode、Secondary NameNode、DataNode等如下所示: Daemon App Hadoop2 Port Hadoop3 Port NameNode Port Hadoop HDFS NameNode 8020 9820 Hadoop HDFS NameNode HTTP UI 50070 9870 Hadoop HDFS NameNode HTTPS UI 50470 9871 Secondary NameNode Port Secondary NameNode HTTP 50091 9869 Secondary NameNode HTTP UI 50090 9868 DataNode Port Hadoop HDFS DataNode IPC 50020 9867 Hadoop HDFS DataNode 50010 9866 Hadoop HDFS DataNode HTTP UI 50075 9864 Hadoop HDFS DataNode HTTPS UI 50475 9865 2.2.6 支持文件系统连接器 Hadoop现在支持与微软 Azure数据和阿里云对象存储系统的集成。它可以作为一种替代Hadoop兼容的文件系统,首先添加微软Azure数据,然后添加阿里云对象存储系统。 2.2.7 DataNode内部负载均衡 单个数据节点配置多个数据磁盘,在正常写入操作期间,数据被均匀的划分,因此,磁盘被均匀填充。但是,在维护磁盘时,添加或者替换磁盘会导致DataNode节点存储出现偏移,这种情况在早期的HDFS文件系统中,是没有被处理的。如图下图所示,维护前和维护后不均衡的情况: 现在Hadoop3通过新的内部DataNode平衡功能来处理这种情况,这是通过hdfs diskbalancer CLI来进行调用的。执行之后,DataNode会进行均衡处理,如下图所示: 2.2.8 重构后台程序和任务堆管理 Hadoop守护进程和MapReduce任务的堆管理已经发生了一系列的变化。 配置守护进程堆大小的新方法:值得注意的是,现在可以根据主机的内存大小进行自动调整,并且已经禁止HADOOP_HEAPSIZE变量。在HADOOP\_HEAPSIZE\_MAX 和 HADOOP\_HEAPSIZE\_MIN位置上,分别设置XMX和XMS。所有全局和守护进程特定堆大小变量现在都支持单元。如果变量仅为一个数,它的大小为MB。 Map和Reduce的堆大小的配置被简化了,所以不再需要任务配置作为一个Java选项指定。已经指定的两个现有配置不受此更改的影响。 3.总结 Hadoop3的这些新特性还是很吸引人的,目前官方推出的稳定版本是2.9.0,发行版是3.1.0,感兴趣的同学可以下载Hadoop3去体验调研学习一下这些新特性。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 目前从Hadoop官网的Wiki来看,稳定版本已经发行到Hadoop2.9.0,最新版本为Hadoop3.1.0,查阅JIRA,社区已经着手迭代Hadoop3.2.0。那么,今天笔者就带着大家来剖析一下Hadoop3,看看它给我们带来了哪些新特性。 2. 内容 从功能上来说,Hadoop3比Hadoop2有些功能得到了增强,具体增加了哪些,后面再讲。首先,我们来看看Hadoop3主要带来了哪些变化: JDK:在Hadoop2时,可以使用JDK7,但是在Hadoop3中,最低版本要求是JDK8,所以低于JDK8的版本需要对JDK进行升级,方可安装使用Hadoop3 EC技术:Erasure Encoding 简称EC,是Hadoop3给HDFS拓展的一种新特性,用来解决存储空间文件。EC技术既可以防止数据丢失,又能解决HDFS存储空间翻倍的问题 YARN:提供YARN的时间轴服务V.2,以便用户和开发人员可以对其进行测试,并提供反馈意见,使其成为YARN Timeline Service v.1的替代品。 优化Hadoop Shell脚本 重构Hadoop Client Jar包 支持随机Container MapReduce任务级本地优化 支持多个NameNode 部分默认服务端口被改变 支持文件系统连接器 DataNode内部添加了负载均衡 重构后台程序和任务对管理 下面,笔者就为大家来一一剖析这些新特性的具体内容,其内容包含JDK版本、EC技术、YARN的时间轴服务这三类特性,其他特性笔者在后面的博客再为大家慢慢剖析。 2.1 JDK 在Hadoop 3中,所有的Hadoop JAR包编译的环境都是基于Java8来完成的,所有如果仍然使用的是Java 7或者更低的版本,你可能需要升级到Java 8才能正常的运行Hadoop3。如下图所示: 2.2 EC技术 首先,我们先来了解一下什么是Erasure Encoding。如下图所示: 一般来说,在存储系统中,EC技术主要用于廉价磁盘冗余阵列,即RAID。如上图,RAID通过Stripping实现EC技术,其中逻辑顺序数据(比如:文件)被划分成更小的单元(比如:位、字节或者是块),并将连续单元存储在不同的磁盘上。 然后,对原始数据单元的每个Stripe,计算并存储一定数量的奇偶校验单位。这个过程称之为编码,通过基于有效数据单元和奇偶校验单元的解码计算,可以恢复任意Stripe单元的错误。当我们想到了擦除编码的时候,我们可以先来了解一下在Hadoop2中复制的早期场景。如下图所示: HDFS默认情况下,它的备份系数是3,一个原始数据块和其他2个副本。其中2个副本所需要的存储开销各站100%,这样使得200%的存储开销,会消耗其他资源,比如网络带宽。然而,在正常操作中很少访问具有低IO活动的冷数据集的副本,但是仍然消耗与原始数据集相同的资源量。 对于EC技术,即擦除编码存储数据和提供容错空间较小的开销相比,HDFS复制,EC技术可以代替复制,这将提供相同的容错机制,同时还减少了存储开销。如下图所示: EC和HDFS的整合可以保持与提供存储效率相同的容错。例如,一个副本系数为3,要复制文件的6个块,需要消耗6*3=18个块的磁盘空间。但是,使用EC技术(6个数据块,3个奇偶校验块)来部署,它只需要消耗磁盘空间的9个块(6个数据块+3个奇偶校验块)。这些与原先的存储空间相比较,节省了50%的存储开销。 由于擦除编码需要在执行远程读取时,对数据重建带来额外的开销,因此他通常用于存储不太频繁访问的数据。在部署EC之前,用户应该考虑EC的所有开销,比如存储、网络、CPU等。 2.3 YARN的时间线V.2服务 Hadoop引入YARN Timeline Service v.2是为了解决两个主要问题: 提高时间线服务的可伸缩性和可靠性; 通过引入流和聚合来增强可用性 下面首先,我们来剖析一下它伸缩性。 2.3.1 伸缩性 YARN V1仅限于读写单个实例,不能很好的扩展到小集群之外。YARN V2使用了更具有伸缩性的分布式体系架构和可扩展的后端存储,它将数据的写入与数据的读取进行了分离。并使用分布式收集器,本质上是每个YARN应用的收集器。读则是独立的实例,专门通过REST API服务来查询 2.3.2 可用性 对于可用性的改进,在很多情况下,用户对流或者YARN应用的逻辑组的信息比较感兴趣。启动一组或者一系列的YARN应用程序来完成逻辑应用是很常见的。如下图所示: 2.3.3 架构体系 YARN时间线服务V2采用了一组收集器写数据到后端进行存储。收集器被分配并与它们专用的应用程序主机进行协作,如下图所示,属于该应用程序的所有数据都被发送到应用程序时间轴的收集器中,但是资源管理器时间轴收集器除外。 对于给定的应用程序,应用程序可以将数据写入同一时间轴收集器中。此外,为应用程序运行容器的其他节点的节点管理器,还会向运行应用程序主节点的时间轴收集器写入数据。资源管理器还维护自己的时间手机线收集器,它只发布YARN的通用生命周期事件,以保持其写入量合理。时间的读取器是单独的守护进程从收集器中分离出来的,它旨在服务于REST API查询操作。 3.总结 本篇博客先给大家剖析前面几个特性,其内容由JDK的版本升级、EC技术的作用及优势、YARN的时间轴V2版本的主要作用。Hadoop3后面的几个特性,在下一篇博客为大家再剖析。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 Apache Ignite和Apache Arrow很类似,属于大数据范畴中的内存分布式管理系统。在《Apache Arrow 内存数据》中介绍了Arrow的相关内容,它统一了大数据领域各个生态系统的数据格式,避免了序列化和反序列化所带来的资源开销(能够节省80%左右的CPU资源)。今天来给大家剖析下Apache Ignite的相关内容。 2.内容 Apache Ignite是一个以内存为中心的数据平台,具有强一致性、高可用、强大的SQL、K/V以及其所对应的应用接口(API)。结构分布图如下所示: 在整个Ignite集群中的多个节点中,Ignite内存中的数据模式有三种,分别是LOCAL、REPLICATED和PARTITIONED。这样增加了Ignite的扩展性,Ignite可以自动化的控制数据如何分区,使用者也可以插入自定义的方法,或是为了提供效率将部分数据并存在一起。 Ignite和其他关系型数据库具有相似的行为,但是在处理约束和索引方面略有不同。Ignite支持一级和二级索引,但是只有一级索引支持唯一性。在持久化方面,Ignite固化内存在内存和磁盘中都能良好的工作,但是持久化到磁盘是可以禁用的,一般将Ignite作为一个内存数据库来使用。 由于Ignite是一个全功能的数据网格,它既可以用于纯内存模式,也可以带有Ignite的原生持久化。同时,它还可以与任何第三方的数据库集成,包含RDBMS和NoSQL。比如,在和Hadoop的HDFS、Kafka等,开发基于大数据平台下的SQL引擎,来操作HDFS、Kafka这类的大数据存储介质。 2.1 内存和磁盘 Apache Ignite是基于固化内存架构的,当Ignite持久化存储特性开启时,它可以在内存和磁盘中存储和处理数据和索引。在固化内存和Ignite持久化存储同时开启时,具有以下优势: 2.1.1 内存优势 对外内存 避免显著的GC暂停现象 自动化碎片清理 可预估的内存消耗 高SQL性能 2.1.2 磁盘优势 可选的持久化 支持SSD介质 分布式存储 支持事物 集群瞬时启动 2.2 持久化过程 Ignite的持久化存储时一个分布式的、支持ACID、兼容SQL的磁盘存储。它作为一个可选的磁盘层,可以将数据和索引存储到SSD这类磁盘介质,并且可以透明的与Ignite固化内存进行集成。Ignite的持久化存储具有以下优势: 可以在数据中执行SQL操作,不管数据在内存还是在磁盘中,这意味着Ignite可以作为一个经过内存优化的分布式SQL数据库 可以不用讲所有的数据和索引保持在内存中,持久化存储可以在磁盘上存储数据的大数据集合,然后只在内存中保持访问频繁的数据子集 集群是瞬时启动,如果整个集群宕机,不需要通过预加载数据来对内存进行数据“预热”,只需要将所有集群的节点都连接到一起,整个集群即可正常工作 数据和索引在内存和磁盘中以相似的格式进行存储,避免复杂的格式转化,数据集只是在内存和磁盘之间进行移动 持久化流程如下图所示: 2.3 分布式SQL内存数据库 在Apache Ignite中提供了分布式SQL数据库功能,这个内存数据库可以水平扩展、容错且兼容标准的SQL语法,它支持所有的SQL及DML命令,包含SELECT、INSERT、DELETE等SQL命令。依赖于固化内存架构,数据集和索引可以同时在内存和磁盘中进行存储,这样可以跨越不同的存储层执行分布式SQL操作,来获得可以固化到磁盘的内存级性能。可以使用Java、Python、C++等原生的API来操作SQL与Ignite进行数据交互,也可以使用Ignite的JDBC或者ODBC驱动,这样就具有了真正意义上的跨平台连接性。具体架构体系,如下图所示: 3.代码实践 了解Apache Ignite的作用后,下面我们可以通过模拟编写一个大数据SQL引擎,来实现对Kafka的Topic的查询。首先需要实现一个KafkaSqlFactory的类,具体实现代码如下所示: /** * TODO * * @author smartloli. * * Created by Mar 9, 2018 */ public class KafkaSqlFactory { private static final Logger LOG = LoggerFactory.getLogger(KafkaSqlFactory.class); private static Ignite ignite = null; private static void getInstance() { if (ignite == null) { ignite = Ignition.start(); } } private static IgniteCache<Long, TopicX> processor(List<TopicX> collectors) { getInstance(); CacheConfiguration<Long, TopicX> topicDataCacheCfg = new CacheConfiguration<Long, TopicX>(); topicDataCacheCfg.setName(TopicCache.NAME); topicDataCacheCfg.setCacheMode(CacheMode.PARTITIONED); topicDataCacheCfg.setIndexedTypes(Long.class, TopicX.class); IgniteCache<Long, TopicX> topicDataCache = ignite.getOrCreateCache(topicDataCacheCfg); for (TopicX topic : collectors) { topicDataCache.put(topic.getOffsets(), topic); } return topicDataCache; } public static String sql(String sql, List<TopicX> collectors) { try { IgniteCache<Long, TopicX> topicDataCache = processor(collectors); SqlFieldsQuery qry = new SqlFieldsQuery(sql); QueryCursor<List<?>> cursor = topicDataCache.query(qry); for (List<?> row : cursor) { System.out.println(row.toString()); } } catch (Exception ex) { LOG.error("Query kafka topic has error, msg is " + ex.getMessage()); } finally { close(); } return ""; } private static void close() { try { if (ignite != null) { ignite.close(); } } catch (Exception ex) { LOG.error("Close Ignite has error, msg is " + ex.getMessage()); } finally { if (ignite != null) { ignite.close(); } } } } 然后,模拟编写一个生产者来生产数据,并查询数据集,实现代码如下所示: public static void ignite(){ List<TopicX> collectors = new ArrayList<>(); int count = 0; for (int i = 0; i < 10; i++) { TopicX td = new TopicX(); if (count > 3) { count = 0; } td.setPartitionId(count); td.setOffsets(i); td.setMessage("hello_" + i); td.setTopicName("test"); collectors.add(td); count++; } String sql = "select offsets,message from TopicX where offsets>6 and partitionId in (0,1) limit 1"; long stime = System.currentTimeMillis(); KafkaSqlFactory.sql(sql, collectors); System.out.println("Cost time [" + (System.currentTimeMillis() - stime) / 1000.0 + "]ms"); } 执行结果如下所示: 4.总结 Apache Ignite整体来说,它基本把现在分布式的一些概念都集成了,包含分布式存储、分布式计算、分布式服务、流式计算等等。而且,它对Java语言的支持,与JDK能够很好的整合,能够很友好的兼容JDK的现有API,当你开启一个线程池,你不需要关系是本地线程池还是分布式线程池,只管提交任务就行。Apache Ignite在与RDBMS、Hadoop、Spark、Kafka等传统关系型数据库和主流大数据套件的集成,提供了非常灵活好用的组件API。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 客户端读写数据是先从HBase Master获取RegionServer的元数据信息,比如Region地址信息。在执行数据写操作时,HBase会先写MetaStore,为什么会写到MetaStore。本篇博客将为读者剖析HBase MetaStore和Compaction的详细内容。 2.内容 HBase的内部通信和数据交互是通过RPC来实现,关于HBase的RPC实现机制下篇博客为大家分享。客户端应用程序通过RPC调用HBase服务端的写入、删除、读取等请求,由HBase的Master分配对应的RegionServer进行处理,获取每个RegionServer中的Region地址,写入到HFile文件中,最终进行数据持久化。 在了解HBase MetaStore之前,我们可以先来看看RegionServer的体系结构,其结构图如下所示: 在HBase存储中,虽然Region是分布式存储的最小单元,单并不是存储的最小单元。从图中可知,事实上Region是由一个或者多个Store构成的,每个Store保存一个列族(Columns Family)。而每个Store又由一个MemStore和0到多个StoreFile构成,而StoreFile以HFile的格式最终保存在HDFS上。 2.1 写入流程 HBase为了保证数据的随机读取性能,在HFile中存储RowKey时,按照顺序存储,即有序性。在客户端的请求到达RegionServer后,HBase为了保证RowKey的有序性,不会将数据立即写入到HFile中,而是将每个执行动作的数据保存在内存中,即MetaStore中。MetaStore能够很方便的兼容操作的随机写入,并且保证所有存储在内存中的数据是有序的。当MetaStore到达阀值时,HBase会触发Flush机制,将MetaStore中的数据Flush到HFile中,这样便能充分利用HDFS写入大文件的性能优势,提供数据的写入性能。 整个读写流程,如下所示: 由于MetaStore是存储放在内存中的,如果RegionServer由于出现故障或者进程宕掉,会导致内存中的数据丢失。HBase为了保证数据的完整性,这存储设计中添加了一个WAL机制。每当HBase有更新操作写数据到MetaStore之前,会写入到WAL中(Write AHead Log的简称)。WAL文件会通过追加和顺序写入,WAL的每个RegionServer只有一个,同一个RegionServer上的所有Region写入到同一个WAL文件中。这样即使某一个RegionServer宕掉,也可以通过WAL文件,将所有数据按照顺序重新加载到内容中。 2.2 读取流程 HBase查询通过RowKey来获取数据,客户端应用程序根据对应的RowKey来获取其对应的Region地址。查找Region的地址信息是通过HBase的元数据表来获取的,即hbase:meta表所在的Region。通过读取hbase:meta表可以找到每个Region的StartKey、EndKey以及所属的RegionServer。由于HBase的RowKey是有序分布在Region上,所以通过每个Region的StartKey和EndKey来确定当前操作的RowKey的Region地址。 由于扫描hbase:meta表会比较耗时,所以客户端会存储表的Region地址信息。当请求的Region租约过期时,会重新加载表的Region地址信息。 2.3 Flush机制 RegionServer将数据写入到HFile中不是同步发生的,是需要在MetaStore的内存到达阀值时才会触发。RegionServer中所有的Region的MetaStore的内存占用量达到总内存的设置占用量之后,才会将MetaStore中的所有数据写入到HFile中。同时会记录以及写入的数据的顺序ID,便于WAL的日志清理机制定时删除WAL的无用日志。 MetaStore大小到达阀值后会Flush到磁盘中,关键参数由hbase.hregion.memstore.flush.size属性配置,默认是128MB。在Flush的时候,不会立即去Flush到磁盘,会有一个检测的过程。通过MemStoreFlusher类来实现,具体实现代码如下所示: private boolean flushRegion(final FlushRegionEntry fqe) { HRegion region = fqe.region; if (!region.getRegionInfo().isMetaRegion() && isTooManyStoreFiles(region)) { if (fqe.isMaximumWait(this.blockingWaitTime)) { LOG.info("Waited " + (EnvironmentEdgeManager.currentTime() - fqe.createTime) + "ms on a compaction to clean up 'too many store files'; waited " + "long enough... proceeding with flush of " + region.getRegionNameAsString()); } else { // If this is first time we've been put off, then emit a log message. if (fqe.getRequeueCount() <= 0) { // Note: We don't impose blockingStoreFiles constraint on meta regions LOG.warn("Region " + region.getRegionNameAsString() + " has too many " + "store files; delaying flush up to " + this.blockingWaitTime + "ms"); if (!this.server.compactSplitThread.requestSplit(region)) { try { this.server.compactSplitThread.requestSystemCompaction( region, Thread.currentThread().getName()); } catch (IOException e) { LOG.error( "Cache flush failed for region " + Bytes.toStringBinary(region.getRegionName()), RemoteExceptionHandler.checkIOException(e)); } } } // Put back on the queue. Have it come back out of the queue // after a delay of this.blockingWaitTime / 100 ms. this.flushQueue.add(fqe.requeue(this.blockingWaitTime / 100)); // Tell a lie, it's not flushed but it's ok return true; } } return flushRegion(region, false, fqe.isForceFlushAllStores()); } 从实现方法来看,如果是MetaRegion,会立刻进行Flush,原因在于Meta Region优先级高。另外,判断是不是有太多的StoreFile,这个StoreFile是每次MemStore Flush产生的,每Flush一次就会产生一个StoreFile,所以Store中会有多个StoreFile,即HFile。 另外,在HRegion中也会检查Flush,即通过checkResources()方法实现。具体实现代码如下所示: private void checkResources() throws RegionTooBusyException { // If catalog region, do not impose resource constraints or block updates. if (this.getRegionInfo().isMetaRegion()) return; if (this.memstoreSize.get() > this.blockingMemStoreSize) { blockedRequestsCount.increment(); requestFlush(); throw new RegionTooBusyException("Above memstore limit, " + "regionName=" + (this.getRegionInfo() == null ? "unknown" : this.getRegionInfo().getRegionNameAsString()) + ", server=" + (this.getRegionServerServices() == null ? "unknown" : this.getRegionServerServices().getServerName()) + ", memstoreSize=" + memstoreSize.get() + ", blockingMemStoreSize=" + blockingMemStoreSize); } } 代码中的memstoreSize表示一个Region中所有MemStore的总大小,而其总大小的结算公式为: BlockingMemStoreSize = hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier 其中,hbase.hregion.memstore.flush.size默认是128MB,hbase.hregion.memstore.block.multiplier默认是4,也就是说,当整个Region中所有的MemStore的总大小超过128MB * 4 = 512MB时,就会开始出发Flush机制。这样便避免了内存中数据过多。 3. Compaction 随着HFile文件数量的不断增加,一次HBase查询就可能会需要越来越多的IO操作,其 时延必然会越来越大。因而,HBase设计了Compaction机制,通过执行Compaction来使文件数量基本保持稳定,进而保持读取的IO次数稳定,那么延迟时间就不会随着数据量的增加而增加,而会保持在一个稳定的范围中。 然后,Compaction操作期间会影响HBase集群的性能,比如占用网络IO,磁盘IO等。因此,Compaction的操作就是短时间内,通过消耗网络IO和磁盘IO等机器资源来换取后续的HBase读写性能。 因此,我们可以在HBase集群空闲时段做Compaction操作。HBase集群资源空闲时段也是我们清楚,但是Compaction的触发时段也不能保证了。因此,我们不能在HBase集群配置自动模式的Compaction,需要改为手动定时空闲时段执行Compaction。 Compaction触发的机制有以下几种: 自动触发,配置hbase.hregion.majorcompaction参数,单位为毫秒 手动定时触发:将hbase.hregion.majorcompaction参数设置为0,然后定时脚本执行:echo "major_compact tbl_name" | hbase shell 当选中的文件数量大于等于Store中的文件数量时,就会触发Compaction操作。由属性hbase.hstore.compaction.ratio决定。 至于Region分裂,通过hbase.hregion.max.filesize属性来设置,默认是10GB,一般在HBase生产环境中设置为30GB。 4.总结 在做Compaction操作时,如果数据业务量较大,可以将定时Compaction的频率设置较短,比如:每天凌晨空闲时段对HBase的所有表做一次Compaction,防止在白天繁忙时段,由于数据量写入过大,触发Compaction操作,占用HBase集群网络IO、磁盘IO等机器资源。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1. 概述 Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc. 开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。 2. 内容 在集成Kylin到CDH Hadoop环境中时,发现新版本Kylin-2.2.0无法集成到CDH Hadoop。环境信息如下: Hadoop:CDH-5.4.2,Hadoop-2.6 Hive:Hive-2.1.1 HBase:CDH-5.4.2,HBase-1.0.0 上述版本,如果使用apache-kylin-2.2.0-bin-cdh57.tar.gz集成,Kylin系统可以正常启动,但是在预编译Cube,将编译的结果写入HBase时会出现对应的类找不到。去翻阅CDH-HBase-1.0.0的源代码确实没有对应的类。在Kylin的JIRA中也有记录该现象,大家可以翻阅问题单:[KYLIN-1089] 2.1 Patch 针对该问题可以编辑源代码后,重新编译。需要注意的是,如果你想将pom.xml文件中的属性“hbase-hadoop2.version”改为“1.0.0-cdh5.4.2”,在编译的时候会出现“org.apache.hadoop.hbase.regionserver.ScannerContext.java”找不到。确实,在CDH版的HBase-1.0.0版本中该类不存在,在CDH中最低支持5.5.4,对应的Patch代码如下所示: From c0e053d16fc8fa36947e6181589505b722ea54dd Mon Sep 17 00:00:00 2001 From: shaofengshi <shaofengshi@apache.org> Date: Fri, 11 Nov 2016 08:41:57 +0800 Subject: [PATCH] KYLIN-1089 support CDH 5.5/hbase1.0 --- pom.xml | 12 ++++++------ .../v1/coprocessor/observer/AggregateRegionObserver.java | 4 ++-- .../hbase/cube/v1/filter/TestFuzzyRowFilterV2EndToEnd.java | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 9b84f23..8352e97 100644 --- a/pom.xml +++ b/pom.xml @@ -46,19 +46,19 @@ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Hadoop versions --> - <hadoop2.version>2.6.0-cdh5.7.0</hadoop2.version> - <yarn.version>2.6.0-cdh5.7.0</yarn.version> + <hadoop2.version>2.6.0-cdh5.5.4</hadoop2.version> + <yarn.version>2.6.0-cdh5.5.4</yarn.version> <!-- Hive versions --> - <hive.version>1.1.0-cdh5.7.0</hive.version> - <hive-hcatalog.version>1.1.0-cdh5.7.0</hive-hcatalog.version> + <hive.version>1.1.0-cdh5.5.4</hive.version> + <hive-hcatalog.version>1.1.0-cdh5.5.4</hive-hcatalog.version> <!-- HBase versions --> - <hbase-hadoop2.version>1.2.0-cdh5.7.0</hbase-hadoop2.version> + <hbase-hadoop2.version>1.0.0-cdh5.5.4</hbase-hadoop2.version> <kafka.version>0.8.1</kafka.version> <!-- Hadoop deps, keep compatible with hadoop2.version --> - <zookeeper.version>3.4.5-cdh5.7.0</zookeeper.version> + <zookeeper.version>3.4.5-cdh5.5.4</zookeeper.version> <curator.version>2.7.1</curator.version> <jackson.version>2.2.4</jackson.version> <jsr305.version>3.0.1</jsr305.version> diff --git a/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v1/coprocessor/observer/AggregateRegionObserver.java b/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v1/coprocessor/observer/AggregateRegionObserver.java index 7e25e4c..7139ca7 100644 --- a/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v1/coprocessor/observer/AggregateRegionObserver.java +++ b/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v1/coprocessor/observer/AggregateRegionObserver.java @@ -26,7 +26,7 @@ import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; -import org.apache.hadoop.hbase.regionserver.Region; +import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; import org.apache.hadoop.hbase.regionserver.RegionScanner; import org.apache.kylin.gridtable.StorageSideBehavior; @@ -99,7 +99,7 @@ public class AggregateRegionObserver extends BaseRegionObserver { // start/end region operation & sync on scanner is suggested by the // javadoc of RegionScanner.nextRaw() // FIXME: will the lock still work when a iterator is returned? is it safe? Is readonly attribute helping here? by mhb - Region region = ctxt.getEnvironment().getRegion(); + HRegion region = ctxt.getEnvironment().getRegion(); region.startRegionOperation(); try { synchronized (innerScanner) { diff --git a/storage-hbase/src/test/java/org/apache/kylin/storage/hbase/cube/v1/filter/TestFuzzyRowFilterV2EndToEnd.java b/storage-hbase/src/test/java/org/apache/kylin/storage/hbase/cube/v1/filter/TestFuzzyRowFilterV2EndToEnd.java index 04e2e8b..4e87093 100644 --- a/storage-hbase/src/test/java/org/apache/kylin/storage/hbase/cube/v1/filter/TestFuzzyRowFilterV2EndToEnd.java +++ b/storage-hbase/src/test/java/org/apache/kylin/storage/hbase/cube/v1/filter/TestFuzzyRowFilterV2EndToEnd.java @@ -44,7 +44,6 @@ import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.filter.FilterList.Operator; import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; import org.apache.hadoop.hbase.regionserver.HRegion; -import org.apache.hadoop.hbase.regionserver.Region; import org.apache.hadoop.hbase.regionserver.RegionScanner; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; @@ -225,7 +224,7 @@ public class TestFuzzyRowFilterV2EndToEnd { scan.addFamily(cf.getBytes()); scan.setFilter(filter); List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table.getBytes()); - Region first = regions.get(0); + HRegion first = regions.get(0); first.getScanner(scan); RegionScanner scanner = first.getScanner(scan); List<Cell> results = new ArrayList<Cell>(); -- 2.7.2 安装上述Patch文件中的内容,修改Kylin源代码文件中的内容后,在apache-kylin-2.2.0-bin/build/script/目录中运行package.sh脚本进行编译打包。 2.2 升级HBase版本 由于apache-kylin-2.2.0使用的是HBase1.1.x版本进行编译的,如果不编译Kylin源代码,可以通过升级HBase版本到1.1.x以上。比如,将CDH版的hbase-1.0.0-cdh5.4.2升级到hbase-1.2.0-cdh5.7.0版本。具体升级步骤比较简单这里就不多赘述了。 3. 实战演练 在Kylin-2.2.0中,省略了很多繁琐的配置,许多配置项都改为默认的配置属性了。只需在$KYLIN_HOME/conf目录中,编辑kylin.properties文件,配置如下属性值: kylin.rest.servers=0.0.0.0:7070 kylin.job.jar=/data/soft/new/kylin/lib/kylin-job-2.2.0.jar kylin.coprocessor.local.jar=/data/soft/new/kylin/lib/kylin-coprocessor-2.2.0.jar 在$KYLIN_HOME/bin目录中运行sample.sh脚本,加载批处理Cube。会在Hive仓库中生成如下表: kylin_account kylin_cal_dt kylin_category_groupings kylin_country kylin_sales 这里只是演练MapReduce批处理Cube,对于Spark和Kafka这类流式数据暂不操作。可以在$KYLIN_HOME/bin目录编辑kylin.sh脚本,将Kafka和Spark依赖注释掉。内容如下所示: # .... function retrieveDependency() { #retrive $hive_dependency and $hbase_dependency source ${dir}/find-hive-dependency.sh source ${dir}/find-hbase-dependency.sh source ${dir}/find-hadoop-conf-dir.sh #source ${dir}/find-kafka-dependency.sh #source ${dir}/find-spark-dependency.sh #retrive $KYLIN_EXTRA_START_OPTS if [ -f "${dir}/setenv.sh" ]; then echo "WARNING: ${dir}/setenv.sh is deprecated and ignored, please remove it and use ${KYLIN_HOME}/conf/setenv.sh instead" source ${dir}/setenv.sh fi # ... 然后,运行check-env.sh脚本检测Kylin系统所需要环境依赖,比如Hadoop、Hive、HBase环境变量配置。在启动Kylin系统之前,需要将HBase的hbase-site.xml文件复制到$KYLIN_HOME/conf目录中,并修改该文件的Zookeeper客户端连接地址。在Kylin系统中,读取hbase-site.xml配置文件中的Zookeeper客户端地址时不需要指定2181端口,比如:之前的客户端地址为“dn1:2181,dn2:2181,dn3:2181”,改为“dn1,dn2,dn3”即可。 最后,执行kylin.sh start启动Kylin系统,系统默认登录用户名和密码为ADMIN/KYLIN。 3.1 预编译Cube 在Model中,选择 kylin_sales_cube批处理Cube进行编译,然后在Monitor模块中查看Cube编译的进度,如下图所示: 如果在编译Cube的过程中可能会出现连接异常,如下所示: account.jetbrains.com:10020 failed on connection exception 出现这类问题,是Hadoop的historyserver服务没有启动,执行以下命令启动该进程服务: mr-jobhistory-daemon.sh start historyserver 在编译成功后,在Model模块中,对应的Cube由Disable状态编译Ready状态,如下图所示: 从上图中可以知道,预编译之后的结果是存储在HBase中的,如表名为:KYLIN_Y8ASHHZ0GY 最后,在Insight模块中的SQL编辑区域,编写SQL代码查询对应的结果,如下图所示: 4.总结 在集成的过程当中需要注意版本的兼容性问题。在新版本的Kylin中引入的新特性Diagnosis,如果在预编译Cube中出现错误,在解决不了的情况下,可以使用Diagnosis功能,将编译产生的结果,通过Diagnosis导出发送给Kylin官方寻求解决方式。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 HBase的存储结构和关系型数据库不一样,HBase面向半结构化数据进行存储。所以,对于结构化的SQL语言查询,HBase自身并没有接口支持。在大数据应用中,虽然也有SQL查询引擎可以查询HBase,比如Phoenix、Drill这类。但是阅读这类SQL查询引擎的底层实现,依然是调用了HBase的Java API来实现查询,写入等操作。这类查询引擎在业务层创建Schema来映射HBase表结构,然后通过解析SQL语法数,最后底层在调用HBase的Java API实现。 本篇内容,笔者并不是给大家来介绍HBase的SQL引擎,我们来关注HBase更低层的东西,那就是HBase的存储实现。以及跨集群的HBase集群数据迁移。 2.内容 HBase数据库是唯一索引就是RowKey,所有的数据分布和查询均依赖RowKey。所以,HBase数据库在表的设计上会有很严格的要求,从存储架构上来看,HBase是基于分布式来实现的,通过Zookeeper集群来管理HBase元数据信息,比如表名就存放在Zookeeper的/hbase/table目录下。如下图所示: 2.1 Architecture HBase是一个分布式存储系统,底层数据存储依赖Hadoop的分布式存储系统(HDFS)。HBase架构分三部分来组成,它们分别是:ZooKeeper、HMaster和HRegionServer。 ZooKeeper:HBase的元数据信息、HMaster进程的地址、Master和RegionServer的监控维护(节点之间的心跳,判断节点是否下线)等内容均需要依赖ZooKeeper来完成。是HBase集群中不可缺少的核心之一。 HMaster:HMaster进程在HBase中承担Master的责任,负责一些管理操作,比如给表分配Region、和数据节点的心跳维持等。一般客户端的读写数据的请求操作不会经过Master,所以在分配JVM内存的适合,一般32GB大小即可。 HRegionServer:HRegionServer进程在HBase中承担RegionServer的责任,负责数据的存储。每个RegionServer由多个Region组成,一个Region维护一定区间的RowKey的数据。如下图所示: 图中Region(dn2:16030)维护的RowKey范围为0001~0002。HBase集群的存储结构如下图所示: Zookeeper通常由奇数个组成,便于分布式选举,可参考《分布式系统选举算法剖析》一文了解,这里不多赘述细节。HBase为了保证高可用性(HA),一般都会部署两个Master节点,其中一个作为主,另一个作为Backup节点。这里谁是主,谁是Backup取决于那个HMaster进程能从Zookeeper上对应的Master目录中竞争到Lock,持有该目录Lock的HMaster进程为主Master,而另外一个为Backup,当主Master发生意外或者宕机时,Backup的Master会立刻竞争到Master目录下的Lock从而接管服务,成为主Master对外提供服务,保证HBase集群的高可用性。 2.2 RegionServer HBase负责数据存储的就是RegionServer,简称RS。在HBase集群中,如果只有一份副本时,整个HBase集群中的数据都是唯一的,没有冗余的数据存在,也就是说HBase集群中的每个RegionServer节点上保存的数据都是不一样的,这种模式由于副本数只有一份,即是配置多个RegionServer组成集群,也并不是高可用的。这样的RegionServer是存在单点问题的。虽然,HBase集群内部数据有Region存储和Region迁移机制,RegionServer服务的单点问题可能花费很小的代价可以恢复,但是一旦停止RegionServre上含有ROOT或者META表的Region,那这个问题就严重,由于数据节点RegionServer停止,该节点的数据将在短期内无法访问,需要等待该节点的HRegionServer进程重新启动才能访问其数据。这样HBase的数据读写请求如果恰好指向该节点将会收到影响,比如:抛出连接异常、RegionServer不可用等异常。 3.日志信息 HBase在实现WAL方式时会产生日志信息,即HLog。每一个RegionServer节点上都有一个HLog,所有该RegionServer节点上的Region写入数据均会被记录到该HLog中。HLog的主要职责就是当遇到RegionServer异常时,能够尽量的恢复数据。 在HBase运行的过程当中,HLog的容量会随着数据的写入越来越大,HBase会通过HLog过期策略来进行定期清理HLog,每个RegionServer内部均有一个HLog的监控线程。HLog数据从MemStore Flush到底层存储(HDFS)上后,说明该时间段的HLog已经不需要了,就会被移到“oldlogs”这个目录中,HLog监控线程监控该目录下的HLog,当该文件夹中的HLog达到“hbase.master.logcleaner.ttl”(单位是毫秒)属性所配置的阀值后,监控线程会立即删除过期的HLog数据。 4.数据存储 HBase通过MemStore来缓存Region数据,大小可以通过“hbase.hregion.memstore.flush.size”(单位byte)属性来进行设置。RegionServer在写完HLog后,数据会接着写入到Region的MemStore。由于MemStore的存在,HBase的数据写入并非是同步的,不需要立刻响应客户端。由于是异步操作,具有高性能和高资源利用率等优秀的特性。数据在写入到MemStore中的数据后都是预先按照RowKey的值来进行排序的,这样便于查询的时候查找数据。 5.Region分割 在HBase存储中,通过把数据分配到一定数量的Region来达到负载均衡。一个HBase表会被分配到一个或者多个Region,这些Region会被分配到一个或者多个RegionServer中。在自动分割策略中,当一个Region中的数据量达到阀值就会被自动分割成两个Region。HBase的表中的Region按照RowKey来进行排序,并且一个RowKey所对应的Region只有一个,保证了HBase的一致性。 一个Region中由一个或者多个Store组成,每个Store对应一个列族。一个Store中包含一个MemStore和多个Store Files,每个列族是分开存放以及分开访问的。自动分割有三种策略,分别是: ConstantSizeRegionSplitPolicy:在HBase-0.94版本之前是默认和唯一的分割策略。当某一个Store的大小超过阀值时(hbase.hregion.max.filesize,默认时10G),Region会自动分割。 IncreasingToUpperBoundRegionSplitPolicy:在HBase-0.94中,这个策略分割大小和表的RegionServer中的Region有关系。分割计算公式为:Min(R*R*'hbase.hregion.memstore.flush.size','hbase.hregion.max.filesize'),其中,R表示RegionServer中的Region数。比如:hbase.hregion.memstore.flush.size=256MB,hbase.hregion.max.filesize=20GB,那么第一次分割的大小为Min(1*1*256,20GB)=256MB,也就是在第一次大到256MB会分割成2个Region,后续以此公式类推计算。 KeyPrefixRegionSplitPolicy:可以保证相同前缀的RowKey存放在同一个Region中,可以通过hbase.regionserver.region.split.policy属性来指定分割策略。 6.磁盘合理规划 部署HBase集群时,磁盘和内存的规划是有计算公式的。随意分配可能造成集群资源利用率不高导致存在浪费的情况。公式如下: # 通过磁盘维度的Region数和Java Heap维度的Region数来推导 Disk Size/(RegionSize*ReplicationFactor)=Java Heap*HeapFractionForMemstore/(MemstoreSize/2) 公式中对应的hbase-site.xml文件中的属性中,见下表: Key Property Disk Size 磁盘容量大小,一般一台服务器有多块磁盘 RegionSize hbase.hregion.max.filesize默认10G,推荐范围在10GB~30GB ReplicationFactor dfs.replication默认为3 Java Heap 分配给HBase JVM的内存大小 HeapFractionForMemstore hbase.regionserver.global.memstore.lowerLimit默认为0.4 MemstoreSize hbase.hregion.memstore.flush.size默认为128M 在实际使用中,MemstoreSize空间打下只使用了一半(1/2)的容量。 举个例子,一个RegionServer的副本数配置为3,RegionSize为10G,HBase的JVM内存分配45G,HBase的MemstoreSize为128M,那此时根据公式计算得出理想的磁盘容量为45G*1024*0.4*2*10G*1024*3/128M=8.5T左右磁盘空间。如果此时,分配一个节点中挂载10个可用盘,共27T。那将有两倍的磁盘空间不匹配造成浪费。 为了提升磁盘匹配度,可以将RegionSize值提升至30G,磁盘空间计算得出25.5T,基本和27T磁盘容量匹配。 7.数据迁移 对HBase集群做跨集群数据迁移时,可以使用Distcp方案来进行迁移。该方案需要依赖MapReduce任务来完成,所以在执行迁移命令之前确保新集群的ResourceManager、NodeManager进程已启动。同时,为了查看迁移进度,推荐开启proxyserver进程和historyserver进程,开启这2个进程可以方便在ResourceManager业务查看MapReduce任务进行的进度。 迁移的步骤并不复杂,在新集群中执行distcp命令即可。具体操作命令如下所示: # 在新集群的NameNode节点执行命令 [hadoop@nna ~]$ hadoop distcp -Dmapreduce.job.queue.name=queue_0001_01 -update -skipcrccheck -m 100 hdfs://old_hbase:9000/hbase/data/tabname /hbase/data/tabname 为了迁移方便,可以将上述命令封装成一个Shell脚本。具体实现如下所示: #! /bin/bash for i in `cat /home/hadoop/hbase/tbl` do echo $i hadoop distcp -Dmapreduce.job.queue.name=queue_0001_01 -update -skipcrccheck -m 100 hdfs://old_hbase:9000/hbase/data/$i /hbase/data/$i done hbase hbck -repairHoles 将待迁移的表名记录在/home/hadoop/hbase/tbl文件中,一行代表一个表。内容如下所示: hadoop@nna ~]$ vi /home/hadoop/hbase/tbl # 表名列表 tbl1 tbl2 tbl3 tbl4 最后,在循环迭代迁移完成后,执行HBase命令“hbase hbck -repairHoles”来修复HBase表的元数据,如表名、表结构等内容,会从新注册到新集群的Zookeeper中。 8.总结 HBase集群中如果RegionServer上的Region数量很大,可以适当调整“hbase.hregion.max.filesize”属性值的大小,来减少Region分割的次数。在执行HBase跨集群数据迁移时,使用Distcp方案来进行,需要保证HBase集群中的表是静态数据,换言之,需要停止业务表的写入。如果在执行HBase表中数据迁移时,表持续有数据写入,导致迁移异常,抛出某些文件找不到。 9.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在HBase-1.1.0之前,HBase集群中资源都是全量的。用户、表这些都是没有限制的,看似完美实则隐患较大。今天,笔者就给大家剖析一下HBase的流量限制和表的负载均衡。 2.内容 也许有同学有疑问,为啥要做流量限制,无限制全量跑不是更好吗?举个例子,比如今天的双十一日,数据流量是非常大的。如果不限制用户和表的流量,某些重要的核心业务,需要在资源有限的情况下优先保证正常运行。如果非核心业务在此期间其QPS一直降不下来,严重消耗系统资源,影响核心业务的正常运作。 针对上述问题,可以采取以下方案来解决: 资源限制:针对用户、命名空间及表的请求大小和QPS进行限制。 资源隔离:将不同表中的数据通过物理隔离,均衡到不同的RegionServer上。 3.资源限制 开启HBase资源限制是有条件,其中包含以下两个条件: 版本必须在1.1.0以上,或者在低版本中打上了HBase对应的Patch(HBASE-11598) HBase的资源限制开关默认是关闭的,需要在HBase的配置文件中进行开启。添加内容如下所示: # 编辑HBase配置文件 vi $HBASE_HONE/conf/hbase-site.xml # 添加如下内容 <property> <name>hbase.quota.enabled</name> <value>true</value> </property> # 退出编辑并保存 如果不是在首次启动时配置的,需要额外重启HMaster服务进程才能使之生效。 3.1 Quota语句 HBase中限流是通过Quota语句来操作的,限流的方式有两种,一种是针对用户进行限流;另一种是针对表来进行限流。操作命令如下所示: # 限制用户u1每秒请求10次 hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => '10req/sec' # 限制用户u1每秒的读请求为10次 hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => READ, USER => 'u1', LIMIT => '10req/sec' # 限制用户u1每天的请求量为10M hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => '10M/day' # 限制用户u1的写请求量每秒为10M hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => WRITE, USER => 'u1', LIMIT => '10M/sec' # 限制用户u1在操作表t2时,每分钟的请求量为5K hbase> set_quota TYPE => THROTTLE, USER => 'u1', TABLE => 't2', LIMIT => '5K/min' # 限制用户u1在操作表t2时,每秒的读请求为10次 hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => READ, USER => 'u1', TABLE => 't2', LIMIT => '10req/sec' # 删除用户u1在命令空间ns2的请求限制 hbase> set_quota TYPE => THROTTLE, USER => 'u1', NAMESPACE => 'ns2', LIMIT => NONE # 限制在命名空间ns1中每小时的请求为10次 hbase> set_quota TYPE => THROTTLE, NAMESPACE => 'ns1', LIMIT => '10req/hour' # 限制表t1每小时的请求为10T hbase> set_quota TYPE => THROTTLE, TABLE => 't1', LIMIT => '10T/hour' # 删除用户u1的所有请求限制 hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => NONE # 显示用户u1在命名空间ns2中的所有限制详情 hbase> list_quotas USER => 'u1, NAMESPACE => 'ns2' # 显示命令空间ns2的所有限制详情 hbase> list_quotas NAMESPACE => 'ns2' # 显示表t1的所有限制详情 hbase> list_quotas TABLE => 't1' # 显示所有限制详情 hbase> list_quotas 从操作的命令中可以看出,HBase限制流量支持表和用户。可以通过THROTTLE_TYPE来控制READ(读)、WRITE(写)操作,这类操作在HBase中是随机进行限制的。而LIMIT关键字,可以从两个维度进行资源限制,分别是req/time和size/time。 req/time:这种表示限制单位时间内的请求次数,time可以是秒、分、时、天,req表示次数。 size/time:这种表示单位时间内请求数据的量,time可以是秒、分、时、天,size可以时B (bytes), K (kilobytes), M (megabytes), G (gigabytes), T (terabytes), P (petabytes)。 LIMIT限制默认大小是:10req/day 或 100P/hour。对于命令set_quota来说,执行这条命令仅仅是限制单个RegionServer上的流量,并不是整个集群的限制总量(集群限制总量=每个RegionServer的限制量*RegionNum)。另外,执行set_quota命令后,默认是需要等待300000秒(5分钟)才会生效。如果觉得时间太长,可以将生效时间缩短,通过hbase-site.xml文件中的参数hbase.quota.refresh.period来设置时间,比如: # 一分钟后生效 hbase.quota.refresh.period=60000 3.2 限制命名空间中的表个数 在创建命名空间中的表个数,可以在创建命名空间时指定,也可以在创建之后在此修改表个数,同样也可以删除表限制。通过设置hbase.namespace.quota.maxtables属性值来改变。操作内容如下所示: # 创建一个命令空间最大包含5个表 hbase> create_namespace 'ns1', {'hbase.namespace.quota.maxtables'=>'5'} # 修改一个已存在的命令空间所允许的表数量大小为8个 hbase> alter_namespace 'ns2', {METHOD => 'set', 'hbase.namespace.quota.maxtables'=>'8'} # 显示命令空间下的所有详情 hbase> describe_namespace 'ns2' # 删除命令空间中表个数的限制 hbase> alter_namespace 'ns2', {METHOD => 'unset', NAME=>'hbase.namespace.quota.maxtables'} 3.3 限制命名空间的Region 在创建命名空间时 ,可以限制Region的个数。在创建之后也可以通过命令来修改个数的上限值。具体操作如下所示: # 创建一个命名空间最大包含10个Region hbase> create_namespace 'ns1', {'hbase.namespace.quota.maxregions'=>'10' # 显示命令空间中详情 hbase> describe_namespace 'ns1' # 修改命名空间中最大Region个数为20个 hbase> alter_namespace 'ns2', {METHOD => 'set', 'hbase.namespace.quota.maxregions'=>'20'} # 删除命名空间中Region个数的限制 hbase> alter_namespace 'ns2', {METHOD => 'unset', NAME=> 'hbase.namespace.quota.maxregions'} 这里也许有些同学在操作的过程当中遇到过,在请求操作限制阀值时,日志没有打印出错误信息,这是由于默认日志输出时INFO级别,不会打印这类异常,如果要查看,可以通过修改log4j的日志级别为DEBUG,这样就可以查看到对应的异常信息了。 4.资源隔离 在HBase中可以通过资源隔离的方式来间接的限流。将请求均衡到多个RegionServer中去。通过balance_switch命令来实现自动均衡操作。命令如下: # 查看自动均衡状态 balance_switch status # 停止自动均衡 balance_switch stop # 开启自动均衡 balance_switch start 在实际业务中,如果HBase某个表的RegionServer全部集中在一个上,这时候可以考虑使用move命令手动均衡操作,具体操作语法如下: # move手动操作语法 move [region id] [ServerName] 如下图所示: 从图中一个Table Region来说,”t2,,1510401809742.bd015fc10e75b70a52adc0c32a2321c2.“其中region id为”bd015fc10e75b70a52adc0c32a2321c2“。我们可以在HBase集群客户端执行以下命令来手动指定region。命令如下所示: # 将该Region(dn3)移动到Region(dn1)echo "move 'bd015fc10e75b70a52adc0c32a2321c2','dn1,16020,1510401268652'"|hbase shell 在往HBase表中写数据的时候,默认是往一个Region中写数据,当数据量很大时,才会自动拆分成多个Region,拆分的规则和RowKey设计有关。为了防止出现这种情况,我们可以在创建表的时候进行预分区操作。命令如下所示: # 创建表的预分区(6个Region),RegionTotals = SPLITS.length + 1create 't2', 'cf', SPLITS => ['0001','0002','0003','0004','0005'] 这样我们可以拆分成6个Region,这里也许有同学要问,为什么是6个Region。其实,从上图中就可以看出,表分区中第一个Region是没有StartKey,最后一个Region是没有EndKey的。为什么会出现这种情况,下面就给大家来剖析这个原因。如下图所示: 从图中可知,在第一个Region中只有EndKey,没有StartKey。第一个Region中的EndKey(0001),就是第二个Region的StartKey,以此类推,到最后一个Region就只有StartKey(0005)了。这就是为什么第一个Region没有StartKey,最后一个Region没有EndKey的原因。 其实,我们在使用HBase的Java API获取Region的StartKey和EndKey的时候,有时会出现Null,也就是这个原因。 5.总结 在使用Quota命令进行限流时,需要确保hbase-site.xml文件中的限流属性开启。另外,在对表做手动均衡操作时,使用move命令即可。HBase是有自动均衡的策略的,均衡的Region取决于设计分割的Key,Key的产生又和HBase中中Rowkey的设计息息相关。所以,HBase中表的RowKey设计的是否优秀,决定了Region均衡时,分割Key的选取。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在大数据应用场景中,处理数据分析方面,由于开发者的水平不一样,使用的编程语言也不尽相同,可能会涉及到R、Python、Java、Scala等,数据计算模型也估计不一样,可能涉及的有Spark、Hive、Flink、Kylin等等。本篇博客笔者给大家介绍的内容并不是告诉大家如何去使用。在《Zeppelin使用心得》中有介绍如何使用,这里就不多做赘述了,今天主要是给大家剖析Zeppelin的源码模块。 2.内容 目前Zeppelin官方已经发布版本为0.7.3,源码带托管在Github上,大家可以先将Zeppelin的源码在Github上下载下来。Zeppelin的项目结构是以Maven的形式存在的,由多个Module构成,分为框架核心Module和其他Interpreter Module,源码结构如下图所示: 从截图中可以看出,其实Zeppelin到目前为止,集成了很多插件,比如Beam、HBase、ES、Flink、Kylin、Pig等,这些都是平时大数据场景下常用的。 2.1 模块分析 Zeppelin的入口是ZeppelinServer(在zeppelin-server模块下)这个类下的Main函数,通过Jetty内嵌服务器提供WebSocket服务和Restful服务,还基于Shiro提供了权限认证和用户校验功能,都是使用Java编程语言实现的。在zeppelin-zengine模块下,实现Notebook的持久化和检索功能,同样使用Java语言实现。在zeppelin-interpreter模块下,通过调用zeppelin-zengine中的Thrift服务,来实现解释器的交互功能。在zeppelin-web模块下,用于脚本语言编写以及数据的可视化,使用AngularJS前端框架实现。其他模块详见下表。 名称 说明 实现语言 zeppelin-server 整个系统入口,提供服务器功能、权限认证以及用户校验等功能 Java zeppelin-zengine 实现Zeppelin中Notebook的持久化和检索功能 Java zeppelin-interpreter 执行解释器 Java zeppelin-web 业务脚本语言编写、数据分析界面、数据可视化与结果导出 AngularJS zeppelin-display 让前端的AngularJS元素与后台数据进行绑定,进行数据交互 Scala zeppelin-distribution 用于存放编译后的二进制安装包 zeppelin-examples 示例代码,用于测试 helium-dev 新特性,让解释器,存储插件加入到Zeppelin中时,不需要重启Zeppelin服务 Java Zeppelin项目运用了许多编程语言和框架,属于一个混合项目。 3.源码调试 明白Zeppelin各个模块的功能和作用后,我们可以尝试去调试一下Zeppelin的源码,这里我们将Zeppelin的源码导入到 IDEA 编辑器,然后找到ZeppelinServer启动,如下图所示: 然后选择“Run 'ZeppelinServer.main()'”命令即可,等待服务启动完成,启动成功后,会在编辑器控制台打印日志,如下图所示: 这里,在启动端口默认是8080,在ZeppelinConfiguration类下可以进行编辑,如下图所示: 然后,我们可以到浏览器预览调试结果,如下图所示: 一般在二次开发完成Zeppelin的功能后,我们会将源代码重新编译打包,可以使用Maven打包命令,如下所示: mvn clean package -Pbuild-distr -Dcheckstyle.skip=true -DskipTests 打包完成后,会在zeppelin-distribution的target目录下生成一个二进制的软件安装包。 4.关系图 这里笔者给大家整理了Zeppelin-0.7.3的各个Module之间的关系图,如下所示: 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 Kafka Streams 是一个用来处理流式数据的库,属于Java类库,它并不是一个流处理框架,和Storm,Spark Streaming这类流处理框架是明显不一样的。那这样一个库是做什么的,能应用到哪些场合,如何使用。笔者今天就给大家来一一剖析这些内容。 2.内容 首先,我们研究这样一个库,需要知道它是做什么的。Kafka Streams是一个用来构建流处理应用的库,和Java的那些内置库一样,以一种分布式的容错方式来处理一些事情。当前,业界用于流处理的计算框架包含有:Flink,Spark,Storm等等。Kafka Streams处理完后的结果可以回写到Topic中,也可以外接其他系统进行落地。包含以下特性: 事件区分:记录数据发生的时刻 时间处理:记录数据被流处理应用开始处理的时刻,如记录被消费的时刻 开窗 状态管理:本身应用不需要管理状态,如若需要处理复杂的流处理应用(分组,聚合,连接等) Kafka Streams使用是很简单的,这一点通过阅读官方的示例代码就能发现,另外它利用Kafka的并发模型来完成负载均衡。 2.1 优势 在Kafka集群上,能够很便捷的使用,亮点如下图所示: 能够设计一些轻量级的Client类库,和现有的Java程序整合 不需要额外的Kafka集群,利用现有的Kafka集群的分区实现水平扩展 容错率,高可用性 多平台部署,支持Mac,Linux和Windows系统 权限安全控制 2.2 Sample Kafka Streams是直接构建与Kafka的基础之上的,没有了额外的流处理集群,Table和一些有状态的处理完全整合到了流处理本身。其核心代码非常的简介。简而言之,就和你写Consumer或Producer一样,但是Kafka Streams更加的简洁。 2.3 属性 名称 描述 类型 默认值 级别 application.id 流处理标识,对应一个应用需要保持一致,用作消费的group.id string 高 bootstrap.servers 用来发现Kafka的集群节点,不需要配置所有的Broker list 高 replication.factor 复制因子 int 1 高 state.dir 本地状态存储目录 string /tmp/kafka-streams 高 cache.max.bytes.buffering 所有线程的最大缓冲内存 long 10485760 中 client.id 客户端逻辑名称,用于标识请求位置 string "" 中 default.key.serde 对Key序列化或反序列化类,实现于Serde接口 class org.apache.kafka.common.serialization.Serdes$ByteArraySerde 中 default.value.serde 对Value序列化或反序列化类,实现与Serde接口 class org.apache.kafka.common.serialization.Serdes$ByteArraySerde 中 ... ... ... ... ... 这里只是列举了部分Kafka Streams的属性值,更多的详情可参考Kafka Streams Configs。 3.示例 下面,我们可以通过一个示例代码,来熟悉Kafka Streams的运行流程,如下所示: import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KafkaStreams; import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.kstream.KStream; import org.apache.kafka.streams.kstream.KStreamBuilder; import org.apache.kafka.streams.kstream.KTable; import java.util.Arrays; import java.util.Properties; public class WordCountApplication { public static void main(final String[] args) throws Exception { Properties config = new Properties(); config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount_topic_appid"); config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092,kafka3:9092"); config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); KStreamBuilder builder = new KStreamBuilder(); KStream<String, String> textLines = builder.stream("TextLinesTopic"); KTable<String, Long> wordCounts = textLines .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) .groupBy((key, word) -> word) .count("Counts"); wordCounts.to(Serdes.String(), Serdes.Long(), "WordsWithCountsTopic"); KafkaStreams streams = new KafkaStreams(builder, config); streams.start(); } } 从代码中,我们可以看出Kafka Streams为上层流定义了两种基本抽象: KStream:可以从一个或者多个Topic源来创建 KTable:从一个Topic源来创建 这两者的区别是,前者比较像传统意义上的流,可以把每一个K/V看成独立的,后者的思想更加接近与Map的概念。同一个Key输入多次,后者是会覆盖前者的。而且,KStream和KTable都提供了一系列的转换操作,每个操作可以产生一个或者多个KStream和KTable对象,所有这些转换的方法连接在一起,就形成了一个复杂的Topology。由于KStream和KTable是强类型,这些转换都被定义为通用函数,这样在使用的时候让用户指定输入和输出数据类型。 另外,无状态的转换不依赖于处理的状态,因此不需要状态仓库。有状态的转换则需要进行存储相应的状态用于处理和生成结果。例如,在进行聚合操作的时候,一个窗口状态用于保存当前预定义收到的值,然后转换获取累计的值,再做计算。 在处理完后,对于结果集用户可以持续的将结果回写到Topic,也可以通过KStream.to() 或者 KTable.to() 方法来实现。 4.总结 通过对Kafka Streams的研究,它的优势可以总结为以下几点。首先,它提供了轻量级并且易用的API来有效的降低流数据的开发成本,之前要实现这类处理,需要使用Spark Streaming,Storm,Flink,或者自己编写Consumer。其次,它开发的应用程序可以支持在YARN,Mesos这类资源调度中,使用方式灵活。而对于异步操作,不是很友好,需要谨慎处理;另外,对SQL语法的支持有限,需要额外开发。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 Kafka 快速稳定的发展,得到越来越多开发者和使用者的青睐。它的流行得益于它底层的设计和操作简单,存储系统高效,以及充分利用磁盘顺序读写等特性,和其实时在线的业务场景。对于Kafka来说,它是一个分布式的,可分区的,多副本,多订阅者的,基于Zookeeper统一协调的分布式日志系统。常见的可以用于系统日志,业务日志,消息数据等。那今天笔者给大家分析Kafka的存储机制和副本的相关内容。 2.Replication Replication是Kafka的重要特性之一,针对其Kafka Brokers进行自动调优Replication数,是比较有难度的。原因之一在于要知道怎么避免Follower进入和退出同步 ISR (In-Sync Replicas)。再消息生产的过程当中,在有一大批海量数据写入时,可能会引发Broker告警。如果某些Topic的部分Partition长期处于 “under replicated”,这样是会增加丢失数据的几率的。Kafka 通过多副本机制实现高可用,确保当Kafka集群中某一个Broker宕机的情况下,仍然可用。而 Kafka 的复制算法保证,如果Leader发生故障或者宕机,一个新的Leader会被重新选举出来,并对外提供服务,供客户端写入消息。Kafka 在同步的副本列表中选举一个副本为Leader。 在Topic中,每个分区有一个预写式日志文件,每个分区都由一系列有序,不可变的消息组成,这些消息被连续的追加到分区中,分区中的每个消息都包含一个连续的序列号,即:offset。它用于确定在分区中的唯一位置。如下图所示: 在Kafka中,假如每个Topic的分区有N个副本,由于Kafka通过多副本机制实现故障自动转移,这里需要说明的是,当KafkaController出现故障,进而不能继续管理集群,则那些KafkaController Follower开始竞选新的Leader,而启动的过程则是在KafkaController的startup方法中完成的,如下所示: def startup() = { inLock(controllerContext.controllerLock) { info("Controller starting up") registerSessionExpirationListener() isRunning = true controllerElector.startup info("Controller startup complete") } } 然后启动ZookeeperLeaderElector,在创建临时节点,进行session检查,更新leaderId等操作完成后,会调用故障转移函数onBecomingLeader,也就是KafkaController中的onControllerFailover方法,如下所示: def onControllerFailover() { if(isRunning) { info("Broker %d starting become controller state transition".format(config.brokerId)) readControllerEpochFromZookeeper() incrementControllerEpoch(zkUtils.zkClient) // before reading source of truth from zookeeper, register the listeners to get broker/topic callbacks registerReassignedPartitionsListener() registerIsrChangeNotificationListener() registerPreferredReplicaElectionListener() partitionStateMachine.registerListeners() replicaStateMachine.registerListeners() initializeControllerContext() // We need to send UpdateMetadataRequest after the controller context is initialized and before the state machines // are started. The is because brokers need to receive the list of live brokers from UpdateMetadataRequest before // they can process the LeaderAndIsrRequests that are generated by replicaStateMachine.startup() and // partitionStateMachine.startup(). sendUpdateMetadataRequest(controllerContext.liveOrShuttingDownBrokerIds.toSeq) replicaStateMachine.startup() partitionStateMachine.startup() // register the partition change listeners for all existing topics on failover controllerContext.allTopics.foreach(topic => partitionStateMachine.registerPartitionChangeListener(topic)) info("Broker %d is ready to serve as the new controller with epoch %d".format(config.brokerId, epoch)) maybeTriggerPartitionReassignment() maybeTriggerPreferredReplicaElection() if (config.autoLeaderRebalanceEnable) { info("starting the partition rebalance scheduler") autoRebalanceScheduler.startup() autoRebalanceScheduler.schedule("partition-rebalance-thread", checkAndTriggerPartitionRebalance, 5, config.leaderImbalanceCheckIntervalSeconds.toLong, TimeUnit.SECONDS) } deleteTopicManager.start() } else info("Controller has been shut down, aborting startup/failover") } 正因为有这样的机制存在,所示当Kafka集群中的某个Broker宕机后,仍然保证服务是可用的。在Kafka中发生复制操作时,确保分区的预写式日志有序的写到其他节点,在N个复制因子中,其中一个复制因子角色为Leader,那么其他复制因子的角色则为Follower,Leader处理分区的所有读写请求,同时,Follower会被动的定期去复制Leader上的数据。以上分析可以总结为以下几点,如下所示: Leader负责处理分区的所有读写请求。 Follower会复制Leader上数据。 Kafka 的故障自动转移确保服务的高可用。 3.存储 对于消息对应的性能评估,其文件存储机制设计是衡量的关键指标之一,在分析Kafka的存储机制之前,我们先了解Kafka的一些概念: Broker:Kafka消息中间件节点,一个节点代表一个Broker,多个Broker可以组建成Kafka Brokers,即:Kafka集群。 Topic:消息存储主题,即可以理解为业务数据名,Kafka Brokers能够同时负责多个Topic的处理。 Partition:针对于Topic来说的,一个Topic上可以有多个Partition,每个Partition上的数据是有序的。 Segment:对于Partition更小粒度,一个Partition由多个Segment组成。 Offset:每个Partition上都由一系列有序的,不可变的消息组成,这些消息被连续追加到Partition中。而在其中有一个连续的序列号offset,用于标识消息的唯一性。 3.1 Topic存储 在Kafka文件存储中,同一个Topic下有多个不同的Partition,每个Partition为一个单独的目录,Partition的命名规则为:Topic名称+有序序号,第一个Partition序号从0开始,序号最大值等于Partition的数量减1,如下图所示: 3.2 分区文件存储 每个分区相当于一个超大的文件被均分到多个大小相等的Segment数据文件中,但是每个Segment消息数量不一定相等,正因为这种特性的存在,方便了Old Segment File快速被删除。而对于每个分区只需要支持顺序读写即可,Segment文件生命周期由服务端配置的参数决定。这样即可快速删除无用数据文件,有效提高磁盘利用率。 3.3 Segment文件存储 这里,Segment文件由Index File和Data File组成,文件是一一对应的,后缀为 .index 表示索引文件, .log 表示数据文件,如下图所示: 如上图所示,Segment文件命名规则由分区全局第一个Segment从0开始,后续每一个Segment文件名为上一个Segment文件最后一个消息的Offset值。这里Segment数据文件由许多消息组成,消息物理结构如下所示: Key Describer offset 用于标识每个分区中每条消息的唯一性,Offset的数值标识该分区的第几条消息 message Size 消息大小 CRC32 用CRC32校验消息 “magic” 当前发布Kafka服务程序的协议版本号 “attribute” 独立版本,或标识压缩类型,或者编码类型 key length key的长度 key 可选 payload length 实际消息数据 3.4 分区中查找消息 在分区中,可以通过offset偏移量来查找消息,如上图中,文件00000000000046885905.index的消息起始偏移量为46885906=46885905+1,其他文件依此类推,以起始偏移量命名并排序这些文件,这样能够快速的定位到具体的文件。通过segment file,当offset为46885906时,我们可以定位到00000000000046885905.index元数据物理位置和00000000000046885905.log物理偏移地址。 4.总结 通过对副本和存储机制的分析,我们可以清楚的知道,Kafka通过自动故障转移来确保服务的高可用,Leader负责分区的所有读写操作,Follower会复制Leader上的数据。Kafka针对Topic,使某一个分区中的大文件分成多个小文件,通过多个小的segment file,使之便捷定期清理或删除已经消费的文件,减少磁盘占用。另外,通过索引文件稀疏存储,可以大幅度降低索引文件元数据所占用的空间。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 目前,随着大数据的浪潮,Kafka 被越来越多的企业所认可,如今的Kafka已发展到0.10.x,其优秀的特性也带给我们解决实际业务的方案。对于数据分流来说,既可以分流到离线存储平台(HDFS),离线计算平台(Hive仓库),也可以分流实时流水计算(Storm,Spark)等,同样也可以分流到海量数据查询(HBase),或是及时查询(ElasticSearch)。而今天笔者给大家分享的就是Kafka 分流数据到 ElasticSearch。 2.内容 我们知道,ElasticSearch是有其自己的套件的,简称ELK,即ElasticSearch,Logstash以及Kibana。ElasticSearch负责存储,Logstash负责收集数据来源,Kibana负责可视化数据,分工明确。想要分流Kafka中的消息数据,可以使用Logstash的插件直接消费,但是需要我们编写复杂的过滤条件,和特殊的映射处理,比如系统保留的`_uid`字段等需要我们额外的转化。今天我们使用另外一种方式来处理数据,使用Kafka的消费API和ES的存储API来处理分流数据。通过编写Kafka消费者,消费对应的业务数据,将消费的数据通过ES存储API,通过创建对应的索引的,存储到ES中。其流程如下图所示: 上图可知,消费收集的数据,通过ES提供的存储接口进行存储。存储的数据,这里我们可以规划,做定时调度。最后,我们可以通过Kibana来可视化ES中的数据,对外提供业务调用接口,进行数据共享。 3.实现 下面,我们开始进行实现细节处理,这里给大家提供实现的核心代码部分,实现代码如下所示: 3.1 定义ES格式 我们以插件的形式进行消费,从Kafka到ES的数据流向,只需要定义插件格式,如下所示: { "job": { "content": { "reader": { "name": "kafka", "parameter": { "topic": "kafka_es_client_error", "groupid": "es2", "bootstrapServers": "k1:9094,k2:9094,k3:9094" }, "threads": 6 }, "writer": { "name": "es", "parameter": { "host": [ "es1:9300,es2:9300,es3:9300" ], "index": "client_error_%s", "type": "client_error" } } } } } 这里处理消费存储的方式,将读和写的源分开,配置各自属性即可。 3.2 数据存储 这里,我们通过每天建立索引进行存储,便于业务查询,实现细节如下所示: public class EsProducer { private final static Logger LOG = LoggerFactory.getLogger(EsProducer.class); private final KafkaConsumer<String, String> consumer; private ExecutorService executorService; private Configuration conf = null; private static int counter = 0; public EsProducer() { String root = System.getProperty("user.dir") + "/conf/"; String path = SystemConfigUtils.getProperty("kafka.x.plugins.exec.path"); conf = Configuration.from(new File(root + path)); Properties props = new Properties(); props.put("bootstrap.servers", conf.getString("job.content.reader.parameter.bootstrapServers")); props.put("group.id", conf.getString("job.content.reader.parameter.groupid")); props.put("enable.auto.commit", "true"); props.put("auto.commit.interval.ms", "1000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); consumer = new KafkaConsumer<String, String>(props); consumer.subscribe(Arrays.asList(conf.getString("job.content.reader.parameter.topic"))); } public void execute() { executorService = Executors.newFixedThreadPool(conf.getInt("job.content.reader.threads")); while (true) { ConsumerRecords<String, String> records = consumer.poll(100); if (null != records) { executorService.submit(new KafkaConsumerThread(records, consumer)); } } } public void shutdown() { try { if (consumer != null) { consumer.close(); } if (executorService != null) { executorService.shutdown(); } if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { LOG.error("Shutdown kafka consumer thread timeout."); } } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } class KafkaConsumerThread implements Runnable { private ConsumerRecords<String, String> records; public KafkaConsumerThread(ConsumerRecords<String, String> records, KafkaConsumer<String, String> consumer) { this.records = records; } @Override public void run() { String index = conf.getString("job.content.writer.parameter.index"); String type = conf.getString("job.content.writer.parameter.type"); for (TopicPartition partition : records.partitions()) { List<ConsumerRecord<String, String>> partitionRecords = records.records(partition); for (ConsumerRecord<String, String> record : partitionRecords) { JSONObject json = JSON.parseObject(record.value()); List<Map<String, Object>> list = new ArrayList<>(); Map<String, Object> map = new HashMap<>(); index = String.format(index, CalendarUtils.timeSpan2EsDay(json.getLongValue("_tm") * 1000L)); if (counter < 10) { LOG.info("Index : " + index); counter++; } for (String key : json.keySet()) { if ("_uid".equals(key)) { map.put("uid", json.get(key)); } else { map.put(key, json.get(key)); } list.add(map); } EsUtils.write2Es(index, type, list); } } } } } 这里消费的数据源就处理好了,接下来,开始ES的存储,实现代码如下所示: public class EsUtils { private static TransportClient client = null; static { if (client == null) { client = new PreBuiltTransportClient(Settings.EMPTY); } String root = System.getProperty("user.dir") + "/conf/"; String path = SystemConfigUtils.getProperty("kafka.x.plugins.exec.path"); Configuration conf = Configuration.from(new File(root + path)); List<Object> hosts = conf.getList("job.content.writer.parameter.host"); for (Object object : hosts) { try { client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(object.toString().split(":")[0]), Integer.parseInt(object.toString().split(":")[1]))); } catch (Exception e) { e.printStackTrace(); } } } public static void write2Es(String index, String type, List<Map<String, Object>> dataSets) { BulkRequestBuilder bulkRequest = client.prepareBulk(); for (Map<String, Object> dataSet : dataSets) { bulkRequest.add(client.prepareIndex(index, type).setSource(dataSet)); } bulkRequest.execute().actionGet(); // if (client != null) { // client.close(); // } } public static void close() { if (client != null) { client.close(); } } } 这里,我们利用BulkRequestBuilder进行批量写入,减少频繁写入率。 4.调度 存储在ES中的数据,如果不需要长期存储,比如:我们只需要存储及时查询数据一个月,对于一个月以前的数据需要清除掉。这里,我们可以编写脚本直接使用Crontab来进行简单调用即可,脚本如下所示: #!/bin/sh # <Usage>: ./delete_es_by_day.sh kafka_error_client logsdate 30 </Usage>echo "<Usage>: ./delete_es_by_day.sh kafka_error_client logsdate 30 </Usage>" index_name=$1 daycolumn=$2 savedays=$3 format_day=$4 if [ ! -n "$savedays" ]; then echo "Oops. The args is not right,please input again...." exit 1 fi if [ ! -n "$format_day" ]; then format_day='%Y%m%d' fi sevendayago=`date -d "-${savedays} day " +${format_day}` curl -XDELETE "es1:9200/${index_name}/_query?pretty" -d " { "query": { "filtered": { "filter": { "bool": { "must": { "range": { "${daycolumn}": { "from": null, "to": ${sevendayago}, "include_lower": true, "include_upper": true } } } } } } } }" echo "Finished." 然后,在Crontab中进行定时调度即可。 5.总结 这里,我们在进行数据写入ES的时候,需要注意,有些字段是ES保留字段,比如`_uid`,这里我们需要转化,不然写到ES的时候,会引发冲突导致异常,最终写入失败。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在对Kafka使用层面掌握后,进一步提升分析其源码是极有必要的。纵观Kafka源码工程结构,不算太复杂,代码量也不算大。分析研究其实现细节难度不算太大。今天笔者给大家分析的是其核心处理模块,core模块。 2.内容 首先,我们需要对Kafka的工程结构有一个整体的认知度,Kafka 大家最为熟悉的就是其消费者与生产者。然其,底层的存储机制,选举机制,备份机制等实现细节,需要我们对其源码仔细阅读学习,思考与分析其设计之初的初衷。下面,我们首先来看看Kafka源码工程模块分布,截止当天日期,官方托管在 Github 上的 Kafka 源码版本为:0.10.2.1,其工程分布结构如下图所示: 这里笔记只针对core模块进行说明,其他模块均是启动脚本,文档说明,测试类或是Java客户端的相关代码,本篇博客就不多做赘述了。 模块名 说明 admin kafka的管理员模块,操作和管理其topic,partition相关,包含创建,删除topic,或者拓展分区等。 api 主要负责数据交互,客户端与服务端交互数据的编码与解码。 client 该模块下就一个类,producer读取kafka broker元数据信息,topic和分区,以及leader。 cluster 这里包含多个实体类,有Broker,Cluster,Partition,Replica。其中一个Cluster由多个Broker组成,一个Broker包含多个Partition,一个Topic的所有Partition分布在不同的Broker中,一个Replica包含都个Partition。 common 这是一个通用模块,其只包含各种异常类以及错误验证。 consumer 消费者处理模块,负责所有的客户端消费者数据和逻辑处理。 controller 此模块负责中央控制器的选举,分区的Leader选举,Replica的分配或其重新分配,分区和副本的扩容等。 coordinator 负责管理部分consumer group和他们的offset。 javaapi 提供Java语言的producer和consumer的API接口。 log 这是一个负责Kafka文件存储模块,负责读写所有的Kafka的Topic消息数据。 message 封装多条数据组成一个数据集或者压缩数据集。 metrics 负责内部状态的监控模块。 network 该模块负责处理和接收客户端连接,处理网络时间模块。 producer 生产者的细节实现模块,包括的内容有同步和异步的消息发送。 security 负责Kafka的安全验证和管理模块。 serializer 序列化和反序列化当前消息内容。 server 该模块涉及的内容较多,有Leader和Offset的checkpoint,动态配置,延时创建和删除Topic,Leader的选举,Admin和Replica的管理,以及各种元数据的缓存等内容。 tools 阅读该模块,就是一个工具模块,涉及的内容也比较多。有导出对应consumer的offset值;导出LogSegments信息,以及当前Topic的log写的Location信息;导出Zookeeper上的offset值等内容。 utils 各种工具类,比如Json,ZkUtils,线程池工具类,KafkaScheduler公共调度器类,Mx4jLoader监控加载器,ReplicationUtils复制集工具类,CommandLineUtils命令行工具类,以及公共日志类等内容。 3.源码环境 阅读Kafka源码需要准备以下环境: JDK IDE(Eclipse,IDEA或者其他) gradle 关于环境的搭建,大家可以利用搜索引擎去完成,比较基础,这里就不多赘述了。然后在源码工程目录下执行以下命令: gradle idea(编辑器为IDEA) gradle eclipse(编辑器为Eclipse) 如何选择,可按照自己所使用的编辑器即可。这里笔者所使用的是IDEA,执行命令后,会在源码目录生成以下文件,如下图所示: 然后,在编辑器中导入该源码项目工程即可,如下图所示: 4.运行源码 这里,我们先在config模块下设置server.properties文件,按照自己的需要设置,比如分区数,log的存储路径,zookeeper的地址设置等等。然后,我们在编辑器中的运行中设置相关的启动参数,如下图所示: 启动类Kafka.scala在core模块下,需要注意的是,这里在启动Kafka之前,确保我们之前在server.properties文件中所配置的Zookeeper集群已正常运行,然后我们在编辑器中运行Kafka源码,如下图所示: 5.预览结果 这里,我们做一下简单的修改,在启动类的开头打印一句启动日志和启动时间,部分运行日志和运行结果截图如下所示: Start Kafka,DateTime[1494065094606] [2017-05-06 18:04:54,830] INFO KafkaConfig values: advertised.host.name = null advertised.listeners = null advertised.port = null authorizer.class.name = auto.create.topics.enable = true auto.leader.rebalance.enable = true background.threads = 10 broker.id = 0 broker.id.generation.enable = true broker.rack = null compression.type = producer connections.max.idle.ms = 600000 controlled.shutdown.enable = true 如上图,红色框即是我们简单的添加的一句代码。 6.总结 本篇博客给大家介绍了Kafka源码的core模块下各个子模块所负责的内容,以及如何便捷的去阅读源码,以及在编辑器中运行Kafka源码。后续,再为大家分析Kafka的存储机制,选举机制,备份机制等内容的实现细节。最后,欢迎大家使用Kafka-Eagle监控工具。 7.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在《Kafka 消息监控 - Kafka Eagle》一文中,简单的介绍了 Kafka Eagle这款监控工具的作用,截图预览,以及使用详情。今天笔者通过其源码来解读实现细节。目前该项目已托管于 Github 之上,作者编写了使用手册,告知使用者如何安装,部署,启动该系统。但对于实现的细节并未在参考手册中详细指出。这里,笔者通过本篇博文,来详细解读其实现细节。相关资料文献地址如下所示: Kafka Eagle 源码地址 Kafka Eagle 参考手册 Kafka Eagle 安装包 2.内容 截止到版本 Kafka Eagle v1.1.1 支持监控0.8.2.x(存放消费信息于Zookeeper)以及 0.10.x(存放消费信息于Kafka的topic中)。对于前者,从Zookeeper中获取消息信息,难度不大,编写Zookeeper客户端实现代码即可,该版本在Zookeeper下的存储结构树如下图所示: 对于实现细节,可使用ZkUtils工具类来获取相关数据,以获取消费信息为例,代码如下所示: /** Obtaining kafka consumer information from zookeeper. */ public Map<String, List<String>> getConsumers(String clusterAlias) { ZkClient zkc = zkPool.getZkClient(clusterAlias); Map<String, List<String>> consumers = new HashMap<String, List<String>>(); try { Seq<String> subConsumerPaths = ZkUtils.getChildren(zkc, CONSUMERS_PATH); List<String> groups = JavaConversions.seqAsJavaList(subConsumerPaths); for (String group : groups) { String path = CONSUMERS_PATH + "/" + group + "/owners"; if (ZkUtils.pathExists(zkc, path)) { Seq<String> owners = ZkUtils.getChildren(zkc, path); List<String> ownersSerialize = JavaConversions.seqAsJavaList(owners); consumers.put(group, ownersSerialize); } else { LOG.error("Consumer Path[" + path + "] is not exist."); } } } catch (Exception ex) { LOG.error(ex.getMessage()); } finally { if (zkc != null) { zkPool.release(clusterAlias, zkc); zkc = null; } } return consumers; } 其他监控信息可以按照Zookeeper中结构树路径获取。如下图所示: 然而,对于新版本,官方默认将消费信息迁移到Kafka的topic中,这样原来的接口只能获取topic,broker等信息,对于消费的信息,我们需要从kafka中一个叫__consumer_offsets的topic中获取。为了兼容0.8.2.x版本的Kafka,这里在Kafka Eagle中另外启动一个RpcServer来贡献__consumer_offsets中的消费信息。消费__consumer_offsets这个topic时,需要指定该内部topic不暴露给consumer,将 exclude.internal.topics 设置为 false 即可。这样我们通过一个 kafka.eagle.offset.storage 开关来控制系统获取监控元数据的走向。获取流程如下图所示: 3.消费 Owner 当消费的信息存放于Zookeeper中,我们可以直接从consumer模块下直接获取对应的Owner,但是在Kafka的Topic中,我们需要编码来间接的获取。这里,我们需要知道 Kafka 的Owner的组成规则,其规则由 Group+ConusmerHostAddress+Timespan+UUID+PartitionId组成,实现细节可参考源码,界面展示如下图所示: 4.Kafka SQL 关于Kafka SQL,旨在使用SQL来快速可视化Topic的相关信息,目前 Kafka SQL 实现的功能包含有展示某一个Topic的Partition,Offset,以及其对应的消息记录,若不加limit条件限制,默认展示该Topic下最新的5000条记录,详细实现细节,可参看源码,预览截图如下所示: 查询结果,如下图所示: 5.多集群 Kafka Eagle 目前是支持多集群监控,所谓多集群,是指多个Zookeeper集群下的Kafka集群,通过切换Session来管理不同的Zookeeper集群下的Kafka集群,细节参看源码。管理界面如下图所示: 6.总结 Kafka Eagle总体实现思路基本如上所述。针对,Kafka 0.10.x版本,Kafka Eagle监控部分模块不展示的问题,这里在启动 Kafka Eagle之前,默认启动一个系统consumer来消费kafka.eagle该group下的__system.topic__,保证__consumer_offsets是有数据可供获取的。 7.结束语 这篇博客就和大家分享到这里,该项目会一直维护,喜欢的同学可以在 Github 上 Star 一下,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 Apache Arrow 是 Apache 基金会全新孵化的一个顶级项目。它设计的目的在于作为一个跨平台的数据层,来加快大数据分析项目的运行速度。 2.内容 现在大数据处理模型很多,用户在应用大数据分析时,除了将 Hadoop 等大数据平台作为一个存储和批处理平台之外,同样也得关注系统的扩展性和性能。过去开源社区已经发布了很多工具来完善大数据分析的生态系统,这些工具包含了数据分析的各个层面,例如列式存储格式(Parquet,ORC),内存计算模型(Drill,Spark,Impala 和 Storm)以及其强大的 API 接口。而 Arrow 则是最新加入的一员,它提供了一种跨平台应用的内存数据交换格式。 在数据快速增长和复杂化的情况下,提高大数据分析性能一个重要的途径是对列式数据的设计和处理。列式数据处理借助了向量计算和 SIMD 使我们可以充分挖掘硬件的潜力。而 Apache Drill 其大数据查询引擎无论是在硬盘还是内存中数据都是以列的方式存在的,而 Arrow 就是由 Drill 中的 Value Vector 这一数据格式发展而来。此外,Arrow 也支持关系型和动态数据集。 Arrow 的诞生为大数据生态带来了很多可能性,有了 Arrow 作为今后标准数据交换格式,各个数据分析的系统和应用之间的交互性可以说是揭开了新的篇章。过去大部分的 CPU 周期都花在了数据的序列化与反序列化上,现在我们则能够实现不同系统之间数据的无缝链接。这意味着使用者在不同系统结合时,不用在数据格式上话费过多的时间。 3.Arrow Group Arrow 的内存数据结构如下所示: 从上图中,我们可以很清晰的看出,传统的内存数据格式,各个字段的分布是以没一行呈现,相同字段并未集中排列在一起。而通过 Arrow 格式化后的内存数据,可以将相同字段集中排列在一起。我们可以很方便的使用 SQL 来操作数据。 传统的访问各个数据模型中的数据以及使用 Arrow 后的图,如下所示: 通过上图可以总结出以下观点: 每个系统都有属于自己的内存格式。 70~80% 的 CPU 浪费在序列化和反序列化上。 在多个项目都实现的类似的功能(Copy & Convert)。 而在看上述使用 Arrow 后,得出以下结论: 所有的系统都使用相同的内存格式。 没有跨系统通信开销。 项目可以贡献功能(比如,Parquet 到 Arrow 的读取)。 4.Arrow 数据格式 Arrow 列式数据格式如下所示: persons = [{ name: 'wes', iq: 180, addresses: [ {number: 2, street 'a'}, {number: 3, street 'bb'} ] }, { name: 'joe', iq: 100, addresses: [ {number: 4, street 'ccc'}, {number: 5, street 'dddd'}, {number: 2, street 'f'} ] }] 从上述 JSON 数据格式来看,person.iq 分别是 180 和 100,以如下方式排列: 而 persons.addresses.number 的排列格式如下所示: 5.特性 5.1 Fast Apache Arrow 执行引擎,利用最新的SIMD(单输入多个数据)操作包括在模型处理器,用于分析数据处理本地向量优化。数据的列式布局也允许更好地利用 CPU 缓存,将所有与列操作相关的数据以尽可能紧凑的格式放置。 5.2 Flexible Arrow 扮演着高性能的接口在各个复杂的系统中,它也支持工业化的编程语言。Java,C,C++,Python 以及今后更多的语言。 5.3 Standard Apache Arrow 由 13 个开源项目开发者支持,包含 Calcite, Cassandra, Drill, Hadoop, HBase, Ibis, Impala, Kudu, Pandas, Parquet, Phoenix, Spark, 和 Storm。 6.Example 使用 Python 来处理 Spark 或是 Drill 中的数据,如下图所示: 快速的、语言无关的二进制数据帧格式的文件。 使用 Python 去写。 读取速度接近磁盘 IO 性能。 部分实现示例代码,如下所示: import feather path = 'my_data.feather' feather.write_dataframe(df, path) df = feather.read_dataframe(path) 7.总结 Apache Arrow 当前发布了 0.1.0 第一个版本,官方目前获取的资料的信息较少,大家可以到官方的 JIRA 上获取更多咨询信息,以及 Arrow 提供的开发者聊天室去获取更多的帮助。 8.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 目前,Kafka 官网最新版[0.10.1.1],已默认将消费的 offset 迁入到了 Kafka 一个名为 __consumer_offsets 的Topic中。其实,早在 0.8.2.2 版本,已支持存入消费的 offset 到Topic中,只是那时候默认是将消费的 offset 存放在 Zookeeper 集群中。那现在,官方默认将消费的offset存储在 Kafka 的Topic中,同时,也保留了存储在 Zookeeper 的接口,通过 offsets.storage 属性来进行设置。 2.内容 其实,官方这样推荐,也是有其道理的。之前版本,Kafka其实存在一个比较大的隐患,就是利用 Zookeeper 来存储记录每个消费者/组的消费进度。虽然,在使用过程当中,JVM帮助我们完成了自一些优化,但是消费者需要频繁的去与 Zookeeper 进行交互,而利用ZKClient的API操作Zookeeper频繁的Write其本身就是一个比较低效的Action,对于后期水平扩展也是一个比较头疼的问题。如果期间 Zookeeper 集群发生变化,那 Kafka 集群的吞吐量也跟着受影响。 在此之后,官方其实很早就提出了迁移到 Kafka 的概念,只是,之前是一直默认存储在 Zookeeper集群中,需要手动的设置,如果,对 Kafka 的使用不是很熟悉的话,一般我们就接受了默认的存储(即:存在 ZK 中)。在新版 Kafka 以及之后的版本,Kafka 消费的offset都会默认存放在 Kafka 集群中的一个叫 __consumer_offsets 的topic中。 当然,其实她实现的原理也让我们很熟悉,利用 Kafka 自身的 Topic,以消费的Group,Topic,以及Partition做为组合 Key。所有的消费offset都提交写入到上述的Topic中。因为这部分消息是非常重要,以至于是不能容忍丢数据的,所以消息的 acking 级别设置为了 -1,生产者等到所有的 ISR 都收到消息后才会得到 ack(数据安全性极好,当然,其速度会有所影响)。所以 Kafka 又在内存中维护了一个关于 Group,Topic 和 Partition 的三元组来维护最新的 offset 信息,消费者获取最新的offset的时候会直接从内存中获取。 3.实现 那我们如何实现获取这部分消费的 offset,我们可以在内存中定义一个Map集合,来维护消费中所捕捉到 offset,如下所示: protected static Map<GroupTopicPartition, OffsetAndMetadata> offsetMap = new ConcurrentHashMap<>(); 然后,我们通过一个监听线程来更新内存中的Map,代码如下所示: private static synchronized void startOffsetListener(ConsumerConnector consumerConnector) { Map<String, Integer> topicCountMap = new HashMap<String, Integer>(); topicCountMap.put(consumerOffsetTopic, new Integer(1)); KafkaStream<byte[], byte[]> offsetMsgStream = consumerConnector.createMessageStreams(topicCountMap).get(consumerOffsetTopic).get(0); ConsumerIterator<byte[], byte[]> it = offsetMsgStream.iterator(); while (true) { MessageAndMetadata<byte[], byte[]> offsetMsg = it.next(); if (ByteBuffer.wrap(offsetMsg.key()).getShort() < 2) { try { GroupTopicPartition commitKey = readMessageKey(ByteBuffer.wrap(offsetMsg.key())); if (offsetMsg.message() == null) { continue; } OffsetAndMetadata commitValue = readMessageValue(ByteBuffer.wrap(offsetMsg.message())); offsetMap.put(commitKey, commitValue); } catch (Exception e) { e.printStackTrace(); } } } } 在拿到这部分更新后的offset数据,我们可以通过 RPC 将这部分数据共享出去,让客户端获取这部分数据并可视化。RPC 接口如下所示: namespace java org.smartloli.kafka.eagle.ipc service KafkaOffsetServer{ string query(1:string group,2:string topic,3:i32 partition), string getOffset(), string sql(1:string sql), string getConsumer(), string getActiverConsumer() } 这里,如果我们不想写接口来操作 offset,可以通过 SQL 来操作消费的 offset 数组,使用方式如下所示: 引入依赖JAR <dependency> <groupId>org.smartloli</groupId> <artifactId>jsql-client</artifactId> <version>1.0.0</version> </dependency> 使用接口 JSqlUtils.query(tabSchema, tableName, dataSets, sql); tabSchema:表结构;tableName:表名;dataSets:数据集;sql:操作的SQL语句。 4.预览 消费者预览如下图所示: 正在消费的关系图如下所示: 消费详细 offset 如下所示: 消费和生产的速率图,如下所示: 5.总结 这里,说明一下,当 offset 存入到 Kafka 的topic中后,消费线程ID信息并没有记录,不过,我们通过阅读Kafka消费线程ID的组成规则后,可以手动生成,其消费线程ID由:Group+ConsumerLocalAddress+Timespan+UUID(8bit)+PartitionId,由于消费者在其他节点,我们暂时无法确定ConsumerLocalAddress。最后,欢迎大家使用 Kafka 集群监控 ——[ Kafka Eagle ],[ 操作手册 ]。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在存储业务数据的时候,随着业务的增长,Hive 表存储在 HDFS 的上的数据会随时间的增加而增加,而以 Text 文本格式存储在 HDFS 上,所消耗的容量资源巨大。那么,我们需要有一种方式来减少容量的成本。而在 Hive 中,有一种 ORC 文件格式可以极大的减少存储的容量成本。今天,笔者就为大家分享如何实现流式数据追加到 Hive ORC 表中。 2.内容 2.1 ORC 这里,我们首先需要知道 Hive 的 ORC 是什么。在此之前,Hive 中存在一种 RC 文件,而 ORC 的出现,对 RC 这种文件做了许多优化,这种文件格式可以提供一种高效的方式来存储 Hive 数据,使用 ORC 文件可以提供 Hive 的读写以及性能。其优点如下: 减少 NameNode 的负载 支持复杂数据类型(如 list,map,struct 等等) 文件中包含索引 块压缩 ... 结构图(来源于 Apache ORC 官网)如下所示: 这里笔者就不一一列举了,更多详情,可以阅读官网介绍:[入口地址] 2.2 使用 知道了 ORC 文件的结构,以及相关作用,我们如何去使用 ORC 表,下面我们以创建一个处理 Stream 记录的表为例,其创建示例 SQL 如下所示: create table alerts ( id int , msg string ) partitioned by (continent string, country string) clustered by (id) into 5 buckets stored as orc tblproperties("transactional"="true"); // currently ORC is required for streaming 需要注意的是,在使用 Streaming 的时候,创建 ORC 表,需要使用分区分桶。 下面,我们尝试插入一下数据,来模拟 Streaming 的流程,代码如下所示: String dbName = "testing"; String tblName = "alerts"; ArrayList<String> partitionVals = new ArrayList<String>(2); partitionVals.add("Asia"); partitionVals.add("India"); String serdeClass = "org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe"; HiveEndPoint hiveEP = new HiveEndPoint("thrift://x.y.com:9083", dbName, tblName, partitionVals); 如果,有多个分区,我们这里可以将分区存放在分区集合中,进行加载。这里,需要开启 metastore 服务来确保 Hive 的 Thrift 服务可用。 //------- Thread 1 -------// StreamingConnection connection = hiveEP.newConnection(true); DelimitedInputWriter writer = new DelimitedInputWriter(fieldNames,",", endPt); TransactionBatch txnBatch = connection.fetchTransactionBatch(10, writer); ///// Batch 1 - First TXN txnBatch.beginNextTransaction(); txnBatch.write("1,Hello streaming".getBytes()); txnBatch.write("2,Welcome to streaming".getBytes()); txnBatch.commit(); if(txnBatch.remainingTransactions() > 0) { ///// Batch 1 - Second TXN txnBatch.beginNextTransaction(); txnBatch.write("3,Roshan Naik".getBytes()); txnBatch.write("4,Alan Gates".getBytes()); txnBatch.write("5,Owen O’Malley".getBytes()); txnBatch.commit(); txnBatch.close(); connection.close(); } txnBatch = connection.fetchTransactionBatch(10, writer); ///// Batch 2 - First TXN txnBatch.beginNextTransaction(); txnBatch.write("6,David Schorow".getBytes()); txnBatch.write("7,Sushant Sowmyan".getBytes()); txnBatch.commit(); if(txnBatch.remainingTransactions() > 0) { ///// Batch 2 - Second TXN txnBatch.beginNextTransaction(); txnBatch.write("8,Ashutosh Chauhan".getBytes()); txnBatch.write("9,Thejas Nair" getBytes()); txnBatch.commit(); txnBatch.close(); } connection.close(); 接下来,我们对 Streaming 数据进行写入到 ORC 表进行存储。实现结果如下图所示: 3.案例 下面,我们来完成一个完整的案例,有这样一个场景,每天有许多业务数据上报到指定服务器,然后有中转服务将各个业务数据按业务拆分后转发到各自的日志节点,再由 ETL 服务将数据入库到 Hive 表。这里,我们只说说入库 Hive 表的流程,拿到数据,处理后,入库到 Hive 的 ORC 表中。具体实现代码如下所示: /** * @Date Nov 24, 2016 * * @Author smartloli * * @Email smartdengjie@gmail.com * * @Note TODO */ public class IPLoginStreaming extends Thread { private static final Logger LOG = LoggerFactory.getLogger(IPLoginStreaming.class); private String path = ""; public static void main(String[] args) throws Exception { String[] paths = SystemConfigUtils.getPropertyArray("hive.orc.path", ","); for (String str : paths) { IPLoginStreaming ipLogin = new IPLoginStreaming(); ipLogin.path = str; ipLogin.start(); } } @Override public void run() { List<String> list = FileUtils.read(this.path); long start = System.currentTimeMillis(); try { write(list); } catch (Exception e) { LOG.error("Write PATH[" + this.path + "] ORC has error,msg is " + e.getMessage()); } System.out.println("Path[" + this.path + "] spent [" + (System.currentTimeMillis() - start) / 1000.0 + "s]"); } public static void write(List<String> list) throws ConnectionError, InvalidPartition, InvalidTable, PartitionCreationFailed, ImpersonationFailed, InterruptedException, ClassNotFoundException, SerializationError, InvalidColumn, StreamingException { String dbName = "default"; String tblName = "ip_login_orc"; ArrayList<String> partitionVals = new ArrayList<String>(1); partitionVals.add(CalendarUtils.getDay()); String[] fieldNames = new String[] { "_bpid", "_gid", "_plat", "_tm", "_uid", "ip", "latitude", "longitude", "reg", "tname" }; StreamingConnection connection = null; TransactionBatch txnBatch = null; try { HiveEndPoint hiveEP = new HiveEndPoint("thrift://master:9083", dbName, tblName, partitionVals); HiveConf hiveConf = new HiveConf(); hiveConf.setBoolVar(HiveConf.ConfVars.HIVE_HADOOP_SUPPORTS_SUBDIRECTORIES, true); hiveConf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem"); connection = hiveEP.newConnection(true, hiveConf); DelimitedInputWriter writer = new DelimitedInputWriter(fieldNames, ",", hiveEP); txnBatch = connection.fetchTransactionBatch(10, writer); // Batch 1 txnBatch.beginNextTransaction(); for (String json : list) { String ret = ""; JSONObject object = JSON.parseObject(json); for (int i = 0; i < fieldNames.length; i++) { if (i == (fieldNames.length - 1)) { ret += object.getString(fieldNames[i]); } else { ret += object.getString(fieldNames[i]) + ","; } } txnBatch.write(ret.getBytes()); } txnBatch.commit(); } finally { if (txnBatch != null) { txnBatch.close(); } if (connection != null) { connection.close(); } } } } PS:建议使用多线程来处理数据。 4.预览 实现结果如下所示: 分区详情 该分区下记录数 5.总结 在使用 Hive Streaming 来实现 ORC 追加的时候,除了表本身需要分区分桶以外,工程本身的依赖也是复杂,会设计 Hadoop Hive 等项目的依赖包,推荐使用 Maven 工程来实现,由 Maven 工程去帮我们解决各个 JAR 包之间的依赖问题。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在 Kafka 集群中,我们可以对每个 Topic 进行一个或是多个分区,并为该 Topic 指定备份数。这部分元数据信息都是存放在 Zookeeper 上,我们可以使用 zkCli 客户端,通过 ls 和 get 命令来查看元数据信息。通过 log.dirs 属性控制消息存放路径,每个分区对应一个文件夹,文件夹命名方式为:TopicName-PartitionIndex,该文件夹下存放这该分区的所有消息和索引文件,如下图所示: 2.内容 Kafka 集群在生产消息入库的时候,通过 Key 来进行分区存储,按照相应的算法,生产分区规则,让所生产的消息按照该规则分布到不同的分区中,以达到水平扩展和负载均衡。而我们在消费这些消息的时候,可以使用多线程来消费该 Topic 下的所有分区中的消息。 分区规则的制定,通过实现 kafka.producer.Partitioner 接口,该接口我们可以进行重写,按照自己的方式去实现分区规则。如下,我们按照 Key 的 Hash 值,然后取模得到分区索引,代码如下所示: package cn.hadoop.hdfs.kafka.partition; import kafka.producer.Partitioner; import kafka.utils.VerifiableProperties; /** * @Date Nov 3, 2016 * * @Author dengjie * * @Note 先 Hash 再取模,得到分区索引 */ public class CustomerPartitioner implements Partitioner { public CustomerPartitioner(VerifiableProperties props) { } public int partition(Object key, int numPartitions) { int partition = 0; String k = (String) key; partition = Math.abs(k.hashCode()) % numPartitions; return partition; } } 在创建 Topic 的时候,若按照上述规则创建分区,分区数最后为 Brokers 的整数倍,这样才能发挥其负载均衡的作用,比如:当前我们集群节点由 3 个 Broker 组成,如下图所示: 2.1 创建分区 我们在创建分区的时候,可以通过 Kafka 提供的客户端命令进行创建,如下,我们创建一个6分区,3备份的一个 Topic,命令如下所示: ./kafka-topics.sh --create --zookeeper k1:2181,k2:2181,k3:2181 --replication-factor 3 --partitions 6 --topic ke_test 这里需要注意的是,指定备份数的时候,备份数要小于等于 Brokers 数。否则创建失败。在创建分区的时候,假设,我们只创建 2 个分区,而我们上述图中, Brokers 有 3 个,会造成有一个 Broker 上没有该 Topic 的分区,以致分布不均。 2.2 分区入库 一般,我们在入库消息的时候,都有使用 Kafka 的 API,如下,我们使用生产 API ,按照上述的 Hash 取模规则,进行分区入库,代码如下所示: package cn.hadoop.hdfs.kafka.partition; import java.util.List; import java.util.Properties; import cn.hadoop.hdfs.kafka.partition.data.FileRead; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; /** * @Date Nov 3, 2016 * * @Author dengjie * * @Note 按照先 Hash 再取模的规则,进行分区入库 */ public class PartitionerProducer { public static void main(String[] args) { producerData(); } private static void producerData() { Properties props = new Properties(); props.put("serializer.class", "kafka.serializer.StringEncoder"); props.put("metadata.broker.list", "k1:9092,k2:9092,k3:9092"); props.put("partitioner.class", "cn.hadoop.hdfs.kafka.partition.CustomerPartitioner"); Producer<String, String> producer = new Producer<String, String>(new ProducerConfig(props)); String topic = "ke_test"; List<String> list = FileRead.readData(); for (int i = 0; i < list.size(); i++) { String k = "key" + i; String v = new String(list.get(i)); producer.send(new KeyedMessage<String, String>(topic, k, v)); if (i == (list.size() - 1)) { return; } } producer.close(); } } 这里,我们分析发现,生产者在生产消息入库时,会按照 CustomerPartitioner 的规则,进行分区入库,在入库时,将 Key 先做 Hash,然后分区数取模(这里分区数是 6).我们计算可以得到一下信息: hashCode("key0") % 6 = 1 hashCode("key1") % 6 = 2 hashCode("key2") % 6 = 3 hashCode("key3") % 6 = 4 hashCode("key4") % 6 = 5 hashCode("key5") % 6 = 0 // ... 以此循环 按照该表述规则进行分区入库。 2.3 分区入库验证 接下里,我们通过 Kafka 的消费者 API 来验证,在消费时,消费 Topic 各分区的详情,代码如下所示: package cn.hadoop.hdfs.kafka.partition; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; import kafka.message.MessageAndMetadata; /** * @Date Nov 3, 2016 * * @Author dengjie * * @Note 通过 Kafka 的消费者 API 验证分区入库的消息 */ public class PartitionerConsumer { public static void main(String[] args) { String topic = "ke_test"; ConsumerConnector consumer = Consumer.createJavaConsumerConnector(createConsumerConfig()); Map<String, Integer> topicCountMap = new HashMap<String, Integer>(); topicCountMap.put(topic, new Integer(1)); Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap); KafkaStream<byte[], byte[]> stream = consumerMap.get(topic).get(0); ConsumerIterator<byte[], byte[]> it = stream.iterator(); while (it.hasNext()) { MessageAndMetadata<byte[], byte[]> mam = it.next(); System.out.println("consume: Partition [" + mam.partition() + "] Message: [" + new String(mam.message()) + "] .."); } } private static ConsumerConfig createConsumerConfig() { Properties props = new Properties(); props.put("group.id", "group1"); props.put("zookeeper.connect", "zk1:2181,zk2:2181,zk3:2181"); props.put("zookeeper.session.timeout.ms", "40000"); props.put("zookeeper.sync.time.ms", "200"); props.put("auto.commit.interval.ms", "1000"); props.put("auto.offset.reset", "smallest"); return new ConsumerConfig(props); } } 这里笔者只是验证消费数据,若在实际生产线上,需将上述单线程消费改造成多线程消费,来提升处理消息的能力。 2.4 验证结果 这里,我们线运行生产者,让其生产消息,并分区入库;然后,在启动消费者,消费消息验证其结果,如下图所示: 3.总结 需要注意的是,分区数建议为 Brokers 的整数倍,让其达到均匀分布;备份数必须小于等于 Brokers。以及,多线程消费的控制,其线程数建议和分区数相等。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 在开发工作当中,消费 Kafka 集群中的消息时,数据的变动是我们所关心的,当业务并不复杂的前提下,我们可以使用 Kafka 提供的命令工具,配合 Zookeeper 客户端工具,可以很方便的完成我们的工作。随着业务的复杂化,Group 和 Topic 的增加,此时我们使用 Kafka 提供的命令工具,已预感到力不从心,这时候 Kafka 的监控系统此刻便尤为显得重要,我们需要观察消费应用的详情。 监控系统业界有很多杰出的开源监控系统。我们在早期,有使用 KafkaMonitor 和 Kafka Manager 等,不过随着业务的快速发展,以及互联网公司特有的一些需求,现有的开源的监控系统在性能、扩展性、和 DEVS 的使用效率方面,已经无法满足了。 因此,我们在过去的时间里,从互联网公司的一些需求出发,从各位 DEVS 的使用经验和反馈出发,结合业界的一些开源的 Kafka 消息监控,用监控的一些思考出发,设计开发了现在 Kafka 集群消息监控系统:Kafka Eagle。 Kafka Eagle 用于监控 Kafka 集群中 Topic 被消费的情况。包含 Lag 的产生,Offset 的变动,Partition 的分布,Owner ,Topic 被创建的时间和修改的时间等信息。下载地址如下所示: [Kafka Eagle 下载地址] [Kafka Eagle Github] 2.内容 Kafka Eagle 涉及以下内容模块: Dashboard Topic(Create & List) Consumers Cluster Info 2.1 Dashboard 我们通过在浏览器中输入 http://host:port/ke,访问 Kafka Eagle 的 Dashboard 页面。该页面包含以下内容: Brokers Topics Zookeepers Consumers Kafka Brokers Graph 展示 Kafka 集群的 Topic 数量,消费者数量,Kafka 的 Brokers 数,以及所属的 Zookeeper 集群信息。Dashboard 信息展示截图如下: 2.2 Topic 在 Topic 模块下,包含创建 Topic 和展示 Topic 信息详情。 2.2.1 Create 通过创建模块可以创建一个自定义分区和备份数的 Topic。如下图所示: 2.2.2 List 该模块下列出 Kafka 集群中所有的 Topic,包含 Topic 的分区数,创建时间以及修改时间,如下图所示: 上图中,每一个 Topic 名称对应一个详情的超链接,通过该链接可以查看该 Topic 的详情,如:分区索引号,Leader,Replicas 和 Isr,如下图所示所示: 2.3 Consumers 该模块显示有消费记录的 Topic 信息,其中包含如下内容: Running Pending Active Topic Graph Offsets Rate Graph 2.4 Cluster Info 该模块显示 Kafka 集群信息和 Zookeeper 集群信息,包含如下内容: Kafka Broker Host & IP Kafka Broker Created & Modify Date Zookeeper Host & IP 3.数据采集 Kafka Eagel 监控的消息数据源,来自于 Zookeeper。由于创建,修改或是消费 Kafka 的消息,都会在 Zookeeper 中进行注册,我们可以从中获取数据的变动,例如:Topic,Brokers,Partitions 以及 Group 等,Kafka 在 Zookeeper 的结构存储,如下图所示: 4.总结 Kafka Eagle 的安装使用很简单,下载安装,配置好 Kafka 集群所属的 Zookeeper 集群地址即可,安装部署文档地址如下: Kafka Eagle 使用文档 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在开发工作当中,提交 Hadoop 任务,任务的运行详情,这是我们所关心的,当业务并不复杂的时候,我们可以使用 Hadoop 提供的命令工具去管理 YARN 中的任务。在编写 Hive SQL 的时候,需要在 Hive 终端,编写 SQL 语句,来观察 MapReduce 的运行情况,长此以往,感觉非常的不便。另外随着业务的复杂化,任务的数量增加,此时我们在使用这套流程,已预感到力不从心,这时候 Hive 的监控系统此刻便尤为显得重要,我们需要观察 Hive SQL 的 MapReduce 运行详情以及在 YARN 中的相关状态。 因此,我们经过调研,从互联网公司的一些需求出发,从各位 DEVS 的使用经验和反馈出发,结合业界的一些大的开源的 Hadoop SQL 消息监控,用监控的一些思考出发,设计开发了现在这样的监控系统:Hive Falcon。 Hive Falcon 用于监控 Hadoop 集群中被提交的任务,以及其运行的状态详情。其中 Yarn 中任务详情包含任务 ID,提交者,任务类型,完成状态等信息。另外,还可以编写 Hive SQL,并运 SQL,查看 SQL 运行详情。也可以查看 Hive 仓库中所存在的表及其表结构等信息。下载地址,如下所示: [Hive Falcon 下载地址] [GitHub源代码托管地址] 2.内容 Hive Falcon 涉及以下内容: Dashboard Query Tables Tasks Clients & Nodes 2.1 Dashboard 我们通过在浏览器中输入 http://host:port/hf,访问 Hive Falcon 的 Dashboard 页面。该页面包含以下内容: Hive Clients Hive Tables Hadoop DataNodes YARN Tasks Hive Clients Graph 如下图所示: 2.2 Query Query 模块下,提供一个运行 Hive SQL 的界面,该界面可以用来查看观察 SQL 运行的 MapReduce 详情。包含 SQL 编辑区,日志输出,以及结果展示。如下图所示: 提示:在 SQL 编辑区可以通过 Alt+/ 快捷键,快速调出 SQL 关键字。 2.3 Tables Tables 展示 Hive 中所有的表信息,包含以下内容: 表名 表类型(如:内部表,外部表等) 所属者 存放路径 创建时间 如下图所示: 每一个表名都附带一个超链接,可以通过该超链接查看该表的表结构,如下图所示: 2.4 Tasks Tasks 模块下所涉及的内容是 YARN 上的任务详情,包含的内容如下所示: All(所有任务) Running(正在运行的任务) Finished(已完成的任务) Failed(以失败的任务) Killed(已失败的任务) 如下图所示: 2.5 Clients & Nodes 该模块展示 Hive Client 详情,以及 Hadoop DataNode 的详情,如下图所示: 2.6 脚本命令 命令 描述 hf.sh start 启动 Hive Falcon hf.sh status 查看 Hive Falcon hf.sh stop 停止 Hive Falcon hf.sh restart 重启 Hive Falcon hf.sh stats 查看 Hive Falcon 在 Linux 系统中所占用的句柄数量 3.数据采集 Hive Falcon 系统的各个模块的数据来源,所包含的内容,如下图所示: 4.总结 Hive Falcon 的安装使用比较简单,下载安装,安装文档的描述进行安装配置即可,安装部署文档地址,如下所示: Hive Falcon 使用文档 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 最近有同学问道,除了使用 Storm 充当实时计算的模型外,还有木有其他的方式来实现实时计算的业务。了解到,在使用 Storm 时,需要编写基于编程语言的代码。比如,要实现一个流水指标的统计,需要去编写相应的业务代码,能不能有一种简便的方式来实现这一需求。在解答了该同学的疑惑后,整理了该实现方案的一个案例,供后面的同学学习参考。 2.内容 实现该方案,整体的流程是不变的,我这里只是替换了其计算模型,将 Storm 替换为 Spark,原先的数据收集,存储依然可以保留。 2.1 Spark Overview Spark 出来也是很久了,说起它,应该并不会陌生。它是一个开源的类似于 Hadoop MapReduce 的通用并行计算模型,它拥有 Hadoop MapReduce 所具有的有点,但与其不同的是,MapReduce 的 JOB 中间输出结果可以保存在内存中,不再需要回写磁盘,因而,Spark 能更好的适用于需要迭代的业务场景。 2.2 Flow 上面只是对 Spark 进行了一个简要的概述,让大家知道其作用,由于本篇博客的主要内容并不是讲述 Spark 的工作原理和计算方法,多的内容,这里笔者就不再赘述,若是大家想详细了解 Spark 的相关内容,可参考官方文档。[参考地址] 接下来,笔者为大家呈现本案例的一个实现流程图,如下图所示: 通过上图,我们可以看出,首先是采集上报的日志数据,将其存放于消息中间件,这里消息中间件采用的是 Kafka,然后在使用计算模型按照业务指标实现相应的计算内容,最后是将计算后的结果进行持久化,DB 的选择可以多样化,这里笔者就直接使用了 Redis 来作为演示的存储介质,大家所示在使用中,可以替换该存储介质,比如将结果存放到 HDFS,HBase Cluster,或是 MySQL 等都行。这里,我们使用 Spark SQL 来替换掉 Storm 的业务实现编写。 3.实现 在介绍完上面的内容后,我们接下来就去实现该内容,首先我们要生产数据源,实际的场景下,会有上报好的日志数据,这里,我们就直接写一个模拟数据类,实现代码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 object KafkaIPLoginProducer { private val uid = Array("123dfe", "234weq","213ssf") private val random = new Random() private var pointer = -1 def getUserID(): String = { pointer = pointer + 1 if (pointer >= users.length) { pointer = 0 uid(pointer) } else { uid(pointer) } } def plat(): String = { random.nextInt(10) + "10" } def ip(): String = { random.nextInt(10) + ".12.1.211" } def country(): String = { "中国" + random.nextInt(10) } def city(): String = { "深圳" + random.nextInt(10) } def location(): JSONArray = { JSON.parseArray("[" + random.nextInt(10) + "," + random.nextInt(10) + "]") } def main(args: Array[String]): Unit = { val topic = "test_data3" val brokers = "dn1:9092,dn2:9092,dn3:9092" val props = new Properties() props.put("metadata.broker.list", brokers) props.put("serializer.class", "kafka.serializer.StringEncoder") val kafkaConfig = new ProducerConfig(props) val producer = new Producer[String, String](kafkaConfig) while (true) { val event = new JSONObject() event .put("_plat", "1001") .put("_uid", "10001") .put("_tm", (System.currentTimeMillis / 1000).toString()) .put("ip", ip) .put("country", country) .put("city", city) .put("location", JSON.parseArray("[0,1]")) println("Message sent: " + event) producer.send(new KeyedMessage[String, String](topic, event.toString)) event .put("_plat", "1001") .put("_uid", "10001") .put("_tm", (System.currentTimeMillis / 1000).toString()) .put("ip", ip) .put("country", country) .put("city", city) .put("location", JSON.parseArray("[0,1]")) println("Message sent: " + event) producer.send(new KeyedMessage[String, String](topic, event.toString)) event .put("_plat", "1001") .put("_uid", "10002") .put("_tm", (System.currentTimeMillis / 1000).toString()) .put("ip", ip) .put("country", country) .put("city", city) .put("location", JSON.parseArray("[0,1]")) println("Message sent: " + event) producer.send(new KeyedMessage[String, String](topic, event.toString)) event .put("_plat", "1002") .put("_uid", "10001") .put("_tm", (System.currentTimeMillis / 1000).toString()) .put("ip", ip) .put("country", country) .put("city", city) .put("location", JSON.parseArray("[0,1]")) println("Message sent: " + event) producer.send(new KeyedMessage[String, String](topic, event.toString)) Thread.sleep(30000) } } } 上面代码,通过 Thread.sleep() 来控制数据生产的速度。接下来,我们来看看如何实现每个用户在各个区域所分布的情况,它是按照坐标分组,平台和用户ID过滤进行累加次数,逻辑用 SQL 实现较为简单,关键是在实现过程中需要注意的一些问题,比如对象的序列化问题。这里,细节的问题,我们先不讨论,先看下实现的代码,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 object IPLoginAnalytics { def main(args: Array[String]): Unit = { val sdf = new SimpleDateFormat("yyyyMMdd") var masterUrl = "local[2]" if (args.length > 0) { masterUrl = args(0) } // Create a StreamingContext with the given master URL val conf = new SparkConf().setMaster(masterUrl).setAppName("IPLoginCountStat") val ssc = new StreamingContext(conf, Seconds(5)) // Kafka configurations val topics = Set("test_data3") val brokers = "dn1:9092,dn2:9092,dn3:9092" val kafkaParams = Map[String, String]( "metadata.broker.list" -> brokers, "serializer.class" -> "kafka.serializer.StringEncoder") val ipLoginHashKey = "mf::ip::login::" + sdf.format(new Date()) // Create a direct stream val kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics) val events = kafkaStream.flatMap(line => { val data = JSONObject.fromObject(line._2) Some(data) }) def func(iter: Iterator[(String, String)]): Unit = { while (iter.hasNext) { val item = iter.next() println(item._1 + "," + item._2) } } events.foreachRDD { rdd => // Get the singleton instance of SQLContext val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext) import sqlContext.implicits._ // Convert RDD[String] to DataFrame val wordsDataFrame = rdd.map(f => Record(f.getString("_plat"), f.getString("_uid"), f.getString("_tm"), f.getString("country"), f.getString("location"))).toDF() // Register as table wordsDataFrame.registerTempTable("events") // Do word count on table using SQL and print it val wordCountsDataFrame = sqlContext.sql("select location,count(distinct plat,uid) as value from events where from_unixtime(tm,'yyyyMMdd') = '" + sdf.format(new Date()) + "' group by location") var results = wordCountsDataFrame.collect().iterator /** * Internal Redis client for managing Redis connection {@link Jedis} based on {@link RedisPool} */ object InternalRedisClient extends Serializable { @transient private var pool: JedisPool = null def makePool(redisHost: String, redisPort: Int, redisTimeout: Int, maxTotal: Int, maxIdle: Int, minIdle: Int): Unit = { makePool(redisHost, redisPort, redisTimeout, maxTotal, maxIdle, minIdle, true, false, 10000) } def makePool(redisHost: String, redisPort: Int, redisTimeout: Int, maxTotal: Int, maxIdle: Int, minIdle: Int, testOnBorrow: Boolean, testOnReturn: Boolean, maxWaitMillis: Long): Unit = { if (pool == null) { val poolConfig = new GenericObjectPoolConfig() poolConfig.setMaxTotal(maxTotal) poolConfig.setMaxIdle(maxIdle) poolConfig.setMinIdle(minIdle) poolConfig.setTestOnBorrow(testOnBorrow) poolConfig.setTestOnReturn(testOnReturn) poolConfig.setMaxWaitMillis(maxWaitMillis) pool = new JedisPool(poolConfig, redisHost, redisPort, redisTimeout) val hook = new Thread { override def run = pool.destroy() } sys.addShutdownHook(hook.run) } } def getPool: JedisPool = { assert(pool != null) pool } } // Redis configurations val maxTotal = 10 val maxIdle = 10 val minIdle = 1 val redisHost = "dn1" val redisPort = 6379 val redisTimeout = 30000 InternalRedisClient.makePool(redisHost, redisPort, redisTimeout, maxTotal, maxIdle, minIdle) val jedis = InternalRedisClient.getPool.getResource while (results.hasNext) { var item = results.next() var key = item.getString(0) var value = item.getLong(1) jedis.hincrBy(ipLoginHashKey, key, value) } } ssc.start() ssc.awaitTermination() } } /** Case class for converting RDD to DataFrame */ case class Record(plat: String, uid: String, tm: String, country: String, location: String) /** Lazily instantiated singleton instance of SQLContext */ object SQLContextSingleton { @transient private var instance: SQLContext = _ def getInstance(sparkContext: SparkContext): SQLContext = { if (instance == null) { instance = new SQLContext(sparkContext) } instance } } 我们在开发环境进行测试的时候,使用 local[k] 部署模式,在本地启动 K 个 Worker 线程来进行计算,而这 K 个 Worker 在同一个 JVM 中,上面的示例,默认使用 local[k] 模式。这里我们需要普及一下 Spark 的架构,架构图来自 Spark 的官网,[链接地址] 这里,不管是在 local[k] 模式,Standalone 模式,还是 Mesos 或是 YARN 模式,整个 Spark Cluster 的结构都可以用改图来阐述,只是各个组件的运行环境略有不同,从而导致他们可能运行在分布式环境,本地环境,亦或是一个 JVM 实利当中。例如,在 local[k] 模式,上图表示在同一节点上的单个进程上的多个组件,而对于 YARN 模式,驱动程序是在 YARN Cluster 之外的节点上提交 Spark 应用,其他组件都是运行在 YARN Cluster 管理的节点上的。 而对于 Spark Cluster 部署应用后,在进行相关计算的时候会将 RDD 数据集上的函数发送到集群中的 Worker 上的 Executor,然而,这些函数做操作的对象必须是可序列化的。上述代码利用 Scala 的语言特性,解决了这一问题。 4.结果预览 在完成上述代码后,我们执行代码,看看预览结果如下,执行结果,如下所示: 4.1 启动生产线程 4.2 Redis 结果预览 5.总结 整体的实现内容不算太复杂,统计的业务指标,这里我们使用 SQL 来完成这部分工作,对比 Storm 来说,我们专注 SQL 的编写就好,难度不算太大。可操作性较为友好。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 Flink 1.1.0 版本已经在官方发布了,官方博客于 2016-08-08 更新了 Flink 1.1.0 的变动。在这 Flink 版本的发布,添加了 SQL 语法这一特性。这对于业务场景复杂,依赖于 SQL 来分析统计数据,算得上是一个不错的福利。加上之前有同学和朋友邮件中提到,Flink 官方给的示例运行有困难,能否整合一下 Flink 的案例。笔者通过本篇博客来解答一下相关疑问。 2.内容 2.1 集群部署 首先,集群的部署需要 JDK 环境。下载 JDK 以及配置 JAVA_HOME 环境,这里就不详述了,比较简单。然后,我们去下载 Flink 1.1.0 的安装包,进入到下载页面,如下图所示: 这里需要注意的是,Flink 集群的部署,本身不依赖 Hadoop 集群,如果用到 HDFS 或是 HBase 中的存储数据,就需要选择对应的 Hadoop 版本。大家可以根据 Hadoop 集群的版本,选择相应的 Flink 版本下载。 下载好 Flink 1.1.0 后,按以下步骤进行: 解压 Flink 安装包到 Master 节点 tar xzf flink-*.tgz cd flink-* 配置 Master 和 Slaves vi $FLINK_HOME/conf/master vi $FLINK_HOME/conf/slaves 分发 scp -r flink-1.1.0 hadoop@dn2:/opt/soft/flink scp -r flink-1.1.0 hadoop@dn3:/opt/soft/flink 这里只用了2个 slave 节点。另外,在 flink-conf.yaml 文件中,可以按需配置,较为简单。就不多赘述了。 启动集群 bin/start-cluster.sh 注意,这里没有使用 YARN 来启动集群,若是需要使用 YARN 启动集群,可以参考官方文档进行启动。地址 Flink 集群启动后,系统有一个 WebUI 监控界面,如下图所示: 2.2 案例 这里,我们使用 Flink SQL 的 API 来运行一个场景,对一个销售表做一个聚合计算。这里,笔者将实现代码进行了分解,首先是获取操作 Flink 系统的对象,如下所示: ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); BatchTableEnvironment tableEnv = TableEnvironment.getTableEnvironment(env); 接着是读取数据源,并注册为表,如下所示: CsvTableSource csvTableSource = new CsvTableSource(inPath, new String[] { "trans_id", "part_dt", "lstg_format_name", "leaf_categ_id", "lstg_site_id", "slr_segment_cd", "price", "item_count", "seller_id" }, new TypeInformation<?>[] { Types.LONG(), Types.STRING(), Types.STRING(), Types.LONG(), Types.INT(), Types.INT(), Types.FLOAT(), Types.LONG(), Types.LONG() }); tableEnv.registerTableSource("user", csvTableSource); Table tab = tableEnv.scan("user"); 这里 inPath 使用了 HDFS 上的数据路径。类型可以在 Hive 中使用 desc 命令查看该表的类型。然后,将“表”转化为数据集,如下所示: DataSet<KylinSalesDomain> ds = tableEnv.toDataSet(tab, KylinSalesDomain.class); tableEnv.registerDataSet("user2", ds, "trans_id,part_dt,lstg_format_name,leaf_categ_id,lstg_site_id,slr_segment_cd,price,item_count,seller_id"); Table result = tableEnv.sql("SELECT lstg_format_name as username,SUM(FLOOR(price)) as total FROM user2 group by lstg_format_name"); 最后,对结果进行存储,这里笔者将结果存在了 HDFS 上。如下所示: TableSink<?> sink = new CsvTableSink(outPath, "|"); result.writeToSink(sink); env.setParallelism(1); env.execute("Flink Sales SUM"); 注意,这里并发数是可以设置的,通过 setParallelism 方法来设置并发数。 完整示例,如下所示: try { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); BatchTableEnvironment tableEnv = TableEnvironment.getTableEnvironment(env); CsvTableSource csvTableSource = new CsvTableSource(args[0], new String[] { "trans_id", "part_dt", "lstg_format_name", "leaf_categ_id", "lstg_site_id", "slr_segment_cd", "price", "item_count", "seller_id" }, new TypeInformation<?>[] { Types.LONG(), Types.STRING(), Types.STRING(), Types.LONG(), Types.INT(), Types.INT(), Types.FLOAT(), Types.LONG(), Types.LONG() }); tableEnv.registerTableSource("user", csvTableSource); Table tab = tableEnv.scan("user"); DataSet<KylinSalesDomain> ds = tableEnv.toDataSet(tab, KylinSalesDomain.class); tableEnv.registerDataSet("user2", ds, "trans_id,part_dt,lstg_format_name,leaf_categ_id,lstg_site_id,slr_segment_cd,price,item_count,seller_id"); Table result = tableEnv.sql("SELECT lstg_format_name as username,SUM(FLOOR(price)) as total FROM user2 group by lstg_format_name"); TableSink<?> sink = new CsvTableSink(args[1], "|"); // write the result Table to the TableSink result.writeToSink(sink); // execute the program env.setParallelism(1); env.execute("Flink Sales SUM"); } catch (Exception e) { e.printStackTrace(); } 最后,我们将应用提交到 Flink 集群。如下所示: flink run flink_sales_sum.jar hdfs://master:8020/user/hive/warehouse/kylin_sales/DEFAULT.KYLIN_SALES.csv hdfs://master:8020/tmp/result3 3.Hive 对比 同样的语句,在 Hive 下运行之后,与在 Flink 集群下运行之后,结果如下所示: Hive 运行结果: Flink 运行结果: 通过 WebUI 监控界面观察,任务在 Flink 集群中运行所花费的时间在 2s 以内。其运行速度是比较具有诱惑力的。 4.总结 总体来说,Flink 集群的部署较为简单,其 SQL 的 API 编写需要对官方的文档比较熟悉,需要注意的是,在本地运行 Flink 代码,若是要读取远程 HDFS 文件,那么获取 Flink 对象操作环境,需要采用远程接口(HOST & PORT),或者在本地部署一个开发集群环境,将远程数据源提交到本地 Flink 集群环境运行。若是,读取本地文件,则不需要。其中的原因是当你以集群的方式运行,Flink 会检查本地是否有 Flink 集群环境存在,如若不存在,则会出现远程数据源(如:HDFS 路径地址无法解析等错误)。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 最近收到一些同学和朋友的邮件,说能不能整理一下 Hadoop 生态圈的相关内容,然后分享一些,我觉得这是一个不错的提议,于是,花了一些业余时间整理了 Hadoop 的生态系统,并将其进行了归纳总结,进而将其以表格的形式进行了罗列。涉及的内容有以下几点: 分布式文件系统 分布式编程模型 NoSQL 数据库 SQL-On-Hadoop 数据采集 编程服务中间件 调度系统 系统部署 数据可视化 2.内容 2.1 分布式文件系统 2.1.1 Apache HDFS 在分布式文件系统当中,首先为大家所熟悉的是 Apache 的 HDFS。全称为 Hadoop Distributed File System,由多台机器组建的集群,存储大数据文件。HDFS 的灵感来自于 Google File System(GFS)。Hadoop 2.x 版本之前,NameNode 是存在单点故障的。在 ZooKeeper 的高可用性功能解决了 HDFS 的这个问题,通过提供运行两个冗余的节点在同一个集群中进行主备切换,即:Active & Standby 相关链接地址如下所示: Apache Hadoop Google File System Cloudera Hortonworks 2.1.2 Red Hat GlusterFS GlusterFS 是一个扩展的网络附加存储文件系统。GlusterFS 最初是由 Gluster 公司开发的,然后,由 Red Hat 公司在2011年进行了购买。2012年六月,Red Hat 存储服务器被宣布为商业支持的整合与 Red Hat 企业 Linux GlusterFS。Gluster 文件系统,现在称为 Red Hat 存储服务器。 相关链接地址如下所示: Gluster 官网 Red Hat Hadoop 插件 2.1.3 QFS QFS 是一个开源的分布式文件系统软件包,用于对 MapReduce 批处理工作负载。她被设计为一种 Apache Hadoop 的 HDFS 另一种选择方案,用于大型加工集群提供更好的性能和成本效率。它用 C++ 和固定占用内存管理。QFS 使用 Reed-Solomon 纠错保证可靠的数据访问方法。Reed-Solomon 编码在海量存储系统中被广泛应用,以纠正与媒体缺陷相关的突发错误。而不是存储每个文件或是像 HDFS 一样,存储 3+ 次以上,QFS 仅仅需要 1.5 倍的原始容量,因为它存储在哎九个不同的磁盘驱动上。 相关链接地址如下所示: QFS 官网 Github QFS Hadoop-8885 2.1.4 Ceph Filesystem Ceph 是一个免费的软件存储平台,被设计为对象,块和从单一节点到集群的文件存储。它的主要目标是完全分布式无单点鼓掌,可水平扩展到 PB 容量,对多种工作负载的高性能,以及高可用性。 相关链接地址如下所示: Ceph Filesystem 官网 Ceph and Hadoop HADOOP-6253 2.1.5 Lustre file system Lustre 是由 Linux 和 Cluster 演变而来,是为了解决海量存储问题而设计的全新的文件系统。可支持达 1w 节点,PB 的存储容量,100GB/S 的传输速度。Lustre 是基于对象的存储系统,减少元数据服务器的 iNode。它实际上还是将数据条带化到各个存储目标上,所以可以实现高度聚合 IO 能力。Lustre 原生态支持海量小文件读写;且对大文件读写在 Linux 内核做了特殊优化。另外,Lustre 是个对用户透明的 Share 文件系统,条带化数据的位置信息不能完美的暴露出来,所以要用上 Hadoop 的 MapReduce 优势还需要做很多工作。 相关链接地址如下所示: Lustre WiKi Hadoop with Lustre Inter HPC Hadoop 关于分布式文件系统的内容就赘述到这里;其它分布式文件系统,如:Alluxio,GridGain 以及 XtreemFS[1.官网,2.Flink on XtreemFS,3.Spark XtreemFS] 等这里就不多赘述了,大家可以下去自己普及一下。 2.2 分布式编程模型 2.2.1 Apache Ignite Apache Ignite 内存数组组织框架是一个高性能、集成和分布式的内存计算和事务平台,用于大规模的数据集处理,比传统的基于磁盘或闪存的技术具有更高的性能,同时他还为应用和不同的数据源之间提供高性能、分布式内存中数据组织管理的功能。 它包含一个分布式的 Key/Value 存储在内存中,SQL 执行能力,MapReduce 和其它计算,分布式数据结构,连续查询,消息和事件子系统。Hadoop 和 Spark 均有集成。Ignite 编译于 Java,提供 .NET 和 C++ 的 API 接口。 相关链接地址如下所示: Apache Ignite Apache Ignite Documentation 2.2.2 Apache MapReduce 这个大家应该不陌生,这是一个经典的编程模型,用于在集群上处理并发,分布式大数据集。当前版本编译于 YARN 框架。这里就不多赘述了。 相关链接地址,如下所示: Apache MapReduce Google MapReduce Paper Writing YARN Applications 2.2.3 Apache Spark 这个编程模型,大家也不会陌生,现在 Spark 的应用场景和社区活跃度较高。快速的执行能力,丰富的编程 API 接口,使其备受恩宠。 相关链接地址,如下所示: Apache Spark Mirror of Spark on Github RDDs-Paper Spark Cluster Computing Spark Research 2.2.4 Apache Storm 做实时流水数据处理的同学,应该也不陌生,可以嫁接多种消息中间件(如Kafka,MQ等)。 相关链接地址,如下所示: Storm Project Storm-on-YARN 2.2.5 Apache Flink Apache Flink 是一个面向分布式数据流处理和批量数据处理的开源计算平台,它能够基于同一个Flink运行时(Flink Runtime),提供支持流处理和批处理两种类型应用的功能。现有的开源计算方案,会把流处理和批处理作为两种不同的应用类型,因为他们它们所提供的SLA是完全不相同的:流处理一般需要支持低延迟、Exactly-once保证,而批处理需要支持高吞吐、高效处理,所以在实现的时候通常是分别给出两套实现方法,或者通过一个独立的开源框架来实现其中每一种处理方案。例如,实现批处理的开源方案有MapReduce、Tez、Crunch、Spark,实现流处理的开源方案有Samza、Storm。 Flink在实现流处理和批处理时,与传统的一些方案完全不同,它从另一个视角看待流处理和批处理,将二者统一起来:Flink是完全支持流处理,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。基于同一个Flink运行时(Flink Runtime),分别提供了流处理和批处理API,而这两种API也是实现上层面向流处理、批处理类型应用框架的基础。 相关链接地址,如下所示: Apache Flink Stratosphere site 这里列举了热度较高的分布式编程模型,其它的编程模型,如下表所示: 分布式编程模型 相关链接地址 Apache Pig 1.官网 2.示例 JAQL 1.JAQLL in Google Code 2.What is JAQL? Facebook Corona 1.Corona on Github Apache Twill 1.Twill 官网 Apache Tez 1.Tez 官网 2.Hortonworks Apacha Tez Page 2.3 NoSQL 数据库 2.3.1 列数据模型 2.3.1.1 Apache HBase 灵感来自于 Google 的 BigTable。非关系性分布式数据库。随机实时读写操作列扩展的大表。 相关链接地址,如下所示: Apache HBase Home HBase on Github 2.3.1.2 Apache Cassandra Apache Cassandra 是一套开源分布式 Key-Value 存储系统。它最初由 Facebook 开发,用于储存特别大的数据。 Cassandra 不是一个数据库,它是一个混合型的非关系的数据库,类似于 Google 的 BigTable。Cassandra 的数据模型是基于列族(Column Family)的四维或五维模型。它借鉴了 Amazon 的 Dynamo 和 Google's BigTable 的数据结构和功能特点,采用 Memtable 和 SSTable 的方式进行存储。在 Cassandra 写入数据之前,需要先记录日志 ( CommitLog ),然后数据开始写入到 Column Family 对应的 Memtable 中,Memtable 是一种按照 key 排序数据的内存结构,在满足一定条件时,再把 Memtable 的数据批量的刷新到磁盘上,存储为 SSTable 。 相关链接地址,如下所示: Cassandra On Github Training Resources Cassandra-Paper</> 2.3.1.3 Apache Kudu Kudu 是 Cloudera 开源的列式存储引擎,具有一下几个特点: C++ 语言开发 高效处理类 OLAP 负载 与 MR,Spark 以及 Hadoop 生态系统中其它组件友好集成 可以与 Cloudera Impala 集成 灵活的一致性模型 顺序和随机写并存的场景下,仍能达到良好的性能 高可用,使用 Raft 协议保证数据高可靠存储 结构化数据模型 相关链接地址,如下所示: Apache Kudu Home Kudu on Github Kudu Technical 2.3.2 文档数据模型 2.3.2.1 MongoDB 面向文档的数据库系统。它是数据库系统中 NoSQL 家族的一部分。MongoDB 存储结构化数据以 JSON 格式的文件形式进行存储。 相关链接地址,如下所示: MongoDB 官网 2.3.3 Key-Value 数据模型 2.3.3.1 Redis 数据库 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 相关链接地址,如下所示: Redis Home Redis Labs 2.4 SQL-On-Hadoop 2.4.1 Apache Hive 一款由 Facebook 开发的数据仓库。数据聚合,查询和分析。提供类 SQL 语言:HiveQL 相关链接地址,如下所示: Apache Hive Home Hive on Github 2.4.2 Apache Trafodion Trafodion是一个构建在Hadoop/HBase基础之上的关系型数据库,它完全开源免费。Trafodion能够完整地支持ANSI SQL,并且提供ACID事务保证。和传统关系数据库不同的地方在于,Trafodion利用底层Hadoop的横向扩展能力,可以提供极高的扩展性。而传统数据库,比如MySQL,在数据量达到P级别的时候就很难处理。而Trafodion却可以借助HBase的扩展性,仅通过增加普通Linux服务器就可以增加计算和存储能力,进而支持大数据应用。 相关链接地址,如下所示: Apache Trafodion Home Apache Trafodion WiKi Apache Trafodion On Github 2.4.3 Apache Drill Drill 是 Apache 开源的,用于大数据探索的 SQL 查询引擎。她在大数据应用中,面对结构化数据和变化迅速的数据,她能够去兼容,并且高性能的去分析,同时,还提供业界都熟悉的标准的查询语言,即:ANSI SQL 生态系统。Drill 提供即插即用,在现有的 Hive,HBase,S3 等存储介质中可以随时整合部署。 相关链接地址,如下所示: Apache Drill Home 2.4.4 Cloudera Impala 类似于 Drill 的一款大数据实时查询引擎,依赖 CDH 环境。 相关链接地址,如下所示: Cloudera Impala Home Impala On Github 2.4.5 Apache Kylin Kylin 是一款开源的分布式数据分析引擎由 eBay 公司提供。支持 Hadoop 大数据集 OLAP 业务/ 相关链接地址,如下所示: Apache Kylin Home 另外,还有[Apache Tajo],[Apache Phoenix] 等,这里就不一一列举了。 2.5 数据采集 2.5.1 Apache Flume Flume 是一个分布式,可靠的,可用的服务,有效的收集,聚合和移动海量的日志数据。它有一个简单而灵活的架构,基于流数据流。具有很好的冗余和容错性,以及可靠性和多故障转移和恢复机制。它使用一个简单的可扩展数据模型,并允许在线分析应用。 相关链接地址,如下所示: Apache Flume Home 2.5.2 Apache Sqoop 一款从 HDFS 到 RDBMS 之间做数据交互的工具。类似于 Flume。 相关链接地址,如下所示: Apache Sqoop Project 2.5.3 Apache Kafka 分布式发布-订阅消息系统,用于处理流式海量数据。Kafka 是一个由 LinkedIn 开发的消息队列。能嫁接 HDFS 这样的存储介质,能被 Storm,Spark这类实时或类实时数据模型消费。 相关链接地址,如下所示: Apache Kafka Kafka On Github 2.5.4 Apache NiFi Apache NiFi 是由美国国家安全局(NSA)贡献给 Apache 基金会的开源项目,目前已被顺利孵化完成成为 Apache 的顶级项目之一。Apache NiFi 其设计目标是自动化系统间的数据流。基于其工作流式的编程理念,NiFi 拥有易使用,高可用以及高配置等特性。其尤为突出的两大特性是:强大的用户界面和良好的数据回溯工具。NiFi 的用户界面允许用户在浏览器中直观的理解并与数据流进行交互,快速和安全的进迭代。其数据回溯特性允许用户查看一个对象如何在系统间流转,回放以及可视化关键步骤之前以及之后发生的情况,包括大量复杂的图式转换,Fork,Join 以及其它操作等。另外,NiFi 使用基于组件的扩展模型用以为复杂的数据流快速增加功能,开箱即用的组件中,处理文件系统的包括 FTP,SFTP 以及 HTTP 等,同样也支持 HDFS。 相关链接地址,如下所示: Apache NiFi 另外,还有 Facebook Scribe,Apache Chukwa,Netflix Suro,Apache Samza,Cloudera Morphline,HIHO 等套件就不一一介绍了,大家可以下去了解这些数据采集套件相关内容。 2.6 编程服务中间件 2.6.1 Apache Thrift Thrift 是一个软件框架,用来进行可扩展且跨语言的服务开发。它结合了功能强大的软件堆栈和代码生成引擎,用以构建在 C++,Java,Python,Ruby 等编程语言上,进行无缝,高效的衔接。其最初由 Facebook 开发用做系统内各个语言之间的 RPC 通信,后 Facebook 贡献给 Apache,目前成为 Apache 的顶级项目之一。 相关链接地址,如下所示: Apache Thrift 2.6.2 Apache Zookeeper Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务,状态同步服务,集群管理,分布式应用配置项的管理等。 相关链接地址,如下所示: Apache Zookeeper Google Chubby 2.6.3 Apache Avro Apache Avro 是 Hadoop 中的一个子项目,也是 Apache 中的一个独立的项目,Avro 是一个基于二进制数据传输高性能的中间件。在 Hadoop 的其它项目中,例如 HBase,Hive 的 Client 端与服务端的数据传输也采用了这个工具。Avro 是一个数据序列化的系统,它可以将数据结构或对象转化成便于存储或传输的格式。Avro 设计之初就用来支持数据密集型应用,适合于远程或本地大规模数据的存储和交换。拥有一下特点: 丰富的数据结构类型 快速可压缩的二进制数据形式,对数据二进制序列化后可以节约数据存储空间和网络传输带宽 存储持久数据的文件容器 可以实现远程过程调用 RPC 简单的动态语言结合功能 相关链接地址,如下所示: Apache Avro 另外,还有 Apache Curator,Twitter Elephant Bird,Linkedin Norbert 等工具,这里就不一一介绍了。 2.7 调度系统 2.7.1 Apache Oozie 在 Hadoop 中执行的任务有时候需要把多个 MR 作业连接到一起,这样才能达到目的。在 Hadoop 生态圈中,Oozie 可以把多个 MR 作业组合到一个逻辑工作单元中,从而完成更大型的任务。Oozie 是一种 Java Web 应用程序,它运行在 Java Servlet 容器中(即:Tomcat)中,并使用数据库来存储一下内容: 工作流定义 当前运行的工作流实例,包括实例的状态和变量 Oozie 工作流是放置在控制依赖 DAG 中的一组动作(如 Hadoop 的 MR 作业,Pig 作业等),其中指定了动作执行的顺序。 相关链接地址,如下所示: Apache Oozie Oozie On Github 2.7.2 Linkedin Azkaban Hadoop 工作流管理。提供友好的 Web UI 界面进行批处理作业调度(定时或及时)。 相关链接地址,如下所示: Azkaban Home Azkaban On Github 2.7.3 Apache Falcon Apache Falcon 是一个面向 Hadoop 的,新的数据处理和管理平台,设计用于数据移动,数据管道协调,生命周期管理和数据发现。它使用终端用户可以快速的将他们的数据以及相关的处理和管理任务上载到 Hadoop 集群。在 Apache Falcon 中,基础设施端点,数据集,处理规则均是声明式的。这种声明式配置显式定义了实体之间的依赖关系。这也是该平台的一个特点,它本身只维护依赖关系,而并不做任何繁重的工作,所有的功能和工作流状态管理需求都委托给工作流调度程序来完成。 相关链接地址,如下所示: Apache Falcon 2.8 系统部署 2.8.1 Apache Ambari 用于创建,管理,监控 Hadoop 集群的工具,可以很方便的安装,调试 Hadoop 集群,支持的平台组件也是越来越多,如 Spark,Storm 等计算模型,以及资源调度平台 YARN 等,都能通过 Ambari 轻松部署管理。 相关链接地址,如下所示: Apache Ambari 2.8.2 CDH Cloudera 公司的产品,类似于 Ambari 产品,用于创建,管理,监控 Hadoop 集群。 相关链接地址,如下所示: CDH 2.9 可视化 2.9.1 Apache Zeppelin 你可以制作出漂亮的数据,使用 SQL,Scala 或者其它。它拥有以下特性: 数据收集 数据发掘 数据分析 数据可视化和集成 目前支持的中间件有:Spark,md,sh,Hive,Tajo,Flink,Cassandra,Phoenix,Kylin 等 相关链接地址,如下所示: Apache Zeppelin 3.总结 Hadoop 生态圈是非常庞大的,上述列举的只是其生态圈中常用的一部分,下图给大家展示了本篇博客相关内容的关联图,如下图所示: 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在如今数据爆炸的时代,企业的数据量与日俱增,大数据产品层出不穷。今天给大家分享一款产品—— Apache Flink,目前,已是 Apache 顶级项目之一。那么,接下来,笔者为大家介绍Flink 的相关内容。 2.内容 2.1 What's Flink Apache Flink 是一个面向分布式数据流处理和批量数据处理的开源计算平台,它能够基于同一个Flink运行时(Flink Runtime),提供支持流处理和批处理两种类型应用的功能。现有的开源计算方案,会把流处理和批处理作为两种不同的应用类型,因为他们它们所提供的SLA是完全不相同的:流处理一般需要支持低延迟、Exactly-once保证,而批处理需要支持高吞吐、高效处理,所以在实现的时候通常是分别给出两套实现方法,或者通过一个独立的开源框架来实现其中每一种处理方案。例如,实现批处理的开源方案有MapReduce、Tez、Crunch、Spark,实现流处理的开源方案有Samza、Storm。 Flink在实现流处理和批处理时,与传统的一些方案完全不同,它从另一个视角看待流处理和批处理,将二者统一起来:Flink是完全支持流处理,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。基于同一个Flink运行时(Flink Runtime),分别提供了流处理和批处理API,而这两种API也是实现上层面向流处理、批处理类型应用框架的基础。 Flink 是一款新的大数据处理引擎,目标是统一不同来源的数据处理。这个目标看起来和 Spark 和类似。这两套系统都在尝试建立一个统一的平台可以运行批量,流式,交互式,图处理,机器学习等应用。所以,Flink 和 Spark 的目标差异并不大,他们最主要的区别在于实现的细节。 下面附上 Flink 技术栈的一个总览,如下图所示: 2.2 Compare 了解 Flink 的作用和优缺点,需要有一个参照物,这里,笔者以它与 Spark 来对比阐述。从抽象层,内存管理,语言实现,以及 API 和 SQL 等方面来赘述。 2.2.1 Abstraction 接触过 Spark 的同学,应该比较熟悉,在处理批处理任务,可以使用 RDD,而对于流处理,可以使用 Streaming,然其世纪还是 RDD,所以本质上还是 RDD 抽象而来。但是,在 Flink 中,批处理用 DataSet,对于流处理,有 DataStreams。思想类似,但却有所不同:其一,DataSet 在运行时表现为 Runtime Plans,而在 Spark 中,RDD 在运行时表现为 Java Objects。在 Flink 中有 Logical Plan ,这和 Spark 中的 DataFrames 类似。因而,在 Flink 中,若是使用这类 API ,会被优先来优化(即:自动优化迭代)。如下图所示: 然而,在 Spark 中,RDD 就没有这块的相关优化,如下图所示:: 另外,DataSet 和 DataStream 是相对独立的 API,在 Spark 中,所有不同的 API,比如 Streaming,DataFrame 都是基于 RDD 抽象的。然而在 Flink 中,DataSet 和 DataStream 是同一个公用引擎之上的两个独立的抽象。所以,不能把这两者的行为合并在一起操作,目前官方正在处理这种问题,详见[FLINK-2320] 2.2.2 Memory 在之前的版本(1.5以前),Spark 延用 Java 的内存管理来做数据缓存,这样很容易导致 OOM 或者 GC。之后,Spark 开始转向另外更加友好和精准的控制内存,即:Tungsten 项目。然而,对于 Flink 来说,从一开始就坚持使用自己控制内存。Flink 除把数据存在自己管理的内存之外,还直接操作二进制数据。在 Spark 1.5之后的版本开始,所有的 DataFrame 操作都是直接作用于 Tungsten 的二进制数据上。 PS:Tungsten 项目将是 Spark 自诞生以来内核级别的最大改动,以大幅度提升 Spark 应用程序的内存和 CPU 利用率为目标,旨在最大程度上利用硬件性能。该项目包括了三个方面的改进: 内存管理和二进制处理:更加明确的管理内存,消除 JVM 对象模型和垃圾回收开销。 缓存友好计算:使用算法和数据结构来实现内存分级结构。 代码生成:使用代码生成来利用新型编译器和 CPU。 2.2.3 Program Spark 使用 Scala 来实现的,它提供了 Java,Python 以及 R 语言的编程接口。而对于 Flink 来说,它是使用 Java 实现的,提供 Scala 编程 API。从编程语言的角度来看,Spark 略显丰富一些。 2.2.4 API Spark 和 Flink 两者都倾向于使用 Scala 来实现对应的业务。对比两者的 WordCount 示例,很类似。如下所示,分别为 RDD 和 DataSet API 的示例代码: RDD // Spark WordCount object WordCount { def main(args: Array[String]) { val env = new SparkContext("local","WordCount") val data = List("hi","spark cluster","hi","spark") val dataSet = env.parallelize(data) val words = dataSet.flatMap(value => value.split("\\s+")) val mappedWords = words.map(value => (value,1)) val sum = mappedWords.reduceByKey(_+_) println(sum.collect()) } } DataSet // Flink WordCount object WordCount { def main(args: Array[String]) { val env = ExecutionEnvironment.getExecutionEnvironment val data = List("hello","flink cluster","hello") val dataSet = env.fromCollection(data) val words = dataSet.flatMap(value => value.split("\\s+")) val mappedWords = words.map(value => (value,1)) val grouped = mappedWords.groupBy(0) val sum = grouped.sum(1) println(sum.collect()) } } 对于 Streaming,Spark 把它看成更快的批处理,而 Flink 把批处理看成 Streaming 的特殊例子,差异如下:其一,在实时计算问题上,Flink 提供了基于每个事件的流式处理机制,所以它可以被认为是一个真正意义上的流式计算,类似于 Storm 的计算模型。而对于 Spark 来说,不是基于事件粒度的,而是用小批量来模拟流式,也就是多个事件的集合。所以,Spark 被认为是一个接近实时的处理系统。虽然,大部分应用实时是可以接受的,但对于很多应用需要基于事件级别的流式计算。因而,会选择 Storm 而不是 Spark Streaming,现在,Flink 也许是一个不错的选择。 2.2.5 SQL 目前,Spark SQL 是其组件中较为活跃的一部分,它提供了类似于 Hive SQL 来查询结构化数据,API 依然很成熟。对于 Flink 来说,截至到目前 1.0 版本,只支持 Flink Table API,官方在 Flink 1.1 版本中会添加 SQL 的接口支持。[Flink 1.1 SQL 详情计划] 3.Features Flink 包含一下特性: 高吞吐 & 低延时 支持 Event Time & 乱序事件 状态计算的 Exactly-Once 语义 高度灵活的流式窗口 带反压的连续流模型 容错性 流处理和批处理共用一个引擎 内存管理 迭代 & 增量迭代 程序调优 流处理应用 批处理应用 类库生态 广泛集成 3.1 高吞吐 & 低延时 Flink 的流处理引擎只需要很少配置就能实现高吞吐率和低延迟。下图展示了一个分布式计数的任务的性能,包括了流数据 shuffle 过程。 3.2 支持 Event Time & 乱序事件 Flink 支持了流处理和 Event Time 语义的窗口机制。Event time 使得计算乱序到达的事件或可能延迟到达的事件更加简单。如下图所示: 3.3 状态计算的 exactly-once 语义 流程序可以在计算过程中维护自定义状态。Flink 的 checkpointing 机制保证了即时在故障发生下也能保障状态的 exactly once 语义。 3.4 高度灵活的流式窗口 Flink 支持在时间窗口,统计窗口,session 窗口,以及数据驱动的窗口,窗口可以通过灵活的触发条件来定制,以支持复杂的流计算模式。 3.5 带反压的连续流模型 数据流应用执行的是不间断的(常驻)operators。Flink streaming 在运行时有着天然的流控:慢的数据 sink 节点会反压(backpressure)快的数据源(sources)。 3.6 容错性 Flink 的容错机制是基于 Chandy-Lamport distributed snapshots 来实现的。这种机制是非常轻量级的,允许系统拥有高吞吐率的同时还能提供强一致性的保障。 3.7 流处理和批处理共用一个引擎 Flink 为流处理和批处理应用公用一个通用的引擎。批处理应用可以以一种特殊的流处理应用高效地运行。如下图所示: 3.8 内存管理 Flink 在 JVM 中实现了自己的内存管理。应用可以超出主内存的大小限制,并且承受更少的垃圾收集的开销。 3.9 迭代和增量迭代 Flink 具有迭代计算的专门支持(比如在机器学习和图计算中)。增量迭代可以利用依赖计算来更快地收敛。如下图所示: 3.10 程序调优 批处理程序会自动地优化一些场景,比如避免一些昂贵的操作(如 shuffles 和 sorts),还有缓存一些中间数据。 3.11 流处理应用 DataStream API 支持了数据流上的函数式转换,可以使用自定义的状态和灵活的窗口。下面示例展示了如何以滑动窗口的方式统计文本数据流中单词出现的次数。 case class Word(word: String, freq: Long) val texts: DataStream[String] = ... val counts = text .flatMap { line => line.split("\\W+") } .map { token => Word(token, 1) } .keyBy("word") .timeWindow(Time.seconds(5), Time.seconds(1)) .sum("freq") 3.12 批处理应用 Flink 的 DataSet API 可以使你用 Java 或 Scala 写出漂亮的、类型安全的、可维护的代码。它支持广泛的数据类型,不仅仅是 key/value 对,以及丰富的 operators。下面示例展示了图计算中 PageRank 算法的一个核心循环。 case class Page(pageId: Long, rank: Double) case class Adjacency(id: Long, neighbors: Array[Long]) val result = initialRanks.iterate(30) { pages => pages.join(adjacency).where("pageId").equalTo("pageId") { (page, adj, out : Collector[Page]) => { out.collect(Page(page.id, 0.15 / numPages)) for (n <- adj.neighbors) { out.collect(Page(n, 0.85*page.rank/adj.neighbors.length)) } } } .groupBy("pageId").sum("rank") } 3.13 类库生态 Flink 栈中提供了很多高级 API 和满足不同场景的类库:机器学习、图分析、关系式数据处理。当前类库还在 beta 状态,并且在大力发展。 3.14 广泛集成 Flink 与开源大数据处理生态系统中的许多项目都有集成。Flink 可以运行在 YARN 上,与 HDFS 协同工作,从 Kafka 中读取流数据,可以执行 Hadoop 程序代码,可以连接多种数据存储系统。如下图所示: 4.总结 以上,便是对 Flink 做一个简要的剖析认识,至于如何使用 Flink,以及其编译,安装,部署,运行等流程,较为简单,这里就不多做赘述了,大家可以在 Flink 的官网,阅读其 QuickStart 即可,[访问地址]。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 上次给大家分享了关于 Kafka SQL 的实现思路,这次给大家分享如何实现 Kafka SQL。要实现 Kafka SQL,在上一篇《Kafka - SQL 引擎分享》中分享了其实现的思路,核心包含数据源的加载,以及 SQL 树的映射。今天笔者给大家分享相关实现的代码。 2.内容 这里,将数据映射成 SQL Tree 是使用了 Apache Calcite 来承接这部分工作。在实现代码之前,我们首先来了解下 Apache Calcite 的相关内容,Apache Calcite 是一个面向 Hadoop 的查询引擎,它提供了业界标准的 SQL 语言,以及多种查询优化和连接各种存储介质的适配器。另外,还能处理 OLAP 和流处理场景。因为存在这么多优秀和闪光的特性, Hadoop 生态圈中 Apache Calcite 越发引人注目,被诸多项目所集成,常见的有: Apache Drill:基于大数据的实时查询引擎 Apache Spark:继 Hadoop 之后的新一代大数据分布式处理框架。 更多详情,这里就不一一列举了,详情查看地址:《Adapters》 2.1 数据类型 这里数据源的数据类型,我们分为两种,一种是 SQL,另一种是基于编程语言的,这里我们所使用的是 Java,定义内容如下: public static Map<String, SqlTypeName> SQLTYPE_MAPPING = new HashMap<String, SqlTypeName>(); public static Map<String, Class> JAVATYPE_MAPPING = new HashMap<String, Class>(); public static void initRowType() { SQLTYPE_MAPPING.put("char", SqlTypeName.CHAR); JAVATYPE_MAPPING.put("char", Character.class); SQLTYPE_MAPPING.put("varchar", SqlTypeName.VARCHAR); JAVATYPE_MAPPING.put("varchar", String.class); // ...... } 2.2 表的相关描述 另外,我们需要对表进行一个描述,在关系型数据库中,一个正常的表由行列组成,定义内容如下: public static class Database { public List<Table> tables = new LinkedList<Table>(); } public static class Table { public String tableName; public List<Column> columns = new LinkedList<Column>(); public List<List<String>> data = new LinkedList<List<String>>(); } public static class Column { public String name; public String type; } 在每个集合中存储数据库相关名称,每个数据库存储多个集合的表对象,每个表对象下面又有一系列的列以及绑定的数据源。在每个列对象中包含字段名和类型,层层递进,依次关联。在使用 Calcite 是,需要遵循其 JSON Model,上篇博客我们已经定义过其 JSON Model,这里我们直接拿来使用,内容如下: { version: '1.0', defaultSchema: 'kafka', schemas: [ { name: 'kafka', type: 'custom', factory: 'cn.smartloli.kafka.visual.engine.KafkaMemorySchemaFactory', operand: { database: 'kafka_db' } } ] } 要实现其 Model ,这里需要我们去实现 org.apache.calcite.schema.SchemaFactory 的接口,内容如下所示: public class KafkaMemorySchemaFactory implements SchemaFactory { @Override public Schema create(SchemaPlus parentSchema, String name, Map<String, Object> operand) { return new KafkaMemorySchema(name); } } 而在 KafkaMemorySchema 类中,我们只需要实现它的 getTableMap 方法,内容如下所示: @Override protected Map<String, Table> getTableMap() { Map<String, Table> tables = new HashMap<String, Table>(); Database database = KafkaMemoryData.MAP.get(this.dbName); if (database == null) return tables; for (KafkaMemoryData.Table table : database.tables) { tables.put(table.tableName, new KafkaMemoryTable(table)); } return tables; } 从上述代码中,可以知道通过内存中的 Map 表查看对应的数据库对象,然后根据数据库对象中的表作为 Schema 中的表,而表的类型为 KafkaMemoryTable。 2.3 表类型 这里笔者就直接使用全表扫描,使用 org.apache.calcite.schema.impl.AbstractTable 的默认方式,实现其 getRowType 方法和 scan 方法,内容如下所示: public RelDataType getRowType(RelDataTypeFactory typeFactory) { if(dataType == null) { RelDataTypeFactory.FieldInfoBuilder fieldInfo = typeFactory.builder(); for (KafkaMemoryData.Column column : this.sourceTable.columns) { RelDataType sqlType = typeFactory.createJavaType( KafkaMemoryData.JAVATYPE_MAPPING.get(column.type)); sqlType = SqlTypeUtil.addCharsetAndCollation(sqlType, typeFactory); fieldInfo.add(column.name, sqlType); } this.dataType = typeFactory.createStructType(fieldInfo); } return this.dataType; } public Enumerable<Object[]> scan(DataContext root) { final List<String> types = new ArrayList<String>(sourceTable.columns.size()); for(KafkaMemoryData.Column column : sourceTable.columns) { types.add(column.type); } final int[] fields = identityList(this.dataType.getFieldCount()); return new AbstractEnumerable<Object[]>() { public Enumerator<Object[]> enumerator() { return new KafkaMemoryEnumerator<Object[]>(fields, types, sourceTable.data); } }; } 代码中,表中的字段名和类型是根据初始化时,每个表中的数据类型映射匹配的,在 KafkaMemoryData.SQLTYPE_MAPPING 和 KafkaMemoryData.JAVATYPE_MAPPING 中有描述相关自定义类型映射,这里就不多做赘述了。 实现流程大致就是这个样子,将每次的 SQL 查询,通过 Calcite 解析成标准可执行的 SQL 计划,执行期间会根据定义的信息,初始化每一个 Schema,在通过调用 getTableMap 获取字段名和类型,根据这些信息判断查询的表,字段名,类型以及 SQL 语法是否标准规范。然后在使用 Calcite 内部机制,生成物理执行计划。查询计划是 Tree 形式的,底层是进行扫表操作(可看作为 FROM),获取每个表的数据,之后在根据表数据进行上层的关联操作,如 JOIN,GROUP BY,LIMIT 等操作。 3.测试 完成上述流程后,进行代码测试,测试代码如下所示: public static void main(String[] args) { try { Class.forName("org.apache.calcite.jdbc.Driver"); } catch (Exception ex) { ex.printStackTrace(); } Properties info = new Properties(); try { Connection connection = DriverManager.getConnection("jdbc:calcite:model=/Users/dengjie/hadoop/workspace/kafka/kafka-visual/src/main/resources/plugins.json",info); Statement st = connection.createStatement(); // String sql = "select * from \"Kafka\" where \"_plat\"='1004' limit 1"; String sql = "select * from \"Kafka\" limit 10"; long start = System.currentTimeMillis(); result = st.executeQuery(sql); ResultSetMetaData rsmd = result.getMetaData(); List<Map<String, Object>> ret = new ArrayList<Map<String,Object>>(); while (result.next()) { Map<String, Object> map = new HashMap<String, Object>(); for (int i = 1; i <= rsmd.getColumnCount(); i++) { System.out.print(result.getString(rsmd.getColumnName(i)) + " "); map.put(rsmd.getColumnName(i), result.getString(rsmd.getColumnName(i))); } ret.add(map); System.out.println(); } System.out.println(new Gson().toJson(ret)); result.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } 4.总结 以上便是将 Kafka 中数据消费后,作为数据源加载和 SQL Tree 映射的实现代码,实现不算太困难,在编写 SQL 查询的时候,需要遵循标准的 SQL 语法来操作数据源。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉! 联系方式: 邮箱:smartloli.org@gmail.com Twitter:https://twitter.com/smartloli QQ群(Hadoop - 交流社区1):424769183 温馨提示:请大家加群的时候写上加群理由(姓名+公司/学校),方便管理员审核,谢谢! 热爱生活,享受编程,与君共勉! 作者:哥不是小萝莉 [关于我][犒赏] 出处:http://www.cnblogs.com/smartloli/ 转载请注明出处,谢谢合作!
1.概述 大多数情况下,我们使用 Kafka 只是作为消息处理。在有些情况下,我们需要多次读取 Kafka 集群中的数据。当然,我们可以通过调用 Kafka 的 API 来完成,但是针对不同的业务需求,我们需要去编写不同的接口,在经过编译,打包,发布等一系列流程。最后才能看到我们预想的结果。那么,我们能不能有一种 简便的方式去实现这一部分功能,通过编写 SQL 的方式,来可视化我们的结果。今天,笔者给大家分享一些心得,通过使用 SQL 的形式来完成这些需求。 2.内容 实现这些功能,其架构和思路并不复杂。这里笔者将整个实现流程,通过一个原理图来呈现。如下图所示: 这里笔者给大家详述一下上图的含义,消息数据源存放与 Kafka 集群当中,开启低阶和高阶两个消费线程,将消费的结果以 RPC 的方式共享出去(即:请求者)。数据共享出去后,回流经到 SQL 引擎处,将内存中的数据翻译成 SQL Tree,这里使用到了 Apache 的 Calcite 项目来承担这一部分工作。然后,我们通过 Thrift 协议来响应 Web Console 的 SQL 请求,最后将结果返回给前端,让其以图表的实行可视化。 3.插件配置 这里,我们需要遵循 Calcite 的 JSON Models,比如,针对 Kafka 集群,我们需要配置一下内容: { version: '1.0', defaultSchema: 'kafka', schemas: [ { name: 'kafka', type: 'custom', factory: 'cn.smartloli.kafka.visual.engine.KafkaMemorySchemaFactory', operand: { database: 'kafka_db' } } ] } 另外,这里最好对表也做一个表述,配置内容如下所示: [ { "table":"Kafka", "schemas":{ "_plat":"varchar", "_uid":"varchar", "_tm":"varchar", "ip":"varchar", "country":"varchar", "city":"varchar", "location":"jsonarray" } } ] 4.操作 下面,笔者给大家演示通过 SQL 来操作相关内容。相关截图如下所示: 在查询处,填写相关 SQL 查询语句。点击 Table 按钮,得到如下所示结果: 我们,可以将获取的结果以报表的形式进行导出。 当然,我们可以在 Profile 模块下,浏览查询历史记录和当前正在运行的查询任务。至于其他模块,都属于辅助功能(展示集群信息,Topic 的 Partition 信息等)这里就不多赘述了。 5.总结 分析下来,整体架构和实现的思路都不算太复杂,也不存在太大的难点,需要注意一些实现上的细节,比如消费 API 针对集群消息参数的调整,特别是低阶消费 API,尤为需要注意,其 fetch_size 的大小,以及 offset 是需要我们自己维护的。在使用 Calcite 作为 SQL 树时,我们要遵循其 JSON Model 和标准的 SQL 语法来操作数据源。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在 Kafka 中,官方对外提供了两种消费 API,一种是高等级消费 API,另一种是低等级的消费 API。在 《高级消费 API》一文中,介绍了其高级消费的 API 实现。今天给大家介绍另一种消费 API。 2.内容 在使用过 Kafka 的高级消费 API 后,我们知道它是一种高度抽象的消费 API,使用起来简单,方便,但是对于某些特殊的需求我们可能要用到第二种更加底层的 API。那么,我们首先需要知道低级消费 API 的作用。它能帮助我们去做那些事情: 一个消息进行多次读取 在处理过程中只消费 Partition 其中的某一部分消息 添加事物管理机制以保证消息仅被处理一次 当然,在使用的过程当中也是有些弊端的,其内容如下: 必须在程序中跟踪 Offset 的值 必须找出指定的 Topic Partition 中的 Lead Broker 必须处理 Broker 的变动 使用其 API 的思路步骤如下所示: 从所有处于 Active 状态的 Broker 中找出哪个是指定 Topic Partition 中的 Lead Broker 找出指定 Topic Partition 中的所有备份 Broker 构造请求 发送请求并查询数据 处理 Leader Broker 的变动 3.代码实现 3.1 Java Project 若是使用 Java Project 工程去实现该部分代码,需要添加相关以来 JAR 文件,其内容包含如下: scala-xml_${version}-${version}.jar scala-library-${version}.jar metrics-core-${version}.jar kafka-client-${version}.jar kafka_${version}-${version}.jar 针对 Java Project 工程,需要自己筛选 JAR 去添加。保证代码的顺利执行。 3.2 Maven Project 对 Maven 工程,在 pom.xml 文件中添加相应的依赖信息即可,简单方便。让 Maven 去管理相应的依赖 JAR 文件。内容如下所示: <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.8.2.1</version> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency> 这样在 Maven 工程中相应的依赖 JAR 文件就添加完成了。 3.3 代码实现 在低级消费 API 中,实现代码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 /** * @Date Mar 2, 2016 * * @Author dengjie * * @Note Simple consumer api */ public class SimpleKafkaConsumer { private static Logger log = LoggerFactory.getLogger(SimpleKafkaConsumer.class); private List<String> m_replicaBrokers = new ArrayList<String>(); public SimpleKafkaConsumer() { m_replicaBrokers = new ArrayList<String>(); } public static void main(String[] args) { SimpleKafkaConsumer example = new SimpleKafkaConsumer(); // Max read number long maxReads = SystemConfig.getIntProperty("kafka.read.max"); // To subscribe to the topic String topic = SystemConfig.getProperty("kafka.topic"); // Find partition int partition = SystemConfig.getIntProperty("kafka.partition"); // Broker node's ip List<String> seeds = new ArrayList<String>(); String[] hosts = SystemConfig.getPropertyArray("kafka.server.host", ","); for (String host : hosts) { seeds.add(host); } int port = SystemConfig.getIntProperty("kafka.server.port"); try { example.run(maxReads, topic, partition, seeds, port); } catch (Exception e) { log.error("Oops:" + e); e.printStackTrace(); } } public void run(long a_maxReads, String a_topic, int a_partition, List<String> a_seedBrokers, int a_port) throws Exception { // Get point topic partition's meta PartitionMetadata metadata = findLeader(a_seedBrokers, a_port, a_topic, a_partition); if (metadata == null) { log.info("[SimpleKafkaConsumer.run()] - Can't find metadata for Topic and Partition. Exiting"); return; } if (metadata.leader() == null) { log.info("[SimpleKafkaConsumer.run()] - Can't find Leader for Topic and Partition. Exiting"); return; } String leadBroker = metadata.leader().host(); String clientName = "Client_" + a_topic + "_" + a_partition; SimpleConsumer consumer = new SimpleConsumer(leadBroker, a_port, 100000, 64 * 1024, clientName); long readOffset = getLastOffset(consumer, a_topic, a_partition, kafka.api.OffsetRequest.EarliestTime(), clientName); int numErrors = 0; while (a_maxReads > 0) { if (consumer == null) { consumer = new SimpleConsumer(leadBroker, a_port, 100000, 64 * 1024, clientName); } FetchRequest req = new FetchRequestBuilder().clientId(clientName) .addFetch(a_topic, a_partition, readOffset, 100000).build(); FetchResponse fetchResponse = consumer.fetch(req); if (fetchResponse.hasError()) { numErrors++; // Something went wrong! short code = fetchResponse.errorCode(a_topic, a_partition); log.info("[SimpleKafkaConsumer.run()] - Error fetching data from the Broker:" + leadBroker + " Reason: " + code); if (numErrors > 5) break; if (code == ErrorMapping.OffsetOutOfRangeCode()) { // We asked for an invalid offset. For simple case ask for // the last element to reset readOffset = getLastOffset(consumer, a_topic, a_partition, kafka.api.OffsetRequest.LatestTime(), clientName); continue; } consumer.close(); consumer = null; leadBroker = findNewLeader(leadBroker, a_topic, a_partition, a_port); continue; } numErrors = 0; long numRead = 0; for (MessageAndOffset messageAndOffset : fetchResponse.messageSet(a_topic, a_partition)) { long currentOffset = messageAndOffset.offset(); if (currentOffset < readOffset) { log.info("[SimpleKafkaConsumer.run()] - Found an old offset: " + currentOffset + " Expecting: " + readOffset); continue; } readOffset = messageAndOffset.nextOffset(); ByteBuffer payload = messageAndOffset.message().payload(); byte[] bytes = new byte[payload.limit()]; payload.get(bytes); System.out.println(String.valueOf(messageAndOffset.offset()) + ": " + new String(bytes, "UTF-8")); // Message deal enter numRead++; a_maxReads--; } if (numRead == 0) { try { Thread.sleep(1000); } catch (InterruptedException ie) { } } } if (consumer != null) consumer.close(); } public static long getLastOffset(SimpleConsumer consumer, String topic, int partition, long whichTime, String clientName) { TopicAndPartition topicAndPartition = new TopicAndPartition(topic, partition); Map<TopicAndPartition, PartitionOffsetRequestInfo> requestInfo = new HashMap<TopicAndPartition, PartitionOffsetRequestInfo>(); requestInfo.put(topicAndPartition, new PartitionOffsetRequestInfo(whichTime, 1)); kafka.javaapi.OffsetRequest request = new kafka.javaapi.OffsetRequest(requestInfo, kafka.api.OffsetRequest.CurrentVersion(), clientName); OffsetResponse response = consumer.getOffsetsBefore(request); if (response.hasError()) { log.info("[SimpleKafkaConsumer.getLastOffset()] - Error fetching data Offset Data the Broker. Reason: " + response.errorCode(topic, partition)); return 0; } long[] offsets = response.offsets(topic, partition); return offsets[0]; } /** * @param a_oldLeader * @param a_topic * @param a_partition * @param a_port * @return String * @throws Exception * find next leader broker */ private String findNewLeader(String a_oldLeader, String a_topic, int a_partition, int a_port) throws Exception { for (int i = 0; i < 3; i++) { boolean goToSleep = false; PartitionMetadata metadata = findLeader(m_replicaBrokers, a_port, a_topic, a_partition); if (metadata == null) { goToSleep = true; } else if (metadata.leader() == null) { goToSleep = true; } else if (a_oldLeader.equalsIgnoreCase(metadata.leader().host()) && i == 0) { // first time through if the leader hasn't changed give // ZooKeeper a second to recover // second time, assume the broker did recover before failover, // or it was a non-Broker issue // goToSleep = true; } else { return metadata.leader().host(); } if (goToSleep) { try { Thread.sleep(1000); } catch (InterruptedException ie) { } } } throw new Exception("Unable to find new leader after Broker failure. Exiting"); } private PartitionMetadata findLeader(List<String> a_seedBrokers, int a_port, String a_topic, int a_partition) { PartitionMetadata returnMetaData = null; loop: for (String seed : a_seedBrokers) { SimpleConsumer consumer = null; try { consumer = new SimpleConsumer(seed, a_port, 100000, 64 * 1024, "leaderLookup"); List<String> topics = Collections.singletonList(a_topic); TopicMetadataRequest req = new TopicMetadataRequest(topics); kafka.javaapi.TopicMetadataResponse resp = consumer.send(req); List<TopicMetadata> metaData = resp.topicsMetadata(); for (TopicMetadata item : metaData) { for (PartitionMetadata part : item.partitionsMetadata()) { if (part.partitionId() == a_partition) { returnMetaData = part; break loop; } } } } catch (Exception e) { log.error("Error communicating with Broker [" + seed + "] to find Leader for [" + a_topic + ", " + a_partition + "] Reason: " + e); } finally { if (consumer != null) consumer.close(); } } if (returnMetaData != null) { m_replicaBrokers.clear(); for (kafka.cluster.Broker replica : returnMetaData.replicas()) { m_replicaBrokers.add(replica.host()); } } return returnMetaData; } } 4.总结 在使用 Kafka 低级消费 API 时,要明确我们所使用的业务场景,一般建议还是使用高级消费 API,除非遇到特殊需要。另外,在使用过程中,注意 Leader Broker 的处理,和 Offset 的管理。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在调度 Hadoop 的相关作业时,有以下几种方式: 基于 Linux 系统级别的 Crontab。 Java 应用级别的 Quartz。 第三方的调度系统。 自行开发 Hadoop 应用调度系统。 对于前两种,使用 Crontab 和 Quartz 是基本可以满足业务需求,但有其弊端。在 Job 数量庞大的情况下,Crontab 脚本的编写,变得异常复杂。其调度的过程也不能透明化,让管理变得困难。Quartz 虽然不用编写脚本,实现对应的调度 API 即可,然其调度过程不透明,不涵盖 Job 运行详情。需自行开发其功能。 因而,第三方的调度系统便应运而生了。在《Hadoop - 任务调度系统比较》一文中,介绍第三方调度系统之间的差异。这里笔者就不多赘述了。本篇博文,笔者给大家介绍 Azkaban 的相关使用心得,以及在使用中遇到的种种问题和解决思路。 2.内容 Azkaban 托管在 Github 上,属于开源产品。它由以下几部分组成: Web Server Executor Server MySQL Plugins(HDFS,JobType,HadoopSecurityManager,JobSummary,PigVisualizer,Reportal) 其各个模块的功能,在《Hadoop - 任务调度系统比较》中有对应的介绍,这里就不多赘述了。 2.1 How to use 在介绍完其核心模块后,我们如何使用这样一个调度系统,来调度我们所编写好的应用。下面,笔者将详细为大家介绍如何来完成这部分工作。 首先,Azkaban 是一个独立的系统,不需要依赖 Hadoop 集群环境。我们可以用单独的节点来构建这样一个调度系统。但是根据系统本身的需要,依赖以下环境: JDK MySQL 在准备完成以上依赖环境后,我们可以构建这样一个调度系统。在[官网]上下载二进制安装包。官网更新的二进制安装包比 Github 发布的较低,若需要使用新版本的 Azkaban ,可在 Github 上下载。 在准备好安装包后,我们开始去部署相关安装包。 2.2 How to install 2.2.1 DB Setup 首先,我们要在 MySQL 中新建 Azkaban 的数据库,操作内容如下所示: 1 mysql> CREATE DATABASE azkaban; 然后,我们创建所需要的表,内容如下: 1 mysql>source ${AZKABAN_HOME}/sql/create-all-sql-2.5.0.sql; SQL 文件在你安装包 azkaban-sql-script-2.5.0.tar.gz 中,找到 create-all-sql-2.5.0.sql 执行即可。执行成功后,会在 Azkaban 的数据库下,生成以下表: 2.2.2 Web Server Setup 接下来是安装 Web Server,解压其安装包,然后在 conf 目录下配置相关文件即可: azkaban.properties 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #Azkaban Personalization Settings azkaban.name=Test azkaban.label=My Local Azkaban azkaban.color=#FF3601 azkaban.default.servlet.path=/index web.resource.dir=/home/hadoop/azkaban/server/web/web/ #default.timezone.id=America/Los_Angeles default.timezone.id=Asia/Shanghai #Azkaban UserManager class user.manager.class=azkaban.user.XmlUserManager user.manager.xml.file=/home/hadoop/azkaban/server/web/conf/azkaban-users.xml #Loader for projects executor.global.properties=conf/global.properties azkaban.project.dir=projects #plugins viewer.plugin.dir=/home/hadoop/azkaban/server/web/plugins/viewer/hdfs #viewer.plugin.dir=hdfs #viewer.plugins=hdfs database.type=mysql mysql.port=3306 mysql.host=nna mysql.database=azkaban mysql.user=root mysql.password=root mysql.numconnections=100 # Velocity dev mode velocity.dev.mode=false # Azkaban Jetty server properties. jetty.maxThreads=25 jetty.ssl.port=8443 jetty.port=8081 jetty.keystore=/home/hadoop/azkaban/server/web/conf/keystore jetty.password=password jetty.keypassword=password jetty.truststore=/home/hadoop/azkaban/server/web/conf/keystore jetty.trustpassword=password # Azkaban Executor settings executor.port=12321 # mail settings mail.user=your_mail_server@example.com mail. password=xxxxx lockdown.create.projects=false cache.directory=cache 另外,Azkaban 需要使用到 KeyStore,在 ${AZKABAN_WEB_SERVER}/conf 下运行如下命令,内容如下所示: 1 keytool -keystore keystore -alias azkaban -genkey -keyalg RSA 启动之前先在 ${AZKABAN_WEB_SERVER} 目录下创建 logs 目录,进入 ${AZKABAN_WEB_SERVER} 目录,运行如下命令: 1 ../bin/azkaban-web-start 成功启动,出现以下截图信息: 然后,输入在浏览器中 https://your_host:8443 出现以下界面: 接着输入用户名和密码:azkaban/azkaban 便可进入到调度系统中。 2.2.3 Executor Setup Web Server 只是提供可视化,要想调度我们所编写的应用,需要依赖 Executor 服务。在 ${AZKABAN_EXECUTOR}/conf 下配置以下内容: azkaban.properties 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #Azkaban default.timezone.id=America/Los_Angeles # Azkaban JobTypes Plugins azkaban.jobtype.plugin.dir=plugins/jobtypes #Loader for projects executor.global.properties=conf/global.properties azkaban.project.dir=projects database.type=mysql mysql.port=3306 mysql.host=nna mysql.database=azkaban mysql.user=root mysql.password=root mysql.numconnections=100 # Azkaban Executor settings executor.maxThreads=50 executor.port=12321 executor.flow.threads=30 然后执行以下命令即可: 1 azkaban-executor-start.sh 3.Flow 下面给大家介绍使用流程,首先,我们在 Web Server 的 Web Console 上创建一个项目,如下图所示: 然后,点击上传按钮,将我们编写好的应用进行打包上传。这里 WordCount 为例子。如下图所示: 在 WordCount.zip 文件中,包含两个文件,一个是我们编写需要执行的 JAR 文件,另一个是对 JAR 文件进行描述的 Job 文件,即:WordCount.job,其内容涉及如下: 1 2 type=javaprocess java.class=cn.java.Hello 这里笔者只是做了最小化配置,指明执行类型,和 Java 的 MainClass。 在 Executor Flow 中可以设置,告警通知者,在执行完成,或是失败的时候通知应用开发者,让其知晓执行进度,如下所示: 如上图,我们点击 Schedule 按钮,可以设置调度的时间。如下图所示: 在调度模块,现实该项目任务的调度信息,如下图所示: 在上图中,我们还可以设置 SLA 告警模块,在执行 Job 的过程中,若是任务超出限定时间,会将告警信息通知所这是的人。如下图所示: 另外,我们可以在 Executing 模块查看正在执行的 Job,在 History 模块下可以查看已执行完成的 Job。若是需要使用 Azkaban 来查看 HDFS 文件系统的结构目录,添加对应的插件即可。这里就不多赘述了。 4.总结 这里需要注意的是,由于我们所编写的应用会上传到 MySQL 存储,这里需要设置 MySQL 的 max_allowed_packet 变量,在 /etc/my.cnf 中进行配置,内容如下所示: 1 2 [mysqld] max_allowed_packet=1024M 然后重启 MySQL 的服务即可。另外,官方发布的 Azkaban-2.5 版本,路径设置有问题,解决方式有两种:第一,按照错误提示,配置对应的路径;第二,修改源码中的路径读取代码,然后重新打包编译。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在编写 Flink,Spark,Hive 等相关作业时,要是能快速的将我们所编写的作业能可视化在我们面前,是件让人兴奋的时,如果能带上趋势功能就更好了。今天,给大家介绍这么一款工具。它就能满足上述要求,在使用了一段时间之后,这里给大家分享以下使用心得。 2.How to do 首先,我们来了解一下这款工具的背景及用途。Zeppelin 目前已托管于 Apache 基金会,但并未列为顶级项目,可以在其公布的 官网访问。它提供了一个非常友好的 WebUI 界面,操作相关指令。它可以用于做数据分析和可视化。其后面可以接入不同的数据处理引擎。包括 Flink,Spark,Hive 等。支持原生的 Scala,Shell,Markdown 等。 2.1 Install 对于 Zeppelin 而言,并不依赖 Hadoop 集群环境,我们可以部署到单独的节点上进行使用。首先我们使用以下地址获取安装包: http://zeppelin.incubator.apache.org/download.html 这里,有2种选择,其一,可以下载原文件,自行编译安装。其二,直接下载二进制文件进行安装。这里,为了方便,笔者直接使用二进制文件进行安装使用。 这里有些参数需要进行配置,为了保证系统正常启动,确保的 zeppelin.server.port 属性的端口不被占用,默认是8080,其他属性大家可按需配置即可。[配置链接]2.2 Start/Stop 在完成上述步骤后,启动对应的进程。定位到 Zeppelin 安装目录的bin文件夹下,使用以下命令启动进程: ./zeppelin-daemon.sh start 若需要停止,可以使用以下命令停止进程: ./zeppelin-daemon.sh stop 另外,通过阅读 zeppelin-daemon.sh 脚本的内容,可以发现,我们还可以使用相关重启,查看状态等命令。内容如下: case "${1}" in start) start ;; stop) stop ;; reload) stop start ;; restart) stop start ;; status) find_zeppelin_process ;; *) echo ${USAGE} 3.How to use 在启动相关进程后,可以使用以下地址在浏览器中访问: http://<Your_<IP/Host>:Port> 启动之后的界面如下所示: 该界面罗列出插件绑定项。如图中的 spark,md,sh 等。那我如何使用这些来完成一些工作。在使用一些数据引擎时,如 Flink,Spark,Hive 等,是需要配置对应的连接信息的。在 Interpreter 栏处进行配置。这里给大家列举一些配置示例: 3.1 Flink 可以找到 Flink 的配置项,如下图所示: 然后指定对应的 IP 和地址即可。 3.2 Hive 这里 Hive 配置需要指向其 Thrift 服务地址,如下图所示: 另外,其他的插件,如 Spark,Kylin,phoenix等配置类似,配置完成后,记得点击 “restart” 按钮。 3.3 Use md and sh 下面,我们可以创建一个 Notebook 来使用,我们拿最简单的 Shell 和 Markdown 来演示,如下图所示: 3.4 SQL 当然,我们的目的并不是仅仅使用 Shell 和 Markdown,我们需要能够使用 SQL 来获取我们想要的结果。 3.4.1 Spark SQL 下面,我们使用 Spark SQL 去获取想要的结果。如下图所示: 这里,可以将结果以不同的形式来可视化,量化,趋势,一目了然。 3.4.2 Hive SQL 另外,可以使用动态格式来查询分区数据,以"${partition_col=20160101,20160102|20160103|20160104|20160105|20160106}"的格式进行表示。如下图所示: 3.5 Video Guide 另外,官方也给出了一个快速指导的入门视频,观看地址:[入口] 4.总结 在使用的过程当中,有些地方需要注意,必须在编写 Hive SQL 时,%hql 需要替换为 %hive.sql 的格式;另外,在运行 Scala 代码时,如果出现以下异常,如下图所示: 解决方案,在 zeppelin-env.sh 文件中添加以下内容: export ZEPPELIN_MEM=-Xmx4g 该 BUG 在 0.5.6 版本得到修复,参考码:[ZEPPELIN-305] 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 Apache Kylin是一个开源的分布式分析引擎,提供SQL接口并且用于OLAP业务于Hadoop的大数据集上,该项目由eBay贡献于Apache。 2.What is Kylin 在使用一种模型,我们得知道她是干什么的,那么首先来看看Kylin的特性,其内容如下所示: 可扩展超快的OLAP引擎:Kylin是为减少在Hadoop上百亿级别数据查询延迟而设计的。 Hadoop ANSI SQL接口:Kylin为Hadoop提供标准的SQL,其支持大部分查询功能。 出色的交互式查询能力:通过Kylin,使用者可以于Hadoop数据进行亚秒级交互,在同样的数据集上提供比Hive更好的性能。 多维度Cube:用户能够在Kylin里为百亿以上的数据集定义数据模型并构建Cube。 和BI工具无缝整合:Kylin提供与BI工具,如商业化的Tableau。另外,根据官方提供的信息也在后续逐步提供对其他工具的支持。 其他特性: 对Job的管理和监控 压缩和编码的支持 增量更新Cube 利用HBase Coprocessor去查询 基于HyperLogLog的Distinct Count近似算法 友好的Web界面用于管理、监控和使用Cube 项目及Cube级别的访问控制安全 支持LDAP 3.ECOSYSTEM Kylin有其自己的生态圈,如下图所示: 从上图中,我们可以看到,Kylin的核心包含:Kylin OLAP引擎基础框架,Metadata引擎,查询引擎,Job引擎以及存储引擎等等,同时还包括REST服务器以响应客户端请求。另外,还扩展支持额外 功能和特性的插件,同时整合与调度系统、ETL、监控等生命周期管理系统。在Kylin核心之上扩展的第三方用户界面,ODBC和JDBC驱动用以支持不 同的工具和产品,如:Tableau。 4.Architecture Kylin的架构概述图如下所示: 图中的执行流程很清楚,客户端(REST API或JDBC/ODBC)发送SQL请求,将其交给Kylin的执行引擎去处理,Kylin去拉去对应的数据来做处理,并返回处理结果,这里 Kylin需要依赖HBase。复杂的事情,Kylin的引擎都给我们处理了,我们只需要负责去编写我们的业务SQL。 5.How TO Works 在Kylin中,我们可以处理三维的业务查询,如下图所示: 在明白了业务处理方向,其生态群和架构。我们要如何去集成该系统到Hadoop集群?关于Kylin的集成过程是比较方便的,Kylin需要Hadoop、Hive、HBase、JDK,另外,对版本也是有要求的。本版要求如下: Hadoop:2.4 - 2.7 Hive:0.13 - 0.14 HBase:0.98(这里若是选择Kylin-1.2,需要用到HBase-1.1+以上) JDK1.7+ 另外,安装Kylin步骤也是比较简单的,步骤如下所示: 下载最新的安装包,地址如下:[Kylin.tar.gz] 设置KYLIN_HOME环境变量 确保用户有权限去访问Hadoop、Hive和HBase,如果不确定的话,我们可以在安装包的bin目录下运行check-env.sh脚本,如果我们有问题的话,她会打印详细的信息。 最后,我们可以通过kylin.sh start去启动Kylin,或者使用kylin.sh stop去停止Kylin 在Kylin启动之后,我们可以通过输入http://node_hostname:7070/kylin去访问Kylin,登录默认用户名和密码为:ADMIN/KYLIN 预览截图如下所示: 另外,我们可以通过JDBC去操作,代码片段如下所示: Driver driver = (Driver) Class.forName("org.apache.kylin.jdbc.Driver").newInstance(); Properties info = new Properties(); info.put("user", "ADMIN"); info.put("password", "KYLIN"); Connection conn = driver.connect("jdbc:kylin://dn1:7070/kylin_project_name", info); Statement state = conn.createStatement(); ResultSet resultSet = state.executeQuery("select * from test_table"); while (resultSet.next()) { assertEquals("foo", resultSet.getString(1)); assertEquals("bar", resultSet.getString(2)); assertEquals("tool", resultSet.getString(3)); } 6.总结 在使用Kylin时,我们有必要去首先熟悉其架构,这能让我们更加熟悉其应用场景和业务场景。在集成和使用的过程当中会遇到一些问题,我们可以分析其异常日志,然后利用搜索引擎得到解决。关于Kylin的详细使用,大家可以参考官方撰写的文档。 7.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.Overview Ambari是Apache推出的一个集中管理Hadoop的集群的一个平台,可以快速帮助搭建Hadoop及相关以来组件的平台,管理集群方便。这篇博客记录Ambari的相关问题和注意事项。方便为初学者省去搭配各个社区版的烦恼。 2.How to works 在Ambari的官方WIKI上介绍了如何去使用Ambari,[官方文档]。官方说法比较简要,下面我补充相关注意事项,并给大家罗列一个详细的步骤。 2.1 Env 首先,节点(物理机)需要实现准备好,这里笔者准备了5台节点,大家可以按需选择。所使用的系统为CentOS6.6,JDK为 1.7,MySQL为5.1;另外,需要各个节点SSH面密码登录,关闭各个节点的防火墙,selinux置为disabled。这些环境的准备较为简 单,这里就不多赘述了。大家可以利用搜索引擎去完成。 2.2 Exception 在deploy的过程当中,会出现一些忽略的环境因素,首先是时间同步问题,在HBase集群之间需要保证时间的一致性(或是时间间隔极短),否则,时间不同步,会导致HBase集群异常,因而,这里我们可以事先将时间同步,命令如下所示: 时间同步 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime #设置时区为北京时间,这里为上海,因为centos里面只有上海 定时同步(crontab -e) 0-59/10 * * * * /usr/sbin/ntpdate us.pool.ntp.org | logger -t NTP 在管理Ambari的Meta时,这里我们会使用MySQL去做存储,因而,是需要用到MySQL的Driver的,这个在我们配置或启动Ambari 的Server会有提示,不用过早去准备(有2个位置需要用到,/usr/share/java和/usr/lib/ambari-server会需要用 到该依赖包)。这里,我们可以提前将Ambari的数据库和表在MySQL中创建,创建的脚本在/var/lib/ambari-server /resources,使用在MySQL中使用以下命令来完成,内容如下所示: source /var/lib/ambari-server/resources/Ambari-DDL-MySQL-CREATE.sql 另外,在WebUI中,分配节点之前会对各个节点进行校验,这里笔者在校验过程当中出现以下异常,解决方案如下: 页面报出以下错误: The following hosts have Transparent HugePages (THP) enabled。THP should be disabled to avoid potential Hadoop performance issues. 解决办法,在Linux下执行: echo never >/sys/kernel/mm/redhat_transparent_hugepage/defrag echo never >/sys/kernel/mm/redhat_transparent_hugepage/enabled echo never >/sys/kernel/mm/transparent_hugepage/enabled echo never >/sys/kernel/mm/transparent_hugepage/defrag Install, setup and start Ambari server by default. Reach "Choose services" phase of installer. Actual result: "Confirm hosts" shows warning that ntpd service isn't running on hosts, but it's running in console by command service ntpd status 在启动系统的ntpd后,最后将其设置为开机自启。命令如下所示: chkconfig ntpd on 在完成上述内容后,准备工作基本算是完成了,接下来的工作就是去对各个组件的集成。 3.Plugins 关于组件的选择,大家可以按需而择,后续若是有其他需求可以追加组件功能。节点角色的分配这里需要注意,若是要配置HA,得放在后续配置,这里 得SNameNode表示Secondary NameNode,需要和NameNode配置在一起使用,否则会对集群带来异常。如下图为笔者在跳板机的部分截图: 之后,我们需要对Hive的Meta的存储介质进行配置,这里我们指定MySQL的地址之后,点击Deploy进行部署,下图为等待部署: 等待起完成即可。 4.Architecture Ambari采用的并不是新的架构,只是充分利用了一些优秀的开源软件及其思想,将其巧妙的结合,使其在分布式环境中能够做到集群式服务管理、 监控、展示等。Ambari的架构采用的是C/S模型,即:Server/Client模式,能够集中式管理分布式集群的安装配置及部署。Ambari除 了ambari-server和ambari-agent,另外它还提供了一个界面优美的管理监控页面ambari-web,这些页面由ambari- server提供。ambari-server对外开放了REST API,这些用途有二,其一用于为ambari-web提供管理监控服务,其二用于与ambari-agent交互,接受ambari-agent向ambari-server发送的心跳请求。官方给出的架构图如下所示: 4.1 Ambari-agent ambari-agent是一个无状态的,主要功能如下所示: 采集所在节点的信息并且汇总发送心跳给ambari-server 处理ambari-server的响应请求 因而,它有两种队列:MessageQueue和ActionQueue。 MessageQueue:包含节点状态信息(注册信息等)和执行结果信息,并且汇总后通过心跳发送给ambari-server ActionQueue:用于接收ambari-server返回过来的状态操作,然后能通过执行器按序调用puppet或python脚本等模块完成任务 架构图如下所示: 4.2 Ambari-server 而对于ambari-server来说,其是一个有状态的,它维护着自己的一个有限状态FSM。同时这些状态存储与数据库当中(DB目前可以支持多种,可按序自选),Server端主要维持三类状态: Live Cluster State:集群现有状态,各个节点汇报上来的状态信息会更改该状态 Desired State:使用者希望该节点所处状态,是用户在页面进行了一系列的操作,需要更改某些服务的状态,这些状态还没有在节点商阐述作用 Action State:操作状态,该状态是一种中间状态,这种状态可以辅助Live Cluster State向Desired State状态的转变 其架构图如下所示: ambari-server的Heartbeat Handler模块用于接收各个Agent的心跳请求(其中包含节点状态信息和返回的操作结果),把节点状态信息传递给图中的FSM模块去维护该节点的状 态,并把响应之后的操作结果信息返回给Action Manager去做更加详细的处理。Coordinator模块可以看作API Handler,主要在接收Web端操作请求后,校验其合法性,Stage Planner分解成一组操作,最后提供给Action 过 Manager去完成执行操作。 因而,从上图中,我们可以看出,ambari-server的所有状态信息的维护和变化都会被记录在数据库当中,使用者做一些更改服务的操作都会在数据库商做对应的记录,同时,Agent通过心跳来获取数据库的变动历史信息。 5.总结 在部署Ambari时,主机节点的硬件配置,内存不能过低,标准的服务器配置即可。另外,从Ambari的结构图来看,架构虽然谈不上新颖,但各个模块职责分明,能够充分利用优秀的开源中间件来进行组合,对于我们去涉及类似的系统具有借鉴意义。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在《Hadoop-Drill深度剖析》 一文当中,给大家介绍了Drill的相关内容,就实时查询来说,Drill基本能够满足要求,同时还可以做一个简单业务上的聚合,如果在使用Hive做一 些简单的业务统计(不涉及多维度,比如CUBE,ROLLUP之类的函数),只是用一些基本的聚合函数或是JOIN ON之类的,Drill基本满足要求,而且响应速度可比OLTP。今天给大家剖析的是另外一种工具,由于目前Drill官方不支持对表的插入,更新操作。 所以,在操作HBase的时候,若遇到这些需求,Drill就有点力不从心。那么,Phoenix可以满足以上需求。它更接近与标准的SQL。 2.Architecture 在Phoenix中SQL Query Plan的执行,基本上是通过构建一系列的HBase Scan来完成。为了尽可能减少数据的传输,在Region Server使用Coprocessor来尽可能的执行Aggregate相关的工作,基本实现的思路是使用RegionObserver在 PostScannerOpen Hook中将RegionScanner替换成支持Aggregation工作的定制化的Scanner,具体的Aggregate操作通过Custom 的Scan属性传递给RegionScanner。然与基于MapReduce的框架执行Plan的思想比较,基本上就是通过Coprocessor,使 用RegionServer自身来在各个节点上执行Aggregation。另外,通过各种定制的Filter在HBase的RegionScanner Scan过程中,尽早的将不相关的数据过滤掉。采用JDBC接口和应用程序交互。 3.Grammar 本篇博客所对应的软件版本号,如下所示: HBase:0.98 Phoenix:4.6-HBase-0.98 这里需要注意的是,Phoenix的版本是和HBase版本相匹配的,可以在Phoenix的官网选择对应的HBase版本。就本篇博客截止,官方所支持的语法如下图所示: 3.How to use 如何将Phoenix嵌入到现有业务当中,其实,Phoenix只是一个中间件(或是一个HBase的SQL插件),它的使用较为简单,首先,我们准备好对应的安装包,下载地址如下所示: [Phoenix-HBase-0.98][下载地址] 然后,将Phoenix目录下的phoenix-*.jar拷贝到HBase的lib目录,这里面是将所有的插件均拷贝到HBase了,若是只使用个别插件,大家可按需选择即可。然后重启HBase集群即可。 3.1 Shell Client 这里,我们可以验证Phoenix是否可用,可以通过终端来验证,在Phoenix的bin目录当中有一个sqlline.py脚本,可以通过该脚本来操作HBase中的表,命令如下所示: ./sqlline.py zk01,zk02,zk03:2181 通过英文感叹号可以获取帮助命令,如下图所示: 然后,我们可以做一些测试来,验证起可用性。如下图所示: 脚本如下所示: 1 2 3 4 5 6 7 create table test3 (mykey integer not null primary key, mycolumn varchar); upsert into test3 values (1,'Hello'); upsert into test3 values (2,'World2'); upsert into test3 values (3,'World3'); upsert into test3 values (4,'World4'); upsert into test3 values (5,'World5'); select * from test3; 3.2 JDBC 另外,Phoenix也支持JDBC去访问,这里笔者给大家写了一个示例代码,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package cn.smrtloli.phoenix.demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * @Date Dec 14, 2015 * * @Author dengjie * * @Note TODO */ public class PhoenixDemo { private static String driver = "org.apache.phoenix.jdbc.PhoenixDriver"; public static void main(String[] args) throws SQLException { try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } Statement stmt = null; ResultSet rset = null; Connection con = DriverManager.getConnection("jdbc:phoenix:zk01,zk02,zk03:2181"); stmt = con.createStatement(); String sql = "select * from test3"; rset = stmt.executeQuery(sql); while (rset.next()) { System.out.println(rset.getString("mycolumn")); } stmt.close(); con.close(); } } 另外,在pom.xml中添加如下依赖JAR文件,内容如下所示: <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-core</artifactId> <version>4.6.0-HBase-0.98</version> </dependency> 运行结果,如下所示: 4.总结 就使用的结果来看,虽然满足了一些CRUD的操作,然其在HBase的基础上完成,过于依赖HBase,对其他存储介质的支持有限。总体来说, 对于HBase中已有数据,做标准的SQL操作来说,是足够了。另外,时延要求较高的业务,还是用HBase的API来完成,Phoenix虽说速度、性 能较快,毕竟不能严格意义上达到OLTP。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在《Hadoop - 实时查询Drill》一文当中,笔者给大家介绍如何去处理实时查询这样的业务场景,也是简略的提了一下如何去实时查询HDFS,然起相关细节并未说明。今天给大家细说一下相关细节,其中包含:HDFS,Hive以及HBase等内容。 2.数据源和文件格式 在使用Drill去做实时查询,由于其只是一个中间件,其适配的存储介质是有限制的,目前官方支持以下存储介质: FS HDFS HBase Hive RDBMS MongoDB MapR-DB S3 这里笔者主要给大家介绍HDFS,Hive,HBase这三种介质。另外,Drill支持以下输入格式的数据源: Avro CSV TSV PSV Parquet MapR-DB* Hadoop Sequence Files 2.1 文本类型文件(CSV,TSV,PSV) 下面笔者给大家介绍文本类型的相关细节,文本类型的使用,有其固定的使用方法,通用配置如下: "formats": { "csv": { "type": "text", "extensions": [ "txt" ], "delimiter": "\t" }, "tsv": { "type": "text", "extensions": [ "tsv" ], "delimiter": "\t" }, "parquet": { "type": "parquet" } } 这里以CSV为例子来说明: "csv":表示固定的文本格式 "type":制定文件的类型,这里指定为文本类型 "extensions":扩展名为csv "delimiter":文本内容,每行的分隔符为一个tab占位符 上面的配置,这里我们也可以进行拓展,比如我们的HDFS上的文件格式如下图所示: 我们要达到以下查询结果,内容如下所示: 0: jdbc:drill:zk=local> SELECT * FROM hdfs.`/tmp/csv_with_header.csv2`; +------------------------+ | columns | +------------------------+ | ["hello","1","2","3"] | | ["hello","1","2","3"] | | ["hello","1","2","3"] | | ["hello","1","2","3"] | | ["hello","1","2","3"] | | ["hello","1","2","3"] | | ["hello","1","2","3"] | +------------------------+ 那么,我们可以对其做以下配置,内容如下所示: "csv": { "type": "text", "extensions": [ "csv2" ], "skipFirstLine": true, "delimiter": "," }, 这里skipFirstLine这个属性表示忽略一行结果。 另外,同样用到上面的数据源,我们要实现以下查询结果,内容如下所示: 0: jdbc:drill:zk=local> SELECT * FROM hdfs.`/tmp/csv_with_header.csv2`; +-------+------+------+------+ | name | num1 | num2 | num3 | +-------+------+------+------+ | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | | hello | 1 | 2 | 3 | +-------+------+------+------+ 这该如何去修改CSV的属性,我们添加以下内容即可: "csv": { "type": "text", "extensions": [ "csv2" ], "skipFirstLine": false, "extractHeader": true, "delimiter": "," }, 从单词的意义上可以很直接的读懂属性所要表达的意思,这里就不多做赘述了。由于篇幅问题,这里就不一一列举了。 其他格式文件与此类似,填写指定文件格式,文件类型,扩展名,文本分隔符即可,其他扩展属性可按需添加。 3.Plugins 3.1 HDFS 集成HDFS的Plugins,添加内容如下所示: { "type": "file", "enabled": true, "connection": "hdfs://hdfs.company.com:9000/", "workspaces": { "root": { "location": "/opt/drill", "writable": true, "defaultInputFormat": null } }, "formats": { "csv": { "type": "text", "extensions": [ "txt" ], "delimiter": "\t" }, "tsv": { "type": "text", "extensions": [ "tsv" ], "delimiter": "\t" }, "parquet": { "type": "parquet" } } } PS:连接HDFS地址注意要正确。 3.2 Hive 集成Hive的Plugins,添加内容如下所示: { "type": "hive", "enabled": true, "configProps": { "hive.metastore.uris": "thrift://hdfs.company.com:9083", "fs.default.name": "hdfs://hdfs.company.com/", "hive.metastore.sasl.enabled": "false" } } PS:这里需要指定Hive的metastore的thrift地址,同时也需要指定hdfs的地址。另外,我们需要启动metastore的thrift服务,命令如下所示: hive --service metastore 这里需要注意的是,Drill当前不支持写操作到Hive表,在将Hive表结构中的数据类型做查询映射时,支持以下类型: 支持的SQL类型 Hive类型 BIGINT BIGINT BOOLEAN BOOLEAN VARCHAR CHAR DATE DATE DECIMAL* DECIMAL FLOAT FLOAT DOUBLE DOUBLE INTEGER INT,TINYINT,SMALLINT INTERVAL N/A TIME N/A N/A TIMESPAMP (unix的系统时间) TIMESPAMP TIMESPAMP (JDBC时间格式:yyyy-mm-dd hh:mm:ss) None STRING VARCHAR VARCHAR VARBINARY BINARY 另外,在Drill中,不支持以下Hive类型: LIST MAP STRUCT TIMESTAMP(Unix Epoch format) UNION 3.3 HBase 集成HBase的Plugins,添加内容如下所示: { "type": "hbase", "config": { "hbase.zookeeper.quorum": "hbase-zk01,hbase-zk02,hbase-zk03", "hbase.zookeeper.property.clientPort": "2181" }, "size.calculator.enabled": false, "enabled": true } PS:在使用ZooKeeper集群连接信息时,需要注意的是,Drill在解析HBase的Plugins时,会解析其HBase集群上的 ZK集群信息,如:在HBase集群中的ZK信息配置使用的时域名,这里在配置其HBase的Plugins的ZK连接信息也需使用对应的域名,若是直接 填写IP,解析会失败。保证解析的一致性。 4.总结 另外,在使用JDBC或ODBC去操作Drill的时候,连接信息的使用是需要注意的,直接按照官方给出的连接方式硬套是有问题的,这里我们修 改以下连接信息。连接分2种情况,一种指定其Drill的IP和PORT,第二种,使用ZK的连接方式,如 jdbc:drill:zk=dn1,dn2,dn3:2181即可。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在现实业务当中,存在这样的业务场景,需要实时去查询HDFS上的相关存储数据,普通的查询(如:Hive查询),时延较高。那么,是否存在时 延较小的查询组件。在业界目前较为成熟的有Cloudera的Impala,Apache的Drill,Hortonworks的Stinger。本篇博 客主要为大家介绍Drill,其他两种方式大家可以自行下去补充。 2.Drill Architecture 2.1 Cilent 使用Drill,可以通过以下方式进入到Drill当中,内容如下所示: Drill shell:使用客户端命令去操作 Drill Web Console:Web UI界面去操作相关内容 ODBC/JDBC:使用驱动接口操作 C++ API:C++的API接口 2.2 Drill Query Execution 执行流程如下图所示: 2.3 Core Modules 核心模块图,如下所示: 至于详细的文字描述,这里就不多做赘述了。大家看图若是有疑惑的地方,可以去官方网站,查看详细的文档描述。[官方文档] 3.Drill使用 介绍完Drill的架构流程,下面我们可以去使用Drill去做相关查询操作。安装Drill的过程比较简单,这里就不多做详细的赘述了。首 先,去Apache的官网下载Drill的安装包,这里笔者所使用的本版是drill-1.2.0。可独立部署在物理机上,不必与Hadoop集群部署在 一起。这里需要注意的是,物理机的内存至少留有4G空闲给Drill去使用。不然,在执行查询操作的时候会内容溢出,查询Drill的官方文档,官方给出 的解释是,操作的内容都在内容中完成,不会写磁盘,除非你强制指明去写磁盘,但是,一般考虑到响应速度因素,都会在内容中完成。笔者曾试图降低其内存配置 小于4G,然并卵。所以,在使用Drill做查询时,需要保证物理机空闲内存大于等于4G。 [JDK下载地址] [Drill下载地址] 目前,Drill迭代版本比较快速。大家在下载Drill版本的时候,可以多多留意下版本内容变化。 在解压Drill的压缩包后,在其conf文件夹下有一个drill-override.conf文件,这里我们在里面添加Web UI的访问地址,添加的内容我们可以在drill-override-example.conf模版文件中查找对应的内容。添加内容如下所示: drill.exec: { cluster-id: "drillbits1", zk.connect: "dn1:2181,dn2:2181,dn2:2181", http: { enabled: true, ssl_enabled: false, port: 8047 } } 这里需要注意的是,Drill需要用ZK,这里笔者就直接使用Hadoop集群的ZooKeeper集群连接信息地址。在添加完内容后,可以使用以下命令启动。 ./drillbit.sh start 启动之后,Web UI界面如下所示: 目前条件有限,只有单台物理机,所以只部署了单台Drill。若是,大家条件允许,可以查看官网文档去部署Cluster。Drill插件默认是没有HDFS的,需要我们主动去创建,默认只有以下插件,如下图所示: 这里,笔者已经配置过HDFS的插件,故上图出现HDFS插件信息,其配置信息如下所示: { "type": "file", "enabled": true, "connection": "hdfs://hadoop.company.com:9000/", "workspaces": { "root": { "location": "/opt/drill", "writable": true, "defaultInputFormat": null } }, "formats": { "csv": { "type": "text", "extensions": [ "csv" ], "delimiter": "," }, "tsv": { "type": "text", "extensions": [ "tsv" ], "delimiter": "\t" }, "parquet": { "type": "parquet" } } } PS:这里要保证HDFS的地址信息正确。另外,Drill支持的存储介质较多,大家参考官方文档去添加对应的存储介质。 在添加HDFS插件之后,我们可以通过Web UI界面的查询界面进行文件查询,也可以使用Drill Shell命令在终端去查询。查询方式如下所示: Web UI查询命令: Web UI结果如下: 另外,其查询记录详情可以在Profiles模块下查看。如下图所示: Drill Shell查询: ./sqlline -u jdbc:drill:zk=dn1,dn2,dn3:2181 Drill Shell 查询结果: 4.总结 这里,笔者做过一个性能测试比较,数量级分别为10W,100W,1000W的不重复数据,其响应时间依次递增。结果如下图所示: 通过测试结果可以看出,若是数量级在100W时,响应时间平均在秒级别,可以尝试用Drill去中OLTP业务。若是在1000W以上级别,显然这个延时做OLTP是难以接受的,这个可以去做OLAP业务。 5.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在Hadoop应用,随着业务指标的迭代,而使其日趋复杂化的时候,管理Hadoop的相关应用会变成一件头疼的事情,如:作业的依赖调度,任 务的运行情况的监控,异常问题的排查等,这些问题会是的我们日常的工作变得复杂。那么,在没有条件和精力去开发一套调度系统的情况下,我们去选择一款第三 方开源的调度系统,来尽量减轻和降低我们日常工作的复杂度,也是极好的。今天,笔者给大家比较几种常见的调度系统,供大家去选择。 2.内容 2.1 Oozie Oozie目前是托管在Apache基金会的,开源。在之前的博客《Oozie调度》 一文当中,介绍相关Oozie的调度,如何去调度Hadoop的相关,大家可以从博客的文中所描述的内容看出,配置的过程略显繁琐和复杂,配置相关的调度 任务比较麻烦,然其可视化界面也不是那么的直观,另外,对UI界面要求较高的同学,此调度系统估计会让你失望。若是对改调度系统感兴趣的同学可以到《Oozie调度》一文中做相关细节的了解。这里就不多做赘述了。 2.2 Zeus 它是一个Hadoop的作业平台,从Hadoop任务的调试运行到生产任务的周期调度,它支持任务的整个生命周期。从其功能来看,它支持以下任务: Hadoop的MapReduce任务调度运行 Hive任务的调度运行 Shell任务的运行 Hive元数据的可视化展示查询及数据预览 Hadoop任务的自动调度 其开源地址在Github上面,可在Github搜索Zeus,即可找到相关工程。Zeus是由阿里巴巴开源出来的,文档在Github上描述的也比较详细,其相关安装步骤及使用方法可参考Github上的官方文档,这里就不多做赘述了。 2.3 Azkaban 这是由LinkedIn创建的一个批处理工作流,用于跑Hadoop的Jobs。Azkaban提供了一个易于使用的用户界面来维护和跟踪你的工作流程。其可视化界面如下所示: 另外,Github上贡献的Azkaban调度系统的源码量不大,做二次开发难度不大。其功能点涉及以下内容: 兼容Hadoop版本 易用的Web UI 简单的Web和Http工作流的上传 项目工作区 工作流调度 模块化和插件化 认证和授权 用户行为跟踪 邮件告警失败和成功 SLA告警 重启失败的Jobs Azkaban的设计之初主要是基于可用性的考虑。在LinkedIn运行的有些年头了,一直驱动着它们的Hadoop和数据仓库。 它由3个关键部分组成,分别是: 关系行数据库(MySQL):Azkaban使用MySQL去做一些状态的存储。AzkabanWebServer和AzkabanExecutorServer这两个服务都需要接入到DB库当中。 AzkabanWebServer:WebServer使用DB的原因如下: 项目管理:对项目权限和上传文件的管理。 执行流程状态:对正在执行的程序进行跟踪。 之前的流程或Jobs:通过搜索先前的工作和流程,去访问它们的日志文件。 调度程序:保持预定的工作状态。 SLA:保持所有的SLA规则。 AzkabanExecutorServer:另外,ExecutorServer使用DB的原因如下所示: 获取项目:从数据库中检索项目文件。 执行工作流或Jobs:检索和更新流的数据,并执行。 Logs:存储作业的输出日志,并将其流入数据库。 不同的依赖进行交流:如果一个流在不同的执行器上运行,它将从数据库中取取状态。 三者的关系图,如下所示: 关于其相关配置和使用,官方给出的文档比较详细,这里就不多赘述了。大家可以到Github去阅读官方给出的文档。 3.总结 关于调度系统的选择,这里就比较了这3种,大家可以适情况而定,另外,若是条件允许或是有精力也可以参考这些调度系统的原理,开发一套满足自己当前业务的调度系统,也不失为一种选择。 4.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 最近,有小伙伴涉及到源码编译。然而,在编译期间也是遇到各种坑,在求助于搜索引擎,技术博客,也是难以解决自身所遇到的问题。笔者在被询问多 次的情况下,今天打算为大家来写一篇文章来剖析下编译的细节,以及遇到编译问题后,应该如何去解决这样类似的问题。因为,编译的问题,对于后期业务拓展, 二次开发,编译打包是一个基本需要面临的问题。 2.编译准备 在编译源码之前,我们需要准备编译所需要的基本环境。下面给大家列举本次编译的基础环境,如下所示: 硬件环境 操作系统 CentOS6.6 CPU I7 内存 16G 硬盘 闪存 核数 4核 软件环境 JDK 1.7 Maven 3.2.3 ANT 1.9.6 Protobuf 2.5.0 在准备好这些环境之后,我们需要去将这些环境安装到操作系统当中。步骤如下: 2.1 基础环境安装 关于JDK,Maven,ANT的安装较为简单,这里就不多做赘述了,将其对应的压缩包解压,然后在/etc/profile文件当中添加对应 的路径到PATH中即可。下面笔者给大家介绍安装Protobuf,其安装需要对Protobuf进行编译,故我们需要编译的依赖环境gcc、gcc- c++、cmake、openssl-devel、ncurses-devel,安装命令如下所示: yum -y install gcc yum -y install gcc-c++ yum -y install cmake yum -y install openssl-devel yum -y install ncurses-devel 验证GCC是否安装成功,命令如下所示: 验证Make核CMake是否安装成功,命令如下所示: 在准备完这些环境之后,开始去编译Protobuf,编译命令如下所示: [hadoop@nna ~]$ cd protobuf-2.5.0/ [hadoop@nna protobuf-2.5.0]$ ./configure --prefix=/usr/local/protoc [hadoop@nna protobuf-2.5.0]$ make [hadoop@nna protobuf-2.5.0]$ make install PS:这里安装的时候有可能提示权限不足,若出现该类问题,使用sudo进行安装。 验证Protobuf安装是否成功,命令如下所示: 下面,我们开始进入编译环境,在编译的过程当中会遇到很多问题,大家遇到问题的时候,要认真的去分析这些问题产生的原因,这里先给大家列举一些 可以避免的问题,在使用Maven进行编译的时候,若使用默认的JVM参数,在编译到hadoop-hdfs模块的时候,会出现溢出现象。异常信息如下所 示: java.lang.OutOfMemoryError: Java heap space 这里,我们在编译Hadoop源码之前,可以先去环境变量中设置其参数即可,内容修改如下: export MAVEN_OPTS="-Xms256m -Xmx512m" 接下来,我们进入到Hadoop的源码,这里笔者使用的是Hadoop2.6的源码进行编译,更高版本的源码进行编译,估计会有些许差异,编译命令如下所示: [hadoop@nna tar]$ cd hadoop-2.6.0-src [hadoop@nna tar]$ mvn package -DskipTests -Pdist,native PS:这里笔者是直接将其编译为文件夹,若需要编译成tar包,可以在后面加上tar的参数,命令为 mvn package -DskipTests -Pdist,native -Dtar 笔者在编译过程当中,出现过在编译KMS模块时,下载tomcat不完全的问题,Hadoop采用的tomcat是apache- tomcat-6.0.41.tar.gz,若是在此模块下出现异常,可以使用一下命令查看tomcat的文件大小,该文件正常大小为6.9M左右。查看 命令如下所示: [hadoop@nna downloads]$ du -sh * 若出现只有几K的tomcat安装包,表示tomcat下载失败,我们将其手动下载到/home/hadoop/tar/hadoop-2.6.0- src/hadoop-common-project/hadoop-kms/downloads目录下即可。在编译成功后,会出现以下信息:[INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Apache Hadoop Main ................................. SUCCESS [ 1.162 s] [INFO] Apache Hadoop Project POM .......................... SUCCESS [ 0.690 s] [INFO] Apache Hadoop Annotations .......................... SUCCESS [ 1.589 s] [INFO] Apache Hadoop Assemblies ........................... SUCCESS [ 0.164 s] [INFO] Apache Hadoop Project Dist POM ..................... SUCCESS [ 1.064 s] [INFO] Apache Hadoop Maven Plugins ........................ SUCCESS [ 2.260 s] [INFO] Apache Hadoop MiniKDC .............................. SUCCESS [ 1.492 s] [INFO] Apache Hadoop Auth ................................. SUCCESS [ 2.233 s] [INFO] Apache Hadoop Auth Examples ........................ SUCCESS [ 2.102 s] [INFO] Apache Hadoop Common ............................... SUCCESS [01:00 min] [INFO] Apache Hadoop NFS .................................. SUCCESS [ 3.891 s] [INFO] Apache Hadoop KMS .................................. SUCCESS [ 5.872 s] [INFO] Apache Hadoop Common Project ....................... SUCCESS [ 0.019 s] [INFO] Apache Hadoop HDFS ................................. SUCCESS [04:04 min] [INFO] Apache Hadoop HttpFS ............................... SUCCESS [01:47 min] [INFO] Apache Hadoop HDFS BookKeeper Journal .............. SUCCESS [04:58 min] [INFO] Apache Hadoop HDFS-NFS ............................. SUCCESS [ 2.492 s] [INFO] Apache Hadoop HDFS Project ......................... SUCCESS [ 0.020 s] [INFO] hadoop-yarn ........................................ SUCCESS [ 0.018 s] [INFO] hadoop-yarn-api .................................... SUCCESS [01:05 min] [INFO] hadoop-yarn-common ................................. SUCCESS [01:00 min] [INFO] hadoop-yarn-server ................................. SUCCESS [ 0.029 s] [INFO] hadoop-yarn-server-common .......................... SUCCESS [01:03 min] [INFO] hadoop-yarn-server-nodemanager ..................... SUCCESS [01:10 min] [INFO] hadoop-yarn-server-web-proxy ....................... SUCCESS [ 1.810 s] [INFO] hadoop-yarn-server-applicationhistoryservice ....... SUCCESS [ 4.041 s] [INFO] hadoop-yarn-server-resourcemanager ................. SUCCESS [ 11.739 s] [INFO] hadoop-yarn-server-tests ........................... SUCCESS [ 3.332 s] [INFO] hadoop-yarn-client ................................. SUCCESS [ 4.762 s] [INFO] hadoop-yarn-applications ........................... SUCCESS [ 0.017 s] [INFO] hadoop-yarn-applications-distributedshell .......... SUCCESS [ 1.586 s] [INFO] hadoop-yarn-applications-unmanaged-am-launcher ..... SUCCESS [ 1.233 s] [INFO] hadoop-yarn-site ................................... SUCCESS [ 0.018 s] [INFO] hadoop-yarn-registry ............................... SUCCESS [ 3.270 s] [INFO] hadoop-yarn-project ................................ SUCCESS [ 2.164 s] [INFO] hadoop-mapreduce-client ............................ SUCCESS [ 0.032 s] [INFO] hadoop-mapreduce-client-core ....................... SUCCESS [ 13.047 s] [INFO] hadoop-mapreduce-client-common ..................... SUCCESS [ 10.890 s] [INFO] hadoop-mapreduce-client-shuffle .................... SUCCESS [ 2.534 s] [INFO] hadoop-mapreduce-client-app ........................ SUCCESS [ 6.429 s] [INFO] hadoop-mapreduce-client-hs ......................... SUCCESS [ 4.866 s] [INFO] hadoop-mapreduce-client-jobclient .................. SUCCESS [02:04 min] [INFO] hadoop-mapreduce-client-hs-plugins ................. SUCCESS [ 1.183 s] [INFO] Apache Hadoop MapReduce Examples ................... SUCCESS [ 3.655 s] [INFO] hadoop-mapreduce ................................... SUCCESS [ 1.775 s] [INFO] Apache Hadoop MapReduce Streaming .................. SUCCESS [ 11.478 s] [INFO] Apache Hadoop Distributed Copy ..................... SUCCESS [ 15.399 s] [INFO] Apache Hadoop Archives ............................. SUCCESS [ 1.359 s] [INFO] Apache Hadoop Rumen ................................ SUCCESS [ 3.736 s] [INFO] Apache Hadoop Gridmix .............................. SUCCESS [ 2.822 s] [INFO] Apache Hadoop Data Join ............................ SUCCESS [ 1.791 s] [INFO] Apache Hadoop Ant Tasks ............................ SUCCESS [ 1.350 s] [INFO] Apache Hadoop Extras ............................... SUCCESS [ 1.858 s] [INFO] Apache Hadoop Pipes ................................ SUCCESS [ 5.805 s] [INFO] Apache Hadoop OpenStack support .................... SUCCESS [ 3.061 s] [INFO] Apache Hadoop Amazon Web Services support .......... SUCCESS [07:14 min] [INFO] Apache Hadoop Client ............................... SUCCESS [ 2.986 s] [INFO] Apache Hadoop Mini-Cluster ......................... SUCCESS [ 0.053 s] [INFO] Apache Hadoop Scheduler Load Simulator ............. SUCCESS [ 2.917 s] [INFO] Apache Hadoop Tools Dist ........................... SUCCESS [ 5.702 s] [INFO] Apache Hadoop Tools ................................ SUCCESS [ 0.015 s] [INFO] Apache Hadoop Distribution ......................... SUCCESS [ 8.587 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 28:25 min [INFO] Finished at: 2015-10-22T15:12:10+08:00 [INFO] Final Memory: 89M/451M [INFO] ------------------------------------------------------------------------ 在编译完成之后,会在Hadoop源码的dist目录下生成编译好的文件,如下图所示: 图中hadoop-2.6.0即表示编译好的文件。 3.总结 在编译的过程当中,会出现各种各样的问题,有些问题可以借助搜索引擎去帮助我们解决,有些问题搜索引擎却难以直接的给出解决方案,这时,我们需 要冷静的分析编译错误信息,大胆的去猜测,然后去求证我们的想法。简而言之,解决问题的方法是有很多的。当然,大家也可以在把遇到的编译问题,贴在评论下 方,供后来者参考或借鉴。 4.结束语 这篇文章就和大家分享到这里,如果大家在研究和学习的过程中有什么疑问,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 本课程的视频教程地址:《Kafka实战项目之编码实践》 该课程我以用户实时上报日志案例为基础,带着大家去完成各个KPI的编码工作,实现生产模块、消费模块, 数据持久化,以及应用调度等工作, 通过对这一系列流程的演示,让大家能够去掌握Kafka项目的相关编码以及调度流程。下面,我们首先来预览本课程所包含的课时,他们分别有: 接下来,我们开始第一课时的学习:《数据生产实现》 2.内容 2.1 数据生产实现 本课时主要给大家演示Kafka数据生产的代码实现,在前面搭建好的集群环境下,完成Kafka的数据生产功能,以及一些注意事项,为我们编写 消费代码做好准备,让大家掌握Kafka的数据生产的代码实现。 实践本课时的内容,我们需要设计到两个知识点,他们分别是: 接着,我们先从一个知识点来开始实践,实践数据生产模块所包含的内容,有以下几点: 首先第一点是:对项目工程的文件进行配置(pom) 然后是对集群的链接信息进行配置(这里为什么要将这些链接信息配置在配置文件当中,原因是,这些链接信息单独剥离到一个配置文件,便于我们后期维护, 比如:后期添加新的节点信息,或是移除一个已损坏的节点信息,我们可以轻松,快速的在配置文件中修改节点信息即可,无需在去操作我们的业务代码。)具体演 示细节请大家参考视频操作。 在演示完数据生成模块相关内容后,下面,我带着大家去实践Flume到Kafka模块的相关内容,如下所示: 以上就是本课时的相关内容演示,其中包含了相关信息的配置,数据的收集过程演示等。 2.2 数据消费实现 本课时给大家演示 Kafka 数据消费的代码实现,在前面我们创建的 Kafka 的项目工程的基础上,完成消费代码的编写, 以及编写 Storm 代码消费 Kafka 数据的需要注意的细节,通过本课时让大家能够掌握数据消费的代码实现。 那么,接下来我给大家列出本课时所涉及的核心知识点,如下所示: 下面,我们开始第一个核心知识点的实践,实践的所包含的内容如下所示: Storm集群的信息配置:这部分内容包含集群的依赖链接信息。 依赖文件的选取:这里我们这编写Java代码实现相关功能时,需要选取依赖的JAR包来辅助我们完成编码工作。 接下来我带这大家看看,如何编码实现这一部分内容,即:实现Kafka到Storm模块的 内容实现,该部分涉及的内容如下所示: 具体的演示细节,大家可以参考视频操作,观看地址:《数据消费实现》 2.3 数据持久化 内容涉及给大家,介绍如何将消费后的数据(即我们统计的kpi结果)持久化,在前面数据消费实现的基础上,通过流式计算将统计的结果持久化到 Redis 集群或是 DB 中,让大家掌握数据持久化的代码实现。 那么,接下来,我们去看看本课时所涉及的核心知识点,如下所示: 下面,我们开始第一个知识点的实践,实现基础层代码模块所包含的内容,如下所示: 实现思路:先实现这部分功能之前,我们要清楚它的一个实现思路,如右图所示: 这里,我们在Storm的计算模块中,将相应的KPI统计之后,做对应的持久化,这里我们可以选择 持久化到我们所选择的DB库当中,图中我们持久化到Redis和MySQL当中,那么接下来,我们按照这个思路 去实现。 在实现之前,首先我们需要准备好DAO层的代码,这层代码的作用是与DB交互。 接下来,我去给大家演示这一部分内容。 下面,我们去实现Storm统计结果存储到DB的相关内容,还模块包含如下所示的内容: 实现思路:同样,在实现这一部分功能时,我们也要清楚,在什么地方去持久化我们统计的结果。如右图所示: 我们在Bolt当中,当我们的KPI指标统计完成后,就可以调用相应的存储代码去持久化这部分统计结果。 在清楚了思路之后,我们去实现这一部分的入库流程。 下面我去给大家演示这一部分内容。 具体演示细节,大家可以参考视频操作,观看地址:《数据持久化》 2.4 应用调度 该部分内容将给大家介绍将开发好的应用打包部署到服务器,通过提交 Topology 到 Storm 集群, 完成 Storm 消费的程序的部署,让大家掌握项目的打包部署以及调度流程。下面,我们去看看实践本课时的内容,所涉及那些核心知识点,如下所示: 接下来,我们开始对第一个知识点的实践。关于打包所包含的内容,如下所示: 首先是打包的方式流程,如下图所示: 使用Maven打包,本项目工程所采取的是Maven结构,这里我们使用Maven命令打包对应的工程。 下面,我去给大家演示这一部分内容 下面我们去实践如何将我们打包好的应用部署到Storm集群,去跑相应的任务。 实现该模块所包含的内容,如下所示: 实现思路。如下图所示:这里我们要清楚它的各个阶段的职责,我们在开发阶段,为了开发的便利以及调试的方便, 我们可以使用本地提交,就像前面,我们给大家演示的,直接在IDE当中,提交相应的Topology即可。而早生产环境下, 我们需要依赖集群,利用分布式的思想去跑我们的任务,所以,我们需要使用集群提交,这里在提交任务时,确保Storm 集群是运行正常的。 那么接着的内容就是去实现相应的提交流程。 下面,我去给大家演示这一部分内容。 具体演示细节,大家可以参考视频操作,观看地址:《应用调度》 3.总结 本课程我们对项目的指标进行了编码实践,并指导大家去编码实现了相应的模块功能,以及帮助大家去提交我们开发的应用等知识,应该掌握一下知识: 4.结束语 我们在有了这些知识作为基础会使得我们在今后的工作当中,开发类似实时统计项目变得游刃有余,更加的得心应手。 如果本教程能帮助到您,希望您能点击进去观看一下,谢谢您的支持!
1.概述 在接触了第一代MapReduce和第二代MapReduce之后,或许会有这样的疑惑,我们从一些书籍和博客当中获取MapReduce的一 些原理和算法,在第一代当中会有JobTrack,TaskTrack之类的术语,在第二代会有 ResourceManager,NodeManager,ApplicationMaster等等术语。然又有Shuffle、 Partitioner、Sort、Combiner等关键字,如何区分它们,理顺其之间的联系。 在Hadoop2.x大行其道的年代,其优秀的资源管理框架(系统),高可用的分布式存储系统,备受企业青睐。然因上述之惑,往往不能尽得其中之深意。此篇博客笔者为大家一一解惑。 2.计算模型 在阅读和研究第一代MapReduce和第二代MapReduce之后,我们可以发现MapReduce其实由两部分组成,一者为其计算模型, 二者为其运行环境。到这里,就不难解释为何在第一代MapReduce里面由Shuffle、Sort等内容,而在第二代MapReduce中也同样存在 其相关内容。原因很简单,在Hadoop2.x中,MapReduce的变化,只有其运行环境变化了,然其计算模型依旧不变。 在MapReduce的计算模型当中,对方法进行了高阶抽象,其精华为Map Task和Reduce Task,在Map阶段完成对应的map函数的逻辑实现,与之相对的在Reduce阶段完成对应的reduce函数的逻辑实现,即可编写好整个核心的 MapReduce的处理过程,在Main函数入口之处,申请对应的Job,指定相应的Mapper和Reducer继承类,以及其输入输出类型等相关信 息,即可运行一个完整的MapReduce任务。 虽说我们能够编写一个完成MapReduce程序,并运行它。然其运行的细节,我们却未必清楚,往往初学者在编写一个MapReduce作业时,遇到错误而不去研究分析其错误之根本,转而求助于搜索引擎,在搜索无望之下,会让自己瞬间懵逼,不知所措。 这里,我们去剖析其计算模型的执行细节,虽不敢说剖析之后能解决所有的疑难杂症,但起码能让我们知晓错误原因,能够找到解决问题的方向,继而解决我们所遇之难题。下面为大家剖析MapReduce的计算模型。 Map阶段,简言之: Read:该步骤是去读取我们的数据源,将数据进行filter成一个个的K/V Map:在map函数中,处理解析的K/V,并产生新的K/V Collect:输出结果,存于环形内缓冲区 Spill:内存区满,数据写到本地磁盘,并生产临时文件 Combine:合并临时文件,确保生产一个数据文件 Reduce阶段,简言之: Shuffle:Copy阶段,Reduce Task到各个Map Task远程复制一分数据,针对某一份数据,若其大小超过一定阀值,则写磁盘;否则放到内容 Merge:合并内存和磁盘上的文件,防止内存占用过多或磁盘文件过多 Sort:Map Task阶段进行局部排序,Reduce Task阶段进行一次归并排序 Reduce:将数据给reduce函数 Write:reduce函数将其计算的结果写到HDFS上 上述为其计算模型的执行过程,需有几点要额外注意。这里有些阶段,我们在编写相关应用时,需有谨慎。 这里有一个Combine阶段,这个阶段的使用有助与我们对MapReduce的性能进行优化,为何这么说?细细剖析该过程便可明白。在map 函数时,它只管处理数据,并不负责统计处理数据的结果,也就是说并没有Combine阶段,那么,问题来了,在reduce过程当中,因为每个map函数 处理后的数据没有统计,它除了要统计所有map的汇总数量,还要统计单个map下的处理数。也许,这里有点绕,大家可以参照下图来理解这层意思,如下图所 示: 然而,这样是不行的,所以Reduce为了减轻压力,每个map都必须统计自己旗下任务处理结果,即:Combine。这样,Reduce所做 的事情就是统计每个map统计之后的结果,这样子就会轻松许多。因而,Combine在map所做的事情,减轻了Reduce的事情,省略了上图中的步骤 1。 具体代码细节,可在Job的属性方法中设置对应的参数,如下所示: job.setCombinerClass(DefReducer.class); 另外,我们也有必要理解Partition相关职责,它是分割map节点的结果,按照Key分别映射给不同的Reduce,这里我们可以理解为归类,对一些复杂的数据进行归类。在Job属性中设置对应的分区类,那么你的分区函数就生效了,如下所示: job.setPartitionerClass(DefPartition.class); 3.运行环境 在Hadoop2.x中,由于有了YARN来做资源管理,因而第二代MapReduce的运行环境,对比第一代MapReduce有了些许的改变。具体改变细节,可参考我写的另一篇博客:《MapReduce运行环境剖析》。 4.总结 本篇博客给大家剖析了MapReduce的计算模型和运行环境,其中计算模型不变,变者乃其运行环境。所变内容,简言之:RM下包含AM和 NM,NM会RM申请Container(其可理解为一个运行时的JVM),NM与RM的通信属于“Pull模型”,即NM主动上报状态信息,RM被动接 受上报信息。 5.结束语 这篇文章就和大家分享到这里,如果大家在研究和学习的过程中有什么疑问,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 从HDFS的应用层面来看,我们可以非常容易的使用其API来操作HDFS,实现目录的创建、删除,文件的上传下载、删除、追加 (Hadoop2.x版本以后开始支持)等功能。然而仅仅局限与代码层面是不够的,了解其实现的具体细节和过程是很有必要的,本文笔者给大家从以下几个方 面进行剖析: Create Delete Read Write Heartbeat 下面开始今天的内容分享。 2.Create 在HDFS上实现文件的创建,该过程并不复杂,Client到NameNode的相关操作,如:修改文件名,创建文件目录或是子目录等,而这些操作只涉及Client和NameNode的交互,过程如下图所示: 我们很熟悉,在我们使用Java 的API去操作HDFS时,我们会在Client端通过调用HDFS的FileSystem实例,去完成相应的功能,这里我们不讨论FileSystem 和DistributedFileSystem和关系,留在以后在阅读这部分源码的时候在去细说。而我们知道,在使用API实现创建这一功能时是非常方便 的,代码如下所示: public static void mkdir(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); fs.create(path); fs.close(); } 在代码层面上,我们只需要获取操作HDFS的实例即可,调用其创建方法去实现目录的创建。但是,其中的实现细节和相关步骤,我们是需要清楚的。 在我们使用HDFS的FileSystem实例时,DistributedFileSystem对象会通过IPC协议调用NameNode上的 mkdir()方法,让NameNode执行具体的创建操作,在其指定的位置上创建新的目录,同时记录该操作并持久化操作记录到日志当中,待方法执行成功 后,mkdir()会返回true表示创建成功来结束创建过程。在此期间,Client和NameNode不需要和DataNode进行数据交互。 3.Delete 在执行创建的过程当中不涉及DataNode的数据交互,而在执行一些较为复杂的操作时,如删除,读、写操作时,需要DataNode来配合完成对应的工作。下面以删除HDFS的文件为突破口,来给大家展开介绍。删除流程图如下所示: 在使用API操作删除功能时,我使用以下代码,输入要删除的目录地址,然后就发现HDFS上我们所指定的删除目录就被删除了,而然其中的删除细节和过程却并不一定清楚,删除代码如下所示: public static void rmr(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); boolean status = fs.delete(path, true); LOG.info("Del status is [" + status + "]"); fs.close(); } 通过阅读这部分删除的API实现代码,代码很简单,调用删除的方法即可完成删除功能。但它是如何完成删除的,下面就为大家这剖析一下这部分内容。 在NameNode执行delete()方法时,它只是标记即将要删除的Block(操作删除的相关记录是被记录并持久化到日志当中的,后续的 相关HDFS操作都会有此记录,便不再提醒),NameNode是被动服务的,它不会主动去联系保存这些数据的Block的DataNode来立即执行删 除。而我们可以从上图中发现,在DataNode向NameNode发送心跳时,在心跳的响应中,NameNode会通过DataNodeCommand 来命令DataNode执行删除操作,去删除对应的Block。而在删除时,需要注意,整个过程当中,NameNode不会主动去向DataNode发送 IPC调用,DataNode需要完成数据删除,都是通过DataNode发送心跳得到NameNode的响应,获取DataNodeCommand的执 行命令。 4.Read 在读取HDFS上的文件时,Client、NameNode以及DataNode都会相互关联。按照一定的顺序来实现读取这一过程,读取过程如下图所示: 通过上图,读取HDFS上的文件的流程可以清晰的知道,Client通过实例打开文件,找到HDFS集群的具体信息(我们需要操作的是 ClusterA,还是ClusterB,需要让Client端知道),这里会创建一个输入流,这个输入流是连接DataNode的桥梁,相关数据的读取 Client都是使用这个输入流来完成的,而在输入流创建时,其构造函数中会通过一个方法来获取NameNode中DataNode的ID和Block的 位置信息。Client在拿到DataNode的ID和Block位置信息后,通过输入流去读取数据,读取规则按照“就近原则”,即:和最近的 DataNode建立联系,Client反复调用read方法,并将读取的数据返回到Client端,在达到Block的末端时,输入流会关闭和该 DataNode的连接,通过向NameNode获取下一个DataNode的ID和Block的位置信息(若对象中为缓存Block的位置信息,会触发 此步骤,否则略过)。然后拿到DataNode的ID和Block的位置信息后,在此连接最佳的DataNode,通过此DataNode的读数据接口, 来获取数据。 另外,每次通过向NameNode回去Block信息并非一次性获取所有的Block信息,需得多次通过输入流向NameNode请求,来获取 下一组Block得位置信息。然而这一过程对于Client端来说是透明的,它并不关系是一次获取还是多次获取Block的位置信息,Client端在完 成数据的读取任务后,会通过输入流的close()方法来关闭输入流。 在读取的过程当中,有可能发生异常,如:节点掉电、网络异常等。出现这种情况,Client会尝试读取下一个Block的位置,同时,会标记该 异常的DataNode节点,放弃对该异常节点的读取。另外,在读取数据的时候会校验数据的完整性,若出现校验错误,说明该数据的Block已损坏,已损 坏的信息会上报给NameNode,同时,会从其他的DataNode节点读取相应的副本内容来完成数据的读取。Client端直接联系 NameNode,由NameNode分配DataNode的读取ID和Block信息位置,NameNode不提供数据,它只处理Block的定位请 求。这样,防止由于Client的并发数据量的迅速增加,导致NameNode成为系统“瓶颈”(磁盘IO问题)。 5.Write HDFS的写文件过程较于创建、删除、读取等,它是比较复杂的一个过程。下面,笔者通过一个流程图来为大家剖析其中的细节,如下图所示: Client端通过实例的create方法创建文件,同时实例创建输出流对象,并通过远程调用,通知NameNode执行创建命令,创建一个新 文件,执行此命令需要进行各种校验,如NameNode是否处理Active状态,被创建的文件是否存在,Client创建目录的权限等,待这些校验都通 过后,NameNode会创建一个新文件,完成整个此过程后,会通过实例将输出流返回给Client。 这里,我们需要明白,在向DataNode写数据的时候,Client需要知道它需要知道自身的数据要写往何处,在茫茫Cluster 中,DataNode成百上千,写到DataNode的那个Block块下,是Client需要清楚的。在通过create创建一个空文件时,输出流会向 NameNode申请Block的位置信息,在拿到新的Block位置信息和版本号后,输出流就可以联系DataNode节点,通过写数据流建立数据流管 道,输出流中的数据被分成一个个文件包,并最终打包成数据包发往数据流管道,流经管道上的各个DataNode节点,并持久化。 Client在写数据的文件副本默认是3份,换言之,在HDFS集群上,共有3个DataNode节点会保存这份数据的3个副本,客户端在发送 数据时,不是同时发往3个DataNode节点上写数据,而是将数据先发送到第一个DateNode节点,然后,第一个DataNode节点在本地保存数 据,同时推送数据到第二个DataNode节点,依此类推,直到管道的最后一个DataNode节点,数据确认包由最后一个DataNode产生,并逆向 回送给Client端,在沿途的DataNode节点在确认本地写入成功后,才会往自己的上游传递应答信息包。这样做的好处总结如下: 分摊写数据的流量:由每个DataNode节点分摊写数据过程的网络流量。 降低功耗:减小Client同时发送多份数据到DataNode节点造成的网络冲击。 另外,在写完一个Block后,DataNode节点会通过心跳上报自己的Block信息,并提交Block信息到NameNode保存。当 Client端完成数据的写入之后,会调用close()方法关闭输出流,在关闭之后,Client端不会在往流中写数据,因而,在输出流都收到应答包 后,就可以通知NameNode节点关闭文件,完成一次正常的写入流程。 在写数据的过程当中,也是有可能出现节点异常。然而这些异常信息对于Client端来说是透明的,Client端不会关心写数据失败后 DataNode会采取哪些措施,但是,我们需要清楚它的处理细节。首先,在发生写数据异常后,数据流管道会被关闭,在已经发送到管道中的数据,但是还没 有收到确认应答包文件,该部分数据被重新添加到数据流,这样保证了无论数据流管道的哪个节点发生异常,都不会造成数据丢失。而当前正常工作的 DateNode节点会被赋予新的版本号,并通知NameNode。即使,在故障节点恢复后,上面只有部分数据的Block会因为Blcok的版本号与 NameNode保存的版本号不一致而被删除。之后,在重新建立新的管道,并继续写数据到正常工作的DataNode节点,在文件关闭 后,NameNode节点会检测Block的副本数是否达标,在未达标的情况下,会选择一个新的DataNode节点并复制其中的Block,创建新的副 本。这里需要注意的是,DataNode节点出现异常,只会影响一个Block的写操作,后续的Block写入不会收到影响。 6.Heartbeat 前面说过,NameNode和DataNode之间数据交互,是通过DataNode节点向NameNode节点发送心跳来获取NameNode的操作指令。心跳发送之前,DataNode需要完成一些步骤之后,才能发送心跳,流程图如下所示: 从上图来看,首先需要向NameNode节点发送校验请求,检测是否NameNode节点和DataNode节点的HDFS版本是否一致(有可 能NameNode的版本为2.6,DataNode的版本为2.7,所以第一步需要校验版本)。在版本校验结束后,需要向NameNode节点注册,这 部分的作用是检测该DataNode节点是否属于NameNode节点管理的成员之一,换言之,ClusterA的DataNode节点不能直接注册到 ClusterB的NameNode节点上,这样保证了整个系统的数据一致性。在完成注册后,DataNode节点会上报自己所管理的所有的Block信 息到NameNode节点,帮助NameNode节点建立HDFS文件Block到DataNode节点映射关系(即保存Meta),在完成此流程之后, 才会进入到心跳上报流程。 另外,如果NameNode节点长时间接收不到DataNode节点到心跳,它会认为该DataNode节点的状态处理Dead状态。如果 NameNode有些命令需要DataNode配置操作(如:前面的删除指令),则会通过心跳后的DataNodeCommand这个返回值,让 DataNode去执行相关指令。 7.总结 简而言之,关于HDFS的创建、删除、读取以及写入等流程,可以一言以蔽之,内容如下: Create:Client直接与NameNode交互,不涉及DataNode Delete:Client将删除指令存于NameNode,DataNode通过心跳获取NameNode的操作指令 Read:Client通过NameNode获取读取数据的位置,找到DataNode节点对应的Block位置读取数据 Write:Client通过NameNode后区写数据的位置,找到DataNode节点对应的Block位置进行写入 8.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 JStorm 是一个类似于 Hadoop 的MapReduce的计算系统,它是由Alibaba开源的实时计算模型,它使用Java重写了原生的Storm模型(Clojure和Java混合编 写的),并且再原来的基础上做了许多改进。用户只需按照指定的接口实现一个任务,然后将这个任务提交给JStorm系统,JStorm在接受了任务指令 后,会无间断运行任务,一旦出现异常导致某个Worker发送故障,调度器立刻会分配一个新的Worker去顶替异常的Worker。下面是本次分享的目 录结构: 应用场景 基本术语 JStorm比较 JStorm架构 总结 下面开始今天的内容分享。 2.应用场景 从应用的角度来说,JStorm它是一种分布式的应用;从系统层面来说,它又类似于MapReduce这样的调度系统;而从数据方面来说,它又 是一种基于流水数据的实时处理解决方案。如今,DT时代的当下,用户和企业也不仅仅只满足于离线数据,对于数据的实时性要求也越来越高了。 在早期,Storm和JStorm未问世之前,业界有很多实时计算系统,可谓百家争鸣,自Storm和JStorm出世之后,基本这两者占据主要地位,原因如下: 易开发:接口简单,上手容易,只需要按照Spout,Bolt以及Topology的编程规范即可开发一个扩展性良好的应用,底层的细节我们可以不用去深究其原因。 扩展性:可线性扩展性能。 容错:当Worker异常或挂起,会自动分配新的Worker去工作。 数据精准:其包含Ack机制,规避了数据丢失的风险。使用事物机制,提高数据精度。 JStorm处理数据的方式流程是基于流式处理,因此,我们会用它做以下处理: 日志分析:从收集的日志当中,统计出特定的数据结果,并将统计后的结果持久化到外界存储介质中,如:DB。当下,实时统计主流使用JStorm和Storm。 消息转移:将接受的消息进行Filter后,定向的存储到另外的消息中间件中。 3.基本术语 3.1 Stream 在JStorm当中,有对Stream的抽象,它是一个不间断的无界的连续Tuple,而JStorm在建模事件流时,把流中的事件抽象未Tuple,流程如下图所示: 3.2 Spout和Bolt 在JStorm中,它认为每个Stream都有一个Stream的来源,即Tuple的源头,所以它将这个源头抽象为Spout,而Spout可能是一个消息中间件,如:MQ,Kafka等。并不断的发出消息,也可能是从某个队列中不断读取队列的元数据。 在有了Spout后,接下来如何去处理相关内容,以类似的思想,将JStorm的处理过程抽象为Bolt,Bolt可以消费任意数量的输入流, 只要将流方向导到该Bolt即可,同时,它也可以发送新的流给其他的Bolt使用,因而,我们只需要开启特定的Spout,将Spout流出的Tuple 导向特定的Bolt,然后Bolt对导入的流做处理后再导向其它的Bolt等。 那么,通过上述描述,其实,我们可以用一个形象的比喻来理解这个流程。我们可以认为Spout就是一个个的水龙头,并且每个水龙头中的水是不同 的,我们想要消费那种水就去开启对应的水龙头,然后使用管道将水龙头中的水导向一个水处理器,即Bolt,水处理器处理完后会再使用管道导向到另外的处理 器或者落地到存储介质。流程如下图所示: 3.3 Topology 如图所示,这是一个有向无环图,JStorm将这个图抽象为Topology,它是JStorm中最高层次的一个抽象概念,它可以处理代码层面 当中直接于JStorm打交道的,可以被提交到JStorm集群执行对应的任务,一个Topology即为一个数据流转换图,图中的每个节点是一个 Spout或者Bolt,当Spout或Bolt发送Tuple到流时,它就发送Tuple到每个订阅了该流的Bolt上。 3.4 Tuple JStorm当中将Stream中数据抽象为了Tuple,一个Tuple就是一个Value List,List值的每个Value都有一个Name,并且该Value可以是基本类型,字符类型,字节数组等,当然也可以是其它可序列化的类型。 Topology的每个节点都要说明它所发射出的Tuple的字段的Name,其它节点只需要订阅该Name就可以接收处理相应的内容。 3.5 Worker和Task Work和Task在JStorm中的职责是一个执行单元,一个Worker表示一个进程,一个Task表示一个线程,一个Worker可以运 行多个Task。而Worker可以通过setNumWorkers(int workers)方法来设置对应的数目,表示这个Topology运行在多个JVM(PS:一个JVM为一个进程,即一个Worker);另外 setSpout(String id, IRichSpout spout, Number parallelism_hint)和setBolt(String id, IRichBolt bolt,Number parallelism_hint)方法中的参数parallelism_hint代表这样一个Spout或Bolt有多少个实例,即对应多少个线程,一 个实例对应一个线程。 3.6 Slot 在JStorm当中,Slot的类型分为四种,他们分别是:CPU,Memory,Disk,Port;与Storm有所区别(Storm局限 于Port)。一个Supervisor可以提供的对象有:CPU Slot、Memory Slot、Disk Slot以及Port Slot。 在JStorm中,一个Worker消耗一个Port Slot,默认一个Task会消耗一个CPU Slot和一个Memory Slot 在Task执行较多的任务时,可以申请更多的CPU Slot 在Task需要更多的内存时,可以申请更多的额Memory Slot 在Task磁盘IO较多时,可以申请Disk Slot 4.JStorm比较 当前JStorm已经更新到2.x版本了,较于Storm而言,JStorm在一个Nimbus宕机后,会自动的热切到备份的Nimbus,实现了HA特性。对比与其它的数据产品而言,如下所示: Flume:一个成熟的产品,目前很多企业的日志收集系统均基于此套件开发,可以将数据收集后做一些计算与分析。 S4:它是一个通用的,可扩展的,分布式的,容错,可插拔的平台,使程序员可以很容易地开发用于处理无界的连续数据流应用。数据准确性较差,数据丢失的风险无法规避,导致其发展不是很迅速,社区活跃度不够高。 AKKA:一个Actor模型,系统模型强大,可以做任何你想做的时,当时很多工作都需要自己亲自动手去实现,如序列化、Topology的生成等。 Spark:基于内存计算的MapReduce模型,偏重于数据批量处理。 5.JStorm架构 从设计层面来说,JStorm是一个典型的调度系统。在这个系统中,有以下内容: 角色 作用 Nimbus 调度器 Supervisor Worker的代理角色,负责Kill掉Worker和运行Worker Worker Task的容器 Task 任务的执行者 ZooKeeper 系统的协调者 其整体架构图,如下所示: 6.总结 本篇博客给大家分享了JStorm的相关内容,其中包含一些基本概念,与Storm的区别,它的架构图等内容,后续会大家介绍如何去部署JStorm的相关内容,以及它的编程方式,API的用法等内容会用一些案例给大家去一一的赘述。 7.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 本课程的视频教程地址:《Kafka实战项目之分析与设计》 本课程我通过一个用户实时上报日志案例作为基础,带着大家去分析Kafka这样一个项目的各个环节,从而对项目的整体设计做比较合理的规划,最终让大家能够通过本课程去掌握类似Kafka项目的分析与设计。下面,我给大家介绍本课程包含的课时内容,如下图所示: 接下来,我们开始第一课时的学习:《项目整体概述》。 2.内容 2.1 项目整体设计 项目整体概述主要讲解一个项目产生的背景,以及该项目背后的目的,从而让大家更好的去把握项目的需求。 本课时所涉及的主要知识点,如下图所示: 那么,接下来,我就先从背景来给大家简述一个项目,背景包含一下知识点,如下图所示: 前面我已经给大家说明了,这是一个实时统计项目,我们可以实时的访问记录, 通过实时流式计算后,得到用户实时的访问行迹。这个和离线计算有所区别,离线计算任务,不能立马得到我们想要的结果。 那么,这样一个项目我们能得到什么好处,举个例子: 业绩部门的同事需要知道当天的用户实时浏览行迹,而针对这一需求,我们可以通过实时计算后,将统计后的结果 通过图表可视化出来,让业绩部门的同事可以非常清晰的知道,公司的用户对公司的那些业务模块赶兴趣,需求量比较大, 那么业绩部门的同事,可以这一块重点投入,对那些不是很赶兴趣,需求量较小的模块,业绩部门的同事可以投入的成本相对低一些。 以上便是我为大家介绍的项目背景,下面我给大家介绍项目的目的。 项目的目的所包含的内容,如下图所示: 关于详细的目的内容,这里我就不多做赘述了。《观看地址》 2.2 Producer 模块分析 Producer模块分析一课给大家介绍数据生产环节,我带着大家去分析生产数据来源,让大家掌握数据如何收集到 Kafka 的 Producer 模块。 其主要知识点包含以下内容,如下所示: 下面,我们先去分析数据来源。我们知道,在日志记录中一条日志记录代表用户的一次活动形迹,下面我我从实时日志记录中抽取的一条用户记录,如下所示: 121.40.174.237 yx12345 [21/July/2015 13:25:45 +0000] chrome appid_5 "http://www.***.cn/sort/channel/2085.html" 那么通过观察分析这条记录,我们可以从示例数据中得到那些信息量,这里我给大家总结到一张图上了,如下图所示: 在分析了日志记录的信息量,我们接下来去看看是如何收集到这些数据的,整个收集数据的流程是怎么样的,下面我用一张图来给大家,如下图所示: 从图中,我们可以看出,数据的产生的源头是用户,从图的左边开始看起,用户通过自己手上的终端(有可能是: PC机,手机,pad等设备去访问公司的网站),而这里访问的记录都会被实时的记录到服务器,我们在部署网站的的节点上添加 Flume的Agent代理,将这些实时记录集中收集起来,然后我们在Flume的Sink组件处添加输送的目标地址,这里我们是要将这些 实时的记录输送到Kafka集群的,所以在Sink组件处填写指向Kafka集群的信息,这样收集的实时记录就被存储在Kafka的 Producer端,然后,这部分数据我们就可以在下一个阶段,也就是消费阶段去消费这些数据。 以上就是整个实时数据的采集过程,由用户产生,Flume收集并传输,最后存放与Kafka集群的Producer端等待被消费。 关于具体细节,这里就不赘述了。《观看地址》 2.3 Consumer 模块分析 该课时我给大家介绍数据消费环节,带着大家从消费的角度去分析消费的数据源,让大家掌握数据如何在Kafka中被消费。 其主要知识点包含以下内容,如下所示: 那么,下面我先带着大家去分析消费数据来源,关于消费数据来源的统计的KPI指标,如下图所示: 从图中,我们可以看出,由以下KPI指标: 业务模块的访问量:这里通过记录中的App Id来统计相关指标。 页面的访问量:关于PV,这里我们可以使用浏览记录来完成这部分的指标统计。 当天时段模块的访问量:而时间段的访问量,可以通过用户访问的时间戳,来完成这部分的指标统计。 访问者的客户端类型:在每条访问记录中,都含有对应的访问浏览设备类型,我们提取这部分内容来完成相应的统计指标。 以上便是我给大家分析消费数据的相关信息所设计的内容。 接下来,我带着大家去看看本课时的另一个比较重要的知识点,那就是关于数据源的消费流程。这里,我用一张图来给大家描述了整个消费过程,如下图所示: 我们先从图的最左边看起,这个是Kafka的集群,在这个集群中,存放着我们即将要被消费的数据,这里,我们通过KafkaSpout 将Kafka和Storm联系起来,将Kafka集群中要消费的数据,通过KafkaSpout输送到Storm集群,然后数据进入到Storm集群后, 通过Storm的实时计算模型,按照业务指标做对应的计算,并将计算之后的结果持久化到DB库当中去,这里同时采用MySQL和Redis 来做持久化。 以上,便是我给大家描述的如何去消费Kafka集群中数据的流程。《观看地址》 2.4 项目整体设计 该课时我给大家介绍设计一个项目的整体架构和流程开发,以及 KPI 的设计,让大家能够通过本课时去掌握一个项目的设计流程。 其主要知识点包含以下内容,如下所示: 下面,我先给大家去分析本项目的详细设计流程,这里我绘制了一张图来描述整个项目设计流程的相关信息,如下图所示: 从图的最左边开始,依次是: 数据源:这部分数据在用户访问公司网站的时候就会产生对应的记录,我们只需要在各个网站节点添加对应的Flume的Agent代理即可。 数据收集:这里我们使用Flume集群去收集访问的日志记录,在收集完数据后,进入到下一阶段。 数据接入:在该模块下,使用Kafka来充当一个消息数据的核心中间件,通过Flume的Sink组件,将数据 发送到Kafka集群,这样在Kafka的生产端就有了数据,这些数据等待去被消费。那么接下来,通过KafkaSpout 将Kafka集群和Storm集群关联起来,将Kafka集群中的数据,由KafkaSpout输送到Storm集群,这样消费端的数据就流向了 Storm集群。 流式计算:在数据进入到Storm集群后,通过Storm的实时计算模型,将数据按照业务需要完成对应的指标计算,并将统计的 结果持久化到DB库当中。 持久化层:在持久化层,这里选用MySQL和Redis来做持久化存储,统计结果出来后,进入到下一阶段。 数据接口层:这里我们可以编写一个RPC服务,统一的将统计结果共享出去,这里RPC服务所采用的是Thrift,完成数据的共享。 可视化层:这里由前端统一查询Thrift数据共享接口,将统计结果展示出来,完成数据的可视化。 以上,便是我给大家介绍本项目的整个流程设计的相关内容。关于其他的细节内容,这里就不多赘述了。《观看地址》 3.总结 本课程我们对项目进行了整体分析,并指导大家去分析 Kafka 的 Producer 模块和 Consumer 模块,以及帮助大家去设计项目的开发流程等知识,我们应该掌握以下知识点,如下图所示: 4.结束语 这就是本课程的主要内容,主要就对 Kafka 项目做前期准备,对后续学习 Kafka 项目实战内容奠定良好的基础。 如果本教程能帮助到您,希望您能点击进去观看一下,谢谢您的支持!
1.概述 最近在和人交流时谈到数据相似度和数据共性问题,而刚好在业务层面有类似的需求,今天和大家分享这类问题的解决思路,分享目录如下所示: 业务背景 编码实践 预览截图 下面开始今天的内容分享。 2.业务背景 目前有这样一个背景,在一大堆数据中,里面存放着图片的相关信息,如下图所示: 上图只是给大家列举的一个示例数据格式,第一列表示自身图片,第二、第三......等列表示与第一列相关联的图片信息。那么我们从这堆数据中如何找出他们拥有相同图片信息的图片。 2.1 实现思路 那么,我们在明确了上述需求后,下面我们来分析它的实现思路。首先,我们通过上图所要实现的目标结果,其最终计算结果如下所示: pic_001pic_002 pic_003,pic_004,pic_005 pic_001pic_003 pic_002,pic_005 pic_001pic_004 pic_002,pic_005 pic_001pic_005 pic_002,pic_003,pic_004 ...... 结果如上所示,找出两两图片之间的共性图片,结果未列完整,只是列举了部分,具体结果大家可以参考截图预览的相关信息。 下面给大家介绍解决思路,通过观察数据,我们可以发现在上述数据当中,我们要计算图片两两的共性图片,可以从关联图片入手,在关联图片中我们可 以找到共性图片的关联信息,比如:我们要计算pic001pic002图片的共性图片,我们可以在关联图片中找到两者(pic001pic002组合)后 对应的自身图片(key),最后在将所有的key求并集即为两者的共性图片信息,具体信息如下图所示: 通过上图,我们可以知道具体的实现思路,步骤如下所示: 第一步:拆分数据,关联数据两两组合作为Key输出。 第二步:将相同Key分组,然后求并集得到计算结果。 这里使用一个MR来完成此项工作,在明白了实现思路后,我们接下来去实现对应的编码。 3.编码实践 拆分数据,两两组合。 public static class PictureMap extends Mapper<LongWritable, Text, Text, Text> { @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException { StringTokenizer strToken = new StringTokenizer(value.toString()); Text owner = new Text(); Set<String> set = new TreeSet<String>(); owner.set(strToken.nextToken()); while (strToken.hasMoreTokens()) { set.add(strToken.nextToken()); } String[] relations = new String[set.size()]; relations = set.toArray(relations); for (int i = 0; i < relations.length; i++) { for (int j = i + 1; j < relations.length; j++) { String outPutKey = relations[i] + relations[j]; context.write(new Text(outPutKey), owner); } } } } 按Key分组,求并集 public static class PictureReduce extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { String common = ""; for (Text val : values) { if (common == "") { common = val.toString(); } else { common = common + "," + val.toString(); } } context.write(key, new Text(common)); } } 完整示例 package cn.hadoop.hdfs.example; import java.io.IOException; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.hadoop.hdfs.util.HDFSUtils; import cn.hadoop.hdfs.util.SystemConfig; /** * @Date Aug 31, 2015 * * @Author dengjie * * @Note Find picture relations */ public class PictureRelations extends Configured implements Tool { private static Logger log = LoggerFactory.getLogger(PictureRelations.class); private static Configuration conf; static { String tag = SystemConfig.getProperty("dev.tag"); String[] hosts = SystemConfig.getPropertyArray(tag + ".hdfs.host", ","); conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://cluster1"); conf.set("dfs.nameservices", "cluster1"); conf.set("dfs.ha.namenodes.cluster1", "nna,nns"); conf.set("dfs.namenode.rpc-address.cluster1.nna", hosts[0]); conf.set("dfs.namenode.rpc-address.cluster1.nns", hosts[1]); conf.set("dfs.client.failover.proxy.provider.cluster1", "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"); conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); } public static class PictureMap extends Mapper<LongWritable, Text, Text, Text> { @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException { StringTokenizer strToken = new StringTokenizer(value.toString()); Text owner = new Text(); Set<String> set = new TreeSet<String>(); owner.set(strToken.nextToken()); while (strToken.hasMoreTokens()) { set.add(strToken.nextToken()); } String[] relations = new String[set.size()]; relations = set.toArray(relations); for (int i = 0; i < relations.length; i++) { for (int j = i + 1; j < relations.length; j++) { String outPutKey = relations[i] + relations[j]; context.write(new Text(outPutKey), owner); } } } } public static class PictureReduce extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException { String common = ""; for (Text val : values) { if (common == "") { common = val.toString(); } else { common = common + "," + val.toString(); } } context.write(key, new Text(common)); } } public int run(String[] args) throws Exception { final Job job = Job.getInstance(conf); job.setJarByClass(PictureMap.class); job.setMapperClass(PictureMap.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); job.setReducerClass(PictureReduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.setInputPaths(job, args[0]); FileOutputFormat.setOutputPath(job, new Path(args[1])); int status = job.waitForCompletion(true) ? 0 : 1; return status; } public static void main(String[] args) { try { if (args.length != 1) { log.warn("args length must be 1 and as date param"); return; } String tmpIn = SystemConfig.getProperty("hdfs.input.path.v2"); String tmpOut = SystemConfig.getProperty("hdfs.output.path.v2"); String inPath = String.format(tmpIn, "t_pic_20150801.log"); String outPath = String.format(tmpOut, "meta/" + args[0]); // bak dfs file to old HDFSUtils.bak(tmpOut, outPath, "meta/" + args[0] + "-old", conf); args = new String[] { inPath, outPath }; int res = ToolRunner.run(new Configuration(), new PictureRelations(), args); System.exit(res); } catch (Exception ex) { ex.printStackTrace(); log.error("Picture relations task has error,msg is" + ex.getMessage()); } } } 4.截图预览 关于计算结果,如下图所示: 5.总结 本篇博客只是从思路上实现了图片关联计算,在数据量大的情况下,是有待优化的,这里就不多做赘述了,后续有时间在为大家分析其中的细节。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
1.概述 在《高可用Hadoop平台-Oozie工作流》一篇中,给大家分享了如何去单一的集成Oozie这样一个插件。今天为大家介绍如何去使用Oozie创建相关工作流运行与Hadoop上,已经在创建过程当中需要注意的事项,下面是今天的分享目录: Oozie简介 任务工作流 截图预览 下面开始今天的内容分享。 2.Oozie简介 在Oozie中有几个重要的概念,他们分别是: WorkFlow:工作流,控制工作流的开始和结束过程,以及工作流Job的执行路径,并提供一种机制来控制工作流执行路径(比如:Decision、Fork以及Join节点等),其书写方式如下所示: <workflow-app name="[WF-DEF-NAME]" xmlns="uri:oozie:workflow:0.1"> ... 省略详细内容 ... </workflow-app> Coordinator:多个WorkFlow可以组成一个Coordinator,可以把前几个WorkFlow的输出作为后一个WorkFlow的输入,当然也可以定义WorkFlow的触发条件,来做定时触发,其书写方式如下所示:<coordinator-app name="[CD-DEF-NAME]" frequency="${coord:minutes(10)}" start="${start}" end="${end}" timezone="GMT+0800" xmlns="uri:oozie:coordinator:0.1"> <action> <workflow> <app-path>${workflowAppUri}</app-path> <configuration> <property> <name>jobTracker</name> <value>${jobTracker}</value> </property> <property> <name>nameNode</name> <value>${nameNode}</value> </property> <property> <name>queueName</name> <value>${queueName}</value> </property> </configuration> </workflow> </action> <action> <workflow> ... </workflow> </action> </coordinator-app> Bundle:控制一个或多个Coordinator应用,其写法如下所示:<bundle-app name=[NAME] xmlns='uri:oozie:bundle:0.1'> <controls> <kick-off-time>[DATETIME]</kick-off-time> </controls> <coordinator name=[NAME] > <app-path>[COORD-APPLICATION-PATH]</app-path> <configuration> <property> <name>[PROPERTY-NAME]</name> <value>[PROPERTY-VALUE]</value> </property> ... </configuration> </coordinator> ... </bundle-app> 3.任务工作流 下面,我们在Hadoop平台下去创建这样一个工作流,首先,我们需要配置Hadoop的core-site.xml文件,在该文件中添加以下内容: core-site.xml <!-- OOZIE --> <property> <name>hadoop.proxyuser.hadoop.hosts</name> <value>*</value> </property> <property> <name>hadoop.proxyuser.hadoop.groups</name> <value>*</value> </property> 然后,在去修改Oozie的oozie-site.xml文件,在该文件中添加如下内容如下: oozie-site.xml <property> <name>oozie.services</name> <value> org.apache.oozie.service.SchedulerService, org.apache.oozie.service.InstrumentationService, org.apache.oozie.service.MemoryLocksService, org.apache.oozie.service.UUIDService, org.apache.oozie.service.ELService, org.apache.oozie.service.AuthorizationService, org.apache.oozie.service.UserGroupInformationService, org.apache.oozie.service.HadoopAccessorService, org.apache.oozie.service.JobsConcurrencyService, org.apache.oozie.service.URIHandlerService, org.apache.oozie.service.DagXLogInfoService, org.apache.oozie.service.SchemaService, org.apache.oozie.service.LiteWorkflowAppService, org.apache.oozie.service.JPAService, org.apache.oozie.service.StoreService, org.apache.oozie.service.SLAStoreService, org.apache.oozie.service.DBLiteWorkflowStoreService, org.apache.oozie.service.CallbackService, org.apache.oozie.service.ActionService, org.apache.oozie.service.ShareLibService, org.apache.oozie.service.CallableQueueService, org.apache.oozie.service.ActionCheckerService, org.apache.oozie.service.RecoveryService, org.apache.oozie.service.PurgeService, org.apache.oozie.service.CoordinatorEngineService, org.apache.oozie.service.BundleEngineService, org.apache.oozie.service.DagEngineService, org.apache.oozie.service.CoordMaterializeTriggerService, org.apache.oozie.service.StatusTransitService, org.apache.oozie.service.PauseTransitService, org.apache.oozie.service.GroupsService, org.apache.oozie.service.ProxyUserService, org.apache.oozie.service.XLogStreamingService, org.apache.oozie.service.JvmPauseMonitorService </value> </property> <property> <name>oozie.service.HadoopAccessorService.hadoop.configurations</name> <value>*=/home/hadoop/hadoop-2.6.0/etc/hadoop</value> </property> 在修改完相关文件后,下面我们去创建Oozie的sharelib,其命令如下所示: oozie-setup.sh sharelib create -fs hdfs://cluster1 然后使用shareliblist命令查看相关内容,命令如下所示: oozie admin -shareliblist -oozie http://nna:11000/oozie 若成功创建,会生成如下图所示内容: 若未出现相应内容,请检查相关信息是否配置正确即可。 启动Oozie服务 oozied.sh start 注:在启动时,这里建议打开oozie的启动日志,动态观察相关日志信息,也许会出现一些异常信息,比如: Caused by: java.lang.NoClassDefFoundError: org/htrace/Trace at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:214) Caused by: java.lang.NoClassDefFoundError: com/google/protobuf/ServiceException at org.apache.hadoop.ipc.ProtobufRpcEngine.<clinit>(ProtobufRpcEngine.jav 这些异常信息大多是由于我们在前面打包生成war包时,由于缺少相关的依赖JAR包导致的,这里我们将缺少的JAR从Hadoop的share目录下找到对应的JAR拷贝到Oozie运行war包容器下即可,如: $OOZIE_HOME/oozie-server/webapps/oozie/WEB-INF/lib 这里,异常都有相应的提示,大家耐心的按照提示解决异常即可,在解决相关异常后,我们就可以去创建相关工作流。 Oozie给我们提供了相关示例让我去参考配置,下面我们将examples/apps下的文件上传到HDFS当中去,这里我上传在HDFS的 /oozie目录下。接下来,我给大家去演示一个定时任务。首先,我们进去到apps目录下的cron目录,这是一个定时任务的示例,其中包 含:coordinator.xml、job.properties和workflow.xml三个文件,这里我们对其进行配置。 coordinator.xml <coordinator-app name="cron-coord" frequency="${coord:minutes(10)}" start="${start}" end="${end}" timezone="GMT+0800" xmlns="uri:oozie:coordinator:0.2"> <action> <workflow> <app-path>${workflowAppUri}</app-path> <configuration> <property> <name>jobTracker</name> <value>${jobTracker}</value> </property> <property> <name>nameNode</name> <value>${nameNode}</value> </property> <property> <name>queueName</name> <value>${queueName}</value> </property> </configuration> </workflow> </action> </coordinator-app> 这里配置的频率为10分钟,该属性可配置其它频率(如:小时,天等)。 job.properties nameNode=hdfs://cluster1 jobTracker=nna:8132 queueName=default examplesRoot=examples oozie.coord.application.path=${nameNode}/oozie/${examplesRoot}/apps/cron start=2015-08-25T13:00+0800 end=2015-08-26T01:00+0800 workflowAppUri=${nameNode}/oozie/${examplesRoot}/apps/cron 这里由于使用Hadoop2.x的HA特性,在指定NameNode时,直接使用hdfs://cluster1,在Hadoop2.x后,jobTracker被替换了,这里将其地址指向resourcemanager.address的地址。 workflow.xml <workflow-app xmlns="uri:oozie:workflow:0.2" name="one-op-wf"> <start to="end"/> <end name="end"/> </workflow-app> 工作流使用一个空的Job。 在配置完相关文件后,我们将workflow.xml和coordinator.xml上传到指定的HDFS地址(之前上传examples目录下的corn目录下,上传之前先删除存在的文件),最后,我们启动这样一个工作流,命令如下所示: $OOZIE_HOME/bin/oozie job -oozie http://nna:11000/oozie -config job.properties -run 创建成功后,会生成一个JobID,如下图所示: 注:图中我将命令封装在Shell脚本当中。这里在创建工作流时,同样建议动态开启Oozie的运行日志,便于查看异常信息。 若是需要Kill任务,可以使用以下命令: $OOZIE_HOME/bin/oozie job -oozie http://nna:11000/oozie -kill [JOB_ID] 其Job DAG如下图所示: 4.截图预览 在提交作业后,我们可以通过浏览Oozie的Web监控界面观察相关信息,如下图所示: 5.总结 在将Oozie集成到Hadoop平台时,会有点繁琐,出现的异常都会在日志中有详细的记录,我们可以根据这些记录去解决出现的异常。另外,我 们在提交任务成功后,在Oozie执行期间若是出现异常,我们也可以通过Oozie的监控界面去非常方便的查看对应的异常信息,并排除。异常信息较多,需 要我们耐心的阅读相关记录信息。 6.结束语 这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!