近来工作当中用到hadoop之mapreduce、hive、hbase等做大数据的离线数据分析与挖掘颇多,在进一步巩固了自身对于hadoop相关研发的理论与实战水平,在空闲之余会写一系列相关的编程博文。鉴于前边已有一些博文介绍名词概念、基本理论等,本篇作为开篇博文,重点从开发流程中去梳理hadoop编程做大数据分析挖掘的标准或常用流程。
1、hadoop研发核心流程
(1)数据源
* 来源:数据来源主要分三种情况,一是,自有平台产生,像腾讯、淘宝。二是,靠网络采集系统,像百度、谷歌。三是,像第三方数据供应商提供,如运营商数据、开源大数据共享集合等。
* 存储: 有了数据来源,要想进行挖掘必须要存储于hadoop的分布式文件系统中,即hadoop distributed file system,即hdfs。
(2)数据的处理流程逻辑
严格来讲,(1)、(2)两点是分离的,只是存在一个前后处理的顺序关系。数据是千差万别的,本篇重点讲解处理流程与逻辑,即mapreduce开发环节。
mapreduce开发主要是四个环节:map-->combine-->shuffle-->reduce。
(2.1)其中的map和combine是属于map阶段,
map:即将原始的输入数据split分块打散的过程,也是大数据“分而治之”、“分流”的思想的体现。将海量数据通过设定的拆分规则,拆分成小的数据块block。然后分配给各map进程并行处理,并将本地计算的数据暂存到本地的临时数据目录,待该输出被reduce所使用完毕,hadoop系统会主动将map产生的这些临时文件删除。
combine: 即合并的过程。map的输出会作为reduce的输入,而map、reduce不一定在同一机器节点中,这样势必会经过网络传输。相对于计算而言,网络传输是比较费时的,故为了减少map->reduce的零散的大量小数据块的传输耗时,hadoop提供combiner来合并map输出的零散小数据,从而减小网络传输,提高整体的效率。
(2.2)shuffle
shuffle是中间阶段,负责map的各个节点的输出经过hash处理后映射到相应的reduce的过程。map和reduce并不是一一对应的,而是按照一定的规则将map上的输出的key/value通过一定的hash计算映身到固定的reduce进程中,这样也就保证了相同的key必然会输出到相同的reduce进程中,从而完成了以key为中心的(key,value list)的汇聚。
(2.3)redcue
即对map打散计算完成后,在此对结果合进行汇聚计算与输出。
例如,在经典的wordcount的mapreduce使用实例中,是对以上四步的准确反应。其代码详解在历史博文中有介绍,有疑问题可以详查看看。
2、hadoop开发的标准编程
2.1 关键模块
任务驱动类Driver:即指定要做的hadoop job的描述信息,包括输入与输出数据的路径、类型、处理的map/reduce类、reduce个数的设置等等。具体代码如下示例
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
|
Job job =
new
Job(ConfigurationUtil.conf,
"CrowdCalculator_v2"
);
job.setJarByClass(CrowdCalcDriver.
class
);
job.setMapperClass(CrowdCalcMapper.
class
);
job.setReducerClass(CrowdCalcReducer.
class
);
// 同时设置了map/reduce两部分的key,value对
job.setOutputKeyClass(Text.
class
);
job.setOutputValueClass(Text.
class
);
// 将otherArgs[0]的输入串进行拆分,以","号隔开
if
(StringOperatorUtil.isNotBlank(otherArgs[
0
])
&& StringOperatorUtil.isNotBlank(otherArgs[
1
])) {
String[] inputPathArray = otherArgs[
0
].split(
","
);
for
(String inputPath : inputPathArray) {
FileInputFormat.addInputPath(job,
new
Path(inputPath));
}
}
else
{
System.out.println(
"输入或输出路径有问题,请检查!"
);
System.exit(
0
);
}
FileOutputFormat.setOutputPath(job,
new
Path(otherArgs[
1
]));
// 手动设置reduce个数
job.setNumReduceTasks(SystemParas.mr_reduce_number);
int
finish_status = job.waitForCompletion(
true
) ?
0
:
1
;
return
finish_status;
|
map过程处理类:
也就是数据打散后的业务逻辑处理类,即结合天亮分词对已有的数据做关键词的匹配计算与输出。map中除核心的map函数外,还包括map之前要执行的setup函数,以及map之后要执行的cleanup函数,二者一个负责预置计算,一个负责清理扫尾。
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
|
public
class
PyCookieToQQMapper4StepOne
extends
Mapper<Object, Text, Text, Text> {
@Override
protected
void
cleanup(Context context)
throws
IOException,
InterruptedException {
// TODO Auto-generated method stub
}
public
static
MyLogger logger =
new
MyLogger(PyCookieToQQMapper4StepOne.
class
);
public
static
Set<String> keyword_wanted_set =
new
HashSet<String>();
@Override
protected
void
setup(Context context)
throws
IOException,
InterruptedException {
// 上传到hdfs之后,读取hdfs上的所有缓存文件,初始化分词器
SkyLightAnalyzerManager.init();
/**
* 关于keywordSet的对应关系的加载,此为keyword的白名单
*/
String keyword_path =
"keyword_wanted.txt"
;
ReadConfigUtil readConfigUtil =
new
ReadConfigUtil(keyword_path,
false
);
String host_type_string = readConfigUtil.getLineConfigTxt();
try
{
StringReader sr =
new
StringReader(host_type_string);
BufferedReader br =
new
BufferedReader(sr);
String temp =
null
;
while
((temp = br.readLine()) !=
null
) {
keyword_wanted_set.add(temp.trim());
}
br.close();
}
catch
(Exception e) {
e.printStackTrace();
}
}
private
ExtKeywordManager extKeywordManager =
new
ExtKeywordManager();
private
Text outKey =
new
Text();
private
Text outValue =
new
Text();
// private int count = 0;
String[] columnArray =
null
;
String ts =
null
;
String url =
null
;
String host =
null
;
// 从map方法提出来
ExtKeyResult extKeyResult =
null
;
String query =
null
;
List<TermUnit> termUnitList =
null
;
String splitWord =
null
;
private
String py_cookie_id =
null
;
private
DateUtil dateUtil=
new
DateUtil();
public
void
map(Object key, Text value, Context context)
throws
IOException, InterruptedException {
// 以tab键将数据分成数组
columnArray = value.toString().split(StaticValue.separator_tab);
// 取日志中的内容列,为url
if
(columnArray.length !=
16
|| (SystemParas.is_ad_source_open &&
"0"
.equals(columnArray[
13
]))) {
return
;
}
py_cookie_id = columnArray[
15
];
// 如果py_cookie_id不存在的话,直接跳过
if
(StringOperatorUtil.isBlank(py_cookie_id)) {
return
;
}
ts = columnArray[
2
];
url = columnArray[
3
];
host = columnArray[
4
];
// 其第0列为ad为作为key,第3列作为url,第4列为host
extKeyResult = extKeywordManager.getExtKeyResutlt(url);
// 获取各搜索引擎中
if
(extKeyResult !=
null
&& extKeyResult.isValidUrl()
&& extKeyResult.isFromSearchKey4Match()) {
if
(extKeyResult !=
null
) {
if
(SystemParas.is_open_keyword_crowd) {
query = extKeyResult.getKeyword();
if
(StringOperatorUtil.isNotBlank(query)) {
query = query.replace(
"\t"
,
""
).trim();
termUnitList = SkyLightAnalyzerManager
.filterPosAndNonsense(query);
/**
* 判断搜索分词或分词器分词后的词条集合是否为空
*/
if
(termUnitList !=
null
&& (!termUnitList.isEmpty())) {
for
(TermUnit splitTerm : termUnitList) {
splitWord = splitTerm.getValue();
// 不为空,且长度大于1的才会被查询
if
(StringOperatorUtil.isNotBlank(splitWord)
&& splitWord.length() >
1
) {
if
(keyword_wanted_set.contains(splitWord)) {
// 设置ad_id
outKey.set(py_cookie_id);
outValue.set(host
+ StaticValue.separator_tab
+ query
+ StaticValue.separator_tab
+ dateUtil.formatLongToMMHHssString(ts));
context.write(outKey, outValue);
break
;
}
}
}
}
}
}
}
}
}
}
|
reduce过程处理类:
也就是数据的汇聚过程,将map中的输出key/value经过hash处理映身到各reduce节点中进行计算输出。
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
CrowdCalcReducer
extends
Reducer<Text, Text, Text, Text> {
private
Text result =
new
Text();
public
void
reduce(Text key, Iterable<Text> values, Context context)
throws
IOException, InterruptedException {
for
(Text val : values) {
result.set(val);
context.write(key, result);
}
}
}
|
小结:上述的标准流程是较常见的,为减少学习的初始复杂度,并没有包括combiner和shuffle。会在后续再做介绍。
3、hadoop开发中的注意事向
3.1 并不是所有的数据计算都可以用hadoop来搞定。
只有可以将数据独立分离计算与合并的任务才可以用hadoop来搞定,即可分割。但大量的实战证明,绝大多数的计算任务都可以通过合理的设计化分成可以划分成可以hadoop处理的任务。但像gzip压缩包解压缩这样的操作,一般认为是无法用hadoop来并行计算的,因为gzip是不可分割的,但像lzo,bz2等可分割的压缩格式,均可以被hadoop处理。
3.2 hadoop不适宜于实时性计算较强的计算
它是专为离线大数据而设计的,讲究的是吞吐量而非响应速度。像实时查询、实时计算对于hadoop是不适合的。为此hadoop也开发如hive、hbase等为解决这一问题作出努力,但在实时性方面还是很不适用的,像最近兴趣的spark、storm等,可以作为大数据实时处理的利器,与hadoop互补使用。
3.3 reduce的数量设置要合理
hadoop的map/reduce都是以进程为单位进行计算的,reduce设置的过多会导致reduce的初始化与销毁浪费时间,从而影响整个任务的效率。要根据输出数据量来计算reduce的数量。
3.4 map/reduce的槽位最大的数量设置
该数量的最大数量设置均有固定的参数,该值的计算与cpu个数、单cpu的核数直接相关,可以参考专门的博文设置之。
时间关系,暂定于此,欢迎各位同行交流指正,欢迎加入网络爬虫、nlp群320349384,交流促进发展,共享成就未来。