1.大数据首先要解决的问题是,海量数据怎么存储?
这个就交由 hadoop distribute file system HDFS 来解决;
1.具体怎么存?
hdfs 将海量数据(GB/TB)存储在多个普通机器节点上,将存储空间分成多个块(默认128M),再将这些数据存储分割存储在这些块中。
同时为了避免硬件问题导致数据损坏,hdfs还提供将数据备份到不同节点上的功能。
2.怎么读,读的效率高不高?
hdfs的模型是一次写多次读;
由于其是提供已经存在的历史数据,目标是批处理任务,能够配合批处理任务进行高吞吐作业,而不是针对单个应用的快速响应任务。
3.读出数据后交给计算任务时,是否会有带宽的制约?
将读出的海量数据做过多的网络传输非常消耗资源,而hdfs将计算任务提交到接近所需数据存储的节点来执行,降低传输消耗。
2.hdfs怎么执行客户端写或读请求指令的?
hdfs集群运行一个NameNode作为命名空间的管理,类似于文件目录的管理和读写数据的任务调度;
NameNode 通过心跳 监控 dataNode的健康状况;
同时 dataNode 向 NameNode发送 MateData 告知 数据存储的具体信息,例如目录,大小,副本数等等;
具体读写数据的任务由DataNode执行,DataNode在集群中有多个;
NameNode 和 DataNode 是 master and slave 架构。
3.hdfs具体怎么保证数据不会丢失?
hdfs通过多节点副本备份来保证数据的可靠性;
具体为,hdfs默认和推荐三副本,即一份数据共有三分存储在不同的dataNode上;
1.那么dataNode节点如何选择?
1.写入执行在hdfs节点上的,存在当前节点一份;
1.若是外部客户端写入的,则随机选择节点。
2.再拷贝一份到另一个机架的随机节点。
3.另一个机架除了2的节点外的其他随机节点。
4.只有一个NameNode工作,是否压力太大?
NameNode 还有一个 Secondary NameNode 帮忙处理 各个DataNode的mateData,定期上报到 NameNode。
5.hdfs api 编程从哪里入口?
从 FileSystem 入口。
6.hdfs写数据是怎么写的?
由客户端向NameNode服务请求,获取元数据,获取DataNode信息,获取写入数据副本策略,NameNode判断是否可以写入数据(上传文件是否已经存在)后,客户端请求DataNode,DataNode节点之间互相联系确定已准备好写入数据和写入副本策略,由客户端向DataNode发数据包packet 64k,DataNode收到数据包先行缓存,在另外开启线程将缓存数据写入磁盘,写入时机不确定,一般DataNode缓存占用达到某个程度触发。
7.hdfs读数据是怎么操作的?
由客户端请求NameNode元数据,和副本读取策略,即判断哪个DataNode节点离客户端近,或者随机选一个;
再由客户端请求DataNode读取数据,此时还是以数据包64k的方式发送;
8.NameNode如果保存的元数据原来越多怎么处理?
NN会把元数据写在磁盘上,同时内存中也加载一份供客户端查询;
同时会把对数据的操作命令保存在edits日志里,类似MySql的binlog一样;
同时已经写在磁盘内的元数据为fsImage,edits + fsImage = 完成的元数据;
Secondary NameNode 会定期将 fdImage 与 edits 合并为一个新的 fsImage。
9.有时候hdfs会进入安全模式,为什么?
当可用的数据块备份 / 整个数据块备份 <0.9999时,会触发安全模式,处于安全模式时,为了保证数据的安全,达到备份系数,会将数据块备份到其他节点,当备份完成,会关闭安全模式;
一般当hdfs刚启动时会进入这个模式,是因为NameNode 和 多个 DataNode之间需要通信确定节点状态;
处于安全模式时,还是可以读数据,但是无法写入数据。
10.mapReduce的工作流程是什么?以单词计数为例。
进行一个 mr job,需要设计一个驱动,驱动中定义 驱动类,配置输入输出路径,map任务类,map的输入输出k,v的数据类型(hadoop),reduce任务类,以及其输入输出类型;
map类需要实现hadoop的map接口,以及map方法;
map方法会传入 偏移量(一般是IntWriteable)和数据(一般是Text类型),默认是在数据源中遇到换行符(\n)就会调用map输出一行数据,map方法中由开发者编写数据处理逻辑,此处会将一行数据以逗号分割后的值为key,value 为 1 将其输出(例如 <"a", 1>);
中间会由hadoop的shuffle自动处理,将map输出的数据以key分组,将相同key的数据发送给同一个reduce任务处理;
reduce类需要实现reduce接口的reduce方法,输入为map的输出,具体为map输出的key,以及同一个key下多个values,表现为iterator,输出为业务需要的Text,IntWriteable;
在reduce中迭代values,将其累加后与key输出(例如 a,3 );
11.mr任务运行在不同的节点上,互相传输数据需要序列化提高效率,那么hadoop没有使用Java自带的Serializable,而是使用自己设计的序列化方式 DataOutputStream DataInputStream,既能提高效率,还能自定义扩展到其他协议;
12.传输数据时,hadoop本身的自带的Text IntWriteable 等不满足传输对象的需求,想自定义,怎么做?
要是传输自定义的类型,该类型需要实现hadoop的writable接口的write和readFields方法;
write方法中处理如何序列化输出,使用 DataOutput out, writeUTF writeInt writeLong 等输出对象属性;
readFields方法处理如何反序列化读入数据,使用 DataInput in, readUTF 等写入对象属性;
若是还有对该类对象的排序比较,那就需要实现 writableComparable 接口;
13.具体谈谈Hadoop job是怎么读取数据的?
抽象类 InputFormat
从文件读取数据的场景很常见,对应Hadoop 的 FileInputFormat,将数据读入再交给 mr处理;
为了提高mr处理效率,读取数据后会开启多个 mapTask 来处理,每个mt会处理一部分数据,那么如何拆分数据?
文件很大时,FIF会根据文件系统的Block大小来拆分数据到mt,默认是32M,拆分之后的数据称为 InputSplit,这仅仅是一个抽象概念,不是真正物理上的拆分;
文件很小时,每个文件都会被拆分为一个InputSplit交给mt;
拆分后每个 InputSplit 都会对应一个 mt来处理。
14.怎么读每个InputSplit?
使用 RecordReader 来读取 IS,生成 K V 再交由 mapTask处理;
15.那么具体 IF 是怎么确定 K V 的,以及都有哪些现成的,可用的?
不同的切割方式,都是通过 继承和增强FileInputFormat来实现的;
FileInputFormat 的 RecordReader 读出的是InputSplit每行数据 IntWritable,Text 即偏移量和文本;
TextInputFormat:可以直接在Driver中代替 FIF 来向Job提交输入数据;
重写了isSplitable方法,判断输入的数据是否可以被切割,有些压缩过的数据无法被切割;
重写了 createRecordReader 方法,返回 new LineRecordReader(recordDelimiter),底层是通过 \n 换行符 来读出 偏移量和文本Text的;
场景:输入数据每行头一个单词的一样的行数;
KeyValueInputFormat:需要在Driver中通过job.setInputFormat(KeyValueFormat.class)配置;
重写了isSplitable方法,同TextInputFormat;
重写了createRecordReader方法,底层是通过\t 制表符 来读出 Text Key 和 Text value;
可在configuration中配置分隔符来自定义,例如 逗号;
(例如:zk,zk01 读入的 key => "zk", value=>"zk01")
NLineInputFormat:可以控制将多行(而不是block大小)数据切割到同一个InputSplit中,也就是splits的数量,默认每行切为一个split,可以在NLineInputFormat.setNumLinesPerSplit(job, num)中设置行数;
DBInputFormat:
通过DBConfiguration.configureDB 配置 DB的属性(驱动类,连接,用户名,密码);
读一个表内的数据,输入的数据需要自定义类实现 WritAble DBWritable 和 其中 readFields(ResultSet set) write(PreparedStatement statement)来实现从DB中读写数据;
而在map中接受的 k v就不是默认的,主要是 value必须是自定义序列化类;
需要将 job.setMapOutputKeyClass(NullWritable.class) job.setMapOutputValueClass(自定义类);
DBInputFormat.setInput(job, 自定义类, 表名,condtion,orderby)
16.map处理完数据后,会分给几个reduce来处理?
map处理之后的中间文件的数量被称作 partition ,每个partition 对应一个reduce任务来处理;
将不同的map结果合并为一个partition的策略是什么?
默认是根据处理之后的数据的 key 的hash来分的;
可以通过实现 partitioner类来自定义分区的策略,输入的参数为 key,输出为 数字 代表第几个分区;
再通过job.setPartitionerClass来配置这个分区类;
一个partitioner对应一个reduce,可以配置成不一样的吗?
可以,通过job.setNumReduceTasks(num)来配置reduce的具体数量,但是要会有如下结果:
reduce的数量决定最终输出文件的数量;
1.reduce = par 输出 reduce == par 个
2.reduce > par 则会输出par个文件和reduce - par 个空文件
3.reduce = 1 只输出一个文件
4. 1<reduce<par 则会导致一些par不会被reduce处理并输出导致报错
17.大数据计算,以词频案例来说,默认每个map都会生成 k v 结构的数据,为什么?
从整体来看,大数据的最终目的是聚合,是将海量数据归纳统计总结抽象为规律,那么就要将每条数据分类归纳,那么分类归纳的依据是什么呢?这就看具体的业务需求,词频案例就是以相同单词为归纳依据;
所以 k 就代表着归纳的依据,即多个k 对应的 values 是可以归纳到一起的数据;
所以,无论时 map shuffle reduce 还是 其他实时 离线计算引擎 spark flink 等,最终目的都是 k v,都是聚合归纳;既然目的确定了,那么在思考 map combine reduce shuffle ,以及其他引擎的算子时,就能顺着最终目的来理解其过程。
18.既然每个map都会生成中间的kv形式的输出,那么能不能在map阶段将中间输出的kv先行聚合,以节省向后面任务传输数据的网络开销?
可以使用 combine,job.setCombinerClass
自定义combine类继承reducer类,覆写reduce<输入k vs, 输出 kv>方法,提前聚合。
不适用的场景?
凡是聚合操作,必定要舍弃一些聚合之前的数据,那么当下一步任务还需要的数据在之前的聚合操作中丢失,那么就不适合了;
例如 词频统计,map之后的聚合就不会影响最后的reduce任务;
平均数统计,map之后的聚合即平均数的计算后,传给reduce任务时,缺失了数字个数的数据,会影响reduce的计算。
19.如果想在最终文件输出中有序的数据,怎么做?
在reduce输出的 value 类型中实现WritAbleComparable<value类> 接口以及comparaTo方法,即可实现按照value类对象的排序;
如果最终只有一个reduceTask,即输出一个最终文件,则该reduceTask将处理全局的排序;
若设置了partitioner,将map的结果分区再分给多个reduce处理,则只有每个输出文件内局部的排序。
20.mr之后,最终如何将数据写出的?
写出数据由OutpuntFormat顶层类控制,实际上由继承它的类,覆写 getRecordWriter 创建并返回的 中的自定义的 RecordWriter 中的write 和 close来实现的;
在RecordWriter中,需要在构造函数中获取job,job中的configuration后面会用到构造FileSystem,再由FileSystem构造输入输出流,最终在 write中使用输出流来写数据,close中关闭资源;
使用时,job.setOutputFormatClass来设置输出类,然后 再由 FileOutputFormat.set(job,new Path()) 等来确定具体数据的任务和路径。
另外 DBOutputFormat 也可以用来写入到数据库中,需要DBConfiguration.configureDB 设置连接到DB的参数,在用 DBOutputFormat.setOutput来设置输出;
而job.setOutputKeyClass,最终输出的k v 中将数据都放在k中,需要k类实现 DBWritable接口 其中的 write 使用 preparedstatement 来按位置写入数据;
21.可以谈谈Hadoop整个mapreduce处理流程么?
1.job将数据切分为inputSplits交给mapTask;
1.大数据文件切分依据是按照大小(32M)切分;
2.小文件时,每个文件切分为一个split;
3.也可以按照行数切分。
5.RecordReader来确定怎么读出每条数据为 mt 输入的 kv;
2.交给mt处理输出kv之后,每个mt都会生成一个中间文件;
1.mt输出首先会交给内存,再写到磁盘中多个文件,最终合并为一个文件;
1.在输出到磁盘上时,可压缩;
2.若是有分区,也会记录分区索引;
3.mt输出的数据是根据value类排序好的。
3.shuffle将多个mt生成的多个文件数据根据key/分区再次合并为一个文件交给一个reduce;
1.合并成一个文件时,进行排序;
4.reduce收到输入的数据 kv,注意 k是相同的,最终做一次输出。
1.RecordWriter确定输出kv;
2.输出到文件时,可以压缩;
22.在mr过程中,可以配置资源参数来优化么?
可以,在 mapr-default.xml来配置,其实主要在于 io 方面的调优,加大内存,减少读写磁盘次数,以及在计算任务处增加cpu核数。
23.场景 group by 怎么实现?
1.最后的输出是 根据key分组,然后查询分组之后数据行数之和;
2.那么在rt中,已经获取到了同一个key的所有数据,那就迭代累加value输出;
3.最后只需要 key 和 数据行数,那么mt只需输出 key,1即可;
24.场景distinct 怎么实现?
1.最后输出为 去重后的key,而key在整个mr中一开始就被去重了;
2.rt中 获取到相同的key到一个任务,直接输出即可 key NullWritable;
3.map中提取key后向后输出即可 key NullWritable;
25.场景 select t2.orderId,t1.name from t1 join t2 on t1.uid = t2.uId 怎么实现 ?
t1:
uid name
1 apple
2 bear
t2:
uid orderId
1 1001
1 1002
2 1003
result:
1001 apple
1002 apple
1003 bear
1.最后输出为 t2.orderId,t1.name;
2.rt中,收到的数据是已经根据key(uid/oid)分过组的数据,其value<"t1"/"t2",name/,orderId>,此时的数据为:
rt1 key values
1 <t1, apple>
1 <t2, 1001>
1 <t2, 1002>
rt2 key values
2 <t1, bear>
2 <t2, 1003>
此时 key 不再被需要,根据SQL需要 将其组装成 <t2.orderId,t1.name>输出,相当于 values 中 标记 t1 数据集和 t2 的数据集进行一次笛卡尔积;
3.经过shuffle之后,根据key将数据分组发给rt,这就是完成了 on t1.uid = t2.uId 的条件筛选;
4.mt 从t1中拿到数据 构建为 key:uid,value:<"t1",name>;
mt 从t2中拿到数据 构建为 key:oid,value:<"t2",orderId>;
5. 如何判断是从t1、t2中获取数据?
在mapper中覆写setUp方法,输入参数为 Context context,FileSplit fileSplit = (FileSplit)context.getInputSplit();
通过 fileSplit 的filePath属性可知;
其实在map方法中也传入的Context,也可以这么中,但是会被多次调用,效率不高。
26.join on 场景如上处理,若是不同的key对应的数据量差别很大,导致shuffle阶段不同的任务计算量差距很大,即为数据倾斜,即数据集中在某个任务处理,而其他任务无法分担的情况。
27.能不能不在shuffle阶段进行join,而是在map中就进行join,这样就可以避免数据倾斜?
可以,尤其是在大表 join 小表的场景下,本质上就是遍历大表数据时,再遍历一次小表;
job.cacheFile(Path path) 设置将小表文件读入分布式缓存中 distributed cache;
在mapTask执行时,从mapper的setUp方法中,使用 context 将 cacheFile读到ReadBuffer中,再创建为mapper的成员变量cache中;
在map方法中从cache中获取数据和读入的大表数据进行join。
28.在实际的生产环境下,mt rt运行在哪里?
在实际的生产环境下,任务是在yarn中运行的;
1.具体是怎么运行的?
一个任务要在集群中运行,
首先需要资源来运行它,所以设计出Source Manager来统一管理集群资源;
并且为了保证SM高可用,采用主备模式;
其次该任务还是多个并行的,所以设计出Application Master来统一管理多个任务;
由AM来向SM申请和分配资源;
再由AM监控各个任务的执行情况,在任务挂掉时重新拉起任务;
任务最终运行在集群上,那么集群的资源情况由每个节点的Node Manager来管理;
NM会将资源和运行状态心跳上报SM;
若NM挂掉,SM会通知AM,由AM来处理在NM上挂掉的任务,例如在其他可用的NM上重新拉起任务;
每个任务在NM上运行时,为了不互相影响,由NM抽象出一个资源单位Container来运行该任务;
mr 任务就是具体运行在Container中;
AM 作为一个特殊的任务,也运行在Container中;
29.若同时提交多个任务,有的任务很耗资源,会导致其他任务得不到资源,怎么处理?
1.SM会抽象出多个队列,将整个集群资源汇总并预先将资源以百分比的形式分配给队列;
SM默认只有一个default队列,其他队列需要额外配置;
2.再将任务提交到队列中,该任务将使用该队列的资源;
队列中的任务获取资源的策略是先进先出;
3.yarn中默认使用的CapacityScheduler的队列调度策略;
该策略适合多租户共同使用大集群资源;
多个队列之间在保证该队列的任务完成标准基础上,将空闲队列的资源调给任务繁重的队列;
30.hdfs的多个NameNode(主备) DataNode,以及yarn的SourceManager(主备),NodeManager都是可以组成主从模式的集群服务,那么他们是如何实现高可用的?
使用ZooKeeper来实现的,而zk本质上是个数据库。
1.具体怎么实现的?
zk服务可以当作一个分布式锁来使用,集群中的每个服务都抢锁,谁能抢到锁谁就是集群中的master;
抢锁的具体功能为zk提供原子性写入数据功能,当一个服务成功写入数据,那么其他服务就不能再写入,即抢锁成功;
1.具体怎么写入数据的?
zk是树状目录结构的,且路径是唯一的即代表唯一的锁,每个路径代表一个znode用于存储数据;
2.那么master服务挂了怎么处理,zk怎么知道的?
zk的节点可以是临时节点,即与服务之间的session,当服务挂了,那么session失效,临时节点就会被删除,即锁的释放;
3.那么其他服务怎么知道锁释放?
其他服务在抢不到锁时,会注册监听该节点,当该节点数据变化,则会收到通知,此时就可以尝试写入数据抢锁。
31.集群中那么多服务,每个服务的状态数据都是否上报给master,master存在哪里,如果master挂了,这些信息怎么办?
这些信息可以存在zk中,master切换时会从zk中重新获取和修改集群状态;
这些节点信息可以存在zk的永久节点中供使用;
zknode修改信息同时也会同时记录版本号,有乐观锁的功能;
32.zk服务本身存了这么重要的信息,zk挂了怎么办,是否有高可用的方案?
zk本身支持高可用,zk的master挂了,会由剩下的节点选举,获得半数以上票数的节点会称为新的master,而原来的几点挂掉恢复后称为slave节点;
哪怕不恢复,只要集群中半数以上节点存活即可继续提供服务,所以zk集群要配置奇数个节点;
33.zk集群之间的数据怎么同步的?
zk是强一致性的,即master收到数据后会同步到其他节点,同步成功后才会响应客户端写入成功;
因为写数据较慢,所以建议不要写入太多数据,几k合适;
34.场景,集群多个服务需要集体修改配置,怎么做效率高?
多个服务可以监听同一个znode节点,节点上保存着公共配置信息,当修改时,所有服务都能收到通知;
35.Hadoop高可用怎么实现?
对于hdfs来说,需要有NameNode的主备;
对于yarn来说,需要有ResouceManager主备;
需要公共存储文件的服务JournalNode;
36.使用Hive的场景
SQL是一种通用的语义化检索语言,不固定依赖某个编程环境,可跨平台使用,甚至非开发人员使用,所以有在大数据领域通过SQL来查询和整理数据的需求;
而底层的,对语言的查询已经由MR支持了,hive内置引擎可以将输入的SQL优化转换为类似MR任务执行;
Hive 是使用SQL对历史数据进行结构化查询的客户端引擎,擅长批处理,离线处理;
37.Hive具体怎么工作?
Hive就像使用MySql一样,建立库 表 [分区] 表数据 以结构化数据,结构化的库表分区以及数据以目录结构和文件存储在hdfs上,另外库 表 分区 表字段等元数据存在Mysql上;
hive 提供了 命令行 和 api接口,供用户输入 sql语句来 DDL DML 等;
38.Hive表创建命令怎么使用?
Hive 可以创建永久存在的外部表,顾名思义,表结构存储在外部服务,可反复使用;
创建命令重要参数:
[TEMPORARY]:临时表 [EXTERNAL]:永久外部表
col_name data_type:列名 数据类型
PARTITIONED by:分区
CLUSTERED BY:4大by
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',':原始数据源是多行字符串,需要指定行分隔符(默认\n)。和列分割符 ','
STORED AS:输出数据的存储格式?
textfile orc parquet
LOCATION hdfs_path:指定表的存储路径
like:复制表的结构而不是数据
39.为什么要分为内部表和外部表?
内部表即 Managed tables,建表时默认就是此类型表,全部由Hive控制,如果删除表则会彻底删除;
外部表 External tables,建表时需要加上参数 [EXTERNAL],Hive只有查询而没有控制权,尽管hive命令删除了此表,但是不会删除真正的数据文件;
工作中一般使用 外部表来保证数据的安全,不影响别人使用,具体到某个任务执行周期可以创建内部表来存储临时的中间数据。
40.一般工作使用哪些数据加载方式?
1.load方式,将指定的本地 或者 hdfs数据加载到表中;
2.select... from table 方式,即从以及存在的表中查询出数据加载到目标表中;
1.create table 时就加载
2.table 已存在时 加载或者覆盖
3.也可以使用传统的 insert into values 插入数据,但是每执行一次都会产生一个小文件,不推荐使用;
41.集群之间表和数据的迁移,使用 import 和 export命令
import 将表数据和表的元数据输出到hdfs;
export 将指定表的数据和元数据载入到hive中;
42.hive数据最终都是存在hdfs中的多个文件中,那么如果文件太多即数据太多,查询速度慢怎么办?
1.分区,就像日志文件按天分为一个文件一样,hive数据也可以设置一个字段分区,建立分区表,底层数据就按照此字段拆分成不同的文件,查询时带上分区条件即可只扫描该分区数据文件;
2.可以在表分区后,手工在该表数据的目录中添加分区的数据文件,但是要在hive中刷新该表的元数据才可以被hive查询到;
3.也可以设置多级分区,即多个分区字段,数据文件就像多级目录一样组织,一般最多三级分区;
4.该分区字段不可以出现在表列中,因为分区本身就代表了该字段的值;
5.在严格模式下,向分区表插入数据时,必须显式指定分区字段和分区值;
6.在非严格模式下,支持动态分区,即显式指定分区字段,hive会动态的根据输入的数据的该字段不同值进行分区;
43.hive中的join操作
hive中的join操作有以下五种:
1.inner join on condition
求两个集合的交集,只查出符合条件的数据;
2.left join on condition
取出全左集合,符合条件的右集合字段会展示出来,不符合条件的右集合字段为null;
3.right join on condition
取出全右集合,符合条件的左集合字段会展示出来,不符合条件的左集合字段为null;
4.outer join on condition
取出全左右集合,符合条件的左右集合字段会展示出来,不符合条件的左右集合字段为null;
5.仅 join 无 on condition 或 condition 无效
笛卡尔积,两个集合相乘,会产生大量数据,谨慎使用;
44.hive 四个 by
order by
1.全局排序,数据量大非常消耗资源,严格模式下需要加limit限制任务规模;
2.因为要全局排序,所以最终会由一个reductTask来处理所有数据的聚合操作;
sort by
1.每个rt输出是有序的,即每个分区内的数据是有序的;
2.数字类型按数字大小排序,字符串类型按字典排序 a b c;
distribute by
1.指定分区字段,hash+取余
cluster by
1.等于 distribute by + sort by;对同一个字段进行 分区和分区内排序;
45.hive 调优
1.开启并行执行
1.场景,sql中有多个子查询,这些子查询可以并行执行;
2.若资源不够,并行并不能提升效率;
2.推测式执行
1.场景,当某个任务执行时间过长,则在另一个节点上执行该任务,两个任务哪个执行完成就采用哪个任务的结果并同时终止另一个任务;
2.若是数据倾斜则不是推测式执行适用场景,推测式执行的适用场景是不同的节点资源和效率不同导致任务执行超时;
46.数据倾斜
1.某个key的数据分布过多;
2.解决的方法是将key转换为 "prifix"_key,即将key转换为另一个值,将数据打散之后,再聚合;
1.group by 设置 hive.groupby.skewindata=true,表现为开启多个job;
2.count(disintct)
线用 group by 代替 disintct 分组,在执行count(1)
3.join 设置 hive.auto.convert.join=true 实际上是map扫描大表时读取缓存中的小表;
3.可以通过explain 来查看执行计划;
47.flume在跨集群,跨网络场景时,可以在用数据所在的集群配置flume sink 为 avro即网络输入,在中转的机器上配置 avro source 接受数据再写到 hdfs上;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。