-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
464 lines (464 loc) · 592 KB
/
search.xml
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[kafka和flume的对比]]></title>
<url>%2F2017%2F09%2F20%2FkafkaVSFlume%2F</url>
<content type="text"><![CDATA[flume和kafka对比:Kafka、Flume都可以实现数据的传输,但它们的侧重点不同。Kafka追求的是高吞吐量、高负载(topic下可以有多个partition)。kafka是零拷贝,速度快。Flume追求的是数据的多样性:数据来源的多样性、数据流向的多样性。如果数据来源很单一、想要高吞吐的话可以使用Kafka。如果数据来源很多、数据流向很多的话可以使用Flume。也可以将Kafka和Flume结合起来使用。 摘要:(1)kafka和flume都是日志系统。kafka是分布式消息中间件,自带存储,提供push和pull存取数据功能。flume分为agent(数据采集器),collector(数据简单处理和写入),storage(存储器)三部分,每一部分都是可以定制的。比如agent采用RPC(Thrift-RPC)、text(文件)等,storage指定用hdfs做。 (2)kafka做日志缓存应该是更为合适的,但是 flume的数据采集部分做的很好,可以定制很多数据源,减少开发量。所以比较流行flume+kafka模式,如果为了利用flume写hdfs的能力,也可以采用kafka+flume的方式。 采集层 主要可以使用Flume, Kafka两种技术。 Flume:Flume 是管道流方式,提供了很多的默认实现,让用户通过参数部署,及扩展API. Kafka:Kafka是一个可持久化的分布式的消息队列。 Kafka 是一个非常通用的系统。你可以有许多生产者和很多的消费者共享多个主题Topics。相比之下,Flume是一个专用工具被设计为旨在往HDFS,HBase发送数据。它对HDFS有特殊的优化,并且集成了Hadoop的安全特性。所以,Cloudera 建议如果数据被多个系统消费的话,使用kafka;如果数据被设计给Hadoop使用,使用Flume。 正如你们所知Flume内置很多的source和sink组件。然而,Kafka明显有一个更小的生产消费者生态系统,并且Kafka的社区支持不好。希望将来这种情况会得到改善,但是目前:使用Kafka意味着你准备好了编写你自己的生产者和消费者代码。如果已经存在的Flume Sources和Sinks满足你的需求,并且你更喜欢不需要任何开发的系统,请使用Flume。 Flume可以使用拦截器实时处理数据。这些对数据屏蔽或者过量是很有用的。Kafka需要外部的流处理系统才能做到。 Kafka和Flume都是可靠的系统,通过适当的配置能保证零数据丢失。然而,Flume不支持副本事件。于是,如果Flume代理的一个节点崩溃了,即使使用了可靠的文件管道方式,你也将丢失这些事件直到你恢复这些磁盘。如果你需要一个高可靠行的管道,那么使用Kafka是个更好的选择。 Flume和Kafka可以很好地结合起来使用。如果你的设计需要从Kafka到Hadoop的流数据,使用Flume代理并配置Kafka的Source读取数据也是可行的:你没有必要实现自己的消费者。你可以直接利用Flume与HDFS及HBase的结合的所有好处。你可以使用Cloudera Manager对消费者的监控,并且你甚至可以添加拦截器进行一些流处理。 Flume和Kafka可以结合起来使用。通常会使用Flume + Kafka的方式。其实如果为了利用Flume已有的写HDFS功能,也可以使用Kafka + Flume的方式。 参考资料 https://my.oschina.net/frankwu/blog/355298]]></content>
<categories>
<category>BigData</category>
</categories>
<tags>
<tag>flume</tag>
<tag>数据采集</tag>
<tag>kafka</tag>
</tags>
</entry>
<entry>
<title><![CDATA[机器学习--互信息(mutual information)的理解]]></title>
<url>%2F2017%2F09%2F19%2F%E4%BA%92%E4%BF%A1%E6%81%AF%2F</url>
<content type="text"><![CDATA[再做特征提取是时,那些特征重要,那些不重要呢?怎么确定那个自变量与因变量之间的相关性大小呢?三种方法: 逻辑回归中,权重大的特征自然就是相关性大的变量。 person相关系数 互信息 转自互信息的理解我们在之前研究过两个随机变量的独立性,我们定义若两个随机变量$X,Y$满足$$P(X,Y)=P(X)P(Y)$$则我们说随机变量$X,Y$独立。下面来直观地理解这个公式,可以发现,如果$X,Y$独立,那么已知$X$,将不会对$Y$的分布产生任何影响,即是说$P(Y)=P(Y|X)$,这个结果的证明也很简单,由贝叶斯公式:$$P(Y|X)=\frac{P(X,Y)}{P(X)}=\frac{P(X)P(Y)}{P(X)}=P(Y)$$即证。 由此可以看出,独立性反应了已知$X$的情况下,$Y$的分布是否会改变,或者说,在给定随机变量$X$之后,能否为$Y$带来额外的信息。然而独立性只能表示出两个随机变量之间是否会有关系,但是却不能刻画他们的关系大小。下面我们引入互信息,它不仅能说明两个随机变量之间是否有关系,也能反应他们之间关系的强弱。我们定义互信息$I(X,Y)$:$$I(X;Y)=\int_X \int_Y P(X,Y)\log\frac{P(X,Y)}{P(X)P(Y)}$$ 我们来稍微理解一下,log里面就是$X,Y$的联合分布和边际分布的比值,如果对所有$X,Y$,该值等于1,即是说他们独立的情况下,互信息$I(X;Y)=0$,即是说这两个随机变量引入其中一个,并不能对另一个带来任何信息,下面我们来稍稍对该式做一个变形$$\begin{aligned}I(X;Y)&=\int_X \int_Y P(X,Y)\log\frac{P(X,Y)}{P(X)P(Y)}\&=\int_X \int_Y P(X,Y)\log\frac{P(X,Y)}{P(X)}-\int_X \int_Y P(X,Y)\log{P(Y)}\&=\int_X \int_Y P(X)P(Y|X)\log P(Y|X) -\int_Y \log{P(Y)}\int_X P(X,Y)\&=\int_X P(X)\int_Y P(Y|X)\log P(Y|X)-\int_Y \log{P(Y)}P(Y)\&=-\int_X P(X)H(Y|X=x)+H(Y)\&=H(Y)-H(Y|X)\\end{aligned}$$ 其中,$H(Y)$是$Y$的熵,定义为$$H(Y)=-\int_Y P(Y)\log{P(Y)}$$衡量的是$Y$的不确定度,即使说,$Y$分布得越离散,$H(Y)$的值越高,而$H(Y|X)$则表示在已知$X$的情况下,$Y$的不确定度,而$I(X;Y)$则表示由$X$引入而使$Y$的不确定度减小的量,因而如果$X,Y$关系越密切,$I(X;Y)$越大,$I(X;Y)$最大的取值是$H(Y)$,也就是说,$X,Y$完全相关,由于X的引入,$Y$的熵由原来的$H(Y)$减小了$I(X;Y)=H(Y)$,变成了0,也就是说如果$X$确定,那么$Y$就完全确定了。而当$X,Y$独立时,$I(X;Y)=0$引入$X$,并未给$Y$的确定带来任何好处。 总结下$I(X;Y)$的性质:1)$I(X;Y)\geqslant 0$2)$H(X)-H(X|Y)=I(X;Y)=I(Y;X)=H(Y)-H(Y|X)$3)当$X,Y$独立时,$I(X;Y)=0$4)当$X,Y$知道一个就能推断另一个时,$I(X;Y)=H(X)=H(Y)$]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>相关性</tag>
<tag>机器学习</tag>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[机器学习--朴素贝叶斯分类]]></title>
<url>%2F2017%2F09%2F18%2F%E8%B4%9D%E5%8F%B6%E6%96%AF%2F</url>
<content type="text"><![CDATA[很傻很天真却很很好很强大的贝叶斯定理。。。 机器学习算法中,有种依据概率原则进行分类的朴素贝叶斯算 法,正如气象学家预测天气一样,朴素贝叶斯算法就是应用先 前事件的有关数据来估计未来事件发生的概率 基于朴素贝叶斯的垃圾邮件分类BoW(词袋)模型 Bag-of-words model (BoW model) 最早出现在自然语言处理(Natural Language Processing)和信息检索(Information Retrieval)领域.。该模型忽略掉文本的语法和语序等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的。BoW使用一组无序的单词(words)来表达一段文字或一个文档。 假设我们词库中只有四个单词I,don’t,love,you,分别用符号w1,w2,w3,w4意义对应表示,那么一封邮件就可以由这四个单词是否出现来表示,如:$w1\bigcap \neg w2 \bigcap w2 \bigcap w3$就表示文档I love you,而$w1\bigcap w2 \bigcap w2 \bigcap w3$就表示文档I don't love you。我们将单词出现的频率视为它出现的概率,如下图则P(Viagra)=5%。如果我们知道P(垃圾邮件)和P(Viagra)是相互独立的, 则容易计算P(垃圾邮件&Viagra),即这两个事件同时发生 的概率。20%*5%=1% 独立事件我们可以简单的应用这个方法计算,但是在现实中, P(垃圾邮件)和P(Viagra)更可能是高度相关的,因此上述计算是不正确的,我们需要一个精确的公式来描述这两个事件之间的关系。 贝叶斯公式 垃圾邮件中的朴素贝叶斯公式P(spam)为先验概率,P(spam|Viagra)为在Viagra单词出现后的后验概率。如果你不懂什么是先验概率和后验概率,请戳那些年被教科书绕晕的概率论基础计算贝叶斯定理中每一个组成部分的概率,我们必须构造一个频率表 计算贝叶斯公式P(垃圾邮件|Viagra)=P(Viagra|垃圾邮件)*P(垃圾邮件)/P(Viagra)=(4/20)*(20/100)/(5/100)=0.8因此,如果电子邮件含有单词Viagra,那么该电子邮件是垃圾 邮件的概率为80%。所以,任何含有单词Viagra的消息都需 要被过滤掉。当有额外更多的特征是,这一概念如何被使用呢?利用贝叶斯公式,我们得到概率如下:虽然我们写作时,相邻单词之间其实是有关联的,但是为了方便建立模型,我们假设单词的出现是相互独立,这也是Naive Bayes的Naive之处,很傻很天真,但是在实际应用中却发现其效果很好很强大。由于相互独立,那么就可以转化为接下来的公式:• 分母可以先忽略它,垃圾邮件的总似然为:(4/20)*(10/20)*(20/20)*(12/20)*(20/100)=0.012• 非垃圾邮件的总似然为:(1/80)*(66/80)*(71/80)*(23/80)*(80/100)=0.002• 将这些值转换成概率,我们只需要一步得到垃圾邮件概率为 0.012/(0.012+0.002)=85.7% 存在的问题另一个例子包含了4个单词的邮件呢?• 我们可以计算垃圾邮件的似然如下:(4/20)*(10/20)*(0/20)*(12/20)*(20/100)=0• 非垃圾邮件的似然为:(1/80)*(14/80)*(8/80)*(23/80)*(80/100)=0.00005• 因此该消息是垃圾邮件的概率为0/(0+0.00005)=0• 该消息是非垃圾邮件的概率为0.00005/(0+0.00005)=1• 问题出在Groceries这个单词,所有单词Grogeries有效抵消或否决了所有其他的证据。 拉普拉斯估计拉普拉斯估计本质上是给频率表中的每个计数加上一个较小的数,这样就保证了每一类中每个特征发生概率非零。通常情况下,拉普拉斯估计中加上的数值设定为1,这样就保证每一类特征的组合至少在数据中出现一次。• 然后,我们得到垃圾邮件的似然为:(5/24)*(11/24)*(1/24)*(13/24)*(20/100)=0.0004• 非垃圾邮件的似然为:(2/84)*(15/84)*(9/84)*(24/84)*(80/100)=0.0001• 这表明该消息是垃圾邮件的概率为80%,是非垃圾邮件的概率为20%。]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>机器学习</tag>
<tag>算法</tag>
<tag>贝叶斯</tag>
</tags>
</entry>
<entry>
<title><![CDATA[粗糙集理论整理]]></title>
<url>%2F2017%2F09%2F03%2F%E7%B2%97%E7%B3%99%E9%9B%86%2F</url>
<content type="text"><![CDATA[在自然科学、社会科学和工程技术的很多领域中,都不同程度地涉及到对不确定因素和对不完备信息的处理。从实际系统中采集到的数据常常包含着噪声,不够精确甚至不完整。采用纯数学上的假设来消除或回避这种不确定性,效果往往不理想。反之,如果正视它对这些信息进行合适地处理,常常有助于相关实际系统问题的解决。 多年来,研究人员一直在努力寻找科学地处理不完整性和不确定性的有效途径。模糊集和基于概率方法的证据理论是处理不确定信息的两种方法,已应用于一些实际领域。但这些方法有时需要一些数据的附加信息或先验知识,如模糊隶属函数、基本概率指派函数和有关统计概率分布等,而这些信息有时并不容易得到。1982年波兰学者Z. Paw lak 提出了粗糙集理论——它是一种刻画不完整性和不确定性的数学工具,能有效地分析不精确,不一致(inconsistent)、不完整(incomplete) 等各种不完备的信息,还可以对数据进行分析和推理,从中发现隐含的知识,揭示潜在的规律。 基本概念知识“知识”这个概念在不同的范畴内有多种不同的含义。在粗糙集理论中,“知识”被认为是一种分类能力。人们的行为是基于分辨现实的或抽象的对象的能力,如在远古时代,人们为了生存必须能分辨出什么可以食用,什么不可以食用;医生给病人诊断,必须辨别出患者得的是哪一种病。这些根据事物的特征差别将其分门别类的能力均可以看作是某种“知识”。[4] 不可分辨关系分类过程中,相差不大的个体被归于同一类,它们的关系就是不可分辨关系(indiscernibility relation). 假定只用两种黑白颜色把空间中的物体分割两类,{黑色物体},{白色物体},那么同为黑色的两个物体就是不可分辨的,因为描述它们特征属性的信息相同,都是黑色.如果再引入方,圆的属性,又可以将物体进一步分割为四类: {黑色方物体},{黑色圆物体},{白色方物体},{白色圆物体}. 这时,如果两个同为黑色方物体,则它们还是不可分辨的. 不可分辨关系是一种等效关系(equivalence relationship),两个白色圆物体间的不可分辨关系可以理解为它们在白,圆两种属性下存在等效关系. 基本集基本集(elementary set) 定义为由论域中相互间不可分辨的对象组成的集合,是组成论域知识的颗粒. 不可分辨关系这一概念在粗糙集理论中十分重要,它深刻地揭示出知识的颗粒状结构,是定义其它概念的基础. 知识可认为是一族 等效关系,它将论域分割成一系列的等效类。[5] 集合粗糙集理论延拓了经典的集合论,把用于分类的知识嵌入集合内,作为集合组成的一部分. 一个对象a 是否属于集合X 需根据现有的知识来判断,可分为三种情况:⑴ 对象a 肯定属于集合X ;⑵ 对象a 肯定不属于集X ;⑶ 对象a 可能属于也可能不属于集合X 。集合的划分密切依赖于我们所掌握的关于论域的知识,是相对的而不是绝对的.给定一个有限的非空集合U 称为论域,I 为U 中的一族等效关系,即关于U 的知识,则二元对 K = (U,I) 称为一个近似空间(approximation space). 设x 为U 中的一个对象,X为U 的一个子集,I (x) 表示所有与x 不可分辨的对象所组成的集合,换句话说,是由x 决定的等效类,即I (x) 中的每个对象都与x 有相同的特征属性(attribute)。 举个栗子下面用一个具体的实例说明粗糙集的概念. 在粗糙集中使用信息表(information table) 描述论域中的数据集合. 根据学科领域的不同,它们可能代表医疗,金融,军事,过程控制等方面的数据. 信息表的形式和大家所熟悉的关系数据库中的关系数据模型很相似,是一张二维表格,如下表所示: 姓名 教育程度 是否找到了好工作 王治|高中|否马丽|高中|是李得|小学|否刘保|大学|是赵凯|博士|是表格的数据描述了一些人的教育程度以及是否找到了较好工作,旨在说明两者之间的关系. 其中王治,马丽,赵凯等称为对象(objects),一行描述一个对象. 表中的列描述对象的属性. 粗糙集理论中有两种属性: 条件属性(condition attribute) 和决策属性(decision attribute). 本例中”教育程度”为条件属性;”是否找到了好工作”为决策属性。设O 表示找到了好工作的人的集合,则O = {马丽,刘保,赵凯},设I 表示属性”教育 程度”所构成的一个等效关系,根据教育程度的不同,该论域被分割为四个等效类: {王治,马丽},{李得},{刘保},{赵凯}. 王治和马丽在 同一个等效类中,他们都为高中文化程度,是 不可分辨的. 则:集合O 的下逼近(即正区) 为 I *(O) = PO S (O) = {刘保,赵凯}集合O 的负区为 N EG (O) = {李得}集合O 的边界区为 BND (O) = {王治,马丽}集合O 的上逼近为 I 3 (O) = PO S (O) + BND (O) = {刘保,赵凯,王治,马丽}根据表1,可以归纳出下面几条规则,揭示了教育程度与是否能找到好工作之间的关系.RULE 1: IF (教育程度= 大学) OR (教育程度= 博士) THEN (可以找到好工作)RULE 2: IF (教育程度= 小学) THEN (找不到好工作)RULE 3: IF (教育程度= 高中) THEN (可能找到好工作)从这个简单的例子中,我们还可以体会到粗糙集理论在数据分析,寻找规律方面的作用. 粗糙集方法的简单实用性是令人惊奇的,它能在创立后的不长时间内得到迅速应用是因为具有以下特点:(1) 它能处理各种数据,包括不完整(incomplete) 的数据以及拥有众多变量的数据;(2) 它能处理数据的不精确性和模棱两可(ambiguity),包括确定性和非确定性的情况;(3) 它能求得知识的最小表达(reduct) 和知识的各种不同颗粒(granularity) 层次;(4) 它能从数据中揭示出概念简单,易于操作的模式(pattern) ;(5) 它能产生精确而又易于检查和证实的规则,特别适于智能控制中规则的自动生成 决策支持系统面对大量的信息以及各种不确定因素,要作出科学,合理的决策是非常困难的.决策支持系统是一组协助制定决策的工具,其重要特征就是能够执行IF THEN 规则进行判断分析. 粗糙集理论可以在分析以往大量经验数据的基础上找到这些规则,基于粗糙集的决策支持系统在这方面弥补了常规决策方法的不足,允许决策对象中存在一些不太明确,不太完整的属性,并经过推理得出基本上肯定的结论。[7]下面举一个例子,说明粗糙集理论可以根据以往的病例归纳出诊断规则,帮助医生作出判断。下表描述了八个病人的症状. 从下表中可以归纳出以下几条确定的规则: 病人编号 病理症状诊断结果 是否头痛 体温 是否感冒 病人1 是 正常 否 病人2 是 高 是 病人3 是 很高 是 病人4 否 正常 否 病人5 否 高 否 病人6 否 很高 是 病人7 否 高 是 病人8 否 很高 否 1.IF (体温正常) THEN (没感冒)2.IF (头痛) AND (体温高) THEN (感冒)3.IF (头痛) AND (体温很高) THEN (感冒) 还有几条可能的规则: 4.IF (头不痛) THEN (可能没感冒)5.IF (体温高) THEN (可能感冒了)6.IF (体温很高) THEN (可能感冒了) 病人5 和病人7,病人6 和病人8,症状相同,但是一个感冒另一个却没感冒,这种情况称为不一致(inconsistent). 粗糙集就是靠这种IF THEN 规则的形式表示数据中蕴含的知识. 参考资料 粗糙集 模糊集合 论域 粗糙集的概念和一些例子]]></content>
<categories>
<category>Algorithm</category>
<category>粗糙集</category>
</categories>
<tags>
<tag>粗糙集</tag>
<tag>模糊匹配</tag>
</tags>
</entry>
<entry>
<title><![CDATA[那些年被教科书绕晕的概率论基础]]></title>
<url>%2F2017%2F08%2F28%2F%E6%A6%82%E7%8E%87%E8%AE%BA%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[整理一下概率论的基础知识,通俗易懂的大白话,让你真正理解这些基础定义。 概率(probability)和似然(likelihood)概率(probability)和似然(likelihood),都是指可能性,都可以被称为概率,但在统计应用中有所区别。 概率是给定某一参数值,求某一结果的可能性的函数。 例如,抛一枚匀质硬币,抛10次,6次正面向上的可能性多大? 解读:“匀质硬币”,表明参数值是0.5,“抛10次,六次正面向上”这是一个结果,概率(probability)是求这一结果的可能性。 似然是给定某一结果,求某一参数值的可能性的函数。 例如,抛一枚硬币,抛10次,结果是6次正面向上,其是匀质的可能性多大? 解读:“抛10次,结果是6次正面向上”,这是一个给定的结果,问“匀质”的可能性,即求参数值“匀质”=0.5的可能性。而最大似然估计就是似然最大时对应的参数“匀质=?”估计。 先验概率和后验概率教科书上的解释总是太绕了。其实举个例子大家就明白这两个东西了。 假设我们出门堵车的可能因素有两个(就是假设而已,别当真):车辆太多和交通事故。 堵车的概率就是先验概率 。 那么如果我们出门之前我们听到新闻说今天路上出了个交通事故,那么我们想算一下堵车的概率,这个就叫做条件概率 。也就是P(堵车|交通事故)。这是有因求果。 如果我们已经出了门,然后遇到了堵车,那么我们想算一下堵车时由交通事故引起的概率有多大, 那这个就叫做后验概率 (也是条件概率,但是通常习惯这么说) 。也就是P(交通事故|堵车)。这是有果求因。 下面的定义摘自百度百科: 先验概率是指根据以往经验和分析得到的概率,如全概率公式,它往往作为”由因求果”问题中的”因”出现. 后验概率是指依据得到”结果”信息所计算出的最有可能是那种事件发生,如贝叶斯公式中的,是”执果寻因”问题中的”因”. 那么这两个概念有什么用呢? 最大似然估计我们来看一个例子。 有一天,有个病人到医院看病。他告诉医生说自己头痛,然后医生根据自己的经验判断出他是感冒了,然后给他开了些药回去吃。 有人肯定要问了,这个例子看起来跟我们要讲的最大似然估计有啥关系啊。 关系可大了,事实上医生在不知不觉中就用到了最大似然估计(虽然有点牵强,但大家就勉为其难地接受吧。 怎么说呢? 大家知道,头痛的原因有很多种啊,比如感冒,中风,脑溢血…(脑残>_<这个我可不知道会不会头痛,还有那些看到难题就头痛的病人也不在讨论范围啊!)。 那么医生凭什么说那个病人就是感冒呢?哦,医生说这是我从医多年的经验啊。 咱们从概率的角度来研究一下这个问题。 其实医生的大脑是这么工作的, 他计算了一下 P(感冒|头痛)(头痛由感冒引起的概率,下面类似) P(中风|头痛) P(脑溢血|头痛) … 然后这个计算机大脑发现,P(感冒|头痛)是最大的,因此就认为呢,病人是感冒了。看到了吗?这个就叫最大似然估计(Maximum likelihood estimation,MLE)。 似然函数与最大似然估计下面给出似然函数跟最大似然估计的定义。我们假设f是一个概率密度函数,那么$$x\mapsto f(x|θ)$$是一个条件概率密度函数(θ 是固定的) 而反过来,$$\theta \mapsto f(x\mid\theta)$$叫做似然函数 (x是固定的)。一般把似然函数写成$$L(\theta \mid x)=f(x\mid\theta)$$$\theta$θ是因变量。而最大似然估计 就是求在θ的定义域中,当似然函数取得最大值时θ的大小。 意思就是呢,当后验概率最大时θ的大小。也就是说要求最有可能的原因。 由于对数函数不会改变大小关系,通常会将似然函数求一下对数,将乘法变加法,方便计算。 最大似然估计和EM算法的关系是什么EM算法可以看成是特殊情况下计算极大似然的一种算法。现实的数据经常有一些比较奇怪的问题,比如缺失数据、含有隐变量等问题。当这些问题出现的时候,计算极大似然函数通常是比较困难的,而EM算法可以解决这个问题。EM算法已经有很多应用,比如最经典的Hidden Markov模型等。如果我们关心的参数为θ,观察到的数据为y,隐藏变量为z,那么根据全概率公式:$$P(y\mid\theta)=\int P(y\mid z,\theta)dz$$理论上,只要最大化这个密度函数的对数,就可以得到极大似然估计。然而问题是,对z进行积分很多情况下是非常困难的,特别是z的维数可能与样本量一样大,这个时候如果计算数值积分是非常恐怖的一件事情。而EM算法可以解决这个问题。 根据贝叶斯法则,我们可以得到:$$h(z\mid y,\theta)=\frac{P(y\mid z,\theta)f(z\mid \theta)}{P(y\mid \theta)}$$EM算法的精髓就是使用这个公式处理z的维数问题。直觉上,EM算法就是一个猜测的过程: 给定一个猜测θ’,那么可以根据这个猜测θ’和现实的数据计算隐藏变量取各个值的概率。有了z的概率之后,再根据这个概率计算更有可能的θ。准确的来说,EM算法就是如下的迭代过程:简单来说:EM算法分E步和M步,在这个迭代的算法中,每经历一次M步,实际上就做了一次MLE的估计,而本身EM可以看作是因为存在了隐变量而导致MLE无法直接使用,故而想出来的一种比较聪明算法,但其本质依然是MLE。 GMM的EM算法实现所谓混合高斯模型(GMM)就是指对样本的概率密度分布进行估计,而估计采用的模型(训练模型)是几个高斯模型的加权和(具体是几个要在模型训练前建立好)。每个高斯模型就代表了一个类(一个Cluster)。对样本中的数据分别在几个高斯模型上投影,就会分别得到在各个类上的概率。然后我们可以选取概率最大的类所为判决结果。 高斯混合模型的终极理解GMM的定义这里引用李航老师《统计学习方法》上的定义,如下图:那么,为什么GMM的各个高斯分量的系数之和必须为1呢? 其实答案很简单,我们所谓的GMM的定义本质上是一个概率密度函数。而概率密度函数在其作用域内的积分之和必然为1。GMM整体的概率密度函数是由若干个高斯分量的概率密度函数线性叠加而成的,而每一个高斯分量的概率密度函数的积分必然也是1,所以,要想GMM整体的概率密度积分为1,就必须对每一个高斯分量赋予一个其值不大于1的权重,并且权重之和为1。 参考资料 概率与似然 概率(probability)和似然(likelihood)的共同点和区别 高斯混合模型–GMM(Gaussian Mixture Model)]]></content>
<categories>
<category>Algorithm</category>
<category>概率论</category>
</categories>
<tags>
<tag>likelihood</tag>
<tag>probability</tag>
</tags>
</entry>
<entry>
<title><![CDATA[盖茨推荐的一堂30分钟的经济学]]></title>
<url>%2F2017%2F07%2F29%2FEnglish-economic%2F</url>
<content type="text"><![CDATA[“This knowledge would help everyone as investors and citizens. Watching is a worthwhile 30 minutes investment.” –Bill Gates 经济到底由什么驱动?政府调控利率是如何影响经济的?央行增发货币又是为哪般?经济周期从何而来?经济衰退和经济萧条是一个物种吗?信贷到底是啥?钱原来大部分是信贷?价格到底怎么算?30分钟,带你了解经济引擎的运行原理,真正的经济学远比你忘得干干净净的教科书要简单有趣且精妙的多。 支出总额是经济的驱动力。经济中,政府由中央政府和中央银行组成,钱由货币和信贷组成。 央行调控利率是如何对经济产生影响的? 增加利率,借贷成本上升,信贷减少,通货紧缩。降低利率,借贷成本下降,信贷增多,通货膨胀。 政府又是用哪些办法调控经济运行状况的? 政府的四大法宝:1)财富转移(向富人增税,向穷人发放救济金)2)债务重组3)借贷(发行国债)4)增发钞票 经济周期经济的上下起伏是由债务的波动引起 债务的波动有两大周期短期,持续大约5-8年长期,大约持续75-100年 还记得曾今火爆的妖书《货币战争》,里面的一段话,大意是:大萧条时期,全世界都在亏钱,钱不能凭空消失吧?那么总有个人赚了,于是书里引出了被神化的罗斯柴尔德家族。看完这个视频,突然明白,原来现实生活中所谓的钱实际上由”钱+信贷“组成,且大部分都是信贷,而信贷是可以凭空产生和消失的,萧条中消失的钱,不过是上半个经济周期中大家通过借贷提前消费了。这个时代,各种砖家涌现,如何避免某些专家的忽悠?只有自己去了解这些领域的真正的基层原理,那些很简单却真实有效的原理,以此武装自己的大脑,才能避免被砖家殴打你的智商。 参考资料 HOW THE ECONOMIC MACHINE WORKS– 宏观经济运行的框架]]></content>
<categories>
<category>眼界</category>
</categories>
<tags>
<tag>Economic</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Spark学习笔记--超全总结]]></title>
<url>%2F2017%2F07%2F28%2FSpark%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[Spark架构,运行原理,任务调度和资源调度分析,内存管理分析,SparkSQL,SparkSreaming与kafaka,数据倾斜的解决,调优。 Spark简介Spark是美国加州大学伯克利分校的AMP实验室(主要创始人lester和Matei)开发的通用的大数据处理框架。 Apache Spark™ is a fast and general engine for large-scale data processing.Apache Spark is an open source cluster computing system that aims to make data analytics fast,both fast to run and fast to wrtie Spark应用程序可以使用R语言、Java、Scala和Python进行编写,极少使用R语言编写Spark程序,Java和Scala语言编写的Spark程序的执行效率是相同的,但Java语言写的代码量多,Scala简洁优雅,但可读性不如Java,Python语言编写的Spark程序的执行效率不如Java和Scala。 Spark有4中运行模式: local模式,适用于测试 standalone,并非是单节点,而是使用spark自带的资源调度框架 yarn,最流行的方式,使用yarn集群调度资源 mesos,国外使用的多 Spark比MapReduce快的原因 Spark基于内存迭代,而MapReduce基于磁盘迭代MapReduce的设计:中间结果保存到文件,可以提高可靠性,减少内存占用,但是牺牲了性能。Spark的设计:数据在内存中进行交换,要快一些,但是内存这个东西,可靠性比不过MapReduce。 DAG计算模型在迭代计算上还是比MR的更有效率。在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG) DAG计算模型在Spark任务调度中详解!Spark计算比MapReduce快的根本原因在于DAG计算模型。一般而言,DAG相比MapReduce在大多数情况下可以减少shuffle次数。Spark的DAGScheduler相当于一个改进版的MapReduce,如果计算不涉及与其他节点进行数据交换,Spark可以在内存中一次性完成这些操作,也就是中间结果无须落盘,减少了磁盘IO的操作。但是,如果计算过程中涉及数据交换,Spark也是会把shuffle的数据写磁盘的!有一个误区,Spark是基于内存的计算,所以快,这不是主要原因,要对数据做计算,必然得加载到内存,Hadoop也是如此,只不过Spark支持将需要反复用到的数据给Cache到内存中,减少数据加载耗时,所以Spark跑机器学习算法比较在行(需要对数据进行反复迭代)。Spark基于磁盘的计算也是比Hadoop快。刚刚提到了Spark的DAGScheduler是个改进版的MapReduce,所以Spark天生适合做批处理的任务。Hadoop的MapReduce虽然不如spark性能好,但是HDFS仍然是业界的大数据存储标准。 Spark是粗粒度的资源调度,而MR是细粒度的资源调度。粗细粒度的资源调度,在Spark资源调度中详解! RDD(Resilient Distributed Dataset )-弹性分布式数据集 A list of partitionsA function for computing each partitionA list of dependencies on other RDDs Optionally, a Partitioner for key-value RDDs Optionally, a list of preferred locations to compute each split on RDD之间的依赖关系称作为Lineage——血统 Spark任务执行流程 写一个Spark应用程序的流程1.加载数据集(获得RDD)可以从HDFS,NoSQL数据库中加载数据集 2.使用transformations算子对RDD进行操作transformations算子是一系列懒执行的函数 3.使用actions算子触发执行transformations算子对RDD的操作会被先记录,当actions算子触发后才会真正执行 伪代码示例:12345lines = sc.textFile(“hdfs://...”) //加载数据集errors = lines.filter(_.startsWith(“ERROR”)) //transformations算子lines.filter(x=>{x.startsWith(“ERROR”)}) //transformations算子Mysql_errors = errors.filter(_.contain(“MySQL”)).count //count是actions算子http_errors = errors.filter(_.contain(“Http”)).count 算子 Actionscount:统计RDD中元素的个数12345val rdd = sc.makeRDD(Array("hello","hello","hello","world"))val num = rdd.count()println(num)结果:4 foreach:遍历RDD中的元素1234567val rdd = sc.makeRDD(Array("hello","hello","hello","world"))rdd.foreach(println)结果:hellohellohelloworld foreachPartitionforeach以一条记录为单位来遍历RDDforeachPartition以分区为单位遍历RDDforeach和foreachPartition都是actions算子map和mapPartition可以与它们做类比,但map和mapPartitions是transformations算子 1234567891011121314151617//设置rdd的分区数为2val rdd = sc.parallelize(1 to 6, 2)rdd.foreachPartition(x => { println("data from a partition:") while(x.hasNext) { println(x.next()) }})结果:data from a partition:123data from a partition:456 collect:把运行结果拉回到Driver端1234567891011val rdd = sc.makeRDD(Array( (5,"Tom"),(10,"Jed"),(3,"Tony"),(2,"Jack")))val resultRDD = rdd.sortByKey()val list = resultRDD.collect()list.foreach(println)结果:(2,Jack)(3,Tony)(5,Tom)(10,Jed) take(n):取RDD中的前n个元素12345val rdd = sc.makeRDD(Array("hello","hello","hello","world"))rdd.take(2).foreach(println)结果:hellohello first :相当于take(1)1234val rdd = sc.makeRDD(Array("hello","hello","hello","world"))println(rdd.first)结果:Hello reduce:按照指定规则聚合RDD中的元素123456val numArr = Array(1,2,3,4,5)val rdd = sc.parallelize(numArr)val sum = rdd.reduce(_+_)println(sum)结果:15 countByKey:统计出KV格式的RDD中相同的K的个数12345678910111213val rdd = sc.parallelize(Array( ("销售部","Tom"), ("销售部","Jack"),("销售部","Bob"),("销售部","Terry"), ("后勤部","Jack"),("后勤部","Selina"),("后勤部","Hebe"), ("人力部","Ella"),("人力部","Harry"), ("开发部","Allen")))val result = rdd.countByKey();result.foreach(println)结果:(后勤部,3)(开发部,1)(销售部,4)(人力部,2) countByValue:统计出RDD中每个元素的个数1234567891011val rdd = sc.parallelize(Array( "Tom","Jed","Tom", "Tom","Jed","Jed", "Tom","Tony","Jed"))val result = rdd.countByValue();result.foreach(println)结果:(Tom,4)(Tony,1)(Jed,4) Transformationsfilter:过滤1234val rdd = sc.makeRDD(Array("hello","hello","hello","world"))rdd.filter(!_.contains("hello")).foreach(println)结果:world map 和flatMap sample :随机抽样sample(withReplacement: Boolean, fraction: Double, seed: Long)withReplacement : 是否是放回式抽样 true代表如果抽中A元素,之后还可以抽取A元素 false代表如果抽住了A元素,之后都不在抽取A元素fraction : 抽样的比例 seed : 抽样算法的初始值 123456789val rdd = sc.makeRDD(Array( "hello1","hello2","hello3","hello4","hello5","hello6", "world1","world2","world3","world4"))rdd.sample(false, 0.3).foreach(println)结果:hello4world1在数据量不大的时候,不会很准确 groupByKey和reduceByKey sortByKey:按key进行排序1234567891011val rdd = sc.makeRDD(Array( (5,"Tom"),(10,"Jed"),(3,"Tony"),(2,"Jack")))rdd.sortByKey().foreach(println)结果:(2,Jack)(3,Tony)(5,Tom)(10,Jed)说明:sortByKey(fasle):倒序 sortBy:自定义排序规则1234567891011121314151617181920212223242526272829object SortByOperator { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("TestSortBy").setMaster("local") val sc = new SparkContext(conf) val arr = Array( Tuple3(190,100,"Jed"), Tuple3(100,202,"Tom"), Tuple3(90,111,"Tony") ) val rdd = sc.parallelize(arr) rdd.sortBy(_._1).foreach(println) /* (90,111,Tony) (100,202,Tom) (190,100,Jed) */ rdd.sortBy(_._2).foreach(println) /*(190,100,Jed) (90,111,Tony) (100,202,Tom) */ rdd.sortBy(_._3).foreach(println) /* (190,100,Jed) (100,202,Tom) (90,111,Tony) */ sc.stop(); }} distinct:去掉重复数据distinct算子实际上经过了以下步骤:12345678910111213val rdd = sc.makeRDD(Array( "hello", "hello", "hello", "world"))val distinctRDD = rdd .map {(_,1)} .reduceByKey(_+_) .map(_._1)distinctRDD.foreach {println}等价于:rdd.distinct().foreach {println} join先看看SQL中的join假设有如下两张表:table A是左表,table B是右表 不同join方式会有不同的结果 1.Inner join产生的结果集是A和B的交集执行SQL: 123SELECT * FROM TableAINNER JOIN TableBON TableA.name = TableB.name 结果: 2.Left outer join产生表A的完全集,而B表中匹配的则有值,没有匹配的则以null值取代 执行SQL:123SELECT * FROM TableALEFT OUTER JOIN TableBON TableA.name = TableB.name 结果: 3.Right outer join产生表B的完全集,而A表中匹配的则有值,没有匹配的则以null值取代执行SQL:123SELECT * FROM TableARIGHT OUTER JOIN TableBON TableA.name = TableB.name 结果: 4.Full outer join(MySQL不支持)产生A和B的并集,但是需要注意的是,对于没有匹配的记录,则会以null做为值 执行SQL:123SELECT * FROM TableAFULL OUTER JOIN TableBON TableA.name = TableB.name 结果: 在Spark的算子中,对两个RDD进行join有着类似的作用假设有两个RDD:12345678910111213141516171819202122val nameList = List( (1,"Jed"), (2,"Tom"), (3,"Bob"), (4,"Tony")) val salaryArr = Array( (1,8000), (2,6000), (3,5000))/* * parallelize(Seq[T],Int num) * 使用指定的集合(可以是List、Array等)来创建RDD * num 指定RDD的分区数,默认为1 * 这个方法经常用于测试环境 * join产生的RDD的分区数由分区数最多的父RDD决定 */val nameRDD = sc.parallelize(nameList,2)val salaryRDD = sc.parallelize(salaryArr,3) 分别对4种join做测试:1234567val joinRDD = nameRDD.join(salaryRDD)joinRDD.foreach( x => { val id = x._1 val name = x._2._1 val salary = x._2._2 println(id + "\t" + name + "\t" + salary)}) 结果:1 Jed 80002 Tom 60003 Bob 50001234567val leftOuterJoinRDD = nameRDD.leftOuterJoin(salaryRDD)leftOuterJoinRDD.foreach( x => { val id = x._1 val name = x._2._1 val salary = x._2._2 println(id + "\t" + name + "\t" + salary)}) 结果:1 Jed Some(8000)2 Tom Some(6000)3 Bob Some(5000)4 Tony None1234567val rightOuterJoinRDD = nameRDD.rightOuterJoin(salaryRDD)rightOuterJoinRDD.foreach( x => { val id = x._1 val name = x._2._1 val salary = x._2._2 println(id + "\t" + name + "\t" + salary)}) 结果:1 Some(Jed) 80002 Some(Tom) 60003 Some(Bob) 50001234567val fullOuterJoinRDD = nameRDD.fullOuterJoin(salaryRDD)fullOuterJoinRDD.foreach( x => { val id = x._1 val name = x._2._1 val salary = x._2._2 println(id + "\t" + name + "\t" + salary)}) 结果:1 Some(Jed) Some(8000)2 Some(Tom) Some(6000)3 Some(Bob) Some(5000)4 Some(Tony) None union:把两个RDD进行逻辑上的合并union这个算子关联的两个RDD必须类型一致123val rdd1 =sc.makeRDD(1 to 10)val rdd2 = sc.parallelize(11 until 20)rdd1.union(rdd2).foreach {println} map和mapPartitionsmap()会一条记录为单位进行操作123456789101112131415161718192021222324252627282930val arr = Array("Tom","Bob","Tony","Jerry")//把4条数据分到两个分区中val rdd = sc.parallelize(arr,2) /* * 模拟把RDD中的元素写入数据库的过程 */rdd.map(x => { println("创建数据库连接...") println("写入数据库...") println("关闭数据库连接...") println()}).count()结果:创建数据库连接...写入数据库...关闭数据库连接...创建数据库连接...写入数据库...关闭数据库连接...创建数据库连接...写入数据库...关闭数据库连接...创建数据库连接...写入数据库...关闭数据库连接... mapPartitions以分区为单位进行操作123456789101112131415161718192021/* * 将RDD中的数据写入到数据库中,绝大部分使用mapPartitions算子来实现 */rdd.mapPartitions(x => { println("创建数据库") val list = new ListBuffer[String]() while(x.hasNext){ //写入数据库 list += x.next()+":写入数据库" } //执行SQL语句 批量插入 list.iterator})foreach(println)结果:创建数据库Tom:写入数据库Bob:写入数据库 创建数据库Tony:写入数据库Jerry:写入数据库 mapPartitionsWithIndex1234567891011121314151617181920212223242526272829303132val dataArr = Array("Tom01","Tom02","Tom03" ,"Tom04","Tom05","Tom06" ,"Tom07","Tom08","Tom09" ,"Tom10","Tom11","Tom12")val rdd = sc.parallelize(dataArr, 3);val result = rdd.mapPartitionsWithIndex((index,x) => { val list = ListBuffer[String]() while (x.hasNext) { list += "partition:"+ index + " content:" + x.next } list.iterator})println("分区数量:" + result.partitions.size)val resultArr = result.collect()for(x <- resultArr){ println(x)}结果:分区数量:3partition:0 content:Tom01partition:0 content:Tom02partition:0 content:Tom03partition:0 content:Tom04partition:1 content:Tom05partition:1 content:Tom06partition:1 content:Tom07partition:1 content:Tom08partition:2 content:Tom09partition:2 content:Tom10partition:2 content:Tom11partition:2 content:Tom12 coalesce:改变RDD的分区数123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081/* * false:不产生shuffle * true:产生shuffle * 如果重分区的数量大于原来的分区数量,必须设置为true,否则分区数不变 * 增加分区会把原来的分区中的数据随机分配给设置的分区个数 */val coalesceRdd = result.coalesce(6,true) val results = coalesceRdd.mapPartitionsWithIndex((index,x) => { val list = ListBuffer[String]() while (x.hasNext) { list += "partition:"+ index + " content:[" + x.next + "]" } list.iterator}) println("分区数量:" + results.partitions.size)val resultArr = results.collect()for(x <- resultArr){ println(x)}结果:分区数量:6partition:0 content:[partition:1 content:Tom07]partition:0 content:[partition:2 content:Tom10]partition:1 content:[partition:0 content:Tom01]partition:1 content:[partition:1 content:Tom08]partition:1 content:[partition:2 content:Tom11]partition:2 content:[partition:0 content:Tom02]partition:2 content:[partition:2 content:Tom12]partition:3 content:[partition:0 content:Tom03]partition:4 content:[partition:0 content:Tom04]partition:4 content:[partition:1 content:Tom05]partition:5 content:[partition:1 content:Tom06]partition:5 content:[partition:2 content:Tom09]val coalesceRdd = result.coalesce(6,fasle)的结果是:分区数量:3partition:0 content:[partition:0 content:Tom01]partition:0 content:[partition:0 content:Tom02]partition:0 content:[partition:0 content:Tom03]partition:0 content:[partition:0 content:Tom04]partition:1 content:[partition:1 content:Tom05]partition:1 content:[partition:1 content:Tom06]partition:1 content:[partition:1 content:Tom07]partition:1 content:[partition:1 content:Tom08]partition:2 content:[partition:2 content:Tom09]partition:2 content:[partition:2 content:Tom10]partition:2 content:[partition:2 content:Tom11]partition:2 content:[partition:2 content:Tom12]val coalesceRdd = result.coalesce(2,fasle)的结果是:分区数量:2partition:0 content:[partition:0 content:Tom01]partition:0 content:[partition:0 content:Tom02]partition:0 content:[partition:0 content:Tom03]partition:0 content:[partition:0 content:Tom04]partition:1 content:[partition:1 content:Tom05]partition:1 content:[partition:1 content:Tom06]partition:1 content:[partition:1 content:Tom07]partition:1 content:[partition:1 content:Tom08]partition:1 content:[partition:2 content:Tom09]partition:1 content:[partition:2 content:Tom10]partition:1 content:[partition:2 content:Tom11]partition:1 content:[partition:2 content:Tom12]val coalesceRdd = result.coalesce(2,true)的结果是:分区数量:2partition:0 content:[partition:0 content:Tom01]partition:0 content:[partition:0 content:Tom03]partition:0 content:[partition:1 content:Tom05]partition:0 content:[partition:1 content:Tom07]partition:0 content:[partition:2 content:Tom09]partition:0 content:[partition:2 content:Tom11]partition:1 content:[partition:0 content:Tom02]partition:1 content:[partition:0 content:Tom04]partition:1 content:[partition:1 content:Tom06]partition:1 content:[partition:1 content:Tom08]partition:1 content:[partition:2 content:Tom10]partition:1 content:[partition:2 content:Tom12] 下图说明了三种coalesce的情况: repartition:改变RDD分区数repartition(int n) = coalesce(int n, true) partitionBy:通过自定义分区器改变RDD分区数1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253JavaPairRDD<Integer, String> partitionByRDD = nameRDD.partitionBy(new Partitioner() { private static final long serialVersionUID = 1L; //分区数2 @Override public int numPartitions() { return 2; } //分区逻辑 @Override public int getPartition(Object obj) { int i = (int)obj; if(i % 2 == 0){ return 0; }else{ return 1; } }});``` ##### glom:把分区中的元素封装到数组中```scalaval rdd = sc.parallelize(1 to 10,2) /** * rdd有两个分区 * partition0分区里面的所有元素封装到一个数组 * partition1分区里面的所有元素封装到一个数组 */val glomRDD = rdd.glom()glomRDD.foreach(x => { println("============") x.foreach(println) println("============")})println(glomRDD.count())结果:============12345========================678910============2 randomSplit:拆分RDD12345678910111213/** * randomSplit: * 根据传入的 Array中每个元素的权重将rdd拆分成Array.size个RDD * 拆分后的RDD中元素数量由权重来决定,数据量不大时不一定准确 */val rdd = sc.parallelize(1 to 10)rdd.randomSplit(Array(0.1,0.2,0.3,0.4)).foreach(x => {println(x.count)})理论结果:1234实际结果不一定准确 zip与zip有关的3个算子如下图所示: 算子案例WordCount-Java版1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192/** * 文件中的数据 : * Spark Core * Spark Streaming * Spark SQL * @author root */public class WordCount { public static void main(String[] args) { /* * SparkConf对象主要用于设置Spark运行时的环境参数 : * 1.运行模式 * 2.Application Name * 3.运行时的资源需求 */ SparkConf conf = new SparkConf(); conf.setMaster("local").setAppName("WordCount"); /* * SparkContext是Spark运行的上下文,是通往Spark集群的唯一通道 */ JavaSparkContext jsc = new JavaSparkContext(conf); String path = "cs"; JavaRDD<String> rdd = jsc.textFile(path); //================== wordcount start ================= flatMapRDD.mapToPair(new PairFunction<String, String, Integer>() { /** * */ private static final long serialVersionUID = 1L; @Override public Tuple2<String, Integer> call(String word) throws Exception { return new Tuple2<String, Integer>(word, 1); } }).reduceByKey(new Function2<Integer, Integer, Integer>() { /** * */ private static final long serialVersionUID = 1L; @Override public Integer call(Integer v1, Integer v2) throws Exception { return v1 + v2; } }).mapToPair(new PairFunction<Tuple2<String, Integer>, Integer, String>() { /** * */ private static final long serialVersionUID = 1L; @Override public Tuple2<Integer, String> call(Tuple2<String, Integer> tuple) throws Exception { return new Tuple2<Integer, String>(tuple._2, tuple._1); } }).sortByKey(false) //fasle : 降序 .mapToPair(new PairFunction<Tuple2<Integer,String>, String, Integer>() { /** * */ private static final long serialVersionUID = 1L; @Override public Tuple2<String, Integer> call(Tuple2<Integer, String> tuple) throws Exception { return new Tuple2<String, Integer>(tuple._2, tuple._1); } }).foreach(new VoidFunction<Tuple2<String,Integer>>() { /** * */ private static final long serialVersionUID = 1L; @Override public void call(Tuple2<String, Integer> tuple) throws Exception { System.out.println(tuple); } });; //================= wordcount end ================== jsc.stop(); }}结果:(Spark,3)(SQL,1)(Streaming,1)(Core,1) 过滤掉出现次数最多的数据-Scala版1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/** * 文件中的数据 : * hello java * hello java * hello java * hello java * hello java * hello hadoop * hello hadoop * hello hadoop * hello hive * hello hive * hello world * hello spark * @author root */object FilterMost { def main(args: Array[String]): Unit = { val conf = new SparkConf() .setMaster("local") .setAppName("FilterMost") val sc = new SparkContext(conf) val rdd : RDD[String] = sc.textFile("test") val sampleRDD : RDD[String] = rdd.sample(false, 0.9) val result = sampleRDD .map { x => (x.split(" ")(1),1) } .reduceByKey(_+_) .map { x => {(x._2,x._1)}} .sortByKey(false) .first() ._2 rdd .filter {(!_.contains(result))} .foreach(println) sc.stop(); }}结果:hello hadoophello hadoophello hadoophello hivehello hivehello worldhello spark 统计每个页面的UV部分数据如下:日期 时间戳 用户ID pageID 模块 用户事件2017-05-13 1494643577030 null 54 Kafka View2017-05-13 1494643577031 8 70 Kafka Register2017-05-13 1494643577031 9 12 Storm View2017-05-13 1494643577031 9 1 Scala View2017-05-13 1494643577032 7 73 Scala Register2017-05-13 1494643577032 16 23 Storm Register scala代码: 12345678910111213141516171819202122object CountUV { def main(args: Array[String]): Unit = { val conf = new SparkConf() conf.setMaster("local") conf.setAppName("CountUV") val sc = new SparkContext(conf) val rdd = sc.textFile("userLog") val result = rdd.filter(!_.split("\t")(2).contains("null")) .map(x => { (x.split("\t")(3), x.split("\t")(2)) }) .distinct().countByKey() result.foreach(x => { println("PageId: " + x._1 + "\tUV: " + x._2) }) sc.stop(); }} Java代码:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class CountUV { public static void main(String[] args) { SparkConf sparkConf = new SparkConf().setMaster("local").setAppName("CountUV"); final JavaSparkContext jsc = new JavaSparkContext(sparkConf); JavaRDD<String> rdd = jsc.textFile("userLog"); JavaRDD<String> filteredRDD = rdd.filter(new Function<String, Boolean>() { /** * */ private static final long serialVersionUID = 1L; @Override public Boolean call(String v1) throws Exception { return !"null".equals(v1.split("\t")[2]); } }); JavaPairRDD<String, String> pairRDD = filteredRDD.mapToPair(new PairFunction<String, String, String>() { /** * */ private static final long serialVersionUID = 1L; @Override public Tuple2<String, String> call(String t) throws Exception { String[] splits = t.split("\t"); return new Tuple2<String, String>(splits[3], splits[2]); } }); JavaPairRDD<String, String> distinctRDD = pairRDD.distinct(); Map<String, Object> resultMap = distinctRDD.countByKey(); for(Entry<String, Object> entry : resultMap.entrySet()) { System.out.println("pageId:"+ entry.getKey() + " UV:" + entry.getValue()); } jsc.stop(); }} 部分结果:pageId:45 UV:20pageId:98 UV:20pageId:34 UV:18pageId:67 UV:20pageId:93 UV:20 二次排序-scala版123456789101112131415161718192021222324252627282930313233object SecondSort { def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local").setAppName("SecondSort") val sc = new SparkContext(sparkConf) val rdd = sc.textFile("secondSort.txt") val mapRDD = rdd.map(x => { (new SecondSortKey(x.split(" ")(0).toInt, x.split(" ")(1).toInt), null) }) val sortedRDD = mapRDD.sortByKey(false) //val sortedRDD = mapRDD.sortBy(_._1, false) sortedRDD.map(_._1).foreach(println) sc.stop() }}class SecondSortKey(val first:Int, val second:Int) extends Ordered[SecondSortKey] with Serializable{ def compare(ssk:SecondSortKey): Int = { if(this.first - ssk.first == 0) { this.second - ssk.second }else{ this.first - ssk.first } } override def toString(): String = { this.first + " " + this.second }} 分组取TopN问题:找出每个班级中排名前三的分数-Java版部分数据:class1 100class2 85class3 70class1 102class2 65class1 4512345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394/**思路: java:mapToPair/scala:map (class1,100),(class2,80)... groupByKey class1 [100,101,88,99...] 如果把[100,101,88,99....]封装成List,然后进行Collections.sort(list) 会有问题: 大数据级别的value放到list里排序可能会造成OOM 解决办法: 定义一个定长的数组,通过一个简单的算法解决 * @author root * */public class GroupTopN { private final static Integer N = 3; public static void main(String[] args) { SparkConf sparkConf = new SparkConf() .setMaster("local") .setAppName("GroupTopN"); JavaSparkContext jsc = new JavaSparkContext(sparkConf); JavaRDD<String> rdd = jsc.textFile("scores.txt"); JavaPairRDD<String, Integer> pairRDD = rdd.mapToPair(new PairFunction<String, String, Integer>() { /** * */ private static final long serialVersionUID = 1L; @Override public Tuple2<String, Integer> call(String t) throws Exception { String className = t.split("\t")[0]; Integer score = Integer.valueOf(t.split("\t")[1]); return new Tuple2<String, Integer>(className, score); } }); pairRDD.groupByKey().foreach(new VoidFunction<Tuple2<String,Iterable<Integer>>>() { /** * */ private static final long serialVersionUID = 1L; @Override public void call(Tuple2<String, Iterable<Integer>> t) throws Exception { String className = t._1; Iterator<Integer> iter = t._2.iterator(); Integer[] nums = new Integer[N]; while(iter.hasNext()) { Integer score = iter.next(); for(int i=0; i<nums.length; i++) { if(nums[i] == null) { nums[i] = score;//给数组的前三个元素赋值 break; }else if(score > nums[i]) { for (int j = 2; j > i; j--) { nums[j] = nums[j-1]; } nums[i] = score; break; } } } System.out.println(className); for(Integer i : nums) { System.out.println(i); } } }); jsc.stop(); }}结果:class110210099class2888585class3987070 广播变量有如下伪代码:1234var rdd = sc.textFile(path)val blackName = “Tom”val fliterRDD = rdd.fliter(_.equals(blackName))filterRDD.count() blackName是RDD外部的变量,当把task发送到其他节点执行,需要使用这个变量时,必须给每个task发送这个变量,假如这个变量占用的内存很大,而且task数量也有很多,那么导致集群资源紧张。 广播变量可以解决这个问题: 把这个变量定义为广播变量,发送到每个executor中,每个在executor中执行的task都可以使用这个广播变量,而一个executor可以包含多个task,task数一般是executor数的好几倍,这样就减少了集群的资源负荷。 注意: 广播变量只能在Driver端定义 广播变量在Executor端无法修改 只能在Driver端改变广播变量的值 累加器有如下伪代码:1234567//统计RDD中元素的个数var rdd = sc.textFile(path)var count = 0 //这是定义在Driver端的变量rdd.map(x => { count += 1 //这个计算在Executor端执行})println(count) 这个代码执行完毕是得不到rdd中元素的个数的,原因: 在spark应用程序中,我们经常会有这样的需求,如异常监控,调试,记录符合某特性的数据的数目,这种需求都需要用到计数器,如果一个变量不被声明为一个累加器,那么它将在被改变时不会再driver端进行全局汇总,即在分布式运行时每个task运行的知识原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。 注意: 累加器定义在Driver端 Executor端只能对累加器进行操作,也就是只能累加 Driver端可以读取累加器的值,Executor端不能读取累加器的值 累加器(Accumulator)陷阱及解决办法计数器的测试代码123456789//在driver中定义val accum = sc.accumulator(0, "Example Accumulator")//在task中进行累加sc.parallelize(1 to 10).foreach(x=> accum += 1)//在driver中输出accum.value//结果将返回10res: 10 累加器的错误用法12345678910111213141516171819val accum= sc.accumulator(0, "Error Accumulator")val data = sc.parallelize(1 to 10)//用accumulator统计偶数出现的次数,同时偶数返回0,奇数返回1val newData = data.map{x => { if(x%2 == 0){ accum += 1 0 }else 1}}//使用action操作触发执行newData.count//此时accum的值为5,是我们要的结果accum.value//继续操作,查看刚才变动的数据,foreach也是action操作newData.foreach(println)//上个步骤没有进行累计器操作,可是累加器此时的结果已经是10了//这并不是我们想要的结果accum.value 原因分析我们都知道,spark中的一系列transform操作会构成一串长的任务链,此时需要通过一个action操作来触发,accumulator也是一样。因此在一个action操作之前,你调用value方法查看其数值,肯定是没有任何变化的。 所以在第一次count(action操作)之后,我们发现累加器的数值变成了5,是我们要的答案。 之后又对新产生的的newData进行了一次foreach(action操作),其实这个时候又执行了一次map(transform)操作,所以累加器又增加了5。最终获得的结果变成了10。 解决办法 For accumulator updates performed inside actions only, Spark guarantees that each task’s update to the accumulator will only be applied once, i.e. restarted tasks will not update the value. In transformations, users should be aware of that each task’s update may be applied more than once if tasks or job stages are re-executed. 看了上面的分析,大家都有这种印象了,那就是使用累加器的过程中只能使用一次action的操作才能保证结果的准确性。 事实上,还是有解决方案的,只要将任务之间的依赖关系切断就可以了。什么方法有这种功能呢?你们肯定都想到了,cache,persist。调用这个方法的时候会将之前的依赖切除,后续的累加器就不会再被之前的transfrom操作影响到了。 12345678910111213141516171819202122232425262728object AccumulatorTest { def main(args: Array[String]): Unit = { val sc = new SparkContext(new SparkConf().setAppName("MapPartitionsOperator").setMaster("local")) val accum= sc.accumulator(0, "Error Accumulator") val data = sc.parallelize(1 to 10) //用accumulator统计偶数出现的次数,同时偶数返回0,奇数返回1 val newData = data.map{x => { if(x%2 == 0){ accum += 1 0 }else 1 }} newData.cache.count //使用action操作触发执行// newData.count //此时accum的值为5,是我们要的结果 println(accum.value) println("test") //继续操作,查看刚才变动的数据,foreach也是action操作 newData.foreach(println) //上个步骤没有进行累计器操作,可是累加器此时的结果已经是10了 //这并不是我们想要的结果 println("test") println(accum.value) }} 自定义累加器 所以在第一次count(action操作)之后,我们发现累加器的数值变成了5,是我们要的答案。之后又对新产生的的newData进行了一次foreach(action操作),其实这个时候又执行了一次map(transform)操作,所以累加器又增加了5。最终获得的结果变成了10。 总结使用Accumulator时,为了保证准确性,只使用一次action操作。如果需要使用多次则使用cache或persist操作切断依赖。RDD持久化这段伪代码的瑕疵:1234lines = sc.textFile(“hdfs://...”)errors = lines.filter(_.startsWith(“ERROR”))mysql_errors = errors.filter(_.contain(“MySQL”)).counthttp_errors = errors.filter(_.contain(“Http”)).count errors是一个RDD,mysql_errors这个RDD执行时,会先读文件,然后获取数据,通过计算errors,把数据传给mysql_errors,再进行计算,因为RDD中是不存储数据的,所以http_errors计算的时候会重新读数据,计算errors后把数据传给http_errors进行计算,重复使用errors这个RDD很有必须,这就需要把errors这个RDD持久化,以便其他RDD使用。RDD持久化有三个算子:cache、persist、checkpoint cache:把RDD持久化到内存使用方法:123var rdd = sc.textFile("test")rdd = rdd.cache()val count = rdd.count() //或者其他操作 查看源码,可以发现其实cahce就是persist(StorageLevel.MEMORY_ONLY) 12345/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */def cache(): this.type = persist() persist:可以选择多种持久化方式使用方法:123var rdd = sc.textFile("test")rdd = rdd.persist(StorageLevel.MEMORY_ONLY)val count = rdd.count() //或者其他操作 Persist StorageLevel说明:123456class StorageLevel private( private var _useDisk: Boolean, private var _useMemory: Boolean, private var _useOffHeap: Boolean, private var _deserialized: Boolean, private var _replication: Int = 1) 初始化StorageLevel可以传入5个参数,分别对应是否存入磁盘、是否存入内存、是否使用堆外内存、是否不进行序列化,副本数(默认为1) 使用不同参数的组合构造的实例被预先定义为一些值,比如MEMORY_ONLY代表着不存入磁盘,存入内存,不使用堆外内存,不进行序列化,副本数为1,使用persisit()方法时把这些持久化的级别作为参数传入即可,cache()与persist(StorageLevel.MEMORY_ONLY)是等价的。cache和persist的注意事项 cache 和persist是懒执行算子,需要有一个action类的算子触发执行 cache 和 persist算子的返回执行必须赋值给一个变量,在接下来的job中直接使用这个变量,那么就是使用了持久化的数据了,如果application中只有一个job,没有必要使用RDD持久化 cache 和 persist算子后不能立即紧跟action类算子,比如count算子,但是在下一行可以有action类算子 `error : rdd = cache().count()` `right : rdd = rdd.cache()` `rdd.count()****` cache() = persist(StorageLevel.MEMORY_ONLY) checkpoint : 可以把RDD持久化到HDFS,同时切断RDD之间的依赖使用方法:1234sc.setCheckpointDir("hdfs://...")var rdd = sc.textFile("test")rdd.checkpoint()val count = rdd.count() //或者其他操作 对于切断RDD之间的依赖的说明: 当业务逻辑很复杂时,RDD之间频繁转换,RDD的血统很长,如果中间某个RDD的数据丢失,还需要重新从头计算,如果对中间某个RDD调用了checkpoint()方法,把这个RDD上传到HDFS,同时让后面的RDD不再依赖于这个RDD,而是依赖于HDFS上的数据,那么下次计算会方便很多。checkpoint()执行原理: 当RDD的job执行完毕后,会从finalRDD从后往前回溯 当回溯到调用了checkpoint()方法的RDD后,会给这个RDD做一个标记 Spark框架自动启动一个新的job,计算这个RDD的数据,然后把数据持久化到HDFS上 优化:对某个RDD执行checkpoint()之前,对该RDD执行cache(),这样的话,新启动的job只需要把内存中的数据上传到HDFS中即可,不需要重新计算。 Spark任务调度和资源调度一些术语Master(standalone):资源管理的主节点(进程)Cluster Manager:在集群上获取资源的外部服务(例如standalone,Mesos,Yarn)Worker Node(standalone):资源管理的从节点(进程)或者说管理本机资源的进程Application:基于Spark的用户程序,包含了driver程序和运行在集群上的executor程序Driver Program:用来连接工作进程(Worker)的程序Executor:是在一个worker进程所管理的节点上为某Application启动的一个进程,该进程负责运行任务,并且负责将数据存在内存或者磁盘上,每个应用都有各自独立的executorsTask:被送到某个executor上的工作单元Job:包含很多任务(Task)的并行计算,和action是对应的Stage:一个Job会被拆分很多组任务,每组任务被称为Stage(就像Mapreduce分map task和reduce task一样) 宽窄依赖join既可能是窄依赖,又是宽依赖。 宽窄依赖的作用 在Spark里每一个操作生成一个RDD,RDD之间连一条边,最后这些RDD和他们之间的边组成一个有向无环图,这个就是DAG,Spark内核会在需要计算发生的时刻绘制一张关于计算路径的有向无环图,也就是DAG。 有了计算的DAG图,Spark内核下一步的任务就是根据DAG图将计算划分成Stage,如上图,G与F之间是宽依赖,所以把G和F分为两个Stage,而CD到F,E到F都是窄依赖,所以CDEF最终划分为一个Stage2,A与B之间是宽依赖,B与G之间是窄依赖,所以最终,A被划分为一个Stage1,因为BG的stage依赖于stage1和stage2,所以最终把整个DAG划分为一个stage3,所以说,宽窄依赖的作用就是切割job,划分stage。 Stage:由一组可以并行计算的task组成。Stage的并行度:就是其中的task的数量 与互联网业界的概念有些差异:在互联网的概念中,并行度是指可同时开辟的线程数,并发数是指每个线程中可处理的最大数据量,比如4个线程,每个线程可处理的数据为100万条,那么并行度就是4,并发量是100万,而对于stage而言,即使其中的task是分批进行执行的,也都算在并行度中,比如,stage中有100个task,而这100个task分4次才能执行完,那么该stage的并行度也为100。 Stage的并行度是由最后一个RDD的分区决定的。 RDD中为什么不存储数据以及stage的计算模式有伪代码如下:12345678910var lineRDD = sc.textFile(“hdfs://…”) 从HDFS中读数据var fliterRDD = rdd.fliter(x => { println(“fliter” + x)true})var mapRDD = fliterRDD.map(x => { println(“map” + x) x})mapRDD.count() 执行流程: 在每一个task执行之前,它会把所有的RDD的处理逻辑进行整合,以递归函数的展开式整合,即map(fliter(readFromB())),而spark没有读取文件的方法,用的是MR的读文件的方法,所以readFromB()实际上是一行一行的读数据,所以以上task执行时会输出:flitermapflitermap……stage的计算模式就是:pipeline模式,即计算过程中数据不会落地,也就是不会存到磁盘,而是放在内存中直接给下一个函数使用,stage的计算模式类似于 1+1+1 = 3,而MapReduce的计算模式类似于 1+1=2、2+1=3,就是说MR的中间结果都会写到磁盘上 管道中的数据在以下情况会落地: 对某一个RDD执行控制算子(比如对mapRDD执行了foreach()操作) 在每一个task执行完毕后,数据会写入到磁盘上,这就是shuffle write阶段 任务调度 N(N>=1)个RDD Object组成了一个DAG,它用代码实现后就是一个application DAGScheduler是任务调度的高层调度器的对象,它依据RDD之间的宽窄依赖把DAG切割成一个个Stage,然后把这些stage以TaskSet的形式提交给TaskScheduler(调用了TaskScheduler的某个方法,然后把TaskSet作为参数传进去) TaskScheduler是任务调度的底层调度器的对象 Stage是一组task的组合,TaskSet是task的集合,所以两者并没有本质的区别,只是在不同层次的两个概念而已 TaskScheduler遍历TaskSet,把每个task发送到Executor中的线程池中进行计算 当某个task执行失败后,会由TaskScheduler进行重新提交给Executor,默认重试3次,如果重试3次仍然失败,那么该task所在的stage就执行失败,由DAGScheduler进行重新发送,默认重试4次,如果重试4次后,stage仍然执行失败,那么该stage所在的job宣布执行失败,且不会再重试 TaskScheduler还可以重试straggling tasks,就是那些运行缓慢的task,当TaskScheduler认为某task0是straggling task,那么TaskScheduler会发送一条相同的task1,task0与task1中选择先执行完的task的计算结果为最终结果,这种机制被称为推测执行。 推测执行建议关闭而且默认就是关闭的,原因如下:推测执行可能导致数据重复比如做数据清洗时,某个task正在往关系型数据库中写数据,而当它执行的一定阶段但还没有执行完的时候,此时如果TaskScheduler认为它是straggling task,那么TaskScheduler会新开启一个一模一样的task进行数据写入,会造成数据重复。推测执行可能会大量占用资源导致集群崩溃比如某条task执行时发生了数据倾斜,该task需要计算大量的数据而造成它执行缓慢,那么当它被认为是straggling task后,TaskScheduler会新开启一个一模一样的task进行计算,新的task计算的还是大量的数据,且分配得到与之前的task相同的资源,两条task执行比之前一条task执行还慢,TaskScheduler有可能再分配一条task来计算这些数据,这样下去,资源越来越少,task越加越多,形成死循环后,程序可能永远都跑不完。 资源调度1)2)3)4)5&6)7&8) 注意:application执行之前申请的这批executor可以被这个application中的所有job共享。 粗粒度和细粒度的资源申请粗粒度的资源申请:Spark在Application执行之前,将所有的资源申请完毕,然后再进行任务调度,直到最后一个task执行完毕,才会释放资源 优点:每一个task执行之前不需要自己去申请资源,直接使用资源就可以,每一个task的启动时间就变短了,task执行时间缩短,使得整个Application执行的速度较快 缺点:无法充分利用集群的资源,比如总共有10万的task,就要申请10万个task的资源,即使只剩下一个task要执行,也得等它执行完才释放资源,在这期间99999个task的资源没有执行任何task,但也不能被其他需要的进程或线程使用 细粒度的资源申请:MapReduce在Application执行之前,不需要申请好资源,直接进行任务的调度,在每一个task执行之前,自己去申请资源,申请到就执行,申请不到就等待,每一个task执行完毕后就立马释放资源。优点:可以充分的利用集群的资源缺点:每一个task的执行时间变长了,导致整个Application的执行的速度较慢 yarn如何同时调度粗细两种方式Spark和MapReduce都可以跑在yarn上,那怎么做到一个组粒度一个细粒度呢?原因是他们各自实现ApplicationMaster的方式不同。 资源调度源码分析分析以集群方式提交命令后的资源调度源码资源调度的源码(Master.scala)位置:1.Worker启动后向Master注册2.client向Master发送一条消息,为当前的Application启动一个Driver进程 schedule()方法是对Driver和Executor进行调度的方法,看看启动Driver进程的过程: schedule()方法有一些问题:12345678910111213private def schedule(): Unit = { if (state != RecoveryState.ALIVE) { return } // Drivers take strict precedence over executors val shuffledWorkers = Random.shuffle(workers) // Randomization helps balance drivers for (worker <- shuffledWorkers if worker.state == WorkerState.ALIVE) { for (driver <- waitingDrivers) { if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) { launchDriver(worker, driver) waitingDrivers -= driver } } } startExecutorsOnWorkers()} 1) 如果是以客户端方式命令执行程序,那么不需要Master来调度Worker创建Driver进程,那么waitingDrivers这个集合中就没有元素,所以也就不需要遍历shuffledWorkers,源码并没有考虑这种情况。应该是有if语句进行非空判定。2) 如果waitingDrivers中只有一个元素,那么也会一直遍历shuffledWorkers这个集合,实际上是不需要的。 3.Driver进程向Master发送消息:为当前的Application申请一批Executor。 下面看看Executor的创建过程: 通过以上过程,Executor进程就被启动了 资源调度的三个结论 在默认情况下(没有使用--executor --cores这个选项)时,每一个Worker节点为当前的Application只启动一个Executor,这个Executor会使用这个Worker管理的所有的core(原因:assignedCores(pos) += minCoresPerExecutor) 默认情况下,每个Executor使用1G内存 如果想要在一个Worker节点启动多个Executor,需要使--executor --cores这个选项 spreadOutApps这个参数可以决定Executor的启动方式,默认轮询方式启动,这样有利于数据的本地化。 验证资源调度的三个结论集群中总共有6个core和4G内存可用,每个Worker管理3个core和2G内存 SPARK_HOME/bin下有一个spark-shell脚本文件,执行这个脚本文件就是提交一个application12345function main() {……"${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"……} 默认情况(不指定任何参数)下启动spark-shell:[root@node04 bin]# ./spark-shell --master spark://node01:7077 这个application启动了2个Executor进程,每个Worker节点上启动一个,总共使用了6个core和2G内存,每个Work提供3个core和1G内存。 设置每个executor使用1个core[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-cores 1 那么每个worker为application启动两个executor,每个executor使用1个core,这是因为启动两个executor后,内存已经用完了,所以即使还有剩余的core可用,也无法再启动executor了 设置每个executor使用2个core[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-cores 2 那么每个worker为application启动1个executor,每个executor使用2个core,这是因为启动两个executor后,每个executor剩余的core为1,已经不够再启动一个exexutor了 设置每个executor使用3G内存[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-memory 3G提交任务显示为waiting状态,而不是running状态,也不会启动executor 设置每个executor使用1个core,500M内存[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-cores 1 --executor-memory 500M 设置每个设置每个executor使用1个core,500M内存,集群总共可以使用3个core,集群总共启动3个executor,其中有一个Worker启动了两个executor[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-cores 1 --executor-memory 500M --total-executor-cores 3 设置每个设置每个executor使用1个core,1.2内存,集群总共可以使用3个core,集群总共启动2个executor,每个Worker启动了1个executor,表面上看起来,两个worker加起来的内存(1.6G)和剩余的core数(1),还够启动一个exexutor,但是这里需要注意的是,两个Worker的内存并不能共用,每个Worker剩余的内存(800M)并不足以启动一个executor[root@node04 bin]# ./spark-shell --master spark://node01:7077 --executor-cores 1 --executor-memory 1200M --total-executor-cores 3 任务调度源码分析源码位置:core/src/main/scala/rdd/RDD.scala Spark Standalone集群搭建角色划分 1.解压安装包[root@node01 chant]# tar zxf spark-1.6.0-bin-hadoop2.6.tgz 2.编辑spark-env.sh文件123456789101112[root@node01 chant]# mv spark-1.6.0-bin-hadoop2.6 spark-1.6.0[root@node01 chant]# cd spark-1.6.0/conf/[root@node01 conf]# cp spark-env.sh.template spark-env.sh[root@node01 conf]# vi spark-env.sh# 绑定Master的IPexport SPARK_MASTER_IP=node01# 提交Application的端口export SPARK_MASTER_PORT=7077# 每一个Worker最多可以支配core的个数,注意core是否支持超线程export SPARK_WORKER_CORES=3# 每一个Worker最多可以支配的内存export SPARK_WORKER_MEMORY=2g 3.编辑slaves文件1234[root@node01 conf]# cp slaves.template slaves[root@node01 conf]# vi slavesnode02node03 4.Spark的web端口默认为8080,与Tomcat冲突,进行修改12345[root@node01 spark-1.6.0]# cd sbin/[root@node01 sbin]# vi start-master.shif [ "$SPARK_MASTER_WEBUI_PORT" = "" ]; then SPARK_MASTER_WEBUI_PORT=8081fi 5.同步配置1234[root@node01 conf]# cd /opt/chant/[root@node01 chant]# scp -r spark-1.6.0 node02:`pwd`[root@node01 chant]# scp -r spark-1.6.0 node03:`pwd`[root@node01 chant]# scp -r spark-1.6.0 node04:`pwd` 6.进入spark安装目录的sbin目录下,启动集群12[root@node01 chant]# cd spark-1.6.0/sbin/[root@node01 sbin]# ./start-all.sh 7.访问web界面 8.提交Application验证集群是否工作正常以下scala代码是spark源码包自带的例子程序,用于计算圆周率,可传入参数: 1234567891011121314151617181920212223package org.apache.spark.examplesimport scala.math.randomimport org.apache.spark._/** Computes an approximation to pi */object SparkPi { def main(args: Array[String]) { val conf = new SparkConf().setAppName("Spark Pi") val spark = new SparkContext(conf) val slices = if (args.length > 0) args(0).toInt else 2// avoid overflow val n = math.min(100000L * slices, Int.MaxValue).toInt val count = spark.parallelize(1 to n, slices).map { i => val x = random * 2 - 1 val y = random * 2 - 1 if (x*x + y*y < 1) 1 else 0 }.reduce((v1,v2) => {v1+v2}) println("Pi is roughly " + 4.0 * count / n) spark.stop() }} 此程序的jar包路径为:SPARK_HOME/lib/spark-examples-1.6.0-hadoop2.6.0.jar Standalone模式下提交任务Standalone模式:提交的任务在spark集群中管理,包括资源调度,计算 客户端方式提交任务进入客户端所在节点的spark安装目录的bin目录下,提交这个程序: 12345[root@node04 bin]# ./spark-submit --master spark://node01:7077 #指定 master的地址> --deploy-mode client #指定在客户端提交任务,这个选项可以不写,默认> --class org.apache.spark.examples.SparkPi #指定程序的全名> ../lib/spark-examples-1.6.0-hadoop2.6.0.jar #指定jar包路径> 1000 #程序运行时传入的参数 说明:1.客户端提交,Driver进程就在客户端启动,进程名为SparkSubmit 1234# 注意:任务结束后,该进程就关闭了[root@node04 ~]# jps1646 Jps1592 SparkSubmit 2.在客户端可以看到task执行情况和执行结果 12345……17/08/04 02:55:47 INFO DAGScheduler: Job 0 finished: reduce at SparkPi.scala:36, took 6.602662 sPi is roughly 3.140909217/08/04 02:55:47 INFO SparkUI: Stopped Spark web UI at http://192.168.9.14:4040…… 3.适合场景:测试原因:当提交的任务数量很多时,客户端资源不够用 集群方式提交任务还是在客户端所在节点的spark安装目录的bin目录下提交程序,只是命令需要修改: 12345[root@node04 bin]# ./spark-submit --master spark://node01:7077> --deploy-mode cluster #指定在集群中提交任务,这个选项必须写> --class org.apache.spark.examples.SparkPi> ../lib/spark-examples-1.6.0-hadoop2.6.0.jar> 1000 说明:1.集群方式提交任务,Driver进程随机找一个Worker所在的节点启动,进程名为DriverWrapper 1234[root@node02 ~]# jps1108 Worker1529 Jps1514 DriverWrapper 2.客户端看不到task执行情况和执行结果,可以在web界面查看 3.适合场景:生产环境原因:当task数量很多时,集群方式可以做到负载均衡,解决多次网卡流量激增问题(分摊到集群的Worker节点上),但无法解决单次网卡流量激增问题。 Yarn模式下提交任务yarn模式:把spark任务提交给yarn集群,由yarn集群进行管理,包括资源分配和计算编辑客户端节点中spark配置文件,加入:export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop,启动hadoop集群,不需要启动spark集群 客户端方式提交任务1.命令./spark-submit --master yarn --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 1002.流程 ① 在客户端执行提交命令② 上传应用程序(jar包)及其依赖的jar包到HDFS上,开启Driver进程执行应用程序③ 客户端会向RS发送请求,为当前的Application启动一个ApplicationMaster进程④ RS会找一台NM启动ApplicationMaster,ApplicationMaster进程启动成功后,会向RS申请资源(图画的有误,ApplicationMaster应该在NM上)⑤ RS接受请求后,会向资源充足的NM发送消息:在当前的节点上启动一个Executor进程,去HDFS下载spark-assembly-1.6.0-hadoop2.6.0.jar包,这个jar包中有启动Executor进程的相关类,调用其中的方法就可以启动Executor进程⑥ Executor启动成功后,Driver开始分发task,在集群中执行任务 3.总结Driver负责任务的调度ApplicationMaster负责资源的申请 集群方式提交任务1.命令 1./spark-submit --master yarn-cluster --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 或者 1./spark-submit --master yarn --deploy-mode cluster --class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 100 2.流程 ① 在客户端执行提交命令② 上传应用程序(jar包)及其依赖的jar包到HDFS上③ 客户端会向RS发送请求,为当前的Application启动一个ApplicationMaster(Driver)进程,这个ApplicationMaster就是driver。④ RS会找一台NM启动ApplicationMaster,ApplicationMaster(Driver)进程启动成功后,会向RS申请资源(图画的有误,ApplicationMaster应该在NM上),ApplicationMaster(Driver)进程启动成功后,会向RS申请资源⑤ RS接受请求后,会向资源充足的NM发送消息:在当前的节点上启动一个Executor进程,去HDFS下载spark-assembly-1.6.0-hadoop2.6.0.jar包,这个jar包中有启动Executor进程的相关类,调用其中的方法就可以启动Executor进程⑥ Executor启动成功后,ApplicationMaster(Driver)开始分发task,在集群中执行任务3.总结在cluster提交方式中,ApplicationMaster进程就是Driver进程,任务调度和资源申请都是由一个进程来做的 Spark HA集群搭建Spark高可用的原理 说明:主备切换的过程中,不能提交新的Application。已经提交的Application在执行过程中,集群进行主备切换,是没有影响的,因为spark是粗粒度的资源调度。 角色划分 1.修改spark-env.sh配置文件1234567[root@node01 ~]# cd /opt/chant/spark-1.6.0/conf/[root@node01 conf]# vi spark-env.sh加入以下配置export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=node01:2181,node02:2181,node03:2181 -Dspark.deploy.zookeeper.dir=/spark/ha" 2. 同步配置文件123[root@node01 conf]# scp spark-env.sh node02:`pwd`[root@node01 conf]# scp spark-env.sh node03:`pwd`[root@node01 conf]# scp spark-env.sh node04:`pwd` 3. 修改node02的spark配置文件把master的IP改为node02,把node02的masterUI port改为8082因为node01的masterUI的port设置为8081,同步后,node02的masterUI的port也为8081,那么在node02启动master进程时,日志中会有警告:WARN Utils: Service ‘MasterUI’ could not bind on port 8081导致我们不能通过该port访问node02的masterUI,所以修改的和node01不一样就可以 12345678[root@node02 ~]# cd /opt/chant/spark-1.6.0/conf[root@node02 conf]# vi spark-env.shexport SPARK_MASTER_IP=node02[root@node02 conf]# cd ../sbin[root@node02 sbin]# vi start-master.shif [ "$SPARK_MASTER_WEBUI_PORT" = "" ]; then SPARK_MASTER_WEBUI_PORT=8082fi 4. 启动Zookeeper集群123[root@node02 ~]# zkServer.sh start[root@node03 ~]# zkServer.sh start[root@node04 ~]# zkServer.sh start 5.在node01上启动Spark集群1234[root@node01 conf]# cd ../sbin[root@node01 sbin]# pwd/opt/chant/spark-1.6.0/sbin[root@node01 sbin]# ./start-all.sh 6.在node02上启动Master进程1234[root@node02 bin]# cd ../sbin[root@node02 sbin]# pwd/opt/chant/spark-1.6.0/sbin[root@node02 sbin]# ./start-master.sh 7.验证集群高可用 1234[root@node01 sbin]# jps1131 Master1205 Jps[root@node01 sbin]# kill -9 1131 再次启动node01的master进程,node01成为standby[root@node01 sbin]# ./start-master.sh Spark History Server配置提交一个Application: 12[root@node04 ~]# cd /opt/chant/spark-1.6.0/bin[root@node04 bin]# ./spark-shell --name "testSparkShell" --master spark://node02:7077 点击ApplicationID点击appName查看job信息 提交一个job 12scala> sc.textFile("/tmp/wordcount_data").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).saveAsTextFile("/tmp/wordcount_result") 点击job查看stage信息点击stage查看task信息 退出Spark-shell后,这个Application的信息是不被保存的,需要做一些配置才会保存历史记录,有两种方法设置保存历史记录1.提交命令时指定./spark-shell --master spark://node02:7077 --conf spark.eventLog.enabled=true --conf spark.eventLog.dir="/tmp/spark/historyLog"注意:保存历史数据的目录需要先创建好2.启动history-server修改conf/spark-defaults.conf文件 123spark.eventLog.enabled truespark.eventLog.dir hdfs://node01:9000/spark/historyLogspark.history.fs.logDirectory hdfs://node01:9000/spark/historyLog spark.eventLog.compress true 可以设置保存历史日志时进行压缩注意:保存历史数据的目录需要先创建好然后启动history server:sbin/start-history-server.sh之后提交的所有的Application的执行记录都会被保存,访问18080端口就可以查看 Spark ShufflereduceByKey会将上一个RDD中的每一个key对应的所有value聚合成一个value,然后生成一个新的RDD,元素类型是对的形式,这样每一个key对应一个聚合起来的value 存在的问题:每一个key对应的value不一定都是在一个partition中,也不太可能在同一个节点上,因为RDD是分布式的弹性的数据集,他的partition极有可能分布在各个节点上。 那么如何进行聚合? Shuffle Write:上一个stage的每个map task就必须保证将自己处理的当前分区中的数据相同的key写入一个分区文件中,可能会写入多个不同的分区文件中Shuffle Read:reduce task就会从上一个stage的所有task所在的机器上寻找属于自己的那些分区文件,这样就可以保证每一个key所对应的value都会汇聚到同一个节点上去处理和聚合 普通的HashShuffle上图中,每个节点启动一个Executor来运行Application,每个Executor使用1个core,其中有2条task,所以2条task不是并行执行的。Map task每计算一条数据之后,就写到对应的buffer(默认32K)中(比如key为hello的写入到蓝色buffer,key为world的写入到紫色buffer中),当buffer到达阈值后,把其中的数据溢写到磁盘,当task0执行完后,task2开始执行,在这个过程中,每一个map task产生reduce的个数个小文件,假如总共有m个map task,r个reduce,最终会产生m*r个小文件,磁盘小文件和缓存过多,造成耗时且低效的IO操作,可能造成OOM 内存估算假设:一台服务器24核,支持超线程,1000个maptask,1000个reduceTask。超线程相当于48核,所以并行48个mapTask,每个mapTask产生1000个(reduceTask数)磁盘小文件,也就是对应产生48*1000个buffer,而每个buffer默认值为32k。所以使用内存为:maptask数*reduceTask数*buffer大小 = 48*1000*32k=1536M约1.5G 优化的HashShuffle这里说的优化,是指我们可以设置一个参数,spark.shuffle.consolidateFiles。该参数默认值为false,将其设置为true即可开启优化机制。通常来说,如果我们使用HashShuffleManager,那么都建议开启这个选项。每个map task 之间可以共享buffer,task0执行完成后,task1开始执行,继续使用task0使用的buffer,假如总共有c个core, r个reduce,最终会产生c*r个小文件,因为复用buffer后,每个core执行的所有map task产生r个小文件 普通的SortShuffle下图说明了普通的SortShuffleManager的原理。在该模式下,数据会先写入一个内存数据结构中,此时根据不同的shuffle算子,可能选用不同的数据结构。如果是reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写入内存。接着,每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。 每个maptask将计算结果写入内存数据结构中,这个内存默认大小为5M 会有一个“监控器”来不定时的检查这个内存的大小,如果写满了5M,比如达到了5.01M,那么再给这个内存申请5.02M(5.01M * 2 – 5M = 5.02)的内存,此时这个内存空间的总大小为10.02M 当“定时器”再次发现数据已经写满了,大小10.05M,会再次给它申请内存,大小为 10.05M * 2 – 10.02M = 10.08M 假如此时总的内存只剩下5M,不足以再给这个内存分配10.08M,那么这个内存会被锁起来,把里面的数据按照相同的key为一组,进行排序后,分别写到不同的缓存中,然后溢写到不同的小文件中,而map task产生的新的计算结果会写入总内存剩余的5M中 buffer中的数据(已经排好序)溢写的时候,会分批溢写,默认一次溢写10000条数据,假如最后一部分数据不足10000条,那么剩下多少条就一次性溢写多少条 每个map task产生的小文件,最终合并成一个大文件来让reduce拉取数据,合成大文件的同时也会生成这个大文件的索引文件,里面记录着分区信息和偏移量(比如:key为hello的数据在第5个字节到第8097个字节) 最终产生的小文件数为2*m(map task的数量) SortShuffle的bypass机制下图说明了bypass SortShuffleManager的原理。bypass运行机制的触发条件如下: shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认200)。 不是聚合类的shuffle算子(比如reduceByKey)。因为如果是聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存,而bypass机制下是无法聚合的。 有条件的sort,当shuffle reduce task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认200)时,会触发bypass机制,不进行sort,假如目前有300个reduce task,如果要触发bypass机制,就就设置spark.shuffle.sort.bypassMergeThreshold的值大于300,bypass机制最终产生2*m(map task的数量)的小文件。 SparkShuffle详解先了解一些角色: MapOutputTracker:管理磁盘小文件的地址 主:MapOutputTrackerMaster 从:MapOutputTrackerWorker BlockManager:主:BlockManagerMaster,存在于Driver端管理范围:RDD的缓存数据、广播变量、shuffle过程产生的磁盘小文件包含4个重要对象: ConnectionManager:负责连接其他的BlockManagerSlave BlockTransferService:负责数据传输 DiskStore:负责磁盘管理 Memstore:负责内存管理 从:BlockManagerSlave,存在于Executor端包含4个重要对象: ConnectionManager:负责连接其他的BlockManagerSlave BlockTransferService:负责数据传输 DiskStore:负责磁盘管理 Memstore:负责内存管理 Shuffle调优配置参数的三种方式 在程序中硬编码<br>例如sparkConf.set("spark.shuffle.file.buffer","64k") 提交application时在命令行指定<br>例如spark-submit --conf spark.shuffle.file.buffer=64k --conf 配置信息=配置值 ... 修改SPARK_HOME/conf/spark-default.conf配置文件推荐使用第2种方式 spark.shuffle.file.buffer默认值:32K参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。 spark.reducer.maxSizeInFlight默认值:48M参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96M),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。 spark.shuffle.io.maxRetries默认值:3参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。 spark.shuffle.io.retryWait默认值:5s参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。 spark.shuffle.memoryFraction默认值:0.2参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。 spark.shuffle.manager默认值:sort参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。 spark.shuffle.sort.bypassMergeThreshold默认值:200参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。 spark.shuffle.consolidateFiles默认值:false参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。 Spark内存管理spark1.5之前默认为静态内存管理,之后默认为统一的内存管理,如果对数据比较了解,那么选用静态内存管理可调控的参数多,若想使用静态内存管理,将spark.memory.useLegacyMode从默认值false改为true即可。这里阐述的是spark1.6版本的内存管理机制,想了解更前卫的版本,请戳spark2.1内存管理机制 静态内存管理 Unrolling The memory used for unrolling is borrowed from the storage space. If there are no existing blocks, unrolling can use all of the storage space. Otherwise, unrolling can drop up to M bytes worth of blocks from memory, where M is a fraction of the storage space configurable through spark.storage.unrollFraction(default0.2). Note that this subregion is not staticallyreserved, but dynamically allocated by dropping existing blocks. 所以这里的unrollFraction内存其实是个上限,不是静态固定的0.2,而是动态分配的。 关于Unrolling的详细解读,请戳RDD缓存的过程 RDD在缓存到存储内存之后,Partition被转换成Block,Record在堆内或堆外存储内存中占用一块连续的空间。将Partition由不连续的存储空间转换为连续存储空间的过程,Spark称之为“展开”(Unroll)。Block有序列化和非序列化两种存储格式,具体以哪种方式取决于该RDD的存储级别。非序列化的Block以一种DeserializedMemoryEntry的数据结构定义,用一个数组存储所有的Java对象,序列化的Block则以SerializedMemoryEntry的数据结构定义,用字节缓冲区(ByteBuffer)来存储二进制数据。每个Executor的Storage模块用一个链式Map结构(LinkedHashMap)来管理堆内和堆外存储内存中所有的Block对象的实例[6],对这个LinkedHashMap新增和删除间接记录了内存的申请和释放。 Reduce OOM怎么办? 减少每次拉取的数据量 提高shuffle聚合的内存比例 增加executor的内存 统一内存管理 统一内存管理中互相借用(申请,检查,借用,归还)这一环节会产生额外的计算开销。其中最重要的优化在于动态占用机制,其规则如下: 设定基本的存储内存和执行内存区域(spark.storage.storageFraction 参数),该设定确定了双方各自拥有的空间的范围 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block) 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后”归还”借用的空间 凭借统一内存管理机制,Spark 在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护 Spark 内存的难度,但并不意味着开发者可以高枕无忧。譬如,所以如果存储内存的空间太大或者说缓存的数据过多,反而会导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的 RDD 数据通常都是长期驻留内存的 [5] 。所以要想充分发挥 Spark 的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和实现原理。 Spark SQL简介Spark SQL的前身是shark,Shark是基于Spark计算框架之上且兼容Hive语法的SQL执行引擎,由于底层的计算采用了Spark,性能比MapReduce的Hive普遍快2倍以上,当数据全部load在内存的话,将快10倍以上,因此Shark可以作为交互式查询应用服务来使用。除了基于Spark的特性外,Shark是完全兼容Hive的语法,表结构以及UDF函数等,已有的HiveSql可以直接进行迁移至Shark上。Shark底层依赖于Hive的解析器,查询优化器,但正是由于Shark的整体设计架构对Hive的依赖性太强,难以支持其长远发展,比如不能和Spark的其他组件进行很好的集成,无法满足Spark的一栈式解决大数据处理的需求Hive是Shark的前身,Shark是SparkSQL的前身,相对于Shark,SparkSQL有什么优势呢? SparkSQL产生的根本原因,是因为它完全脱离了Hive的限制 SparkSQL支持查询原生的RDD,这点就极为关键了。RDD是Spark平台的核心概念,是Spark能够高效的处理大数据的各种场景的基础 能够在Scala中写SQL语句。支持简单的SQL语法检查,能够在Scala中写Hive语句访问Hive数据,并将结果取回作为RDD使用 Spark和Hive有两种组合Hive on Spark类似于Shark,相对过时,现在公司一般都采用Spark on Hive。 Spark on HiveHive只是作为了存储的角色SparkSQL作为计算的角色 Hive on SparkHive承担了一部分计算(解析SQL,优化SQL…)的和存储Spark作为了执行引擎的角色 Dataframe简介Spark SQL是Spark的核心组件之一,于2014年4月随Spark 1.0版一同面世,在Spark 1.3当中,Spark SQL终于从alpha(内测版本)阶段毕业。Spark 1.3更加完整的表达了Spark SQL的愿景:让开发者用更精简的代码处理尽量少的数据,同时让Spark SQL自动优化执行过程,以达到降低开发成本,提升数据分析执行效率的目的。与RDD类似,DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表格,除了数据以外,还掌握数据的结构信息,即schema。同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。从API易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。 RDD VS DataFrameDataFrame = SchemaRDD = RDD<ROW>从图虫颜色来区分,DataFrame是列式存储。当要取Age这一列时,RDD必须先取出person再取Age,而DataFrame可以直接取Age这一列。 DataFrame底层架构 Predicate Pushdown谓词下推机制执行如下SQL语句: 123SELECT table1.name,table2.scoreFROM table1 JOIN table2 ON (table1.id=table2.id)WHERE table1.age>25 AND table2.score>90 我们比较一下普通SQL执行流程和Spark SQL的执行流程 DataFrame创建方式1.读JSON文件(不能嵌套)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455/** * people.json * {"name":"Michael"} {"name":"Andy", "age":30} {"name":"Justin", "age":19} */object DataFrameOpsFromFile { def main(args: Array[String]): Unit = { val conf = new SparkConf() conf.setAppName("SparkSQL") conf.setMaster("local") val sc = new SparkContext(conf)val sqlContext = new SQLContext(sc)//val df = sqlContext.read.format("json").load("people.json") val df = sqlContext.read.json("people.json") //将DF注册成一张临时表,这张表是逻辑上的,数据并不会落地 //people是临时表的表名,后面的SQL直接FROM这个表名 df.registerTempTable("people") //打印DataFrame的结构df.printSchema()/* * 结果:nullable=true代表该字段可以为空 * root |-- age: long (nullable = true) |-- name: string (nullable = true) */ //查看DataFrame中的数据, df.show(int n)可以指定显示多少条数据 df.show() /* * 结果: * +----+-------+ | age| name| +----+-------+ |null|Michael| | 30| Andy| | 19| Justin| +----+-------+ */ //SELECT name from tabledf.select("name").show() //SELECT name,age+10 from tabledf.select(df("name"), df("age").plus(10)).show() //SELECT * FROM table WHERE age > 10df.filter(df("age")>10).show() //SELECT count(*) FROM table GROUP BY agedf.groupBy("age").count.show()sqlContext.sql("select * from people where age > 20").show() }} 2.JSON格式的RDD转为DataFrame1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public class DataFrameOpsFromJsonRdd { public static void main(String[] args) { SparkConf conf = new SparkConf().setAppName("DataFrameFromJsonRdd").setMaster("local"); JavaSparkContext sc = new JavaSparkContext(conf); //若想使用SparkSQL必须创建SQLContext,必须是传入SparkContext,不能是SparkConf SQLContext sqlContext = new SQLContext(sc); //创建一个本地的集合,类型String,集合中元素的格式为json格式 List<String> nameList = Arrays.asList( "{'name':'Tom', 'age':20}", "{'name':'Jed', 'age':30}", "{'name':'Tony', 'age':22}", "{'name':'Jack', 'age':24}"); List<String> scoreList = Arrays.asList( "{'name':'Tom','score':100}", "{'name':'Jed','score':99}" ); JavaRDD<String> nameRDD = sc.parallelize(nameList); JavaRDD<String> scoreRDD = sc.parallelize(scoreList); DataFrame nameDF = sqlContext.read().json(nameRDD); DataFrame scoreDF = sqlContext.read().json(scoreRDD); /** * SELECT nameTable.name,nameTable.age,scoreTable.score FROM nameTable JOIN nameTable ON (nameTable.name = scoreTable.name) */ nameDF.join(scoreDF, nameDF.col("name").$eq$eq$eq(scoreDF.col("name"))).select(nameDF.col("name"),nameDF.col("age"),scoreDF.col("score")).show(); nameDF.registerTempTable("name"); scoreDF.registerTempTable("score"); String sql = "SELECT name.name,name.age,score.score " + "FROM name join score ON (name.name = score.name)"; sqlContext.sql(sql).show(); /* * +----+---+-----+ |name|age|score| +----+---+-----+ | Tom| 20| 100| | Jed| 30| 99| +----+---+-----+ */ }} 3.非JSON格式的RDD转为DataFrame1.反射的方式Person类 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import org.apache.spark.{SparkConf, SparkContext}import org.apache.spark.sql.SQLContextobject RDD2DataFrameByReflectionScala { /** * * 使用反射的方式将RDD转换成为DataFrame * * 1.自定义的类必须是public * * 2.自定义的类必须是可序列化的 * * 3.RDD转成DataFrame的时候,会根据自定义类中的字段名进行排序 * *所以这里直接用case class类 * * Peoples.txt内容: * 1,Tom,7 * 2,Tony,11 * 3,Jack,5 * * @author root * */ case class Person(name: String, age: Int) def main(args: Array[String]): Unit = { val conf = new SparkConf() //创建sparkConf对象 conf.setAppName("My First Spark App") //设置应用程序的名称,在程序运行的监控页面可以看到名称 conf.setMaster("local") val sc = new SparkContext(conf) val sqlContext = new SQLContext(sc) import sqlContext.implicits._ //传入进去Person.class的时候,sqlContext是通过反射的方式创建DataFrame //在底层通过反射的方式或得Person的所有field,结合RDD本身,就生成了DataFrame val people = sc.textFile("/Users/Chant/Documents/大数据0627/14spark/code/SparkJavaOperator/Peoples.txt") .map(_.split(",")).map(p => Person(p(1), p(2).trim.toInt)).toDF() people.registerTempTable("people") val teenagers = sqlContext.sql("SELECT name, age FROM people WHERE age >= 6 AND age <= 19") /** * 对dataFrame使用map算子后,返回类型是RDD<Row> */ // teenagers.map(t => "Name: " + t(0)).foreach(println) // teenagers.map(t => "Name: " + t.get(0)).foreach(println) // 不推荐此方法,因为RDD转成DataFrame的时候,他会根据自定义类中的字段名(按字典序)进行排序。 // 如果列很多,那你就得自己去按字典序排序列名,然后才知道该列对应的索引位。 // or by field name: 推荐用列名来获取列值 // teenagers.map(t => "Name: " + t.getAs[String]("name")).foreach(println) // teenagers.map(t => t.getAs("name")).foreach(println)//这样会报错java.lang.ClassCastException: java.lang.String cannot be cast to scala.runtime.Nothing$ teenagers.map(t => t.getAs[String]("name")).foreach(println) }} 2.动态创建Schema123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172import org.apache.spark.sql.types._import org.apache.spark.sql.{Row, SQLContext}import org.apache.spark.{SparkConf, SparkContext}/** * Created by Chant. */object RDD2DataFrameByProgrammatically { def main(args: Array[String]): Unit = { val sc = new SparkContext(new SparkConf().setMaster("local").setAppName("RDD2DataFrameByProgrammatically")) val sQLContext = new SQLContext(sc)//将数据封装到Row中,RDD[Row]就可以转成DataFrame val RowRDD = sc.textFile("/Users/Chant/Documents/大数据0627/14spark/code/SparkScalaOperator/Peoples.txt") .map(_.split(",")).map(x => Row(x(0).toInt, x(1), x(2).toInt)) /** * 读取配置文件的方式 */ val schemaString = "id:Int name:String age:Int" val schema = StructType(schemaString.split(" ") .map(x => StructField(x.split(":")(0), if (x.split(":")(1) == "String") StringType else IntegerType, true))) //true代表该字段是否可以为空 //构建StructType,用于最后DataFrame元数据的描述 //基于已有的MetaData以及RDD<Row> 来构造DataFrame // //最后一定要写else,不像java可以只有个if。// val schema = StructType(schemaString.split(" ")// .map(x =>{// val pair = x.split(":")// if (pair(1) == "String") StructField(pair(0), StringType,true)// else if(pair(1) == "Int") StructField(pair(0), IntegerType,true)// else StructField(pair(0), IntegerType,true)// })) /** * 如果列的数据类型比较多,可以用match case */// val schemaString = "id:Int name:String age:Int"//// val schema = StructType(schemaString.split(" ")// .map(x => {// val pair = x.split(":")//// var dataType = null.asInstanceOf[DataType]//巧用这一招// var dataType : DataType = null// pair(1) match {// case "String" => dataType = StringType// case "Int" => dataType = IntegerType// case "Double" => dataType = DoubleType// case "Long" => dataType = LongType// case _ => println("default, can't match")// }//// StructField(pair(0),dataType,true)// })) /** * 傻瓜式 */ // val structField = Array(StructField("id", IntegerType, true), StructField("name", StringType, true), StructField("age", IntegerType, true)) // // val schema = StructType.apply(structField) // val schema = StructType(structField) val df = sQLContext.createDataFrame(RowRDD, schema) df.printSchema() df.show() df.registerTempTable("people") val res = sQLContext.sql("select * from people where age > 6") res.show() res.map(x => "Name: " + x.getAs[String]("name") + "\t Age: " + x.getAs[Int]("age")).foreach(println) }} 4. 读取MySQL中的数据来创建DataFrame12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667package com.chant.sql.jdbcimport java.sql.{Connection, DriverManager, PreparedStatement}import org.apache.spark.sql.SQLContextimport org.apache.spark.{SparkConf, SparkContext}/** * Created by Chant. */object JDBCDataSource2 { def main(args: Array[String]): Unit = { val sc = new SparkContext(new SparkConf().setAppName("JDBCDataSource2").setMaster("local")) val sqlContext = new SQLContext(sc) val reader = sqlContext.read.format("jdbc") reader.option("url", "jdbc:mysql://node01:3306/testdb") reader.option("driver", "com.mysql.jdbc.Driver") reader.option("user", "root") reader.option("password", "123456") reader.option("dbtable", "student_info") val stuInfoDF = reader.load() reader.option("dbtable","student_score") val stuScoreDF = reader.load()// 分别将mysql中两张表的数据加载并注册为DataFrame stuInfoDF.registerTempTable("stuInfos") stuScoreDF.registerTempTable("stuScores") val sql = "select stuInfos.name, age, score from stuInfos join stuScores " + "on (stuInfos.name = stuScores.name) where stuScores.score > 80" //执行sql,join两个DF val resDF = sqlContext.sql(sql) resDF.show()// 将join后的数据写入的数据库 resDF.rdd.foreachPartition(p =>{ Class.forName("com.mysql.jdbc.Driver") var conn : Connection = null//这样写,在finnaly的时候才能访问到并将其关闭 var ps : PreparedStatement = null val sql2 = "insert into good_student_info values(?,?,?)" try{ conn = DriverManager.getConnection("jdbc:mysql://node01:3306/testdb", "root", "123456")//获取数据库链接 conn.setAutoCommit(false)//关闭自动提交 ps = conn.prepareStatement(sql2)//准备sql //迭代添加数据,并添加带batch p.foreach(row =>{ ps.setString(1, row.getAs[String]("name")) ps.setInt(2, row.getAs[Int]("age")) ps.setInt(3, row.getAs[Int]("score")) ps.addBatch() }) //执行并提交 ps.executeBatch() conn.commit()//貌似数据量少的时候,不提交也会有数据写入数据库???尚存疑问。但是肯定要写 }catch{ case e:Exception => e.printStackTrace() }finally { if(ps != null) ps.close() if(conn != null) conn.close() } }) sc.stop() }} 5. 读取Hive中的数据创建一个DataFrame(Spark on Hive)Spark与Hive整合:1) 编辑spark客户端的配置文件hive-site.xml 12345678910node04:vi /opt/chant/spark-1.6.0/conf/hive-site.xml<configuration> <property> <name>hive.metastore.uris</name> <value>thrift://node04:9083</value> <description>Thrift uri for the remote metastore. Used by metastore client to connect to remote metastore.</description> </property></configuration> 2) 把hadoop的core-site.xml和hdfs-site.xml copy到SPARK_HOME/conf/下3) node0{1,2,3}:zkServer.sh start4) node01:start-dfs.sh5) node01:service mysqld start6) node04:hive –service metastore7) node01:/opt/chant/spark-1.6.0/sbin/start-all.sh8) node02:/opt/chant/spark-1.6.0/sbin/start-master.sh9) node04:/opt/chant/spark-1.6.0/bin/spark-submit–master spark://node01:7077,node02:7077–class com.bjchant.java.spark.sql.hive.HiveDataSource../TestHiveContext.jarjar包中的测试代码如下: 12345678910111213141516171819202122232425262728293031323334353637import org.apache.spark.SparkConfimport org.apache.spark.SparkContextimport org.apache.spark.sql.hive.HiveContextobject HiveDataSource { def main(args: Array[String]): Unit = { val conf = new SparkConf() .setAppName("HiveDataSource"); val sc = new SparkContext(conf); val hiveContext = new HiveContext(sc); hiveContext.sql("DROP TABLE IF EXISTS student_infos"); hiveContext.sql("CREATE TABLE IF NOT EXISTS student_infos (name STRING, age INT) row format delimited fields terminated by '\t'"); hiveContext.sql("LOAD DATA " + "LOCAL INPATH '/root/resource/student_infos' " + "INTO TABLE student_infos"); hiveContext.sql("DROP TABLE IF EXISTS student_scores"); hiveContext.sql("CREATE TABLE IF NOT EXISTS student_scores (name STRING, score INT) row format delimited fields terminated by '\t'"); hiveContext.sql("LOAD DATA " + "LOCAL INPATH '/root/resource/student_scores' " + "INTO TABLE student_scores"); val goodStudentsDF = hiveContext.sql("SELECT si.name, si.age, ss.score " + "FROM student_infos si " + "JOIN student_scores ss ON si.name=ss.name " + "WHERE ss.score>=80"); hiveContext.sql("DROP TABLE IF EXISTS good_student_infos");// goodStudentsDF.saveAsTable("good_student_infos"); hiveContext.sql("USE result") //将goodStudentsDF里面的值写入到Hive表中,如果表不存在,会自动创建然后将数据插入到表中 goodStudentsDF.write.saveAsTable("good_student_infos") }} DataFrame数据存储 存储到hive表中把hive表读取为dataFramedataFrame = hiveContext().table("table_name");把dataFrame转为hive表存储到hive中,若table不存在,自动创建dataFrame.write().saveAsTable("table_name"); 2.存储到MySQL/HBase/Redis…中 123dataFrame.javaRDD().foreachPartition(new VoidFunction<Row>() { ……}) 3.存储到parquet文件(压缩比大,节省空间)中 123456789101112131415DataFrame usersDF = sqlContext.read().format("parquet").load("hdfs://node01:9000/input/users.parquet");usersDF.registerTempTable("users");DataFrame resultDF = sqlContext.sql("SELECT * FROM users WHERE name = 'Tom'");resultDF.write().format("parquet ").mode(SaveMode.Ignore).save("hdfs://node01:9000/output/result. parquet ");resultDF.write().format("json").mode(SaveMode. Overwrite).save("hdfs://node01:9000/output/result.json");public enum SaveMode { Append, //如果文件已经存在,追加 Overwrite, //如果文件已经存在,覆盖 ErrorIfExists, //如果文件已经存在,报错 Ignore//如果文件已经存在,不对原文件进行任何修改,即不存储DataFrame} parquet数据源会自动推断分区,类似hive里面的分区表的概念文件存储的目录结构如下: 12345/users|/country=US |data:id,name|/country=ZH |data:id,name 当执行以下代码 12345678910DataFrame usersDF = sqlContext.read().parquet( "hdfs://node01:9000/users"); //路径只写到usersusersDF.printSchema();/**(id int, name string, country string)*/usersDF.show();usersDF.registerTempTable("table1");sqlContext.sql("SELECT count(0) FROM table1 WHERE country = 'ZH'").show();//执行这个sql只需要去country=ZH文件夹下去遍历即可 自定义函数UDF123456789101112131415161718192021222324252627282930313233343536373839import org.apache.spark.SparkConfimport org.apache.spark.SparkContextimport org.apache.spark.sql.SQLContextimport org.apache.spark.sql.Rowimport org.apache.spark.sql.types.StructTypeimport org.apache.spark.sql.types.StructFieldimport org.apache.spark.sql.types.StringTypeobject UDF { def main(args: Array[String]): Unit = { val conf = new SparkConf() .setMaster("local") .setAppName("UDF") val sc = new SparkContext(conf) val sqlContext = new SQLContext(sc) val names = Array("yarn", "Marry", "Jack", "Tom") val namesRDD = sc.parallelize(names, 4) val namesRowRDD = namesRDD.map { name => Row(name) } val structType = StructType(Array(StructField("name", StringType, true))) val namesDF = sqlContext.createDataFrame(namesRowRDD, structType) // 注册一张names表 namesDF.registerTempTable("names") sqlContext.udf.register("strLen", (str: String) => str.length()) // 使用自定义函数 sqlContext.sql("select name,strLen(name) from names").show }} UDAF:实现对某个字段进行count1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465import org.apache.spark.sql.expressions.UserDefinedAggregateFunctionimport org.apache.spark.sql.types.StructTypeimport org.apache.spark.sql.types.DataTypeimport org.apache.spark.sql.expressions.MutableAggregationBufferimport org.apache.spark.sql.Rowimport org.apache.spark.sql.types.StructFieldimport org.apache.spark.sql.types.StringTypeimport org.apache.spark.sql.types.IntegerTypeclass StringCount extends UserDefinedAggregateFunction { //输入数据的类型 def inputSchema: StructType = { StructType(Array(StructField("12321", StringType, true))) } // 聚合操作时,所处理的数据的类型 def bufferSchema: StructType = { StructType(Array(StructField("count", IntegerType, true))) } def deterministic: Boolean = { true } // 为每个分组的数据执行初始化值 def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0) = 0 } /** * update可以认为是,一个一个地将组内的字段值传递进来实现拼接的逻辑 * buffer.getInt(0)获取的是上一次聚合后的值 * 相当于map端的combiner,combiner就是对每一个map task的处理结果进行一次小聚合 * 大聚和发生在reduce端 */ //每个组,有新的值进来的时候,进行分组对应的聚合值的计算 def update(buffer: MutableAggregationBuffer, input: Row): Unit = { buffer(0) = buffer.getAs[Int](0) + 1 } /** * 合并 update操作,可能是针对一个分组内的部分数据,在某个节点上发生的 * 但是可能一个分组内的数据,会分布在多个节点上处理 * 此时就要用merge操作,将各个节点上分布式拼接好的串,合并起来 * buffer1.getInt(0) : 大聚和的时候上一次聚合后的值 * buffer2.getInt(0) : 这次计算传入进来的update的结果 */ // 最后merger的时候,在各个节点上的聚合值,要进行merge,也就是合并 def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1(0) = buffer1.getAs[Int](0) + buffer2.getAs[Int](0) } // 最终函数返回值的类型 def dataType: DataType = { IntegerType } // 最后返回一个最终的聚合值 要和dataType的类型一一对应 def evaluate(buffer: Row): Any = { buffer.getAs[Int](0) }} 开窗函数row_number()开窗函数的作用:按照我们每一个分组的数据,按其照顺序,打上一个分组内的行号 id=2016 [111,112,113]那么对这个分组的每一行使用row_number()开窗函数后,三行数据会一次得到一个组内的行号 id=2016 [111 1,112 2,113 3] SparkStreamingStrom VS SparkStreaming Storm是一个纯实时的流式处理框,SparkStreaming是一个准实时的流式处理框架,(微批处理:可以设置时间间隔) SparkStreaming的吞吐量比Storm高(因为它是微批处理) Storm的事务机制要比SparkStreaming好(每个数据只处理一次) Storm支持动态资源调度( 可以在数据的低谷期使用更少的资源,而spark的粗粒度资源调度默认是不支持这样的) SparkStreaming的应用程序中可以写SQL语句来处理数据,可以和spark core及sparkSQL无缝结合,所以SparkingStreaming擅长复杂的业务处理,而Storm不擅长复杂的业务处理,它擅长简单的汇总型计算(天猫双十一销量) SparkStreaming执行流程 总结:receiver task是7*24h一直在执行,一直接收数据,将接收到的数据保存到batch中,假设batch interval为5s,那么把接收到的数据每隔5s切割到一个batch,因为batch是没有分布式计算的特性的,而RDD有,所以把batch封装到RDD中,又把RDD封装到DStream中进行计算,在第5s的时候,计算前5s的数据,假设计算5s的数据只需要3s,那么第5-8s一边计算任务,一边接收数据,第9-11s只是接收数据,然后在第10s的时候,循环上面的操作。 如果job执行时间大于batch interval,那么未执行的数据会越攒越多,最终导致Spark集群崩溃。测试:1.开启scoket server[root@node01 ~]# nc -lk 99992.启动spark集群 12[root@node01 ~]# /opt/chant/spark-1.6.0/sbin /start-all.sh[root@node02 ~]# /opt/chant/spark-1.6.0/sbin /start-master.sh 3.运行测试程序 1234567891011121314151617181920212223242526272829303132333435363738394041import org.apache.spark.SparkConfimport org.apache.spark.streaming.StreamingContextimport org.apache.spark.streaming.Durationsimport org.apache.spark.storage.StorageLevel/** * 1.local的模拟线程数必须大于等于2,因为一条线程被receiver(接受数据的线程)占用,另外一个线程是job执行 * 2.Durations时间的设置,就是我们能接受的延迟度,这个我们需要根据集群的资源情况以及监控,要考虑每一个job的执行时间 * 3.创建StreamingContext有两种方式 (sparkconf、sparkcontext) * 4.业务逻辑完成后,需要有一个output operator * 5.StreamingContext.start(),straming框架启动之后是不能在次添加业务逻辑 * 6.StreamingContext.stop()无参的stop方法会将sparkContext一同关闭,如果只想关闭StreamingContext,在stop()方法内传入参数false * 7.StreamingContext.stop()停止之后是不能在调用start */object WordCountOnline { def main(args: Array[String]): Unit = { val sparkConf = new SparkConf() sparkConf.setMaster("local[2]") sparkConf.setAppName("WordCountOnline") //在创建streaminContext的时候设置batch Interval val ssc = new StreamingContext(sparkConf,Durations.seconds(5)) val linesDStream = ssc.socketTextStream("node01", 9999, StorageLevel.MEMORY_AND_DISK) // val wordsDStream = linesDStream.flatMap { _.split(" ") } val wordsDStream = linesDStream.flatMap(_.split(" ")) val pairDStream = wordsDStream.map { (_,1) } val resultDStream = pairDStream.reduceByKey(_+_) resultDStream.print() //outputoperator类的算子 ssc.start() ssc.awaitTermination() ssc.stop() }} 结果:在server 端输入数据,例如,hello world,控制台实时打印wordwount结果:(hello,1)(world,1) Output Operations on DStreamsforeachRDD(func)123456789dstream.foreachRDD { rdd => rdd.foreachPartition { partitionOfRecords => // ConnectionPool is a static, lazily initialized pool of connections val connection = ConnectionPool.getConnection() partitionOfRecords.foreach(record => connection.send(record))ConnectionPool.returnConnection(connection) // return to the pool for future reuse }} saveAsTextFiles(prefix, [suffix])Save this DStream’s contents as text files. saveAsObjectFiles(prefix, [suffix])Save this DStream’s contents as SequenceFiles of serialized Java objects. saveAsHadoopFiles(prefix, [suffix])Save this DStream’s contents as Hadoop files. Transformations on Dstreamstransform(func)Return a new DStream by applying a RDD-to-RDD function to every RDD of the source DStream. This can be used to do arbitrary RDD operations on the DStream. 1234567val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam informationval cleanedDStream = wordCounts.transform(rdd => { rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning ...}) updateStateByKey(func)Return a new “state” DStream where the state for each key is updated by applying the given function on the previous state of the key and the new values for the key. This can be used to maintain arbitrary state data for each key.UpdateStateByKey的主要功能:1.Spark Streaming中为每一个Key维护一份state状态,state类型可以是任意类型的,可以是一个自定义的对象,那么更新函数也可以是自定义的。2.通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172import org.apache.spark.SparkConfimport org.apache.spark.streaming.{Durations, StreamingContext}import spire.std.option/** wordcount,实时计算结果,与之前的worCountOnline不同,它是增量计算的。 * * UpdateStateByKey的主要功能: * 1、Spark Streaming中为每一个Key维护一份state状态,state类型可以是任意类型的的, 可以是一个自定义的对象,那么更新函数也可以是自定义的。 * 2、通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新 * 第6s的计算结果1-5s * hello,3 * world,2 * * 第11s的时候 6-11秒 * 接收数据 * hello 1 * hello 1 * world 1 *计算逻辑 * hello 3+1+1 * world 2+1 * * 第16s 11-15s * 接收数据 * hello 1 * hello 1 * world 1 * 计算逻辑 * hello 5+1+1 * world 3+1 * 如果要不断的更新每个key的state,就一定涉及到了状态的保存和容错,这个时候就需要开启checkpoint机制和功能 * * 全面的广告点击分析 * * 有何用? 统计广告点击流量,统计这一天的车流量,统计。。。。点击量 */object UpdateStateByKeyOperator { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local[2]").setAppName("UpdateStateByKeyOperator") val streamContext = new StreamingContext(conf, Durations.seconds(5)) /** * 上一次的计算结果会保存两份: * 1.内存 * 2.我们设置的checkPoint目录下 * 因为内存不稳定,放在checkPoint目录下更安全。 * 多久会将内存中的数据(每一个key所对应的状态)写入到磁盘上一份呢? * 如果你的batch interval小于10s 那么10s会将内存中的数据写入到磁盘一份 * 如果bacth interval 大于10s,那么就以bacth interval为准 */ streamContext.checkpoint("hdfs://node01:8020/sscheckpoint01") val lines = streamContext.socketTextStream("node01",8888) val wordsPair = lines.flatMap(_.split(" ")).map((_,1))// val wordsPair = streamContext.socketTextStream("node01",8888).flatMap(_.split(" ")).map((_,1)) //注意这里的各种泛型 val counts = wordsPair.updateStateByKey[Int]((values:Seq[Int], state:Option[Int]) =>{ var updateValue = 0 if(!state.isEmpty) updateValue = state.get values.foreach(x =>{updateValue += x})// Option.apply[Int](updateValue) Some(updateValue) }) counts.print() streamContext.start() streamContext.awaitTermination() streamContext.stop() }} Window Operations 总结:batch interval:5s每隔5s切割一次batch封装成DStreamwindow length:15s进行计算的DStream中包含15s的数据,这里也就是3个batch。sliding interval:10s每隔10s取3个batch封装的DStream,封装成一个更大的DStream进行计算window length和sliding interval必须是batch interval的整数倍问题:time3的RDD被计算两次 123456789101112131415161718192021222324252627282930313233import org.apache.spark.SparkConfimport org.apache.spark.streaming.{Durations, StreamingContext}object WindowOperator { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("WindowOperator").setMaster("local[2]") val streamingContext = new StreamingContext(conf, Durations.seconds(5)) //优化版必须设置cehckpoint目录,因为内存不稳定,保证数据不丢失。 streamingContext.checkpoint("hdfs://node01:8020/sscheckpoint02") val logDStrem = streamingContext.socketTextStream("node01", 8888) val wordsPair = logDStrem.flatMap(_.split(" ")).map((_, 1)) /** batch interval:5s * sliding interval:10s * window length:60s * 所以每隔10s会取12个rdd,在计算的时候会将这12个rdd聚合起来 * 然后一起执行reduceByKeyAndWindow操作 * reduceByKeyAndWindow是针对窗口操作的而不是针对DStream操作的 */// val wordCountDStrem = wordsPair.reduceByKeyAndWindow((a, b) => a + b, Durations.seconds(60)) //为什么这里一定要声明参数类型???什么时候需要声明,什么时候不用,什么时候必须不声明?// val wordCountDStrem = wordsPair.reduceByKeyAndWindow((a: Int, b: Int) => a+b, Durations.seconds(60), Durations.seconds(10)) //优化版 val wordCountDStrem = wordsPair.reduceByKeyAndWindow((a, b) => a+b, (a, b) => a-b, Durations.minutes(1), Durations.seconds(10)) wordCountDStrem.print() streamingContext.start() streamingContext.awaitTermination() streamingContext.stop() }} 优化: 假设batch=1s,window length=5s,sliding interval=1s,那么每个DStream重复计算了5次,优化后,(t+4)时刻的Window由(t+3)时刻的Window和(t+4)时刻的DStream组成,由于(t+3)时刻的Window包含(t-1)时刻的DStream,而(t+4)时刻的Window中不需要包含(t-1)时刻的DStream,所以还需要减去(t-1)时刻的DStream,所以:Window(t+4) = Window(t+3) + DStream(t+4) - DStream(t-1) 优化后的代码: 12//优化版必须要设置checkPoint目录 val wordCountDStrem = wordsPair.reduceByKeyAndWindow((a, b) => a+b, (a, b) => a-b, Durations.minutes(1), Durations.seconds(10)) NOTE: updateStateByKey和优化版的reduceByKeyAndWindow都必须要设置checkPoint目录。 Driver HA提交任务时设置spark-submit –superviseSpark standalone or Mesos with cluster deploy mode only:–supervise If given, restarts the driver on failure.以集群方式提交到yarn上时,Driver挂掉会自动重启,不需要任何设置提交任务,在客户端启动Driver,那么不管是提交到standalone还是yarn,Driver挂掉后都无法重启代码中配置上面的方式重新启动的Driver需要重新读取application的信息然后进行任务调度,实际需求是,新启动的Driver可以直接恢复到上一个Driver的状态(可以直接读取上一个StreamingContext的DSstream操作逻辑和job执行进度,所以需要把上一个StreamingContext的元数据保存到HDFS上),直接进行任务调度,这就需要在代码层面进行配置。 123456789101112131415161718192021222324252627282930import org.apache.spark.SparkConfimport org.apache.spark.streaming.{Durations, StreamingContext}/** Spark standalone or Mesos 1with cluster deploy mode only: * 在提交application的时候 添加 --supervise 选项 如果Driver挂掉 会自动启动一个Driver * SparkStreaming */object SparkStreamingOnHDFS2 { def main(args: Array[String]): Unit = { val checkpointPath = "hdfs://node01:8020/sscheckpoint03"// val ssc = new StreamingContext(conf, Durations.seconds(5)) val ssc = StreamingContext.getOrCreate(checkpointPath,() => { println("Creating new context") //这里可以设置一个线程,因为不需要一个专门接收数据的线程,而是监控一个目录 val conf = new SparkConf().setAppName("SparkStreamingOnHDFS").setMaster("local[1]") //每隔15s查看一下监控的目录中是否新增了文件 val ssc = new StreamingContext(conf, Durations.seconds(15)) ssc.checkpoint(checkpointPath) /** 只是监控文件夹下新增的文件,减少的文件是监控不到的 文件内容有改动也是监控不到 */ ssc }) val wordCount = ssc.textFileStream("hdfs://node01:8020/hdfs/").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_) wordCount.print() ssc.start() ssc.awaitTermination() ssc.stop() }} 执行一次程序后,JavaStreamingContext会在checkpointDirectory中保存,当修改了业务逻辑后,再次运行程序,JavaStreamingContext.getOrCreate(checkpointDirectory, factory);因为checkpointDirectory中有这个application的JavaStreamingContext,所以不会调用JavaStreamingContextFactory来创建JavaStreamingContext,而是直接checkpointDirectory中的JavaStreamingContext,所以即使业务逻辑改变了,执行的效果也是之前的业务逻辑,如果需要执行修改过的业务逻辑,可以修改或删除checkpointDirectory。 Kafka简介kafka是一个高吞吐的分部式消息系统使用kafka和SparkStreaming组合的好处: 解耦,SparkStreaming不用关心数据源是什么,只需要消费数据即可 缓冲,数据提交给kafka消息队列,SparkStreaming按固定时间和顺序处理数据,减轻数据量过大造成的负载 异步通信,kafka把请求队列提交给服务端,服务端可以把响应消息也提交给kafka的消息队列中,互不影响 消息系统特点 生产者消费者模式 可靠性自己不丢数据当消费者消费一条数据后,这条数据还会保存在kafka中,在一周(默认)后再删除消费者不丢数据 消费者消费数据的策略,“至少一次”,即消费者至少处理这条数据一次,“严格一次”,即消费者必须且只能消费这条数据一次 Kafka架构 图里还少了个zookeeper,用于存储元数据和消费偏移量(根据偏移量来保证消费至少一次和严格消费一次)producer:消息生产者consumer:消息消费者broker:kafka集群的每一台节点叫做broker,负责处理消息读、写请求,存储消息topic:消息队列/分类kafka里面的消息是有topic来组织的,简单的我们可以想象为一个队列,一个队列就是一个topic,然后它把每个topic又分为很多个partition,这个是为了做并行的,在每个partition里面是有序的,相当于有序的队列,其中每个消息都有个序号,比如0到12,从前面读往后面写。一个partition对应一个broker,一个broker可以管多个partition,比如说,topic有6个partition,有两个broker,那每个broker就管理3个partition。这个partition可以很简单想象为一个文件,当数据发过来的时候它就往这个partition上面append,追加就行,kafka和很多消息系统不一样,很多消息系统是消费完了我就把它删掉,而kafka是根据时间策略删除,而不是消费完就删除,在kafka里面没有消费完这个概念,只有过期这个概念 一个topic分成多个partition,每个partition内部消息强有序,其中的每个消息都有一个序号叫offset,一个partition只对应一个broker,一个broker可以管多个partition,消息直接写入文件,并不是存储在内存中,根据时间策略(默认一周)删除,而不是消费完就删除,producer自己决定往哪个partition写消息,可以是轮询的负载均衡,或者是基于hash的partition策略,而这样容易造成数据倾斜。所以建议使用轮询的负载均衡。 consumer自己维护消费到哪个offset,每个consumer都有对应的group,group内部是queue消费模型,各个consumer消费不同的partition,一个消息在group内只消费一次,各个group各自独立消费,互不影响partition内部是FIFO的,partition之间不是FIFO的,当然我们可以把topic设为一个partition,这样就是严格的FIFO(First Input First Output,先入先出队列) kafka特点 高性能:单节点支持上千个客户端,百MB/s吞吐 持久性:消息直接持久化在普通磁盘上,性能好,直接写到磁盘里面去,就是直接append到磁盘里面去,这样的好处是直接持久话,数据不会丢,第二个好处是顺序写,然后消费数据也是顺序的读,所以持久化的同时还能保证顺序读写 分布式:数据副本冗余、流量负载均衡、可扩展分布式,数据副本,也就是同一份数据可以到不同的broker上面去,也就是当一份数据,磁盘坏掉的时候,数据不会丢失,比如3个副本,就是在3个机器磁盘都坏掉的情况下数据才会丢。 灵活性:消息长时间持久化+Client维护消费状态消费方式非常灵活,第一原因是消息持久化时间跨度比较长,一天或者一星期等,第二消费状态自己维护消费到哪个地方了,可以自定义消费偏移量 kafka与其他消息队列对比 RabbitMQ:分布式,支持多种MQ协议,重量级 ActiveMQ:与RabbitMQ类似 ZeroMQ:以库的形式提供,使用复杂,无持久化 redis:单机、纯内存性好,持久化较差本身是一个内存的KV系统,但是它也有队列的一些数据结构,能够实现一些消息队列的功能,当然它在单机纯内存的情况下,性能会比较好,持久化做的稍差,当持久化的时候性能下降的会比较厉害 kafka:分布式,较长时间持久化,高性能,轻量灵活天生是分布式的,不需要你在上层做分布式的工作,另外有较长时间持久化,在长时间持久化下性能还比较高,顺序读和顺序写,还通过sendFile这样0拷贝的技术直接从文件拷贝到网络,减少内存的拷贝,还有批量读批量写来提高网络读取文件的性能 零拷贝“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。 Kafka集群搭建node01,node02,node03 1. 解压[root@node01 chant]# tar zxvf kafka_2.10-0.8.2.2.tgz 2. 修改server.properties配置文件123456[root@node01 chant]# cd kafka_2.10-0.8.2.2/config[root@node01 config]# vi server.propertiesbroker.id=0 #node01为0,node02为1,node03为2log.dirs=/var/kafka/logs #真实数据存储路径auto.leader.rebalance.enable=true #leader均衡机制开启zookeeper.connect=node02:2181,node03:2181,node04:2181 #zookeeper集群 3. 同步配置,记得修改每台机器的broker.id4. 启动zookeeper集群5. 在每台kafka节点上启动kafka集群1nohup /opt/chant/kafka_2.10-0.8.2.2/bin/kafka-server-start.sh /opt/chant/kafka_2.10-0.8.2.2/config/server.properties & 6. 测试在node01上创建topic: 123456/opt/chant/kafka_2.10-0.8.2.2/bin/kafka-topics.sh --create --zookeeper node02:2181,node03:2181,node04:2181 --replication-factor 3 --partitions 3 --topic test_create_topic 生产数据: 123/opt/chant/kafka_2.10-0.8.2.2/bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic test_create_topic 在node02上启动消费者 1234/opt/chant/kafka_2.10-0.8.2.2/bin/kafka-console-consumer.sh --zookeeper node02:2181,node03:2181,node04:2181 --from-beginning --topic test_create_topic 在node01输入消息,在node02会接收并打印 查看在集群中有哪些topic: 123/opt/chant/kafka_2.10-0.8.2.2/bin/kafka-topics.sh --list --zookeeper node02:2181,node03:2181,node04:2181 结果:test_create_topic 查看某个topic信息: 123456789/opt/chant/kafka_2.10-0.8.2.2/bin/kafka-topics.sh --describe --zookeeper node02:2181,node03:2181,node04:2181 --topic test_create_topic结果:Topic:test_create_topic PartitionCount:3 ReplicationFactor:3 Configs: Topic: test_create_topic Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2 Topic: test_create_topic Partition: 1 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0 Topic: test_create_topic Partition: 2 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1 解释一下leader均衡机制(auto.leader.rebalance.enable=true):每个partition是有主备结构的,当partition 1的leader,就是broker.id = 1的节点挂掉后,那么leader 0 或leader 2成为partition 1 的leader,那么leader 0 或leader 2 会管理两个partition的读写,性能会下降,当leader 1 重新启动后,如果开启了leader均衡机制,那么leader 1会重新成为partition 1 的leader,降低leader 0 或leader 2 的负载 Kafka和SparkStreaming整合Receiver方式原理: 获取kafka传递的数据来计算:123456789101112131415161718192021222324SparkConf conf = new SparkConf() .setAppName("SparkStreamingOnKafkaReceiver") .setMaster("local[2]") .set("spark.streaming.receiver.writeAheadLog.enable","true"); JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));//设置持久化数据的目录jsc.checkpoint("hdfs://node01:8020/spark/checkpoint");Map<String, Integer> topicConsumerConcurrency = new HashMap<String, Integer>();//topic名 receiver task数量topicConsumerConcurrency.put("test_create_topic", 1);JavaPairReceiverInputDStream<String,String> lines = KafkaUtils.createStream( jsc, "node02:2181,node03:2181,node04:2181", "MyFirstConsumerGroup", topicConsumerConcurrency, StorageLevel.MEMORY_AND_DISK_SER());/* * 第一个参数是StreamingContext * 第二个参数是ZooKeeper集群信息(接受Kafka数据的时候会从Zookeeper中获得Offset等元数据信息) * 第三个参数是Consumer Group * 第四个参数是消费的Topic以及并发读取Topic中Partition的线程数 * 第五个参数是持久化数据的级别,可以自定义 *///对lines进行其他操作…… 注意 需要spark-examples-1.6.0-hadoop2.6.0.jar kafka_2.10-0.8.2.2.jar和kafka-clients-0.8.2.2.jar版本号要一致 kafka客户端生产数据的代码:12345678910111213141516171819202122232425262728293031323334public class SparkStreamingDataManuallyProducerForKafka extends Thread { private String topic; //发送给Kafka的数据的类别 private Producer<Integer, String> producerForKafka; public SparkStreamingDataManuallyProducerForKafka(String topic){ this.topic = topic; Properties conf = new Properties(); conf.put("metadata.broker.list","node01:9092,node02:9092,node03:9092"); conf.put("serializer.class", StringEncoder.class.getName()); producerForKafka = new Producer<Integer, String>( new ProducerConfig(conf)) ; } @Override public void run() { while(true){ counter ++; String userLog = createUserLog();//生产数据这个方法可以根据实际需求自己编写 producerForKafka.send(new KeyedMessage<Integer, String>(topic, userLog)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) {new SparkStreamingDataManuallyProducerForKafka("test_create_topic").start();//test_create_topic是topic名 }} Direct方式把kafka当作一个存储系统,直接从kafka中读数据,SparkStreaming自己维护消费者的消费偏移量,不再将其存储到zookeeper。与Receiver方式相比,他有的优点是: one-to-one,直接读取卡夫卡中数据,Partion数与kafka一致。 Efficiency,不开启WAL也不会丢失数据,因为事实上kafka的的数据保留时间只要设定合适,可以直接从kafka恢复。 Exactly-once semantics,其实这只保证kafka与spark是一致的,之后写数据的时候还需要自己注意才能保证整个事务的一致性。而在receiver模式下,offset交由kafka管理,而kafaka实际交由zookeeper管理,可能出现数据不一致。 12345678910111213141516171819SparkConf conf = new SparkConf() .setAppName("SparkStreamingOnKafkaDirected") .setMaster("local[1]"); JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(10)); Map<String, String> kafkaParameters = new HashMap<String, String>();kafkaParameters.put("metadata.broker.list", "node01:9092,node02:9092,node03:9092"); HashSet<String> topics = new HashSet<String>();topics.add("test_create_topic");JavaPairInputDStream<String,String> lines = KafkaUtils.createDirectStream(jsc, String.class, String.class, StringDecoder.class, StringDecoder.class, kafkaParameters, topics);//对lines进行其他操作…… 两种方式下提高SparkStreaming并行度的方法Receiver方式调整SparkStreaming的并行度的方法: spark.streaming.blockInterval假设batch interval为5s,Receiver Task会每隔200ms(spark.streaming.blockInterval默认)将接收来的数据封装到一个block中,那么每个batch中包括25个block,batch会被封装到RDD中,所以RDD中会包含25个partition,所以提高接收数据时的并行度的方法是:调低spark.streaming.blockInterval的值,建议不低于50ms其他配置: spark.streaming.backpressure.enabled 默认false,设置为true后,sparkstreaming会根据上一个batch的接收数据的情况来动态的调整本次接收数据的速度,但是最大速度不能超过spark.streaming.receiver.maxRate设置的值(设置为n,那么速率不能超过n/s) spark.streaming.receiver.writeAheadLog.enable 默认false 是否开启WAL机制 Direct方式并行度的设置:第一个DStream的分区数是由读取的topic的分区数决定的,可以通过增加topic的partition数来提高SparkStreaming的并行度 参考资料 Spark Shuffle原理、Shuffle操作问题解决和参数调优 Spark性能优化指南——高级篇 Spark性能优化指南——基础篇 Unified Memory Management in Spark 1.6 Garbage Collection Tuning spark2.1内存管理机制 Spark内存管理详解(下)——内存管理]]></content>
<categories>
<category>Bigdata</category>
<category>Spark</category>
</categories>
<tags>
<tag>Spark</tag>
<tag>Spark原理及其搭建</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python笔记(五)-- 高级特性]]></title>
<url>%2F2017%2F06%2F27%2FpyhthonNote5%2F</url>
<content type="text"><![CDATA[切片,全局变量,生成器 切片(slice)切片(slice)用于取一个list或tuple的部分元素,比如,一个list如下:1>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。如果第一个索引是0,还可以省略:12>>> L[:3]['Michael', 'Sarah', 'Tracy'] 类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片。1234>>> L[-2:]['Bob', 'Jack']>>> L[-2:-1]['Bob'] 所有数,每5个取一个:123>>> L = list(range(100))>>> L[::5][0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] 甚至什么都不写,只写[:]就可以原样复制一个list(这可以用来解决默认参数重复赋值问题):12>>> L[:][0, 1, 2, 3, ..., 99] tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:12>>> (0, 1, 2, 3, 4, 5)[:3](0, 1, 2) 字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:1234>>> 'ABCDEFG'[:3]'ABC'>>> 'ABCDEFG'[::2]'ACEG' 全局变量和局部变量12345678A = 10def fun(): global A A = 20print(A) # 10fun()print(A) # 20 generator要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator: 123456>>> L = [x * x for x in range(10)]>>> L[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> g = (x * x for x in range(10))>>> g<generator object <genexpr> at 0x1022ef630> 创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值: 123456789>>> next(g)0>>> next(g)1# 执行到最后一个元素的时候>>> next(g)Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。 通常,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。 1234567>>> g = (x * x for x in range(3))>>> for n in g:... print(n)... 014 yeild 定义generatorFibonacci sequence(斐波那契数列)123456789def fib(max): n, a, b = 0, 0, 1 while n < max: print(a) a, b = b, a+b n = n+1 return 'done'fib(10) 仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。 也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了: 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。 这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。12345678print("====================")def fibGenerator(max): n, a, b = 0, 0, 1 while n< max: yield a a, b =b, a+b n += 1 return 'done' 调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值: 1234567891011121314151617181920f = fibGenerator(10)print(next(f))print(next(f))print(next(f))print('=====================')for x in f: print(x)# 输出结果如下011=====================2358132134 同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:1234567print(fibGenerator(10))for x in fibGenerator(10): print(x)print("====================")L = [x for x in fibGenerator(10)]print(L) 用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中: 12345678910111213141516171819while True: try: x = next(f) print('f: ', x) except StopIteration as e: print('Generator return value: ', e.value) break# 输出结果如下f: 0f: 1f: 1f: 2f: 3f: 5f: 8f: 13f: 21f: 34Generator return value: done 练习–杨辉三角杨辉三角定义如下:123456 1 1 1 1 2 1 1 3 3 1 1 4 6 4 11 5 10 10 5 1 把每一行看做一个list,试写一个generator,不断输出下一行的list: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748# 以下是我写的渣渣版def triangles(max): n = 1 if n == 1: L = [1] # print(L) yield L n = n + 1 L = [1,1] # print(L) yield L while n < max: L = list([*L, 1]) L[0] = 1 L[n-1] = 1 l = L[:] for i in range(1, n): L[i] = l[i-1] + l[i] n = n + 1 yield L # print(L)t = triangles(10)for i in t: print(i)# 输出结果如下[1][1, 1][1, 2, 1][1, 3, 3, 1][1, 4, 6, 4, 1][1, 5, 10, 10, 5, 1][1, 6, 15, 20, 15, 6, 1][1, 7, 21, 35, 35, 21, 7, 1][1, 8, 28, 56, 70, 56, 28, 8, 1][1, 9, 36, 84, 126, 126, 84, 36, 9, 1]# 下面是网友给出的简洁版,再次被秒成渣def trian(max): L=[1] n = 0 while n < max: yield L L = [1] + [ L[x-1] + L[x] for x in range(1,len(L)) ] + [1] n += 1t = trian(10)for i in t: print(i) 参考资料 廖雪峰python教程]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python笔记(四)-- 函数]]></title>
<url>%2F2017%2F06%2F26%2FPython%E7%AC%94%E8%AE%B04%E5%87%BD%E6%95%B0%2F</url>
<content type="text"><![CDATA[函数中的各种参数,lambda表达式,递归函数,汉诺塔示例。 空函数有啥卵用?骚年,还记得大明湖畔(java中)的抽象函数么?12def nop(): pass 函数的参数类型检查:if not isinstance(x, (int, float)): raise TypeError('bad operand type')1234567def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x >= 0: return x else: return -x 参数positional arguments(位置参数):就是普通的参数了,我猜是因为传参时其位置必须一一对应而得名。key-word arguments(关键字参数):形式为kwarg=value,(如果出现在函数定义中,这也就是默认参数)或通过词典拆包**dict来传递。arbitrary argument lists(可变参数表): 形式为*anameNOTE:在函数参数列表中的顺序为:位置参数,可变参数表,关键字参数 1234567891011121314151617181920""" Arbitrary Argument Lists """>>> def concat(*args, sep="/"):... return sep.join(args)...>>> concat("earth", "mars", "venus")'earth/mars/venus'>>> concat("earth", "mars", "venus", sep=".")'earth.mars.venus'# 可变参数之后是关键字参数,必须以键值对的形式传入,否则会被认为是可变参数,或者报错SyntaxError: positional argument follows keyword argument>>> concat("earth", "mars", "venus", ".")'earth/mars/venus/.'# Unpacking Argument Lists>>> def parrot(voltage, state='a stiff', action='voom'):... print("-- This parrot wouldn't", action, end=' ')... print("if you put", voltage, "volts through it.", end=' ')... print("E's", state, "!")...>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}>>> parrot(**d)-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised ! 关键字参数可以传入0个或者任意个含参数名的参数,这些参数名在函数定义中并没有出现,这些参数在函数内部自动封装成一个字典(dict).123456def portrait(name, **kw): print('name is', name) for k,v in kw.items(): print(k, v) portrait('Mike', age=24, country='China', education='bachelor') 如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:12def person(name, age, *, city, job): print(name, age, city, job) 调用方式如下:12>>> person('Jack', 24, city='Beijing', job='Engineer')Jack 24 Beijing Engineer 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:def person(name, age, *args, city, job): print(name, age, args, city, job)命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:1234>>> person('Jack', 24, 'Beijing', 'Engineer')Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: person() takes 2 positional arguments but 4 were given 由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。 通过可变参数和关键字参数,任何函数都可以用 universal_func(*args, **kw) 表达。 参数小结:默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!要注意定义可变参数和关键字参数的语法: *args是可变参数,args接收的是一个tuple;**kw是关键字参数,kw接收的是一个dict。 以及调用函数时如何传入可变参数和关键字参数的语法: 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。 使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符,否则定义的将是位置参数。如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符``了。 Lambda Expressions123456789101112def make_incrementor(n): return lambda x: x + n # lambda新建了一个匿名函数,相当于如下代码 # def test(x): # return x + n # return testf = make_incrementor(42) # 将42赋值给n,并将返回的lambda匿名函数赋值给fprint(f)print(f(0))print(f(1)) The above example uses a lambda expression to return a function. Another use is to pass a small function as an argument:1234>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]>>> pairs.sort(key=lambda pair: pair[0])>>> pairs[(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')] 递归函数在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 1234def fact(n): if n==1: return 1 return n * fact(n - 1) 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000): 1234567>>> fact(1000)Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in fact ... File "<stdin>", line 4, in factRuntimeError: maximum recursion depth exceeded in comparison 一些语言可以通过尾递归来解决这个问题,但是Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。 汉诺塔–递归函数的应用实例请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法。画图分析一下:从图中的分析可以看出f(n,a,b,c)=f(n-1,a,c,b)+f(1,a,b,c)+f(n-1,b,a,c),于是使用递归函数,代码如下:12345678910def move(n, a, b, c): if n == 1: print(a + '-->' + c) return move(n-1, a, c, b) move(1, a, b, c) move(n-1, b, a, c)move(7, 'A', 'B', 'C') 参考资料 官方文档– Defining Functions 莫烦Python 廖雪峰的Python教程]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python笔记(三)-- Important Warning]]></title>
<url>%2F2017%2F06%2F25%2FPython%E7%AC%94%E8%AE%B03warning%2F</url>
<content type="text"><![CDATA[本篇整理下python中容易出错的那些坑。在学习Python的过程中,发现其很多与java不同的地方,很多人并没有仔细研读过python,只是大概地看下相关的语法就开始上手写代码去了,最容易被忽视的就是简单的for,if等控制语句,实际上python的控制语句与java等高级语言是不同的,所谓失之毫厘差之千里,请看下面的例子。 for循环操作可变序列12345678910111213a = [-1, 2, -4, -3, -1, -2, 3]print(a)for x in a: if x < 0: a.remove(x)print(a) # [2, -3, -2, 3]print("=========================")b = [-1, 2, -4, -3, -1, -2, 3]print(b)for x in b[:]: if x < 0: b.remove(x)print(b) # [2, 3] Note There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop. This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence.简而言之,循环时指针会移动,所以直接删除是不安全的。而解决办法也很精妙(真是完美契合subtlety这个词),使用b[:]会在内存中临时复制出一个b,在b[:]中循环,在原本的b上remove。 for else我问了身边三个会python的程序猿,他们都震惊地表示:还有这种操作?!然而官方文档写得很清楚,for else,try else都是python的独特的正确的语法。12for_stmt ::= "for" target_list "in" expression_list ":" suite ["else" ":" suite] A break statement executed in the first suite terminates the loop without executing the else clause’s suite. A continue statement executed in the first suite skips the rest of the suite and continues with the next item, or with the else clause if there is no next item. 也就是说,正常情况下else语句会在循环完成(遍历完整个expression_list)后执行。但是如果有break发生时,else语句不执行,也就是说break会同时跳出for和else的整程序组(suite)。而continue会在循环中跳过else语句,然后在循环完毕后执行else语句。 示例:12345678for n in range(2, 10): for x in range(2, n): if n % x == 0: print(n, 'equals', x, '*', n//x) break else: # loop fell through without finding a factor print(n, 'is a prime number') 换个说法再来解释一遍: Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement. 你能不用for else语句来实现相同的功能么?(if x == n-1:) 默认参数函数 Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls: 1234567891011def f(a, L=[]): L.append(a) return Lprint(f(1))print(f(2))print(f(3))# 输出结果为 [1][1, 2][1, 2, 3] 官方的解释不够详细,廖雪峰的解释如下:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。所以,定义默认参数要牢记一点:默认参数必须指向不变对象!要修改上面的例子,我们可以用None这个不变对象来实现。解决办法:1234567def f(a, L=None): if L is None: L = [] L.append(a) return Lprint(f(1), f(2), f(3)) # [1] [2] [3] 参考文章 官方文档–More Control Flow Tools]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python笔记(二)-- 容器]]></title>
<url>%2F2017%2F06%2F22%2Fpython%E7%AC%94%E8%AE%B02%2F</url>
<content type="text"><![CDATA[List 和 Tuplelist 和 tuple均为有序表,即可用索引访问,但是list可变,tuple不可变。 先来几个基础单词:parenthesis/parentheses 圆括号()square brackets 方括号[]curly braces 大括号/花括号{}heterogeneous [‘hɛtərə’dʒinɪəs] 不同种类的homogeneous [,homə’dʒinɪəs]同种类的 Listlist类似于数组,是一个可变的有序表,元素的数据类型也可以不同,ist元素也可以是另一个list。如classmates = ['Michael', 'Bob', 'Tracy'],索引从0开始到len(classmates)-1,如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:classmates[-1],以此类推,可以获取倒数第2个、倒数第3个classmates[-2]。方法:list.append(x)Add an item to the end of the list. Equivalent to a[len(a):] = [x].list.extend(iterable)Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.list.insert(i, x)Insert an item at a given position. The first argument is the index of the element before which to insert, so a.insert(0, x) inserts at the front of the list, and a.insert(len(a), x) is equivalent to a.append(x).list.remove(x)Remove the first item from the list whose value is x. It is an error if there is no such item.list.pop([i])Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list. (The square brackets around the i in the method signature denote that the parameter is optional, not that you should type square brackets at that position. You will see this notation frequently in the Python Library Reference.)list.clear()Remove all items from the list. Equivalent to del a[:].list.index(x[, start[, end]])Return zero-based index in the list of the first item whose value is x. Raises a ValueError if there is no such item.The optional arguments start and end are interpreted as in the slice notation and are used to limit the search to a particular subsequence of the list. The returned index is computed relative to the beginning of the full sequence rather than the start argument.list.count(x)Return the number of times x appears in the list.list.sort(key=None, reverse=False)Sort the items of the list in place (the arguments can be used for sort customization, see sorted() for their explanation).list.reverse()Reverse the elements of the list in place.list.copy()Return a shallow copy of the list. Equivalent to a[:]. del() 删除指定位置元素list(tup) 将不可变的元祖tup转换为可变的list1234567891011121314151617181920212223242526272829303132333435363738394041424344>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']>>> fruits.count('apple')2>>> fruits.count('tangerine')0>>> fruits.index('banana')3>>> fruits.index('banana', 4) # Find next banana starting a position 46>>> fruits.reverse()>>> fruits['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']>>> fruits.append('grape')>>> fruits['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']>>> fruits.sort()>>> fruits['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']>>> fruits.pop()'pear'classmates.append('Adam')classmates.insert(1, 'Jack')>>> classmates.pop()'Adam'>>> classmates.pop(1)'Jack'# del语句,通过角标删除元素>>> a = [-1, 1, 66.25, 333, 333, 1234.5]>>> del a[0]>>> a[1, 66.25, 333, 333, 1234.5]# 删除部分>>> del a[2:4]>>> a[1, 66.25, 1234.5]# 清空>>> del a[:]>>> a[]# del还可以直接删除整个list变量(不是清空)>>> del a#tuple转换为listaTuple = (123, 'xyz', 'zara', 'abc');aList = list(aTuple) Nested List ComprehensionsThe initial expression in a list comprehension can be any arbitrary expression, including another list comprehension.Consider the following example of a 3x4 matrix implemented as a list of 3 lists of length 4: 12345matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12],] The following list comprehension will transpose rows and columns:12>>> [[row[i] for row in matrix] for i in range(4)][[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] As we saw in the previous section, the nested listcomp is evaluated in the context of the for that follows it, so this example is equivalent to:123456>>> transposed = []>>> for i in range(4):... transposed.append([row[i] for row in matrix])...>>> transposed[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] In the real world, you should prefer built-in functions to complex flow statements. The zip() function would do a great job for this use case:12>>> list(zip(*matrix))[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)] Tupletuple是一个不可变的有序表,tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:classmates = ('Michael', 'Bob', 'Tracy')现在,classmates这个tuple不能变了,自然也就没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。Tuples may be constructed in a number of ways: Using a pair of parentheses to denote the empty tuple: () Using a trailing comma for a singleton tuple: a, or (a,) Separating items with commas: a, b, c or (a, b, c) Using the tuple() built-in: tuple() or tuple(iterable) 不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。 Though tuples may seem similar to lists, they are often used in different situations and for different purposes. Tuples are immutable, and usually contain a heterogeneous sequence of elements that are accessed via unpacking (see later in this section) or indexing (or even by attribute in the case of namedtuples). Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.The statement t = 12345, 54321, 'hello!' is an example of tuple packing: the values 12345, 54321 and 'hello!' are packed together in a tuple. The reverse operation is also possible:1>>> x, y, z = t This is called, appropriately enough, sequence unpacking and works for any sequence on the right-hand side. Sequence unpacking requires that there are as many variables on the left side of the equals sign as there are elements in the sequence. Note that multiple assignment is really just a combination of tuple packing and sequence unpacking.注意: 1234# 定义一个空的tuple>>> t = ()# 定义一个只有一个元素的tuple, 需要加逗号>>> t = (4,) # 这里可以的括号可以省略即t = 4, t = (1)定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义。 当tuple中嵌套list时,tuple是对list的指向不变,但list可变,。 Dictionary和SetDictdict全称dictionary,类似于java中的map,使用键-值(key-value)存储(key要求唯一性),具有极快的查找速度。示例:123456789101112131415161718>>> tel = {'jack': 4098, 'sape': 4139}>>> tel['guido'] = 4127>>> tel{'sape': 4139, 'guido': 4127, 'jack': 4098}>>> tel['jack']4098>>> del tel['sape']>>> tel['irv'] = 4127>>> tel{'guido': 4127, 'irv': 4127, 'jack': 4098}>>> list(tel.keys())['irv', 'guido', 'jack']>>> sorted(tel.keys())['guido', 'irv', 'jack']>>> 'guido' in telTrue>>> 'jack' not in telFalse 使用dict()构造器、comprehension、直接赋值法来创建dict。123456789# The dict() constructor builds dictionaries directly from sequences of key-value pairs:>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]){'sape': 4139, 'jack': 4098, 'guido': 4127}# dict comprehensions>>> {x: x**2 for x in (2, 4, 6)}{2: 4, 4: 16, 6: 36}# When the keys are simple strings, it is sometimes easier to specify pairs using keyword arguments:>>> dict(sape=4139, guido=4127, jack=4098){'sape': 4139, 'jack': 4098, 'guido': 4127} 方法:get()pop() 删除指定元素12345678910d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}# 若key不存在,则返回None。# 注意:返回None的时候,Python的交互式命令行不显示结果。d.get('Tom')# 指定默认值。如果key不存在自己指定的value>>> d.get('Tom', -1)-1# pop(key),删除key-value,返回被删除的value>>> d.pop('Bob')75 When looping through dictionaries, the key and corresponding value can be retrieved at the same time using the items() method. 123456>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}>>> for k, v in knights.items():... print(k, v)...gallahad the purerobin the brave 请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。和list比较,dict有以下几个特点: 1. 查找和插入的速度极快,不会随着key的增加而变慢; 2. 需要占用大量的内存,内存浪费多。 而list相反: 1. 查找和插入的时间随着元素的增加而增加; 2. 占用空间小,浪费内存很少。 所以,dict是用空间来换取时间的一种方法。 Dictionary的迭代取出因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。1234567891011121314>>> d = {'a': 1, 'b': 2, 'c': 3}>>> for key in d:... print(key)...acb# 字符串也能迭代>>> for ch in 'ABC':... print(ch)...ABC 当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。 那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:123456>>> isinstance('abc', Iterable) # str是否可迭代True>>> isinstance([1,2,3], Iterable) # list是否可迭代True>>> isinstance(123, Iterable) # 整数是否可迭代False Setset是无序不重复的集合(A set is an unordered collection with no duplicate elements.)。可以这样理解:set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。 由于官方文档写得实在是太好,再加上想练习英语,所以我打算直接贴英文原文了。Curly braces or the set() function can be used to create sets. Note: to create an empty set you have to use set(), not {}; the latter creates an empty dictionary. Here is a brief demonstration: 123456789101112131415161718192021>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}>>> print(basket) # show that duplicates have been removed{'orange', 'banana', 'pear', 'apple'}>>> 'orange' in basket # fast membership testingTrue>>> 'crabgrass' in basketFalse# Demonstrate set operations on unique letters from two words>>> a = set('abracadabra')>>> b = set('alacazam')>>> a # unique letters in a{'a', 'r', 'b', 'c', 'd'}>>> a - b # letters in a but not in b{'r', 'd', 'b'}>>> a | b # letters in a or b or both{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}>>> a & b # letters in both a and b{'a', 'c'}>>> a ^ b # letters in a or b but not both{'r', 'd', 'b', 'm', 'z', 'l'} Similarly to list comprehensions, set comprehensions are also supported: 123>>> a = {x for x in 'abracadabra' if x not in 'abc'}>>> a{'r', 'd'} Looping TechniquesWhen looping through dictionaries, the key and corresponding value can be retrieved at the same time using the items() method. 123456>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}>>> for k, v in knights.items():... print(k, v)...gallahad the purerobin the brave When looping through a sequence, the position index and corresponding value can be retrieved at the same time using the enumerate() function. 123456>>> for i, v in enumerate(['tic', 'tac', 'toe']):... print(i, v)...0 tic1 tac2 toe To loop over two or more sequences at the same time, the entries can be paired with the zip() function.12345678>>> questions = ['name', 'quest', 'favorite color']>>> answers = ['lancelot', 'the holy grail', 'blue']>>> for q, a in zip(questions, answers):... print('What is your {0}? It is {1}.'.format(q, a))...What is your name? It is lancelot.What is your quest? It is the holy grail.What is your favorite color? It is blue. To loop over a sequence in reverse, first specify the sequence in a forward direction and then call the reversed() function.12345678>>> for i in reversed(range(1, 10, 2)):... print(i)...97531 To loop over a sequence in sorted order, use the sorted() function which returns a new sorted list while leaving the source unaltered.123456>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']>>> a = sorted(set(basket))>>> print(a)['apple', 'banana', 'orange', 'pear'] # 注意sort还有去重的效果>>> print(basket)['apple', 'orange', 'apple', 'pear', 'orange', 'banana'] It is sometimes tempting to change a list while you are looping over it; however, it is often simpler and safer to create a new list instead.123456789>>> import math>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]>>> filtered_data = []>>> for value in raw_data:... if not math.isnan(value):... filtered_data.append(value)...>>> filtered_data[56.2, 51.7, 55.3, 52.5, 47.8] 或者参见Python笔记(三)– for else还有这种操作中的临时复制法。 扩展阅读Comparing Sequences and Other Types Sequence objects may be compared to other objects with the same sequence type. The comparison uses lexicographical ordering: first the first two items are compared, and if they differ this determines the outcome of the comparison; if they are equal, the next two items are compared, and so on, until either sequence is exhausted. If two items to be compared are themselves sequences of the same type, the lexicographical comparison is carried out recursively. If all items of two sequences compare equal, the sequences are considered equal. If one sequence is an initial sub-sequence of the other, the shorter sequence is the smaller (lesser) one. Lexicographical ordering for strings uses the Unicode code point number to order individual characters. Some examples of comparisons between sequences of the same type: 1234567(1, 2, 3) < (1, 2, 4)[1, 2, 3] < [1, 2, 4]'ABC' < 'C' < 'Pascal' < 'Python'(1, 2, 3, 4) < (1, 2, 4)(1, 2) < (1, 2, -1)(1, 2, 3) == (1.0, 2.0, 3.0)(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4) Note that comparing objects of different types with < or > is legal provided that the objects have appropriate comparison methods. For example, mixed numeric types are compared according to their numeric value, so 0 equals 0.0, etc. Otherwise, rather than providing an arbitrary ordering, the interpreter will raise a TypeError exception. 参考资料 官方文档–Data Structures 莫烦Python 廖雪峰的Python教程]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python笔记(一)--数据类型]]></title>
<url>%2F2017%2F06%2F22%2Fpython%E7%AC%94%E8%AE%B01%2F</url>
<content type="text"><![CDATA[有的人用“Python还用学么?”来形容Python,看来Python实在是一门简单易学的语言,特别是在你已经掌握一门高级语言之后。但是千万不能因此就真的“不学”,完整的理论知识是你之后“敲键盘如有神”的基本条件。这里罗列一些我没见过的小知识点和它与java等语言的不同之处。仅作为个人笔记,不供参考。 浮点数浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如,1.23x109和12.3x108是相等的。浮点数可以用数学写法,如1.23,3.14,-9.01,等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把10用e替代,1.23x109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5,等等。 整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。顺便一提,与java不同,9/4输出结果为2.5,9//4输出结果为2。 字符串–转义与换行如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r’’表示’’内部的字符串默认不转义。12>>> print(r'换行/n还是不换行,这是一个问题')换行/n还是不换行,这是一个问题 如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用’’’…’’’的格式表示多行内容.在终端repl里这样用,IDE里就用不着了。 Boolean0和0.0表示False其余为True布尔值不外乎True和False,但Python中整数和浮点数也能进行 Boolean 数据操作, 具体规则,如果该值等于 0 或者 0.0 将会返回 False,其余的返回 True。1234con = 10while con: print(con) con -= 1 逻辑运算符and or not布尔值可以用and、or和not运算,而非 & | ! 。如果你测试发现它们也能用的话,那只是巧合,因为& | !是位运算符,比如1 > 2 & 1事实上是1 > (2 & 1)结果为false, 它是按照位运算之后的结果。如果是逻辑运算1 > 2 and 1,结果应该是true。 Python中还支持x < y < z(PS:还有这种操作!),等同于x < y and y < z,虽然有人认为第一种操作可能会让其他语言的程序猿看不懂,但是PyCharm却会在第二种格式中提示你将其转为第一种格式,因为更简洁,一贯的JetBrains智能处女座风格。 空值空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。 变量变量在程序中就是用一个变量名表示了,可以是任意数据类型,变量名必须是大小写英文、数字和_的组合,且不能用数字开头, 常量所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量,如PI = 3.14159265359但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。 格式化在Python中,采用的格式化方式和C语言是一致的,用%实现,如'Hello, %s' % 'world'。常见的占位符有:%d 整数%f 浮点数%s 字符串%x 十六进制整数其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数1234>>> '%2d-%02d' % (3,1)' 3-01'>>> '%d-%.2f' % (3,1.5647)'3-1.56' 参考文章 莫烦Python 廖雪峰的Python教程]]></content>
<categories>
<category>Python</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[挣脱你的轨道--一则公益小短片《cogs》]]></title>
<url>%2F2017%2F06%2F11%2FEnglish-cogs%2F</url>
<content type="text"><![CDATA[var dplayer0 = new DPlayer({"element":document.getElementById("dplayer0"),"autoplay":false,"theme":"#FADFA3","loop":true,"video":{"url":"http://ugcydzd.qq.com/flv/92/216/g0512cgb51w.p712.1.mp4?sdtfrom=v1010&guid=72ffbc53bc13455246dcec4efd2c2b02&vkey=0CE5FC72FB6FA6D97FD1077E0449F0AB0ADDF71FFE82014D6FA31F80237EA5C3A40C048E207507FA283E0EB1C1C3E10188B0D7CAD66E072FF8AB6BBC2D2E8E9E34631C122081535D7168D0D2723548E25E94E04EC20FD1A10848CDB66FBE45E35E6F7D1D0C3C0520AFA5331498386C8D","pic":"/media/14972328758364.jpg"}}); Here is a related report: AIME crafts beautiful animated film for global launchThe Australian charity has taken its ambitions to help underprivileged children to the world with a new film by M&C Saatchi Sydney and Zeilt Productions. With over a thousand shares on Facebook within a few hours of its launch, a new animated film for AIME appears to have already struck a chord. The overwhelmingly positive reviews—“So beautiful and inspiring”, “ profound and beautifully created”—will be a boon for the charity’s founder, 31-year-old Australian Jack Manning Bancroft, as he prepares to take his education charity international for the first time. Creative partners M&C Saatchi Sydney and director Laurent Witz (winner of a 2013 Academy Award for the animated short film Mr Hublot) and his team at Zeilt Productions will also be celebrating the release of a project that has been over a year in development. ‘Cogs’ tells the story of two boys who find themselves, quite literally, on separate, pre-determined tracks in their lives, and the drama hinges on their effort to break free from those imposed limitations. The tagline, “If we want to change the world, we need to change the way it works”, reflects the goal Manning Bancroft has aimed for since establishing AIME 12 years ago to end education inequality in Australia. On average, 61.5 percent of indigenous children in Australia finish school, with only 42 percent going onto further education. AIME’s programme pairs university student volunteers with underprivileged school children, and the numbers of those completing school and going on to university leap to 87.9 percent and 74 percent as a result. With the launch of Cogs, says Manning Bancroft, AIME’s battle against “the broken system” that breeds inequality is heading overseas. “We are calling out to find 10 young people around the world who want to take our model of mentoring to their country to fight inequality and create a fairer world.” AIME has already had enquiries from Nigeria, France, the USA, Canada, New Zealand, Uganda and more, he says: 33 young people have started their applications in the last 24 hours since the film’s release. “AIME is addictively positive,” says Andy Flemming, group creative director, M&C Saatchi, Sydney, who describes his work on the film as a “dream job”. “They’re run by an incredible dreamer / thinker / storyteller who has an unwavering mission to change the world for the better. It’s impossible not to be swept away by their boundless enthusiasm, which is backed up by results that show that their system works. We live in a particularly shitty world, so you just can’t pass up the chance to maybe, just maybe help them do something about it.” The specific theme for the film came from the need, says Flemming, to find “a wonderfully simple idea to introduce AIME to the world.” “The team was playing with the idea of a system that’s fractured and broken. That evolved into people not realising that their lives are on set tracks, and this is precisely how society works—it needs that separation. We then imagined a machine-like city that’s incredibly old and the whole thing just clicked together. It was emotional.” Once the agency engaged with Zeilt Productions, Witz had between 20 and 30 people working on the film for many months, with a continuous team engaged from the agency side too. “All of us had a singular vision and every month the work just got better and better,” says Flemming. “Obviously the time helped. We crafted the hell out of this, and it’s not often we get to pour so much love into advertising projects these days.” As the message in Cogs takes flight and online shares start to gather speed, Manning Bancroft says he hopes the film will mark the incremental rise of mentors around the world, with all the subsequent benefits this could have for improving lives. “Success will be when we see every university student in the world being mentors for their most disadvantaged high school kids,” he says. vocabularyanimated /ˈænɪˌmeɪtɪd/ ADJ Someone who is animated or who is having an animated conversation is lively and is showing their feelings. 热烈的 ADJ An animated film is one in which puppets or drawings appear to move. 动画的 underprivileged /ˌʌndəˈprɪvɪlɪdʒd/ ADJ Underprivileged people have less money and fewer possessions and opportunities than other people in their society. 贫穷的; 缺少机遇的 strike a chord 打动(某人的)心弦,在(某人心中)引起共鸣. 引起共鸣,触动心弦 overwhelmingly adv. 压倒性地;不可抵抗地 profound /prəˈfaʊnd/ ADJ You use profound to emphasize that something is very great or intense. 深刻的; 极大的例:…discoveries which had a profound effect on many areas of medicine.…对医学的许多领域都有深刻影响的一些发现。例:…profound disagreement.…极大的分歧。 ADJ A profound idea, work, or person shows great intellectual depth and understanding. 高深的 ADV 深刻地; 极大地 例:This has profoundly affected my life. 这已极大地影响了我的生活。 boon /buːn/ N-COUNT You can describe something as a boon when it makes life better or easier for someone. 福音 例:It is for this reason that television proves such a boon to so many people. 正是这个原因电视机成为这么多人的一大福音。 ADJ close, special, or intimate (in the phrase boon companion) 亲密的; 特别的 animated short film 动画短片 cog /kɒɡ/ N-COUNT A cog is a wheel with square or triangular teeth around the edge, which is used in a machine to turn another wheel or part. 齿轮 PHRASE If you describe someone as a cog in a machine or wheel, you mean that they are a small part of a large organization or group. (大型机构或组织中的)小人物 例:Mr. Lake was an important cog in the republican campaign machine. 雷克先生是共和党竞选机器中一个小人物,但却发挥了重要作用。 N-COUNT a tenon that projects from the end of a timber beam for fitting into a mortise (木工)凸榫 literally /ˈlɪtərəlɪ/ ADV You can use literally to emphasize an exaggeration. Some careful speakers of English think that this use is incorrect. 真地 例:We’ve got to get the economy under control or it will literally eat us up. 我们必须控制住经济,否则它真地就会把我们困住。 ADV You use literally to emphasize that what you are saying is true, even though it seems exaggerated or surprising. 确实地 例:Putting on an opera is a tremendous enterprise involving literally hundreds of people. 上演一台话剧是一项巨大的事业,它确实要几百个人参与。 ADV If a word or expression is translated literally, its most simple or basic meaning is translated. 字面上地 例:The word “volk” translates literally as “folk.” “”这个词照字面意思翻译为“”。 predetermined /ˌpriːdɪˈtɜːmɪnd/ ADJ If you say that something is predetermined, you mean that its form or nature was decided by previous events or by people rather than by chance. 预先确定的 例:The prince’s destiny was predetermined from the moment of his birth. 该王子的命运从出生那一刻起就已经被决定了。 例:The capsules can be made to release the pesticides at a predetermined time. 可使这些胶囊在预定时间释放出杀虫剂. track [铁路] 轨道(track的复数);[计] 磁道;轮胎 impose /ɪmˈpəʊz/ V-T If you impose something on people, you use your authority to force them to accept it. 强制实行 N-UNCOUNT 强制实行 例:…the imposition of sanctions against Pakistan. …对巴基斯坦制裁的强制实行。 V-T If you impose your opinions or beliefs on other people, you try and make people accept them as a rule or as a model to copy. 把 (观点、信仰等) 强加于 例:Parents should beware of imposing their own tastes on their children. 父母应该提防把自己的兴趣强加给孩子。 V-T If something imposes strain, pressure, or suffering on someone, it causes them to experience it. 使承受 (令人不快之事物) 例:The filming imposed an additional strain on her. 影片拍摄使她承受了额外的压力。 V-I If someone imposes on you, they unreasonably expect you to do something for them which you do not want to do. 不合理地要求例:I was afraid you’d feel we were imposing on you. 我担心你会觉得我们在不合理地要求你N-COUNT 不合理的要求 例:I know this is an imposition. But please hear me out. 我知道这是个不合理的要求,但请听我把话说完。 V-T If someone imposes themselves on you, they force you to accept their company although you may not want to. 使强迫接受 tagline n. 标语;品牌口号 indigenous [ɪn’dɪdʒənəs] adj. 本土的;土著的;国产的;固有的 breed /briːd/ N-COUNT A breed of a pet animal or farm animal is a particular type of it. For example, terriers are a breed of dog. (动物的) 品种 例:…rare breeds of cattle. …稀有牛种。 养殖 breed animals or plant 繁殖 When animals breed, they have babies. V-T If you say that something breeds bad feeling or bad behaviour, you mean that it causes bad feeling or bad behaviour to develop. 酿成 (不良情绪或不良行为)例:If they are unemployed it’s bound to breed resentment.如果他们失业了,一定会酿成怨恨。 storyteller /ˈstɔːrɪˌtɛlə/ n. 说故事的人;故事作者;短篇小说作家 unwavering /ʌnˈweɪvərɪŋ/ ADJ If you describe a feeling or attitude as unwavering, you mean that it is strong and firm and does not weaken. (情感、态度)强烈的; 坚定的 例:She has been encouraged by the unwavering support of her family. 她家人坚定地支持鼓舞着她。 boundless /ˈbaʊndlɪs/ ADJ If you describe something as boundless, you mean that there seems to be no end or limit to it. 无限的 例:His reforming zeal was boundless. 他的改革热情是无穷尽的。 shitty /ˈʃɪtɪ/ ADJ If someone describes something as shitty, they do not like it or they think that it is of poor quality. adj. 较差的;劣等的,狗屎的 fractured [‘fræktʃəd]adj. 断裂的;挫伤的;折裂的 v. 断裂(fracture的过去式) evolve /ɪˈvɒlv/ V-I When animals or plants evolve, they gradually change and develop into different forms. 进化 例:Birds are widely believed to have evolved from dinosaurs. 鸟类普遍被认为是从恐龙进化而来的。 V-T/V-I If something evolves or you evolve it, it gradually develops over a period of time into something different and usually more advanced. 使…逐步发展; 逐步发展 例:…a tiny airline which eventually evolved into Pakistan International Airlines. …最终发展成为巴基斯坦国际航空公司的一家小型航空公司。 incremental /ˌɪnkrɪˈmɛntəl/ ADJ Incremental is used to describe something that increases in value or worth, often by a regular amount. 递增的 原文链接AIME crafts beautiful animated film for global launch]]></content>
<categories>
<category>English Learning</category>
</categories>
<tags>
<tag>English</tag>
<tag>cogs</tag>
<tag>animated short film</tag>
</tags>
</entry>
<entry>
<title><![CDATA[那些你常用但是却不知道如何用英文表达的句子]]></title>
<url>%2F2017%2F06%2F06%2FEnglish-%E5%B8%B8%E7%94%A8%E5%8F%A5%2F</url>
<content type="text"><![CDATA[掌握另外一种语言(外语),真正的难点在于掌握它与已习得语言(母语)的不重合之处。直观的一一对应,其实没什么难度:“那是一本书”对应着“That’s a book”。而那些你常用到的句子或短语,翻译成英文,却一听就知道是中式英文。“若果我没记错的话…” 用英语怎么说?是 If I didn’t remembered wrong…吗?是英语里就没有这样的表达,还是我们不知道它的地道表达方式呢? 而所谓的“不地道”,其实不过用母语的表达习惯去说外语。比如: • 如果我没记错的话…… • 我从未想过…… • 你竟然跟我这么说话! • 就知道你有这本事! • (这)听着耳熟吧? • 我想不起来那名字了……这些句子,基本上都是我们日常生活中必然用到的句子(或片段),可是,如果“直译”的话,就很别扭: • If I didn’t remembered wrong… • I never thought of/that… • How dare you talking to me like this! • I know you have such a capability! • Does it sound familiar? • I cannot remember that name…同样的语境里,“地道”的说法是这样的: • If my memory serves, … • It never occurred to me that… • Are we really having this conversation? • Always knew you had it in you! • Does it ring a bell to you? • That name escapes me….看完这几个地道的翻译后,是不是有种一拍大腿,醍醐灌顶之快感。 无意中看到李笑来的 《人人都能用英语》,方法论类的书不需要多读,这些书在我看来在我看来其实挺不靠谱,要学英语就去学英语啊,看英语的学习资料啊,但是,闲来无事时选一两本还是就可以一读的,或许会有耳目一新的论见颠覆你的一些固有思维,或者称之为常识的东西,不一定让你的学习事半功倍,但至少开拓了你的思维,也舒缓下学习带来的的紧绷状态。 参考资料: 知笔墨 –《人人都能用英语 – 李笑来》]]></content>
<categories>
<category>English Learning</category>
</categories>
<tags>
<tag>English</tag>
<tag>英语学习</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Aaron Ralston’s Story]]></title>
<url>%2F2017%2F06%2F05%2FEnglish-Aron's%20story%2F</url>
<content type="text"><![CDATA[Aaron Ralston, a 27-year-old mountain sports fanatic from Colorado in the United States, found himself in dire straits alone in a canyon in the desert when a 500kg rock came crashing down the canyon to smash his right hand and trap it against the canyon wall. A terrible accident, but the situation was made all the more serious because on this occasion Aaron had failed to tell anyone where he was going. At the last minute the plans for a trip with his climbing partners had fallen through, and on the spur of the moment he decided to head out on his own to cycle up a long mountain trail, leave his bike and then walk down the Blue John canyon. No one had the slightest idea where he was. After three days of not seeing or hearing any sign of life Aaron realised he would die there if he didn’t do something drastic. The course of action was horrific, but there was no other way. He would have to amputate his right hand. Fortunately he had a small multitool knife with him and he had some straps that he could use to make a tourniquet to stop himself bleeding to death when he cut the arteries. The knife had two blades. When he tried with the larger blade he found that it was too blunt to cut the skin. The following day he found the courage to try the shorter blade, and with that he managed to cut through the skin. Only when he had made a large hole in his arm did he realise that it was going to be impossible to use any of the little tools on his knife to cut through the bones. After another 24 hours of pain and despair the idea and the strength came to him in a flash on the sixth day. With a final burst of energy he broke both bones in his arm and freed himself. The ordeal was not over, though. He was still a long way from help. He had to carefully strap up his right arm and then find a way of lowering himself down a 20m drop in the canyon with a rope and only his left arm, and then walk the 10 km back to his car. Despite his ingenuity* and all his efforts he would have bled to death if it hadn’t been for a very happy coincidence: the moment he got out of the canyon into the open desert the rescue helicopter just happened to be flying overhead. One of the doctors at the hospital recalls being impressed to see Ralston walk into the hospital on his own, in spite of his injuries and the gruelling experience of being in the desert for six days with almost nothing to eat and only a couple of litres of water. He describes the amputation as remarkable. “It’s a perfect example of someone improvising in a dire situation,” he said. “He took a small knife and was able to amputate his arm in such a way that he did not bleed to death.”Slim and pale with short reddish-brown hair, Ralston believes that his story was not simply about an isolated individual who rose to a formidable challenge. For him there was a spiritual dimension to the experience. In his news conference he said, “I may never fully understand the spiritual aspects of what I experienced, but I will try. The source of the power I felt was the thoughts and prayers of many people, most of whom I will never know.” Vocabularydire /‘daɪə/ ADJ Dire is used to emphasize how serious or terrible a situation or event is. 严重的; 可怕的 ADJ If you describe something as dire, you are emphasizing that it is of very low quality. 质量低劣的 strait /streɪt/ N-COUNT/N-IN-NAMES You can refer to a narrow strip of sea which joins two large areas of sea as a strait or the straits. 海峡 N-PLURAL If someone is in dire or desperate straits, they are in a very difficult situation, usually because they do not have much money. (常指缺钱造成的) 困境 canyon /ˈkænjən/ N-COUNT/N-IN-NAMES A canyon is a long, narrow valley with very steep sides. 峡谷 smash /smæʃ/ V-T/V-I If you smash something or if it smashes, it breaks into many pieces, for example, when it is hit or dropped. 打碎; 破碎 V-T/V-I If you smash through a wall, gate, or door, you get through it by hitting and breaking it. 撞破 (墙或门) 而入 V-T/V-I If something smashes or is smashed against something solid, it moves very fast and with great force against it. 使猛撞; 撞击 V-T To smash a political group or system means to deliberately destroy it. 搞垮 (政治集团或体制) fall through PHRASAL VERB If an arrangement, plan, or deal falls through, it fails to happen. 落空 slight /slaɪt/ ADJ Something that is slight is very small in degree or quantity. 轻微的; 细微的 ADJ A slight person has a fairly thin and delicate looking body. 瘦小的; 纤细的 V-T If you are slighted, someone does or says something that insults you by treating you as if your views or feelings are not important. 轻蔑; 怠慢 N-COUNT Slight is also a noun. 轻视; 冷落 PHRASE You use in the slightest to emphasize a negative statement. (用于加强否定的陈述语气) 一点也 drastic /ˈdræstɪk/ ADJ If you have to take drastic action in order to solve a problem, you have to do something extreme to solve it. 极端的 ADJ A drastic change is a very great change. 剧烈的 horrific /hɒˈrɪfɪk, hə-/ ADJ If you describe a physical attack, accident, or injury as horrific, you mean that it is very bad, so that people are shocked when they see it or think about it. 极其可怕的 例: I have never seen such horrific injuries. 我从没见过这么严重的伤。 ADV 极其可怕地 例:He had been horrifically assaulted before he died. 他死之前曾遭人毒打。 ADJ If you describe something as horrific, you mean that it is so big that it is extremely unpleasant. 大得骇人的 例:…piling up horrific extra amounts of money on top of your original debt. …在你原有债务的基础上再加上数目骇人的几大笔钱。 ADV 大得骇人地 例:Opera productions are horrifically expensive. 歌剧的演出花销大得吓人。 amputate /ˈæmpjʊˌteɪt/1 V-T To amputate someone's arm or leg means to cut all or part of it off in an operation because it is diseased or badly damaged. 截 (肢) 2 N-VAR 截肢 amputation. strap /stræp/1 N-COUNT A strap is a narrow piece of leather, cloth, or other material. Straps are used to carry things, fasten things together, or to hold a piece of clothing in place. 带子 例:Nancy gripped the strap of her beach bag. 南希抓住自己海滩休闲包的带子。 例:She pulled the strap of her nightgown onto her shoulder. 她把睡衣的带子拉到她的肩上。 2 V-T If you strap something somewhere, you fasten it there with a strap. 用带子绑 例: She strapped the baby seat into the car. 她把婴儿座椅用带子绑在那辆汽车上。 参考资料1.2017 JUNE 3RD 阅读文章]]></content>
<categories>
<category>English Learning</category>
</categories>
<tags>
<tag>test</tag>
<tag>English</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java基本数据类型转换原理以及数据溢出处理]]></title>
<url>%2F2017%2F03%2F24%2Fjava%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%2F</url>
<content type="text"><![CDATA[朋友发来的一个小问题,让我又把java底层数据原理深入学习了一遍。 Java中x=x+1 与x+=1 的一点区别许多程序员都会认为这里的表达式(s1 +=1)只是上面表达式(s1 = s1 + 1)的简写方式,至少以前我是这样认为的。但是这并不十分准确。 Java语言规范中讲到,复合赋值E1 op= E2等价于简单赋值E1 = (T)((E1)op(E2) 其中T是E1的数据类型,op为操作符。这种类型转换或者是一个恒等转换,或者是一个窄化转换. 1234567891011121314151617181920212223public class PlusEqualTest { public static void main(String[] args) { short s1 = 1; s1 = (short) (s1 + 1);//如果不加转换是不能通过编译的 System.out.println("s1 = " + s1); short s2 = 1; s2 += 1; System.out.println("s2 = " + s2); short s3 = Short.MAX_VALUE;//32767 short s4 = s3; s3 += 1; System.out.println("s3 += 1 结果为" + s3); s4 = (short) (s4 + 1); System.out.println("s3 = (short)(s3 + 1) 结果为" + s4); }}//输出结果如下:s1 = 2s2 = 2s3 += 1 结果为-32768s3 = (short)(s3 + 1) 结果为-32768 数据溢出咋办呢如上程序,为什么s3 = (short)(s3 + 1) 结果为-32768呢?很明显是数据超出其表示范围了,可是数据溢出后是怎么处理的呢?此事说来话长…… 数据类型基础知识储备 基本类型 位数 范围 默认值 byte(字节) 8 -128 至 127 0 shot(短整型) 16 -32768(-2^15 ) 至 32767(2^15-1 ) 0 int(整型) 32 -2147483648(-2^31 )至2147483647(2^31-1 ) 0 long(长整型) 64 -2^63 至2^63-1 0L或0l boolean 1 0,1或true,false false char 8 \u0000至\uffff \u0000\ float 32 1.4E-45至3.4028235E38 0.0f double 64 4.9E-324至1.7976931348623157E308 0.0d 类型转换的原理 真值:这个就看字面意思,int a = -1;a的真值就是-1. 原码:int类型的-1的二进制表示,由于-1是负数,又是int类型的,所以他需要32个二进制来表示,二进制的最高位是符号位,所以为1,1的二进制为1,所以-1的二进制表示为1000 0000 0000 0000 0000 0000 0000 0001; 反码:正数的反码就是原码,负数的反码是在原码的基础上,符号位不变,其余位取反。所以int类型的-1的反码是1111 1111 1111 1111 1111 1111 1111 1110。 补码:正数的补码就是原码,负数的补码是在其反码的基础上加1。所以,int类型的-1的补码是 1111 1111 1111 1111 1111 1111 1111 1111。 补位:补位是二进制中在扩充位数的时候,位数不够需要在左边补齐,补齐的方式为如果是正数的补位,左边全部补0,负数左边全部补1(也就是说补位的时候补足的是符号位)。 自动转换(从高位向低位转换):将a位的变量A转换为b位的B:A–>补码–>截取(去掉前a-b位,只保留后b位)–>补码–>B。示例:1int a=-1;byte b=(byte)a;//b的结果是-1。 1000 0000 0000 0000 0000 0000 0000 0001补码1111 1111 1111 1111 1111 1111 1111 1111截取(保留后8位)1111 1111 1111 1111 1111 1111 1111 1111即1111 1111补码1000 0001即真值为-1 强制转换(从低位向高位转换时):(byte->short->int->long->float->double)补码–>补位–>补码示例:1byte a =-1;int b=a;//b的结果是-1。 1000 0001补码1111 1111补位1111 1111 1111 1111 1111 1111 1111 1111补码1000 0000 0000 0000 0000 0000 0000 0001即真值为-1 数据溢出处理示例1:1int a=255;byte b=(byte)a; //b的值为什么是-1? 0000 0000 0000 0000 0000 0000 1111 1111补码0000 0000 0000 0000 0000 0000 1111 1111截取1111 1111补码1000 0001即真值为-1示例2:大家在自己算一下(byte)234的结果是什么,然后在IDE里在运行下看和自己手写算出来的一样吗。这里(byte)234的手写结果是150,但是150是超出了byte的表示范围的,所以这里还有一次转化???后面是怎么转化的呢? 定点数与浮点数区别定点数在计算机系统的发展过程中,曾经提出过多种方法表达实数。典型的比如相对于浮点数的定点数(Fixed Point Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。还有一种提议的表达方式为有理数表达方式,即用两个整数的比值来表达实数。定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。 浮点数(float-point-number)最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 10^2 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。 NOTE: 尾数有时也称为有效数字(Significand)。尾数实际上是有效数字的非正式说法。 同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 10^1 ,0.12345 × 10^3 或者 1.2345 × 10^2 。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式: $±d_0.d_1d_2…d_i × β^e , (0 ≤ d_i < β)$ 其中 d.dd…d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p 来表示。每个数字 d 介于 0 和基数之间,包括 0。小数点左侧的数字不为 0。 基于规范表达的浮点数对应的具体值可由下面的表达式计算而得:$±(d_0 + d1β^{e-1} + … + d{i-1}β ^ {e-(i-1)}+ d_iβ^{e-i} ), (0 ≤ d_i < β)$对于十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解,也很直白。计算机内部的数值表达是基于二进制的。从上面的表达式,我们可以知道,二进制数同样可以有小数点,也同样具有类似于十进制的表达方式。只是此时 β 等于 2,而每个数字$d_i$ 只能在 0 和 1 之间取值。比如二进制数 1001.101 相当于 1 × 2^3 + 0 × 2^2 + 0 × 2^1 + 1 × 2^0 + 1 × 2^-1 + 0 × 2^-2 + 1 × 2^-3 ,对应于十进制的 9.625。其规范浮点数表达为 1.001101 × 2^3。 IEEE 浮点数计算机中是用有限的连续字节保存浮点数的。保存这些浮点数当然必须有特定的格式,Java 平台上的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。 float和double内存结构在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。float的指数位有8位,而double的指数位有11位,分布如下: float: 1bit(符号位) 8bits(指数位) 23bits(尾数位) double: 1bit(符号位) 11bits(指数位) 52bits(尾数位) 第二个域为指数域(图中标注为指数位),对应于我们之前介绍的二进制科学计数法中的指数部分。其中单精度数为 8 位,双精度数为 11 位。以单精度数为例,8 位的指数为可以表达 0 到 255 之间的 255 个指数值。但是,指数可以为正数,也可以为负数。为了处理负指数的情况,实际的指数值按要求需要加上一个偏差(Bias)值作为保存在指数域中的值,单精度数的偏差值为 127,而双精度数的偏差值为 1023。比如,单精度的实际指数值 0 在指数域中将保存为 127;而保存在指数域中的 64 则表示实际的指数值 -63。 偏差的引入使得对于单精度数,实际可以表达的指数值的范围就变成 -127 到 128 之间(包含两端)。我们不久还将看到,实际的指数值 -127(保存为 全 0)以及 +128(保存为全 1)保留用作特殊值的处理。这样,实际可以表达的有效指数范围就在 -127 和 127 之间。在本文中,最小指数和最大指数分别用 emin 和 emax 来表达。 于是,float的指数范围为-128~+127,而double的指数范围为-1024~+1023,并且指数位是按补码的形式来划分的。其中负指数决定了浮点数所能表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。float的范围为???-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;double的范围为???-2^1024 ~ +2^1023,也即-1.79E+308(这里与IDEA打印出的最小值不符,博主会慢慢查证再来修改) ~ +1.79E+308。 之所以不能用f1==f2来判断两个数相等,是因为虽然f1和f2在可能是两个不同的数字,但是受到浮点数表示精度的限制,有可能会错误的判断两个数相等 精度float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。 float:2^23 = 8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数: 2*8388608 = 16777216 。有8位有效数字,但绝对能保证的为7位,也即==float的精度为7~8位有效数字==; double:2^52 = 4503599627370496,一共16位,同理,==double的精度为16~17位==。 图例中的第三个域为尾数域,其中单精度数为 23 位长,双精度数为 52 位长。除了我们将要讲到的某些特殊值外,IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为 1,因此我们在保存尾数的时候,可以省略小数点前面这个 1,从而腾出一个二进制位来保存更多的尾数。这样我们实际上用 23 位长的尾数域表达了 24 位的尾数。比如对于单精度数而言,二进制的 1001.101(对应于十进制的 9.625)可以表达为 1.001101 × 23,所以实际保存在尾数域中的值为 00110100000000000000000,即去掉小数点左侧的 1,并用 0 在右侧补齐。 值得注意的是,对于单精度数,由于我们只有 24 位的尾数(其中一位隐藏),所以可以表达的最大尾数为 224 - 1 = 16,777,215。特别的,16,777,216 是偶数,所以我们可以通过将它除以 2 并相应地调整指数来保存这个数,这样 16,777,216 同样可以被精确的保存。相反,数值 16,777,217 则无法被精确的保存。由此,我们可以看到单精度的浮点数可以表达的十进制数值中,真正有效的数字不高于 8 位。事实上,对相对误差的数值分析结果显示有效的精度大约为 7.22 位。参考下面的示例: true value|16,777,215|16,777,216|16,777,217|16,777,218|16,777,219|—|—|—|—|—|—stored value|16,777,215|1.6777215E7|1.6777216E7|1.6777216E7|1.6777218E7 根据标准要求,无法精确保存的值必须向最接近的可保存的值进行舍入。这有点像我们熟悉的十进制的四舍五入,即不足一半则舍,一半以上(包括一半)则进。不过对于二进制浮点数而言,还多一条规矩,就是当需要舍入的值刚好是一半时,不是简单地进,而是在前后两个等距接近的可保存的值中,取其中最后一位有效数字为零者。从上面的示例中可以看出,奇数都被舍入为偶数,且有舍有进。我们可以将这种舍入误差理解为”半位”的误差。所以,为了避免 7.22 对很多人造成的困惑,有些文章经常以 7.5 位来说明单精度浮点数的精度问题。 可以试试在IDE中运行一下代码12345float f1 = 16777215f; for (int i = 0; i < 10; i++) { System.out.println(f1); f1++; } 输出结果除了第一个是1.6777215E7其余全都是1.6777216E7,不是for循环出了问题,而是因为16,777,216在计算机中即1.6777216E7,+1后依旧等于1.6777216E7。 参考文章 Java中x=x+1 与x+=1 的一点区别 java中数据溢出处理 java 类型转换的原理 深入浅出浮点数Floating Point Number In a Nutshell]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>数据类型</tag>
</tags>
</entry>
<entry>
<title><![CDATA[IDEA中使用maven编译Spark源码]]></title>
<url>%2F2017%2F03%2F06%2FsparkSourceCompile%2F</url>
<content type="text"><![CDATA[Spark源码编译及测试。 spark官网下载源码包 修改pom文件修改pom.xml文件中的com.google.guava,将其从provided改为compile。或者编译源码之后,打开project structure,将guava包的scpoe改为compile。如果不改的话,之后运行example包里的示例程序时,会报classNotFoundException。 scope选项中provided和compile的区别对于scope=compile的情况(默认scope),也就是说这个项目在编译,测试,运行阶段都需要这个artifact对应的jar包在classpath中。 而对于scope=provided的情况,则可以认为这个provided是目标容器已经provide这个artifact。换句话说,它只影响到编译,测试阶段。在编译测试阶段,我们需要这个artifact对应的jar包在classpath中,而在运行阶段,假定目标的容器(比如我们这里的liferay容器)已经提供了这个jar包,所以无需我们这个artifact对应的jar包了。 做一个实验就可以很容易发现,当我们用maven install生成最终的构件包ProjectABC.war后,在其下的WEB-INF/lib中,会包含我们被标注为scope=compile的构件的jar包,而不会包含我们被标注为scope=provided的构件的jar包。这也避免了此类构件当部署到目标容器后产生包依赖冲突。 添加assembly包在example包的依赖(dependencies)中添加assembly包。spark-assembly-1.6.0-hadoop2.6.0.jar可以在部署包的lib中找到。 解决Flume Sink的依赖问题View->Tool Windows->Maven Projects找到Flume Sink,右键点击Generate Sources and Update Folders。右键项目,选择Open Module Settings,然后找到flume-sink, 先点击target(默认是黄色,也就是和上面的Exclude对应),点击Exclude将其改为正常的目录,再点击Sources将其设为源,再将scala-2.10也source。 编译Build->Build Project,编译时间比较长,耐心等待吧。 测试Run->Edit Configuration。新建Application,然后选择Main class,VM options设置为-Dspark.master=local。点击example包的SparkPi,运行并查看结果。 参考资料 maven dependency中scope=compile 和 provided区别]]></content>
<categories>
<category>Bigdata</category>
<category>Spark</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>Spark</tag>
</tags>
</entry>
<entry>
<title><![CDATA[概率论迷思]]></title>
<url>%2F2016%2F12%2F07%2F%E6%A6%82%E7%8E%87%E8%AE%BA%2F</url>
<content type="text"><![CDATA[当你抛起一枚硬币,你不知道它会是正面还是反面,但你确切的知道正面与反面的概率都是50%。概率论的神奇之处在于,它居然能从不确定性中找到确定性。 本文不教科书,只是阐述我的观点和思考,如有谬误,欢迎讨论或指正。 一些有趣的观点:一个事情有N种发生的可能性,我们不能确信哪种会发生,是因为我们不能控制结果的发生,影响结果的许多因素不在我们的支配范围之内,这些因素影响结果的机理或者我们不知道,或者太复杂以至于超出了我们大脑或电脑的运算能力。比如:我们不确定掷硬币得到正面或反面,是因为我们的能力不足以用一些物理方程来求解这个结果。再比如:你不能断定你期末能考88分,因为出题、阅卷的不是你。 对于未发生之事,我们无法掌握其所有参数或无法计算。对于已经发生之事,事情都已经发生了,结果已定,也会因为掌握的信息不全而产生所谓概率。即过去发生的事情虽然事实上是确定的,但因为我们的无知,它成了随机的。 我们在某个地方挖出了一块瓷器的碎片,它可能是孔子的夜壶,可能是秦始皇的餐具,也可能是隔壁老王的破茶壶从他家到垃圾站又被埋在了这个地方。 因此:概率在实质上就是无知,而不是说事物本身是随机的。 这一点很重要,不要误以为概率应该是客观事实。如果你有上帝视角的话,那么一切都是注定,任何事的概率都是100%,也就没有所谓概率之说了。 所以概率论是建立在人们有限的认知中的,不是真正的客观事实。也就是说当孔子一看,这貌似是自己的夜壶啊,他认为这是夜壶的概率为70%,秦始皇一看那块碎片,朕心中只装的下江山,哪来的餐具,在他看来的餐具的概率是1/3,然而,老王的却早已看穿一切,那块碎片割过他的手所以他记得格外清楚,茶壶概率为100%。每个人所知道的信息决定了他所认知的概率。 就像狼人杀,这里假设游戏是7个人+上帝,1号和2号玩家是狼人,发完牌的时候就已经注定谁是狼人了。对于上帝和1、2号而言,没有概率可言,或者说1、2号是狼人的概率是100%。而对于平民而言,他除了自己,他无法找出理由认为谁是狼人,只好用古典概率的等可能假设,认为其他每个人是狼人的概率都是1/6,随着游戏的进行,预言家掌握更多的信息,他修正了自己的概率,而平民也根据自己掌握的信息修正自己概率,于是大家对于谁是狼人这件事都有了不同的概率。 注意到上面这个故事中,不难发现,假设碎片只有夜壶,餐具,茶壶这三种可能,即一开始概率应该是各1/3。从孔子到老王,他们都用各自掌握的信息修正了关于这个碎片是什么的概率。这就引出了先验概率和后验概率的概念。 先验概率(Prior probability)与后验概率(Posterior probability)事情还没有发生,要求这件事情发生的可能性的大小,是先验概率.事情已经发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,是后验概率. 先看看来自wiki的定义: Similarly, the prior probability of a random event or an uncertain proposition is the unconditional probability that is assigned before any relevant evidence is taken into account. In Bayesian statistics, the posterior probability of a random event or an uncertain proposition is the conditional probability that is assigned after the relevant evidence or background is taken into account. Similarly, the posterior probability distribution is the probability distribution of an unknown quantity, treated as a random variable, conditional on the evidence obtained from an experiment or survey. “Posterior”, in this context, means after taking into account the relevant evidence related to the particular case being examined.要注意的是这是在贝叶斯统计中。不是公理化的概率定义。 再看看书上的解释在此墙裂推荐陈希孺院士的《概率论与数理统计》,这是豆瓣、知乎的书评和推荐。陈老这本书之所以受到如此簇拥,在于它授人以渔而非授人以鱼,你读一读就是知道。 举一个的简单的例子:一口袋里有3只红球、2只白球,采用不放回方式摸取,求:⑴ 第一次摸到红球(记作A)的概率;⑵ 第二次摸到红球(记作B)的概率;⑶ 已知第二次摸到了红球,求第一次摸到的是红球的概率。解:⑴ P(A)=3/5,还没还有摸球,就问概率,这就是验前概率;⑵ P(B)=P(A)P(B|A)+P(A逆)P(B|A逆)=3/5⑶ P(A|B)=P(A)P(B|A)/P(B)=1/2,这就是后验概率,第一次和第二次摸球这件事都已经发生了,但是我们不知道,比如第一次我们是闭着眼摸完又放回去了,便产生了概率之说。第一问事情未发生(或者说发生了,但是相对于未发生得情况,我们并没有掌握任何更多的信息)我们认为概率是3/5,第三问,我们知道了第二次摸到红球这件事,或者说证据,以此来修正这个概率,就像推理小说一样。关于先验概率和后验概率,推荐阅读:数理统计中的两个学派——频率学派和Bayes学派(1990年的期刊,能找到也是不容易) 一个笑话引发的血案:病人:我听说这个手术成功概率为1%,我是不是该放弃治疗?医生:你放心,我敢保证这次手术100%会成功。病人:真的?为什么?医生:因为我已经失败了99次了。 这是很多人都会犯的“常识”错误,也是经常让人迷惑的地方。可能在这个笑话里,大家没什么深刻感受,那换个例子,比如:A已经抛了100次硬币,每次都是正面,那么下一次反面的几率是不是更大?即使是统计学专业的学生也经常迷糊(比如统计学渣的我),我就一直纳闷,按照大数定律(知乎的解释),如果抛硬币的次数足够多,他就应该是正反各1/2的分布啊,A都抛了100次正面了,下一次就该是反面几率更大了啊。可是每次抛硬币应该是相互独立的,也就是说之前抛无数次也不该影响下一次的概率,即1/2。这个问题的争论,请参考先验概率与后验概率的区别(老迷惑了)。 我比较认可比较的解释是其评论中的一段话,当然,前提是你得清楚频率( 千万别把频率直接等同于概率),概率的古典定义和统计定义以及公理化定义。 从网上的资料来看,概率本身有多种解释。每一种解释都有一定的漏洞。如果不是研究概率的根本问题(逻辑一致),我们在自己的研究领域不太可能会遇到这些漏洞。(PS:这里必须要说明的是,概率有多种定义(古典定义和统计定义以及公理化定义),但这不是三国演义,我柯早已一统天下,概率的公理化定义有且只有一个,没有漏洞。当然这里所说的逻辑不一致应该是指数理统计中的频率学派和贝叶斯学派之争) 频率学派和贝叶斯学派都有存在的理由。频率学派的“无穷次实验”,贝叶斯学派的“先验概率”,都是有争议的。楼主的第4问,前提是承认了频率学派的观点,但注意它们的观点有一个无穷次实验的假设。我揣测楼主的观点是,因为已经抛了100次正面了,那么,后面抛反面的次数应该多一点,不然总和就不是1/2了,再根据频率学派的观点,出现的频率多一点,于是概率也就大一点。从理论上来说,任何有限次的正面,都不会影响无穷次实验的结果比率。所以,“后面抛反面的次数应该多一点”这一点是不成立的,即使后面的反面的次数少一些,也不会影响这个比率。常数+无穷大=无穷大 关于概率论,一直有许多搞不懂的问题,迷迷糊糊混过四年。在学习HMM和CRF高楼大厦时,发现地基已碎,一边百度基础概念一边学,更是痛苦万分。在搜寻问题时产生更多的问题,终于在重新读了概率论前几章后,算是豁然开朗了很多,所以打算重读概率论,夯实基础,我应该会开个重读概率论的分类,有很多事要做,就并行处理吧,不知道会不会半途而废,也不知道半途而废的概率是多少(当然,我可以凭经验先给出一个主观先验概率,在以后的过程中再慢慢修正得到后验概率,直到概率为0或1),但是有些疑惑终究会推着我去探寻。 推荐阅读:数学之美番外篇:平凡而又神奇的贝叶斯方法 参考资料 先验概率与后验概率的区别(老迷惑了) 先验概率与后验概率及贝叶斯公式 《概率论与数理统计》–陈希孺 数理统计中的两个学派——频率学派和Bayes学派 《概率论与数理统计》–盛骤,谢式千,潘承毅编.-4版]]></content>
<categories>
<category>Algorithm</category>
<category>重读概率论</category>
</categories>
<tags>
<tag>概率论</tag>
<tag>先验概率</tag>
<tag>后验概率</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hive学习笔记]]></title>
<url>%2F2016%2F11%2F28%2F%20Hive%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[背景架构,原理,搭建,练习及优化 Hive的产生Hive产生的原因:方便非java编程者(熟悉SQL语言)对hdfs的数据做mapreduce操作。Hive是数据仓库数据库:用户与数据库交互,提交SQL语句后,马上见到执行结果;存放业务数据;数据库提出范式的概念是为了解决数据冗余和耦合的问题;数据库给业务数据提供存储支撑。数据仓库:不与用户交互;存放历史数据;反范式设计,专门引入冗余数据,保证数据完整。数据仓库面向分析,里面存放的数据用来做分析和挖掘。Hive将SQL转化为MapReduce可以识别的操作,承担解释器、编译器、优化器等角色Hive运行时,元数据(表的结构、属性)存储在关系型数据库里面。因为元数据信息需要高效的读取。 Hive的架构 用户接口主要有三个:Client(命令行),JDBC/ODBC 和 WEB GUI。其中最常用的是Client,Client启动的时候,会同时启动一个Hive副本。Client是Hive的客户端,用户连接至Hive Server。在启动 Client模式的时候,需要指出Hive Server所在节点,并且在该节点启动Hive Server。 WEB GUI是通过浏览器访问Hive。 Hive将元数据存储在数据库中,如mysql、derby。Hive中的元数据包括表的名字,表的列和分区及其属性,表的属性(是否为外部表等),表的数据所在目录等。解释器、编译器、优化器完成HQL查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在HDFS中,并在随后有MapReduce调用执行。编译器将一个Hive QL转换操作符,操作符是Hive的最小的处理单元每个操作符代表HDFS的一个操作或者一道MapReduce作业。Operator是hive定义的一个处理过程,是一个树形结构:protected List]]></content>
<categories>
<category>Bigdata</category>
<category>Hive</category>
</categories>
<tags>
<tag>Hive</tag>
<tag>Hive原理及其搭建</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HBase学习笔记]]></title>
<url>%2F2016%2F11%2F28%2FHBase%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[Hbase架构,原理,搭建,练习,表设计,写表,读表的优化 Hadoop生态系统 HBase简介HBase -Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩、可实时读写的分布式数据库,利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为其分布式协同服务,主要用来存储非结构化和半结构化的松散数据(列存 NoSQL 数据库)。 列式数据库列式数据库是以列相关存储架构进行数据存储的数据库,主要适合与批量数据处理和即席查询。相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合与小批量的数据处理,常用于联机事务型数据处理。列式数据库以行、列的二维表的形式存储数据,但是却以一维字符串的方式存储,例如以下的一个表:这个简单的表包括员工代码(EmpId), 姓名字段(Lastname and Firstname)及工资(Salary).这个表存储在电脑的内存(RAM)和存储(硬盘)中。虽然内存和硬盘在机制上不同,电脑的操作系统是以同样的方式存储的。数据库必须把这个二维表存储在一系列一维的“字节”中,操作系统把它们写到内存或硬盘中。 行式数据库把一行中的数据值串在一起存储起来,然后再存储下一行的数据,以此类推。1,Smith,Joe,40000;2,Jones,Mary,50000;3,Johnson,Cathy,44000;列式数据库把一列中的数据值串在一起存储起来,然后再存储下一列的数据,以此类推。1,2,3;Smith,Jones,Johnson;Joe,Mary,Cathy;40000,50000,44000; HBase数据模型在HBase中,数据是存储在有行有列的表格中。这是与关系型数据库重复的术语,但不能做类比。相反,HBase可以被认为是一个多维度的映射。 HBase数据模型术语Table(表格)一个HBase表格由多行组成。 Row(行)HBase中的行里面包含一个key和一个或者多个包含值的列。行按照行的key字母顺序存储在表格中。Row key只能存储64k的字节数据。因为这个原因,行的key的设计就显得非常重要。数据的存储目标是相近的数据存储到一起。一个常用的行的key的格式是网站域名。如果你的行的key是域名,你应该将域名进行反转(org.apache.www, org.apache.mail, org.apache.jira)再存储。这样的话,所有Apache域名将会存储在一起,好过基于子域名的首字母分散在各处。 Column(列)HBase中的列包含用:分隔开的列族和列的限定符。 Column Family(列族)因为性能的原因,列族物理上包含一组列和它们的值。每一个列族拥有一系列的存储属性,例如值是否缓存在内存中,数据是否要压缩或者他的行key是否要加密等等。表格中的每一行拥有相同的列族,尽管一个给定的行可能没有存储任何数据在一个给定的列族中。 Column Qualifier(列的限定符)列的限定符是列族中数据的索引。例如给定了一个列族content,那么限定符可能是content:html,也可以是content:pdf。列族在创建表格时是确定的了,但是列的限定符是动态地并且行与行之间的差别也可能是非常大的。HBase表中的每个列都归属于某个列族,列族必须作为表模式(schema)定义的一部分预先给出。如 create ‘test’, ‘course’;列名以列族作为前缀,每个“列族”都可以有多个列成员(column);如course:math, course:english,新的列族成员(列)可以随后按需、动态加入;权限控制、存储以及调优都是在列族层面进行的;HBase把同一列族里面的数据存储在同一目录下,由几个文件保存。 Cell(单元)由行和列的坐标交叉决定;单元格是有版本的;单元格的内容是未解析的字节数组;单元格是由行、列族、列限定符、值和代表值版本的时间戳组成的({row key, column( = +), version} )唯一确定单元格。cell中的数据是没有类型的,全部是字节码形式存储。 Timestamp(时间戳)时间戳是写在值旁边的一个用于区分值的版本的数据。默认情况下,时间戳表示的是当数据写入时RegionSever的时间点,但你也可以在写入数据时指定一个不同的时间戳。在HBase每个cell存储单元对同一份数据有多个版本,根据唯一的时间戳来区分每个版本之间的差异,不同版本的数据按照时间倒序排序,最新的数据版本排在最前面。时间戳的类型是64位整型。时间戳可以由HBase(在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值,如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。 概念视图例子:一个名为webable的表格,表格中有两行(com.cnn.www 和 com.example.www)和三个列族(contents, anchor和people)。在这个例子当中,第一行(com.cnn.www)中anchor包含两列(anchor:cssnsi.com, anchor:my.look.ca)和content包含一列(contents:html)。这个例子中com.cnn.www拥有5个版本而com.example.www有一个版本。contents:html列中包含给定网页的整个HTML。anchor限定符包含能够表示行的站点以及链接中文本。People列族表示跟站点有关的人。 在HBase中,表格中的单元如果是空将不占用空间或者事实上不存在。这就使得HBase看起来“稀疏”。表格视图不是唯一方式来查看HBase中数据,甚至不是最精确的。下面的方式以多维度映射的方式来表达相同的信息。1234567891011121314151617181920212223{"com.cnn.www": {contents: {t6: contents:html: "<html>..."t5: contents:html: "<html>..."t3: contents:html: "<html>..."} anchor: { t9: anchor:cnnsi.com = "CNN"t8: anchor:my.look.ca = "CNN.com" }people: {} }"com.example.www": { contents: {t5: contents:html: "<html>..." } anchor: {} people: { t5: people:author: "John Doe" } }} 物理视图尽管一个概念层次的表格可能看起来是由一些列稀疏的行组成,但他们是通过列族来存储的。一个新建的限定符(column_family:column_qualifier)可以随时地添加到已存在的列族中。 概念视图中的空单元实际上是没有进行存储的。因此对于返回时间戳为t8的contents:html的值的请求,结果为空。同样的,一个返回时间戳为t9的anchor:my.look.ca的值的请求,结果也为空。然而,如果没有指定时间戳的话,那么会返回特定列的最新值。对有多个版本的列,优先返回最新的值,因为时间戳是按照递减顺序存储的。因此对于一个返回com.cnn.www里面所有的列的值并且没有指定时间戳的请求,返回的结果会是时间戳为t6的contents:html 的值、时间戳 t9的anchor:cnnsi.com f的值和时间戳t8的anchor:my.look.ca。 HBase体系架构 Client包含访问HBase的接口并维护cache来加快对HBase的访问 Zookeeper 保证任何时候,集群中只有一个工作状态和master。 存储所有Region的寻址入口。 实时监控Region server的上线和下线信息。并实时通知Master。 存储HBase的schema和table元数据。 Master 为Region server分配region 负责Region server的负载均衡 发现失效的Region server并重新分配其上的region 管理用户对table的增删改操作 RegionServerRegion server维护region,处理对这些region的IO请求。Region server负责切分在运行过程中变得过大的region。 存储模型regionHBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据;每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值(hbase.hregion.max.filesize默认为256M)的时候,region就会等分会两个新的region(裂变);当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver 上。 HLog(WAL log)HLog文件就是一个普通的Hadoop Sequence File,Sequence File的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是“写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中的sequence number。HLog SequeceFile的Value是HBase的KeyValue对象,即对应HFile中的KeyValue。 数据模型:Memstore 与 storefile一个region由多个store组成,一个store对应一个CF(列族)。store包括位于内存中的memstore和位于磁盘的storefile,写操作先写入memstore,当memstore中的数据达到某个阈值,hregionserver会启动flashcache进程写入storefile,每次写入形成单独的一个storefile,当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),在合并过程中会进行版本合并和删除工作(majar),形成更大的storefile当一个region所有storefile的大小和数量超过一定阈值后,会把当前的region分割为两个,并由hmaster分配到相应的regionserver服务器,实现负载均衡,客户端检索数据,先在memstore找,找不到再找storefile。 HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的 HRegion server上。HRegion由一个或者多个Store组成,每个store保存一个columns family。每个Strore又由一个memStore和0至多个StoreFile组成。如图:StoreFile以HFile格式保存在HDFS上。 HBase伪分布式搭建(node01)0.启动zookeeper集群和hadoop集群zkServer.sh startstart-dfs.shstart-yarn.shyarn-daemon.sh start resourcemanagerJDK安装好,环境变量配置好 1.解压hbase安装包[root@node01 sxt]# tar xf hbase-0.98.12.1-hadoop2-bin.tar.gz 2.配置环境变量12345678910[root@node01 sxt]# vi + /etc/profileexport JAVA_HOME=/usr/java/jdk1.7.0_67export PATH=$PATH:$JAVA_HOME/binexport HADOOP_PREFIX=/opt/sxt/hadoop-2.6.5export PATH=$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbinexport ZOOKEEPER_PREFIX=/opt/sxt/zookeeper-3.4.6export PATH=$PATH:$ZOOKEEPER_PREFIX/binexport HBASE_HOME=/opt/sxt/hbase-0.98.12.1export PATH=$PATH:$HBASE_HOME/bin[root@node01 sxt]# . /etc/profile 3.hbase-env.sh中配置JAVA_HOME123[root@node01 sxt]# cd hbase-0.98.12.1/conf/[root@node01 conf]# vi hbase-env.shexport JAVA_HOME=/usr/java/jdk1.7.0_67 4.修改hbase-site.xml配置文件1234567891011121314[root@node01 conf]# vi hbase-site.xml<configuration><property> <name>hbase.rootdir</name> <value>file:///var/hbase/local</value></property><property> <name>hbase.zookeeper.property.dataDir</name> <value>/var/hbase/local/zookeeper</value></property></configuration> 5.启动hbase[root@node01 conf]# start-hbase.sh 6.进入hbase命令行[root@node01 conf]# hbase shell 基础命令练习123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899hbase删除已经打好的命令:ctrl+backspacehbase(main):002:0> version0.98.12.1-hadoop2, rb00ec5da604d64a0bdc7d92452b1e0559f0f5d73, Sun May 17 12:55:03 PDT 2015hbase(main):003:0> whoamiSLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/opt/sxt/hbase-0.98.12.1/lib/slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [jar:file:/opt/sxt/hadoop-2.6.5/share/hadoop/common/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.root (auth:SIMPLE) groups: root建表 表名 列族 列族hbase(main):004:0>hbase(main):006:0> create 'person','name', 'age'查看有哪些表hbase(main):007:0> listTABLE person查看表描述hbase(main):010:0> describe 'person'Table person is ENABLED person COLUMN FAMILIES DESCRIPTION {NAME => 'age', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE',MIN_VERSIONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'} {NAME => 'name', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE',BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}NAME:列族名VERSIONS:最大版本号MIN_VERSIONS:最小版本号TTL(Time To Live):存活时间IN_MEMORY:是否开启缓存,默认false,应该开启,否则与BLOCKCACHE冲突BLOCKCACHE:读缓存是否开启,默认开启,64M插入数据hbase(main):012:0> put 'person','0001','name:firstname','Jed'hbase(main):013:0> put 'person','0001','age:zhousui','20'查看全部数据hbase(main):014:0> scan 'person'ROW COLUMN+CELL 0001 column=age:zhousui, timestamp=1499929503879, value=20 0001 column=name:firstname, timestamp=1499929407656, value=Jed查看个别数据hbase(main):015:0> get 'person','0001','name:firstname'COLUMN CELL name:firstname timestamp=1499929407656, value=Jed修改数据hbase(main):016:0> put 'person','0001','name:firstname','Tom'hbase(main):017:0> get 'person','0001','name:firstname'COLUMN CELL name:firstname timestamp=1499929924936, value=Tom 查看表空间hbase(main):018:0> list_namespaceNAMESPACE default #用户创建的表放在这里 hbase #系统表空间进入存放数据的目录/var/hbase/local(在hbase-site.xml中配置过)[root@node01 ~]# cd /var/hbase/local/[root@node01 local]# lsarchive data hbase.id hbase.version oldWALs WALs zookeeperdata是存放数据的目录,oldWAL和WALs是Hlog[root@node01 local]# cd data[root@node01 data]# lsdefault hbase[root@node01 data]# cd default/[root@node01 default]# lsperson[root@node01 default]# cd person/[root@node01 person]# lsb14e1200e562fb736ce81df88d712823[root@node01 person]# cd b14e1200e562fb736ce81df88d712823/[root@node01 b14e1200e562fb736ce81df88d712823]# lsage name[root@node01 b14e1200e562fb736ce81df88d712823]# cd age[root@node01 age]# cd ls总用量 0age和name下没有数据,因为数据还在内存中,我们设置强制溢写hbase(main):028:0> flush 'person'root@node01 b14e1200e562fb736ce81df88d712823]# cd age/[root@node01 age]# ls06c01947d23e4fafa3a95bd407cc8c94[root@node01 age]# hbase hfile -p -f 06c01947d23e4fafa3a95bd407cc8c94K: 0001/age:zhousui/1499931020776/Put/vlen=2/mvcc=0 V: 20删除表hbase(main):021:0> disable 'person' #先让表禁用hbase(main):022:0> drop 'person' #再删除表 HBase完全分布式搭建角色分布 在生产中,不要把namenode和HbaseMaster配置到一台机器上。 0. 再准备一台机器node0512345678vi /etc/sysconfig/network-scripts/ifcfg-eth0vi /etc/sysconfig/networkvi /etc/hosts编辑C:\Windows\System32\drivers\etc\hosts文件node05免秘钥[root@node01 .ssh]# vi authorized_keys 复制一行,末尾改为node05[root@node05 ~]# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa[root@node01 .ssh]# scp ./* node04:`pwd` 安装jdk一定要保证集群中所有机器的系统时间相同! 1.从node01上分发hbase目录到其他机器1234[root@node01 sxt]# scp -r hbase-0.98.12.1 node02:`pwd`[root@node01 sxt]# scp -r hbase-0.98.12.1 node03:`pwd`[root@node01 sxt]# scp -r hbase-0.98.12.1 node04:`pwd`[root@node01 sxt]# scp -r hbase-0.98.12.1 node05:`pwd` 2.修改node01中hbase的配置文件hbase-env.sh123[root@node01 conf]# vi hbase-env.sh#不使用自带的zookeeperexport HBASE_MANAGES_ZK=false 3.修改node01中hbase的配置文件hbase-site.xml123456789101112131415161718192021222324252627[root@node01 conf]# vi hbase-site.xml<configuration><!-- 可以不配置,如果要配置,需要和zookeeper配置文件中的路径相同 $ZOOKEEPER_HOME/conf/zoo.cfg中dataDir=/var/sxt/zookeeper<property> <name>hbase.zookeeper.property.dataDir</name> <value>/var/sxt/zookeeper </value> </property> --> <property> <name>hbase.rootdir</name> <value>hdfs://mycluster/hbase</value> </property> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <property> <name>hbase.zookeeper.quorum</name> <value>node02,node03,node04</value> </property></configuration> 4.修改node01中hbase的配置文件regionservers[root@node01 conf]# vi regionserversnode02node03node04 5.新建backup-masters文件,并做修改[root@node01 conf]# vi backup-mastersnode05 6.把hdfs-site.xml copy到hbase的配置目录下[root@node01 conf]# cp /opt/sxt/hadoop-2.6.5/etc/hadoop/hdfs-site.xml ./ 7.同步配置文件1234[root@node01 conf]# scp ./* node02:`pwd`[root@node01 conf]# scp ./* node03:`pwd`[root@node01 conf]# scp ./* node04:`pwd`[root@node01 conf]# scp ./* node05:`pwd` 8.启动集群(先启动ZK和Hadoop集群)[root@node01 conf]# start-hbase.sh 9.验证1234hbase(main):004:0> create 'person','0001','col1','col2'hbase(main):005:0> listTABLE person 配置成功! 验证集群的高可用在node01的hbase中创建了一个表person杀死node01的HMaster123456789101112[root@node01 ~]# jps3220 DFSZKFailoverController6210 Jps5740 HMaster2876 NameNode3062 JournalNode[root@node01 ~]# kill -9 5740[root@node01 ~]# jps3220 DFSZKFailoverController2876 NameNode6295 Jps3062 JournalNode 此时node01的hbase已经不能访问了node05变成了Master:在node05上进入hbase客户端,查看表:123456789101112131415161718192021hbase(main):001:0> listTABLE person ``` #### Bug解决1配置好后启动hbase,发现node05的jps中有HMaster,而node01没有,访问网页,node01无法连接,node05无法获取masterHadoop集群和Zookeeper没有任何问题,HBASE_MANAGES_ZK=false也设置了,查看日志,报错:Caused by: org.apache.hadoop.hbase.MasterNotRunningException: java.io.IOException: Can't get master address from ZooKeeper; znode data == null百思不得其解,最后发现,配置文件有问题:```xml<property> <name>hbase.rootdir</name> <value>hdfs://mycluster</value></property> 这里正确应该是:<property> <name>hbase.rootdir</name> <value>hdfs://mycluster/hbase</value></property> Bug解决2验证hbase高可用,down掉主机后,备机并没有变成主机,两台机器的HMaster服务都关闭了,原因:解决上个bug修改配置文件后,没有同步!解决办法:(一) 停止hbase进程主机上执行stop-hbase.sh(二) 如果HregionServer进程还在,手动停止node02、node03、node03上的HregionServerkill -9 HregionServerId(三) 清除脏数据删除HDFS中的hbase目录:hdfs dfs -rm -r /hbase删除Zookeeper中的 hbase目录:1234567[root@node02 conf]# zkCli.shWelcome to ZooKeeper![zk: localhost:2181(CONNECTED) 0] ls /[hbase, hadoop-ha, yarn-leader-election, zookeeper][zk: localhost:2181(CONNECTED) 1] rmr /hbase[zk: localhost:2181(CONNECTED) 2] ls /[hadoop-ha, yarn-leader-election, zookeeper] (四) 修改之前出错的配置文件同步node01和node05的配置文件(五) 重启hbase,问题解决一定要同步配置文件! APIHBase依赖jar包为安装包lib目录下的jar包,可以全部引入,也可以只引入最少依赖包,再引入之前的hadoop依赖包,最少依赖包是hbase开头的包和high-scale-lib-1.1.1.jar、htrace-core-2.04.jar、netty-3.6.6.Final.jar三个包。项目需要添加hbase-site.xml配置文件,与MapReduce整合时需要hadoop的相关配置文件。 需求:通话记录查询表:本机号码 主叫/被叫(0/1) 通话时间 对方号码123456789101112131415161718192021222324HBaseAdmin hbaseAdmin;HTable hTable;String tableName = "call"; @Beforepublic void begin() throws Exception {Configuration conf = new Configuration();//指定hbase的zk集群//一定要手动设置//如果是伪分布式,指定伪分布式那台服务器 conf.set("hbase.zookeeper.quorum","node02,node03,node04"); hbaseAdmin = new HBaseAdmin(conf);hTable = new HTable(conf, tableName);} @Afterpublic void end() throws Exception { if(hbaseAdmin != null){ hbaseAdmin.close(); }if(hTable != null){ hTable.close();}} 建表:1234567891011121314151617181920212223242526272829@Testpublic void createTable() throws Exception { if(hbaseAdmin.tableExists(tableName)) { hbaseAdmin.disableTable(tableName); hbaseAdmin.deleteTable(tableName); } //表名 HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName)); /* * 列族的数目 * HBase currently does not do well with anything * above two or three column families * so keep the number of column families in your schema low. * hbase对超过3个的列族,支持不太好 */ HColumnDescriptor family = new HColumnDescriptor("cf1"); family.setBlockCacheEnabled(true);//开启读缓存,默认就为true family.setInMemory(true);//开启缓存,默认false family.setMaxVersions(1);//最大版本数,默认就是1 desc.addFamily(family); hbaseAdmin.createTable(desc); } 插入数据:1234567891011121314151617@Testpublic void insert() throws Exception { //rowkey设置为:手机号_时间戳 String rowkey = "18734590000_20170714104600"; Put put = new Put(rowkey.getBytes()); //通话类型:主叫/被叫(1/0) put.add("cf1".getBytes(), "type".getBytes(), "1".getBytes()); //通话时间 put.add("cf1".getBytes(), "time".getBytes(), "2017-07-14 10:46:00".getBytes()); //对方号码 put.add("cf1".getBytes(), "oppoPhoneNum".getBytes(), "13876580000".getBytes()); hTable.put(put);} 查询数据:12345678910111213141516171819@Testpublic void get() throws Exception { String rowkey = "18734590000_20170714104600"; Get get = new Get(rowkey.getBytes()); get.addColumn("cf1".getBytes(), "time".getBytes()); get.addColumn("cf1".getBytes(), "oppoPhoneNum".getBytes()); Result result = hTable.get(get); Cell cell1 = result.getColumnLatestCell("cf1".getBytes(), "time".getBytes()); Cell cell2 = result.getColumnLatestCell("cf1".getBytes(), "oppoPhoneNum".getBytes()); System.out.println(new String(CellUtil.cloneValue(cell1))); System.out.println(new String(CellUtil.cloneValue(cell2))); } 插入10个手机号,每个手机号有100条通话记录, 时间按降序排列:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253@Testpublic void insertDB() throws Exception { List<Put> puts = new ArrayList<>(); for(int i=0; i<10; i++){ String rowkey; String phoneNum = getPhoneNum("186"); for(int j=0; j<100; j++){ String date = getDate("2017"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); long time = sdf.parse(date).getTime(); rowkey = phoneNum + (Long.MAX_VALUE - time); System.out.println(rowkey); Put put = new Put(rowkey.getBytes()); put.add("cf1".getBytes(),"type".getBytes(),(random.nextInt(2)+"").getBytes()); put.add("cf1".getBytes(),"time".getBytes(),(date).getBytes()); put.add("cf1".getBytes(),"oppoPhoneNum".getBytes(),getPhoneNum("137").getBytes()); puts.add(put); } } hTable.put(puts);}public Random random = new Random(); /** * 随机生成手机号 * @param prefix 手机号前三位 */public String getPhoneNum(String prefix) {//生成的数字如果不够8位,用0补充 return prefix + String.format("%08d", random.nextInt(99999999));} /** * 随机生成时间 * @param year 年 * @return 时间 格式:yyyyMMddHHmmss */public String getDate(String year) { return year + String.format("%02d%02d%02d%02d%02d", new Object[] {random.nextInt(12)+1,random.nextInt(29)+1, random.nextInt(24),random.nextInt(60), random.nextInt(60)});} 查询某个手机号某个月的通话记录:12345678910111213141516171819202122232425262728293031/** * 手机号:18698423056 * 时间:2017年1月 */@Testpublic void scanDB() throws Exception { Scan scan = new Scan(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");String startRowKey = "18698423056" + (Long.MAX_VALUE - sdf.parse("20170201000000").getTime());String stopRowKey = "18698423056" + (Long.MAX_VALUE - sdf.parse("20170101000000").getTime()); scan.setStartRow(startRowKey.getBytes()); scan.setStopRow(stopRowKey.getBytes()); ResultScanner rss = hTable.getScanner(scan); for(Result rs : rss){ Cell cell1 = rs.getColumnLatestCell( "cf1".getBytes(), "type".getBytes());Cell cell2 = rs.getColumnLatestCell( "cf1".getBytes(), "time".getBytes());Cell cell3 = rs.getColumnLatestCell( "cf1".getBytes(), "oppoPhoneNum".getBytes());System.out.println( new String(CellUtil.cloneValue(cell1)) + "-" + new String(CellUtil.cloneValue(cell2)) + "-" + new String(CellUtil.cloneValue(cell3))); } } 查询某个手机号所有的主叫(type=0)的通话记录:12345678910111213141516171819202122232425262728293031323334353637/** * 手机号:18696384891 */ @Test public void scanDB2() throws Exception { FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL); PrefixFilter filter1 = new PrefixFilter("18696384891".getBytes()); SingleColumnValueFilter filter2 = new SingleColumnValueFilter("cf1".getBytes(), "type".getBytes(), CompareOp.EQUAL, "0".getBytes()); list.addFilter(filter1); list.addFilter(filter2); Scan scan = new Scan(); scan.setFilter(list); ResultScanner rss = hTable.getScanner(scan); for (Result rs : rss) { Cell cell1 = rs.getColumnLatestCell("cf1".getBytes(), "type".getBytes()); Cell cell2 = rs.getColumnLatestCell("cf1".getBytes(), "time".getBytes()); Cell cell3 = rs.getColumnLatestCell("cf1".getBytes(), "oppoPhoneNum".getBytes()); System.out.println( new String(CellUtil.cloneValue(cell1)) + "-" + new String(CellUtil.cloneValue(cell2)) + "-" + new String(CellUtil.cloneValue(cell3)) ); }} 设计表人员-角色人员有多个角色,角色考虑优先级,角色有多个人员人员可以添加删除角色角色可以添加删除人员人员和角色可以添加删除 组织架构组:部门-子部门 微博表设计关注列表:添加、取消粉丝列表查看首页:所有关注过的好友微博,时间降序排序查看某个用户的微博,时间降序排序发布微博 ProtobufProtocol Buffers是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。 安装protobuf(node05)12345[root@node05 sxt]# tar zxvf protobuf-2.5.0.tar.gz# 安装一些常用软件需要依赖的包[root@node05 protobuf-2.5.0]# yum groupinstall Development tools -y[root@node05 protobuf-2.5.0]# ./configure --prefix=/opt/sxt/protobuf[root@node05 protobuf-2.5.0]# make && make install 对通话记录案例中的列和行进行整合优化12345678910111213141516171819202122232425[root@node05 ~]# vi call.protopackage com.sxt.hbase;#通话记录详情message Record { required string type = 1; required string time = 2; required string oppoPhoneNum = 3;}#一天的通话记录message RecordList { repeated Record rlist = 1;} [root@node05 ~]# /opt/sxt/protobuf/bin/protoc --java_out=./ call.proto[root@node05 ~]# lsanaconda-ks.cfg call.proto com install.log install.log.syslog[root@node05 ~]# cd com[root@node05 com]# lssxt[root@node05 com]# cd sxt[root@node05 sxt]# lshbase[root@node05 sxt]# cd hbase[root@node05 hbase]# lsCall.java# Call.java是对call.proto的封装 把Call.java放到查询通话记录的项目中,Call.java对列和行分别进行了封装12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879/** * 随机生成时间 * @param time yyyyMMdd * @return 时间 格式:yyyyMMddHHmmss */public String getDate2(String time) { return time + String.format("%02d%02d%02d", new Object[] { random.nextInt(24), random.nextInt(60), random.nextInt(60) });}/** * 插入10个手机号,每个手机号一天产生100条通话记录 * 一天当中所有的通话记录封装到一起 * 时间按降序排列 */@Testpublic void insertDB2() throws Exception { List<Put> puts = new ArrayList<>(); for(int i=0; i<10; i++){ String rowkey; String phoneNum = getPhoneNum("186"); String date = "20170714000000"; SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); rowkey = phoneNum + "_" + (Long.MAX_VALUE-(sdf.parse(date).getTime())); Call.RecordList.Builder recordList = Call.RecordList.newBuilder(); for(int j=0; j<100; j++){ String dnum = getPhoneNum("138"); String dateStr = getDate2("20170714"); String type = random.nextInt(2) + ""; Call.Record.Builder record = Call.Record.newBuilder(); record.setType(type); record.setTime(dateStr); record.setOppoPhoneNum(dnum); recordList.addRlist(record); } Put put = new Put(rowkey.getBytes()); //这一个手机号一天的记录放到cf1中,名称为records,可以任意命名 put.add("cf1".getBytes(), "records".getBytes(), recordList.build().toByteArray()); puts.add(put); } hTable.put(puts);}/** * 查询某个手机号1天的通话记录 * @throws Exception */@Testpublic void getDB2() throws Exception { String rowkey = "186970848059223370536893175807"; Get get = new Get(rowkey.getBytes()); get.addColumn("cf1".getBytes(), "records".getBytes()); Result rs = hTable.get(get); Cell cell = rs.getColumnLatestCell("cf1".getBytes(), "records".getBytes()); Call.RecordList recordList = Call.RecordList.parseFrom( CellUtil.cloneValue(cell)); for(Call.Record record : recordList.getRlistList()) { System.out.println(record.getType() + " - " + record.getTime() + " - " + record.getOppoPhoneNum()); } } HBase优化设计表的优化1.Pre-Creating Regions(预分区)默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。 例如:123456789101112131415161718192021222324252627282930313233public static boolean createTable(HBaseAdmin admin, HTableDescriptor table, byte[][] splits) throws IOException { try { admin.createTable(table, splits); return true; } catch (TableExistsException e) { logger.info("table " + table.getNameAsString() + " already exists"); // the table already exists... return false; }}/** * 当rowkey是数字类型时,使用以下规则分区 * start:001 * endkey:100 * region:10个,[001,010][011,020]... */public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) { byte[][] splits = new byte[numRegions-1][]; BigInteger lowestKey = new BigInteger(startKey, 16); BigInteger highestKey = new BigInteger(endKey, 16); BigInteger range = highestKey.subtract(lowestKey); BigInteger regionIncrement = range.divide(BigInteger.valueOf(numRegions)); lowestKey = lowestKey.add(regionIncrement); for(int i=0; i < numRegions-1;i++) { BigInteger key = lowestKey.add(regionIncrement.multiply(BigInteger.valueOf(i))); byte[] b = String.format("%016x", key).getBytes(); splits[i] = b; } return splits;} 2.rowkey的设计HBase中row key用来检索表中的记录,支持以下三种方式:• 通过单个row key访问:即按照某个row key键值进行get操作;• 通过row key的range进行scan:即通过设置startRowKey和endRowKey,在这个范围内进行扫描;• 全表扫描:即直接扫描整张表中所有行记录。在HBase中,row key可以是任意字符串,最大长度64KB,实际应用中一般为10~100 bytes,存为byte[]字节数组,一般设计成定长的。row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE - timestamp作为row key,这样能保证新写入的数据在读取时可以被快速命中。Rowkey规则:1) 越小越好2) Rowkey的设计是要根据实际业务来3) 散列性a) 取反 001 002 :100 200 取反后,rowkey可能落在不同的region上b) Hash rowkey取hash值后,可能会均匀分布在不同的region上散列的弊端:降低了范围查找的效率 3.Column Family不要在一张表里定义太多的column family。目前Hbase并不能很好的处理超过2~3个column family的表。因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。 4.In Memory创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。 标记IN_MEMORY=>’true’的column family的总体积最好不要超过in-memory cache的大小(in-memory cache = heap size hfile.block.cache.size 0.85 * 0.25),特别是当总体积远远大于了in-memory cache时,会在in-memory cache上发生严重的颠簸。 换个角度再看,普遍提到的使用in-memory cache的场景是把元数据表的column family声明为IN_MEMORY=>’true’。实际上这里的潜台词是:元数据表都很小。其时我们也可以大胆地把一些需要经常访问的,总体积不会超过in-memory cache的column family都设为IN_MEMORY=>’true’从而更加充分地利用cache空间。就像前面提到的,普通的block永远是不会被放入in-memory cache的,只存放少量metadata是对in-memory cache资源的浪费(未来的版本应该提供三种区段的比例配置功能) 5.Max Version创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。 6.Time To Live创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 24 60 * 60)。 7.Compact & Split在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值(64M)时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。于此同时,系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile,这里相当于把一个大的region分割成两个region。由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。hbase为了防止小文件(被刷到磁盘的menstore)过多,以保证保证查询效率,hbase需要在必要的时候将这些小的store file合并成相对较大的store file,这个过程就称之为compaction。在hbase中,主要存在两种类型的compaction:minor compaction和major compaction。minor compaction是较小、很少文件的合并。major compaction是将所有的store file合并成一个,触发major compaction的可能条件有:major_compact 命令、majorCompact() API、region server自动运行(相关参数:hbase.hregion.majoucompaction 默认为24 小时、hbase.hregion.majorcompaction.jetter 默认值为0.2 ,作用是防止region server在同一时间进行major compaction)。hbase.hregion.majorcompaction.jetter参数的作用是:对参数hbase.hregion.majoucompaction 规定的值起到浮动的作用,假如两个参数都为默认值24和0.2,那么major compact最终使用的数值为:19.2~28.8 这个范围。major compaction执行时,就无法对HBase进行访问,所以通常是关闭自动方式,根据业务,手动编程来控制其操作时间。1.关闭自动major compaction,使用手动方式。2.手动编程major compaction:Timer类(java),contab(shell) minor compaction的运行机制要复杂一些,我们一把也不会来对它进行控制, 它由一下几个参数共同决定:hbase.hstore.compaction.min默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动hbase.hstore.compaction.max默认值为10,表示一次minor compaction中最多选取10个store filehbase.hstore.compaction.min.size表示文件大小小于该值的store file 一定会加入到minor compaction的store file中hbase.hstore.compaction.max.size表示文件大小大于该值的store file一定会被minor compaction排除hbase.hstore.compaction.ratio将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择 写表的优化1.多HTable并发写创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子:12345678static final Configuration conf = HBaseConfiguration.create();static final String table_log_name = "user_log";wTableLog = new HTable[tableN];for (int i = 0; i < tableN; i++) { wTableLog[i] = new HTable(conf, table_log_name); wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB wTableLog[i].setAutoFlush(false);} 2.HTable参数设置Auto Flush通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。 Write Buffer通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。 WAL Flag在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。 3.批量写通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。 4.多线程并发写在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。下面给个具体的例子:12345678910111213141516171819202122for (int i = 0; i < threadN; i++) { Thread th = new Thread() { public void run() { while (true) { try { sleep(1000); //1 second } catch (InterruptedException e) { e.printStackTrace(); } synchronized (wTableLog[i]) { try { wTableLog[i].flushCommits(); } catch (IOException e) { e.printStackTrace(); } } } } }; th.setDaemon(true); th.start();} 但实际上,自己写多线程或多Htable维护比较麻烦且不稳定,用mapreduce或spark写数据时,本身就是多线程的只需要批量写就可以了。 读表的优化1. 多个HTable并发读创建多个HTable客户端用于读操作,提高读数据的吞吐量,一个例子:1234567static final Configuration conf = HBaseConfiguration.create();static final String table_log_name = "user_log";rTableLog = new HTable[tableN];for (int i = 0; i < tableN; i++) { rTableLog[i] = new HTable(conf, table_log_name); rTableLog[i].setScannerCaching(50);} 2.HTable参数设置Scanner Cachinghbase.client.scanner.caching配置项可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录。有三个地方可以进行配置:a) 在HBase的conf配置文件中进行配置;b) 通过调用HTable.setScannerCaching(int scannerCaching)进行配置;c) 通过调用Scan.setCaching(int caching)进行配置。三者的优先级越来越高。少的RPC是提高hbase执行效率的一种方法,理论上一次性获取越多数据就会越少的RPC,也就越高效。但是内存是最大的障碍。设置这个值的时候要选择合适的大小,一面一次性获取过多数据占用过多内存,造成其他程序使用内存过少。或者造成程序超时等错误(这个超时与hbase.regionserver.lease.period相关)。hbase.regionserver.lease.period默认值:60000说明:客户端租用HRegion server 期限,即超时阀值。调优:这个配合hbase.client.scanner.caching使用,如果内存够大,但是取出较多数据后计算过程较长,可能超过这个阈值,适当可设置较长的响应时间以防被认为宕机。 Scan Attribute Selectionscan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。 Close ResultScanner通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。 3.批量读通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。 4.多线程并发读在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。下面是一个多线程并发读取HBase,获取店铺一天内各分钟PV值的例子:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148public class DataReaderServer { // 获取店铺一天内各分钟PV值的入口函数 public static ConcurrentHashMap<String, String> getUnitMinutePV(long uid, long startStamp, long endStamp) { long min = startStamp; int count = (int) ((endStamp - startStamp) / (60 * 1000)); List<String> lst = new ArrayList<String>(); for (int i = 0; i <= count; i++) { min = startStamp + i * 60 * 1000; lst.add(uid + "_" + min); } return parallelBatchMinutePV(lst); } // 多线程并发查询,获取分钟PV值 private static ConcurrentHashMap<String, String> parallelBatchMinutePV(List<String> lstKeys) { ConcurrentHashMap<String, String> hashRet = new ConcurrentHashMap<String, String>(); int parallel = 3; List<List<String>> lstBatchKeys = null; if (lstKeys.size() < parallel) { lstBatchKeys = new ArrayList<List<String>>(1); lstBatchKeys.add(lstKeys); } else { lstBatchKeys = new ArrayList<List<String>>(parallel); for (int i = 0; i < parallel; i++) { List<String> lst = new ArrayList<String>(); lstBatchKeys.add(lst); } for (int i = 0; i < lstKeys.size(); i++) { lstBatchKeys.get(i % parallel).add(lstKeys.get(i)); } } List<Future<ConcurrentHashMap<String, String>>> futures = new ArrayList<Future<ConcurrentHashMap<String, String>>>(5); ThreadFactoryBuilder builder = new ThreadFactoryBuilder(); builder.setNameFormat("ParallelBatchQuery"); ThreadFactory factory = builder.build(); ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors. newFixedThreadPool(lstBatchKeys.size(), factory); for (List<String> keys : lstBatchKeys) { Callable<ConcurrentHashMap<String, String>> callable = new BatchMinutePVCallable(keys); FutureTask<ConcurrentHashMap<String, String>> future = (FutureTask<ConcurrentHashMap<String, String>>)executor.submit(callable); futures.add(future); } executor.shutdown(); // Wait for all the tasks to finish try { boolean stillRunning = !executor.awaitTermination(5000000, TimeUnit.MILLISECONDS); if (stillRunning) { try { executor.shutdownNow(); } catch (Exception e) { e.printStackTrace(); } } } catch (InterruptedException e) { try { Thread.currentThread().interrupt(); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } // Look for any exception for (Future f : futures) { try { if (f.get() != null) { hashRet.putAll((ConcurrentHashMap<String, String>) f.get()); } } catch (InterruptedException e) { try { Thread.currentThread().interrupt(); } catch (Exception e1) { e1.printStackTrace(); } } catch (ExecutionException e) { e.printStackTrace(); } } return hashRet; } // 一个线程批量查询,获取分钟PV值 protected static ConcurrentHashMap<String, String> getBatchMinutePV(List<String> lstKeys) { ConcurrentHashMap<String, String> hashRet = null; List<Get> lstGet = new ArrayList<Get>(); String[] splitValue = null; for (String s : lstKeys) { splitValue = s.split("_"); long uid = Long.parseLong(splitValue[0]); long min = Long.parseLong(splitValue[1]); byte[] key = new byte[16]; Bytes.putLong(key, 0, uid); Bytes.putLong(key, 8, min); Get g = new Get(key); g.addFamily(fp); lstGet.add(g); } Result[] res = null; try { res = tableMinutePV[rand.nextInt(tableN)].get(lstGet); } catch (IOException e1) { logger.error("tableMinutePV exception, e=" + e1.getStackTrace()); } if (res != null && res.length > 0) { hashRet = new ConcurrentHashMap<String, String>(res.length); for (Result re : res) { if (re != null && !re.isEmpty()) { try { byte[] key = re.getRow(); byte[] value = re.getValue(fp, cp); if (key != null && value != null) { hashRet.put( String.valueOf( Bytes.toLong(key, Bytes.SIZEOF_LONG)), String.valueOf(Bytes.toLong(value))); } } catch (Exception e2) { logger.error(e2.getStackTrace()); } } } } return hashRet; }}// 调用接口类,实现Callable接口class BatchMinutePVCallable implements Callable<ConcurrentHashMap<String, String>> { private List<String> keys; public BatchMinutePVCallable(List<String> lstKeys) { this.keys = lstKeys; } public ConcurrentHashMap<String, String> call() throws Exception { return DataReadServer.getBatchMinutePV(keys); }} 5.缓存查询结果对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。 6.BlockcacheHBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize hbase.regionserver.global.memstore.upperLimit 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize hfile.block.cache.size 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。 HTable和HTablePoolHTable和HTablePool都是HBase客户端API的一部分,可以使用它们对HBase表进行CRUD操作。下面结合在项目中的应用情况,对二者使用过程中的注意事项做一下概括总结。123456Configuration conf = HBaseConfiguration.create();try (Connection connection = ConnectionFactory.createConnection(conf)) { try (Table table = connection.getTable(TableName.valueOf(tablename)) { //use table as needed, the table returned is lightweight }} HTableHTable是HBase客户端与HBase服务端通讯的Java API对象,客户端可以通过HTable对象与服务端进行CRUD操作(增删改查)。它的创建很简单:Configuration conf = HBaseConfiguration.create();HTable table = new HTable(conf, “tablename”);//TODO CRUD Operation…… HTable使用时的一些注意事项:1) 规避HTable对象的创建开销因为客户端创建HTable对象后,需要进行一系列的操作:检查.META.表确认指定名称的HBase表是否存在,表是否有效等等,整个时间开销比较重,可能会耗时几秒钟之长,因此最好在程序启动时一次性创建完成需要的HTable对象,如果使用Java API,一般来说是在构造函数中进行创建,程序启动后直接重用。2) HTable对象不是线程安全的HTable对象对于客户端读写数据来说不是线程安全的,因此多线程时,要为每个线程单独创建复用一个HTable对象,不同对象间不要共享HTable对象使用,特别是在客户端auto flash被置为false时,由于存在本地write buffer,可能导致数据不一致。3) HTable对象之间共享ConfigurationHTable对象共享Configuration对象,这样的好处在于:• 共享ZooKeeper的连接:每个客户端需要与ZooKeeper建立连接,查询用户的table regions位置,这些信息可以在连接建立后缓存起来共享使用;• 共享公共的资源:客户端需要通过ZooKeeper查找-ROOT-和.META.表,这个需要网络传输开销,客户端缓存这些公共资源后能够减少后续的网络传输开销,加快查找过程速度。因此,与以下这种方式相比:12HTable table1 = new HTable("table1");HTable table2 = new HTable("table2"); 下面的方式更有效些:123Configuration conf = HBaseConfiguration.create();HTable table1 = new HTable(conf, "table1");HTable table2 = new HTable(conf, "table2"); 备注:即使是高负载的多线程程序,也并没有发现因为共享Configuration而导致的性能问题;如果你的实际情况中不是如此,那么可以尝试不共享Configuration。 HTablePoolHTablePool可以解决HTable存在的线程不安全问题,同时通过维护固定数量的HTable对象,能够在程序运行期间复用这些HTable资源对象。Configuration conf = HBaseConfiguration.create();HTablePool pool = new HTablePool(conf, 10); 1) HTablePool可以自动创建HTable对象,而且对客户端来说使用上是完全透明的,可以避免多线程间数据并发修改问题。2) HTablePool中的HTable对象之间是公用Configuration连接的,能够可以减少网络开销。HTablePool的使用很简单:每次进行操作前,通过HTablePool的getTable方法取得一个HTable对象,然后进行put/get/scan/delete等操作,最后通过HTablePool的putTable方法将HTable对象放回到HTablePool中。下面是个使用HTablePool的简单例子:1234567891011121314151617181920public void createUser(String username, String firstName, String lastName, String email, String password, String roles) throws IOException { HTable table = rm.getTable(UserTable.NAME); Put put = new Put(Bytes.toBytes(username)); put.add(UserTable.DATA_FAMILY, UserTable.FIRSTNAME,Bytes.toBytes(firstName)); put.add(UserTable.DATA_FAMILY, UserTable.LASTNAME,Bytes.toBytes(lastName)); put.add(UserTable.DATA_FAMILY, UserTable.EMAIL, Bytes.toBytes(email)); put.add(UserTable.DATA_FAMILY, UserTable.CREDENTIALS,Bytes.toBytes(password)); put.add(UserTable.DATA_FAMILY, UserTable.ROLES, Bytes.toBytes(roles)); table.put(put); table.flushCommits(); rm.putTable(table);} HBase和关系型数据库的区别1.不能使用column之间过滤查询2.不支持全文索引。需要和solr整合完成全文搜索。a) 使用MR批量读取hbase中的数据,在solr里面建立索引(no store)之保存rowkey的值。b) 根据关键词从索引中搜索到rowkey(分页)c) 根据rowkey从hbase查询所有数据 HBase与MapReduce整合使用HBase与MapReduce完成wordcount,输出的结果存入HBase的表中 创建表:hbase(main):004:0> create 'wc','cf'Job类:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455public class WCJob { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://node01:8020"); // 指定hbase的zk集群 // 如果是伪分布式指定伪分布式那台服务器 conf.set("hbase.zookeeper.quorum", "node02,node03,node04"); // 在eclipse中直接运行MR程序,加这个配置 conf.set("mapreduce.app-submission.cross-platform", "true"); Job job = Job.getInstance(conf); job.setJobName("wc job"); job.setJarByClass(WCJob.class); // 在eclipse中直接运行MR程序,加这个配置, // 把项目打成jar包,并在这里说明jar包存放位置 job.setJar("E:\\package\\HBaseWC.jar"); job.setMapperClass(WCMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); TableMapReduceUtil.initTableReducerJob( "wc", // output table WCReducer.class, // reducer class job); // 参数boolean addDependencyJars // 当以本地方式运行MR时 ,必须设置为false /*TableMapReduceUtil.initTableReducerJob( "wc", WCReducer.class, job, null, null, null, null, false);*/ // 对initTableReducerJob()方法的参数的解析 Path path = new Path("/user/root/wordcount/input/wordcount.txt"); FileInputFormat.addInputPath(job, path); if(job.waitForCompletion(true)) { System.out.println("success!"); } }} Mapper类1234567891011public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] strs = StringUtils.split(value.toString(), ' '); for (String s : strs) { context.write(new Text(s), new IntWritable(1)); } }} Reducer类12345678910111213141516pulic class WCReducer extends TableReducer<Text, IntWritable, ImmutableBytesWritable> { public static final byte[] CF = "cf".getBytes(); public static final byte[] COUNT = "count".getBytes(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int i = 0; for (IntWritable val : values) { i += val.get(); } Put put = new Put(Bytes.toBytes(key.toString())); put.add(CF, COUNT, Bytes.toBytes(i)); context.write(null, put); }} 查看结果:123456789hbase(main):007:0> scan 'wc'ROW COLUMN+CELL C column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x04 C++ column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x02 Hadoop column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x01 Java column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x04 MySQL column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x03 Oracle column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x01 Spring column=cf:count, timestamp=1500100742828, value=\x00\x00\x00\x01 参考资料 HBase Block Cache的重要实现细节和In-Memory Cache的特点 HBase的Block Cache实现机制分析]]></content>
<categories>
<category>Bigdata</category>
<category>HBase</category>
</categories>
<tags>
<tag>HBase</tag>
<tag>HBase原理及其搭建</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--hadoop运行原理及其搭建]]></title>
<url>%2F2016%2F11%2F08%2FHADOOP--hadoop%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%85%B6%E6%90%AD%E5%BB%BA%2F</url>
<content type="text"><![CDATA[hadoop从运行机制到搭建,加网上常见的天气、好友推荐及协同过滤案列的实践。 Hadoop的三大核心模块分布式存储系统HDFS(Hadoop Distributed File System),提供了高可靠性、高扩展性和高吞吐率的数据存储服务;分布式计算框架MapReduce,具有易于编程、高容错性和高扩展性等优点;分布式资源管理框架YARN(Yet Another Resource Management),负责集群资源的管理和调度。Hadoop的生态系统中包含多种技术:比如Hive、Hbase、Spark等。 分布式存储系统HDFS存储模型 文件线性切割成块(Block):大文件切分为小文件 偏移量offset(byte):每一个块的起始位置相对于原始文件的字节索引 Block分散存储在集群节点中,单一文件Block大小一致,文件与文件之间Block大小可以不一致,Block可以设置副本数,副本分散在不同节点中,副本数不要超过节点数量,文件上传可以设置Block大小和副本数,已上传的文件Block副本数可以调整,大小不变 只支持一次写入多次读取(修改是泛洪操作,集群开销很大,所有不允许在块中增删改操作),同一时刻只有一个写入者 可以append追加数据(加块,单点操作) 架构模型概念:文件元数据MetaData,文件数据元数据:理解为文件的属性,比如权限、修改日期,文件名等数据本身:理解为文件中的内容NameNode节点(主)保存文件元数据:单节点 posixDataNode节点(从)保存文件Block数据:多节点DataNode与NameNode保持心跳,提交Block列表HdfsClient与NameNode交互元数据信息HdfsClient与DataNode交互文件Block数据HDFS架构HDFS设计思想 NameNode(NN)基于内存存储,不会和磁盘发生交换,只存在内存中,但也有持久化的功能,只是单方向的存储,防止断电丢失,不会发生内存和磁盘的交换,NameNode的metadate信息在启动后会加载到内存,metadata存储到磁盘文件名为”fsimage”,Block的位置信息不会保存到fsimage,由DataNode汇报,“edits”记录对metadata的操作日志。NameNode主要功能:接受客户端的读写服务,收集DataNode汇报的Block列表信息,NameNode保存metadata信息包括:文件owership、permissions、文件大小、时间、Block列表、Block偏移量和位置信息(副本位置由DataNode汇报,实时改变,不会持久化)等。 DataNode(DN)使用本地磁盘目录以文件形式存储数据(Block),同时存储Block的元数据信息文件(校验和,用于检测数据块是否损坏),启动DN时会向NN汇报block信息,通过向NN发送心跳保持与其联系(3秒一次),如果NN 10分钟没有收到DN的心跳,则认为其已经lost,并copy其上的block到其它DN。 SecondaryNameNode(SNN)它不是NN的备份(但可以做备份),它的主要工作是帮助NN合并edits log,减少NN启动时间。SNN执行合并时机:根据配置文件设置的时间间隔fs.checkpoint.period,默认3600秒或者根据配置文件设置edits log大小fs.checkpoint.size规定edits文件的最大值,默认是64MB。SNN在hadoop2.0后就不再使用。SNN合并流程: NN创建一个新的edits log来接替老的edits的工作 NN将fsimage和旧的edits拷备到SNN上 SNN上进行合并操作,产生一个新的fsimage 将新的fsimage复制一份到NN上 使用新的fsimage和新的edits log Block的副本放置策略第一个副本:放置在上传文件的DN;如果是集群外提交,则随机挑选一台磁盘不太满,CPU不太忙的节点。第二个副本:放置在于第一个副本不同的机架的节点上。第三个副本:与第二个副本相同机架的节点。更多副本:随机节点。HDFS写流程下图描述的是上传一个block的流程: 客户端创建DistributedFileSystem对象. DistributedFileSystem对象调用元数据节点,在文件系统的命名空间中创建一个新的文件,元数据节点首先确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件,并标识为“上传中”状态,即可以看见,但不能使用。 DistributedFileSystem返回DFSOutputStream,客户端用于写数据。 客户端开始写入数据,DFSOutputStream将数据分成块,写入data queue(Data queue由Data Streamer读取),并通知元数据节点分配数据节点,用来存储数据块(每块默认复制3块)。分配的数据节点放在一个pipeline里。Data Streamer将数据块写入pipeline中的第一个数据节点。第一个数据节点将数据块发送给第二个数据节点。第二个数据节点将数据发送给第三个数据节点。注意:并不是第一个数据节点完全接收完block后再发送给后面的数据节点,而是接收到一部分就发送,所以三个节点几乎是同时接收到完整的block的。DFSOutputStream为发出去的数据块保存了ack queue,等待pipeline中的数据节点告知数据已经写入成功。如果block在某个节点的写入的过程中失败:关闭pipeline,将ack queue放至data queue的开始。已经写入节点中的那些block部分会被元数据节点赋予新的标示,发生错误的节点重启后能够察觉其数据块是过时的,会被删除。失败的节点从pipeline中移除,block的其他副本则写入pipeline中的另外两个数据节点。元数据节点则被通知此block的副本不足,将来会再创建第三份备份。 ack queue返回成功。 客户端结束写入数据,则调用stream的close函数,最后通知元数据节点写入完毕 总结: 客户端切分文件Block,按Block线性地和NN获取DN列表(副本数),验证DN列表后以更小的单位流式传输数据,各节点两两通信确定可用,Block传输结束后,DN向NN汇报Block信息,DN向Client汇报完成,Client向NN汇报完成,获取下一个Block存放的DN列表,最终Client汇报完成,NN会在写流程更新文件状态。 HDFS读流程 客户端(client)用FileSystem的open()函数打开文件。 DistributedFileSystem调用元数据节点,得到文件的数据块信息。对于每一个数据块,元数据节点返回保存数据块的数据节点的地址。 DistributedFileSystem返回FSDataInputStream给客户端,用来读取数据。 客户端调用stream的read()函数开始读取数据(也会读取block的元数据)。DFSInputStream连接保存此文件第一个数据块的最近的数据节点(优先读取同机架的block)。 Data从数据节点读到客户端。当此数据块读取完毕时,DFSInputStream关闭和此数据节点的连接,然后连接此文件下一个数据块的最近的数据节点。 当客户端读取完毕数据的时候,调用FSDataInputStream的close函数。 在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。失败的数据节点将被记录,以后不再连接。 总结:客户端和NN获取一部分Block(获取部分block信息,而不是整个文件全部的block信息,读完这部分block后,再获取另一个部分block的信息)副本位置列表,线性地和DN获取Block,最终合并为一个文件,在Block副本列表中按距离择优选取。 安全模式namenode启动的时候,首先将映像文件(fsimage)载入内存,并执行编辑日志(edits)中的各项操作。一旦在内存中成功建立文件系统元数据的映射,则创建一个新的fsimage文件(这个操作不需要SecondaryNameNode)和一个空的编辑日志。此刻namenode运行在安全模式。即namenode的文件系统对于客服端来说是只读的(显示目录,显示文件内容等。写、删除、重命名都会失败)。在此阶段Namenode收集各个datanode的报告,当数据块达到最小副本数以上时,会被认为是“安全”的, 在一定比例(可设置)的数据块被确定为“安全”后,再过若干时间,安全模式结束。当检测到副本数不足的数据块时,该块会被复制直到达到最小副本数,系统中数据块的位置并不是由namenode维护的,而是以块列表形式存储在datanode中。 HDFS优点 高容错性数据自动保存多个副本,副本丢失后,自动恢复 适合批处理移动计算而非数据,数据位置暴露给计算框架(Block偏移量) 适合大数据处理GB 、TB 、甚至PB 级数据,百万规模以上的文件数量,10K+节点数量 可构建在廉价机器上通过多副本提高可靠性,提供了容错和恢复机制 HDFS缺点 低延迟数据访问HDFS不太适合于那些要求低延时(数十毫秒)访问的应用程序,因为HDFS是设计用于大吞吐量数据的,这是以一定延时为代价的。HDFS是单Master的,所有对文件的请求都要经过它,当请求多时,肯定会有延时。 小文件存取时占用NameNode 大量内存,寻道时间超过读取时间 一个文件只能有一个写者,且仅支持append Pseudo-Distributed Mode安装配置1. 安装配置JDK2. 解压hadoop-2.6.5.tar.gz[root@node01 software]# tar xf hadoop-2.6.5.tar.gz[root@node01 software]# mv hadoop-2.6.5 /opt/sxt/ 3. 在环境变量中加入hadoop安装路径1234[root@node01 hadoop-2.6.5]# vi /etc/profileexport HADOOP_PREFIX=/opt/sxt/hadoop-2.6.5export PATH=$JAVA_HOME/bin:$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin[root@node01 hadoop-2.6.5]# . /etc/profile 4. 设置ssh登录本机免秘钥123456789101112[root@node01 ~]# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa#生成了.ssh目录[root@node01 ~]# ll -adrwx------ 2 root root 4096 7月 4 12:53 .ssh[root@node01 ~]# cd .ssh[root@node01 .ssh]# ll总用量 12-rw------- 1 root root 672 7月 4 12:53 id_dsa #私钥-rw-r--r-- 1 root root 601 7月 4 12:53 id_dsa.pub #公钥-rw-r--r-- 1 root root 802 7月 3 20:47 known_hosts#想登陆哪台机器,就把公钥copy给哪台机器,登陆自身免密执行如下命令[root@node01 .ssh]# cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys 5. 修改hadoop配置文件-env.sh在-env.sh文件中配置JAVA_HOME,因为在远程调用时,不会读取/etc/profile文件,所以取不到JAVA_HOME123456[root@node01 hadoop]# cd /opt/sxt/hadoop-2.6.5/etc/hadoop/#以下3个文件中加入JAVA_HOME[root@node01 hadoop]# vi hadoop-env.sh[root@node01 hadoop]# vi mapred-env.sh [root@node01 hadoop]# vi yarn-env.shexport JAVA_HOME=/usr/java/jdk1.7.0_67 6. 修改hadoop配置文件,配置伪分布式相关的内容1234567891011121314151617181920212223242526[root@node01 hadoop]# vi core-site.xml<configuration> <!--设置namenode所在节点--> <property> <name>fs.defaultFS</name> <value>hdfs://node01:9000</value></property><!--设置hadoop存放数据的临时目录--><property> <name>hadoop.tmp.dir</name> <value>/var/sxt/hadoop/pseudo</value></property></configuration>[root@node01 hadoop]# vi hdfs-site.xml<configuration><!--设置副本数,不能超过节点数--> <property> <name>dfs.replication</name> <value>1</value></property><!—设置secondaryNode在哪个节点--><property> <name>dfs.namenode.secondary.http-address</name> <value>node01:50090</value></property></configuration> 说明:Hadoop默认把数据块的元数据和数据存放在操作系统的/tmp目录下,但操作系统的/tmp目录会定期清空,所以要做修改 name value hadoop.tmp.dir /tmp/hadoop-${user.name} dfs.namenode.name.dir file://${hadoop.tmp.dir}/dfs/name dfs.datanode.data.dir file://${hadoop.tmp.dir}/dfs/data 7. 设置datanode在哪些节点[root@node01 hadoop]# vi slavesnode01 8. 格式化文件系统123456789101112131415161718[root@node01 hadoop]# hdfs namenode –format#成功信息:Storage directory /var/sxt/hadoop/pseudo/dfs/name has been successfully formatted.[root@node01 hadoop]# cd /var/sxt/hadoop/pseudo/dfs/name/current[root@node01 current]# ll总用量 16-rw-r--r-- 1 root root 320 7月 4 13:36 fsimage_0000000000000000000-rw-r--r-- 1 root root 62 7月 4 13:36 fsimage_0000000000000000000.md5-rw-r--r-- 1 root root 2 7月 4 13:36 seen_txid-rw-r--r-- 1 root root 204 7月 4 13:36 VERSION[root@node01 current]# vi VERSION#Tue Jul 04 13:36:11 CST 2017namespaceID=160288852clusterID=CID-e2d83b00-0c46-45b2-9f86-e824c42795e8 #集群的IDcTime=0storageType=NAME_NODEblockpoolID=BP-1597373193-192.168.9.11-1499146571542layoutVersion=-60 9. 启动hdfs系统123456[root@node01 current]# start-dfs.shStarting namenodes on [node01]node01: starting namenode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-namenode-node01.outnode01: starting datanode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-datanode-node01.outStarting secondary namenodes [node01]node01: starting secondarynamenode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-secondarynamenode-node01.out 10. 查看进程是否启动12345[root@node01 current]# jps3375 SecondaryNameNode3241 DataNode3481 Jps3136 NameNode 说明:12345678910[root@node01 current]# cd /var/sxt/hadoop/pseudo/dfs/data/current[root@node01 current]# vi VERSION#Tue Jul 04 13:42:12 CST 2017storageID=DS-4fd6b46c-6567-4875-9dcf-17577873d372#与name下的VERSION中的clusterID相同,否则NN与DN不会通信clusterID=CID-e2d83b00-0c46-45b2-9f86-e824c42795e8 cTime=0datanodeUuid=e18d8595-35b6-40b3-9dde-abc56f90c77bstorageType=DATA_NODElayoutVersion=-56 11.去网页验证 HDFS的基本操作刚创建好文件系统后,只是一个空系统,存放数据的目录为空123[root@node01 ~]# cd /var/sxt/hadoop/pseudo/dfs/data/current/BP-1597373193-192.168.9.11-1499146571542/current/finalized[root@node01 finalized]# ll总用量 0 1.创建目录:[root@node01 finalized]# hdfs dfs -mkdir -p /user/root 2.上传文件:[root@node01 finalized]# hdfs dfs -put /etc/profile /user/root在finalized目录下新增了subdir0目录12345678910111213141516171819[root@node01 finalized]# ll总用量 4drwxr-xr-x 3 root root 4096 7月 4 14:01 subdir0[root@node01 finalized]# cd subdir0/[root@node01 subdir0]# ll总用量 4drwxr-xr-x 2 root root 4096 7月 4 14:01 subdir0[root@node01 subdir0]# cd subdir0/[root@node01 subdir0]# ll总用量 8-rw-r--r-- 1 root root 1950 7月 4 14:01 blk_1073741825-rw-r--r-- 1 root root 23 7月 4 14:01 blk_1073741825_1001.meta[root@node01 subdir0]# cat blk_1073741825# /etc/profile……export JAVA_HOME=/usr/java/jdk1.7.0_67export HADOOP_PREFIX=/opt/sxt/hadoop-2.6.5export PATH=$JAVA_HOME/bin:$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin…… blk_1073741825文件就是刚才上传的/etc/profile文件 3.上传文件时指定块大小为1M12[root@node01 ~]# hdfs dfs -D dfs.blocksize=1048576 -put /usr/local/software/hadoop-2.6.5.tar.gz #不写目标目录,就会上传到当前用户家目录,即/user/root,前提是该目录存在 Fully-Distributed Mode安装配置角色划分 NN SNN DN node01 √ node02 √ √ node03 √ node04 √ 补充:使用集群的时候要保证每台服务器的系统时间一致,使用data查看系统时间,data -s “yyyy-mm-dd hh:mm:ss”格式的命令修改系统时间。 准备工作如果有别的hdfs进程在运行,先停掉,比如停掉之前的伪分布式系统 [root@node01 ~]# stop-dfs.sh 1.在每台node上安装jdk并配置环境变量2.配置免秘钥登录使用node02、node03、node04使用ssh登录本机再退出,目的是让它们创建.ssh目录及known_hosts文件12345678#把node01的公钥copy到其他节点[root@node01 .ssh]# scp ./id_dsa.pub node02:/root/node01.pub[root@node01 .ssh]# scp ./id_dsa.pub node03:/root/node01.pub[root@node01 .ssh]# scp ./id_dsa.pub node04:/root/node01.pub#把node0{2,3,4}中的node01公钥内容copy到各自的authorized_keys文件中[root@node02 ~]# cat ~/node01.pub >> ~/.ssh/authorized_keys[root@node03 ~]# cat ~/node01.pub >> ~/.ssh/authorized_keys[root@node04 ~]# cat ~/node01.pub >> ~/.ssh/authorized_keys 3. 在node01中对hadoop的配置文件做修改1234567891011121314151617181920212223242526272829303132[root@node01 ~r# cd /opt/sxt/hadoop-2.6.5/etc#将hadoop文件夹copy为hadoop-pseudo,hdfs默认会找hadoop目录中的配置文件[root@node01 etc]# cp -r hadoop hadoop-pseudo[root@node01 etc]# cd hadoop#如有必要,在-env.sh文件中配置JAVA_HOME[root@node01 hadoop]# vi core-site.xml<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://node01:9000</value> </property> <!—数据临时存放目录最好设置为空目录--> <property> <name>hadoop.tmp.dir</name> <value>/var/sxt/hadoop/full</value> </property></configuration>[root@node01 hadoop]# vi hdfs-site.xml<configuration> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.secondary.http-address</name> <value>node02:50090</value> </property></configuration>[root@node01 hadoop]# vi slavesnode02node03node04 4.将node01的hadoop安装目录分发给其他节点123[root@node01 sxt]# scp -r hadoop-2.6.5 node02:`pwd`[root@node01 sxt]# scp -r hadoop-2.6.5 node03:`pwd`[root@node01 sxt]# scp -r hadoop-2.6.5 node04:`pwd` 5.格式化文件系统(只在第一次启动时格式化)[root@node01 sxt]# hdfs namenode -format 6.启动服务12345678[root@node01 sxt]# start-dfs.shStarting namenodes on [node01]node01: starting namenode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-namenode-node01.outnode04: starting datanode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-datanode-node04.outnode02: starting datanode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-datanode-node02.outnode03: starting datanode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-datanode-node03.outStarting secondary namenodes [node02]node02: starting secondarynamenode, logging to /opt/sxt/hadoop-2.6.5/logs/hadoop-root-secondarynamenode-node02.out 7.进行一些操作1234[root@node01 ~]# for i in `seq 100000`;do echo "$i Hello sxt You Are Good" >>/tmp/hello.txt;done[root@node01 ~]# hdfs dfs -D dfs.blocksize=1048576 -put /tmp/hello.txt#hdfs切割文件时,按照字节来切分,思考,如果是中文,怎么切割? 去node02上查看Block11234567[root@node02 ~]# cd /var/sxt/hadoop/full/dfs/data/current/BP-362752143-192.168.9.11-1499153025245/current/finalized/subdir0/subdir0[root@node02 subdir0]# vi blk_1073741826e Good36542 Hello sxt You Are Good……72698 Hello sxt You Are Good72699 Hello sxt Hadoop 2.0产生背景Hadoop 1.0中HDFS和MapReduce在高可用、扩展性等方面存在问题HDFS存在的问题:NameNode单点故障,难以应用于在线场景NameNode压力过大,且内存受限,影响系统扩展性MapReduce存在的问题:JobTracker访问压力大,影响系统扩展性难以支持除MapReduce之外的计算框架,比如Spark、Storm等 Hadoop 1.0和2.0架构比较 Hadoop 2.x由HDFS、MapReduce和YARN三个分支构成。HDFS:NN Federation(联邦)、HA(只支持2个节点,3.0实现了一主多从)。MapReduce:运行在YARN上的MR;离线计算,基于磁盘I/O计算。YARN:资源管理系统。 HDFS 2.xHDFS 2.x解决HDFS 1.0中单点故障和内存受限问题。解决单点故障:HDFS HA:通过主备NameNode解决,如果主NameNode发生故障,则切换到备NameNode上。解决内存受限问题:HDFS Federation(联邦)水平扩展,支持多个NameNode。每个NameNode分管一部分目录,所有NameNode共享所有DataNode存储资源。2.x仅是架构上发生了变化,使用方式不变,对HDFS使用者透明,HDFS 1.x中的命令和API仍可以使用。 HDFS 2.x Federation通过多个namenode/namespace把元数据的存储和管理分散到多个节点中,使到namenode/namespace可以通过增加机器来进行水平扩展。能把单个namenode的负载分散到多个节点中,在HDFS数据规模较大的时候不会也降低HDFS的性能。可以通过多个namespace来隔离不同类型的应用,把不同类型应用的HDFS元数据的存储和管理分派到不同的namenode中。核心:多台namenode管理的是同一个集群! HDFS 2.x HA(High Availability)主备NameNode,解决单点故障:ANN:ActiveNameNode,对外提供服务,SNN同步ANN元数据,以待切换。SNN:StandbyNameNode,完成了edits.log文件的合并产生新的image,推送回ANN。JNN:JournalNode,ANN和SNN通过JNN集群来共享信息。两个NameNode为了数据同步,会通过一组称作JournalNodes的独立进程进行相互通信。当ANN的命名空间有任何修改时,会告知大部分的JournalNodes进程。SNN有能力读取JNs中的变更信息,并且一直监控edit log的变化,把变化应用于自己的命名空间。SNN可以确保在集群出错时,命名空间状态已经完全同步了。在HA架构里面SecondaryNameNode这个冷备角色已经不存在了,为了保持SNN实时的与ANN的元数据保持一致,他们之间交互通过一系列守护的轻量级进程JournalNode。基本原理就是用2N+1台JN存储editlog,每次写数据操作有超过半数(>=N+1)返回成功时即认为该次写成功,数据不会丢失了。当然这个算法所能容忍的是最多有N台机器挂掉,如果多于N台挂掉,这个算法就失效了。任何修改操作在ANN上执行时,JN进程同时也会记录修改log到至少半数以上的JN中,这时SNN监测到JN里面的同步log发生变化了会读取JN里面的修改log,然后同步到自己的的目录镜像树里面。当发生故障时,ANN挂掉后,SNN会在它成为ANN前,读取所有的JN里面的修改日志,这样就能高可靠的保证与挂掉的NN的目录镜像树一致,然后无缝的接替它的职责,维护来自客户端请求,从而达到一个高可用的目的。DN:同时向两个NameNode汇报数据块信息(位置)。两个NN之间的切换:手动切换:通过命令实现主备之间的切换,可以用HDFS升级等场合。自动切换:基于Zookeeper实现。HDFS 2.x提供了ZookeeperFailoverController角色,部署在每个NameNode的节点上,作为一个deamon进程, 简称zkfc,zkfc主要包括三个组件:HealthMonitor:监控NameNode是否处于unavailable或unhealthy状态。当前通过RPC调用NN相应的方法完成。ActiveStandbyElector:管理和监控自己在ZK中的状态。ZKFailoverController:它订阅HealthMonitor和ActiveStandbyElector的事件,并管理NameNode的状态。ZKFailoverController主要职责: 健康监测:周期性的向它监控的NN发送健康探测命令,从而来确定某个NameNode是否处于健康状态,如果机器宕机,心跳失败,那么zkfc就会标记它处于一个不健康的状态 记它会话管理:如果NN是健康的,zkfc就会在zookeeper中保持一个打开的会话,如果NameNode同时还是Active状态的,那么zkfc还会在Zookeeper中占有一个类型为短暂类型的znode,当这个NN挂掉时,这个znode将会被删除,然后备用的NN,将会得到这把锁,升级为主NN,同时标记状态为Active,当宕机的NN新启动时,它会再次注册zookeper,发现已经有znode锁了,便会自动变为Standby状态,如此往复循环,保证高可靠,需要注意,目前仅仅支持最多配置2个NN. master选举:如上所述,通过在zookeeper中维持一个短暂类型的znode,来实现抢占式的锁机制,从而判断那个NameNode为Active状态。 HDFS 2.x HA 搭建角色划分步骤: 1.在node02上解压zookeeper包[root@node02 software]# tar xf zookeeper-3.4.6.tar.gz[root@node02 ###### [root@node02 software]# mv zookeeper-3.4.6 /opt/sxt/ 2.修改/etc/profile文件,配置zookeeper路径1234567[root@node02 sxt]# vi + /etc/profileexport JAVA_HOME=/usr/java/jdk1.7.0_67export HADOOP_PREFIX=/opt/sxt/hadoop-2.6.5export ZOOKEEPER_PREFIX=/opt/sxt/zookeeper-3.4.6export PATH=$JAVA_HOME/bin:$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin:$ZOOKEEPER_PREFIX/bin[root@node02 sxt]# . /etc/profile 3.配置zookeeper123456789101112[root@node02 sxt]# cd zookeeper-3.4.6/conf[root@node02 conf]# cp zoo_sample.cfg zoo.cfg[root@node02 conf]# vi zoo.cfg# zookeeper数据存放目录dataDir=/var/sxt/zookeeper# 在文件末尾追加以下内容server.1=node02:2888:3888server.2=node03:2888:3888server.3=node04:2888:3888[root@node02 conf]# mkdir /var/sxt/zookeeper[root@node02 conf]# cd /var/sxt/zookeeper/[root@node02 zookeeper]# echo 1 > myid 4.配置node03和node04的zookeeper1234567891011[root@node02 sxt]# cd /opt/sxt[root@node02 sxt]# scp -r zookeeper-3.4.6 node03:`pwd`[root@node02 sxt]# scp -r zookeeper-3.4.6 node04:`pwd`[root@node02 sxt]# scp /etc/profile node03:/etc[root@node02 sxt]# scp /etc/profile node04:/etc[root@node03 ~]# . /etc/profile[root@node03 ~]# mkdir /var/sxt/zookeeper[root@node03 ~]# echo 2 > /var/sxt/zookeeper/myid[root@node04 ~]# . /etc/profile[root@node04 ~]# mkdir /var/sxt/zookeeper[root@node04 ~]# echo 3 > /var/sxt/zookeeper/myid 5.启动zookeeper集群12345678910[root@node02 ~]# zkServer.sh start[root@node03 ~]# zkServer.sh start[root@node04 ~]# zkServer.sh start# 当正常启动服务的集群过半(我们这里过半是两台)时,zookeeper就可以决策出主从关系,当启动的服务不过半(我们这里是只启动一台)时,zkServer的状态是报错状态[root@node02 ~]# zkServer.sh statusMode: follower[root@node03 ~]# zkServer.sh statusMode: follower[root@node04 ~]# zkServer.sh statusMode: leader 6.配置hdfs,在Fully-Distributed Mode基础上进行修改123[root@node01 ~]# cd /opt/sxt/hadoop-2.6.5/etc[root@node01 etc]# cp -r hadoop hadoop-full[root@node01 etc]# cd hadoop 7.修改hdfs-site.xml文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667[root@node01 hadoop]# vi hdfs-site.xml<configuration> <!--副本数--> <property> <name>dfs.replication</name> <value>2</value> </property><!-- secondary namenode不需要了,有这个配置会报错--><!-- <property> <name>dfs.namenode.secondary.http-address</name> <value>node02:50090</value></property>--><!--以下配置对两台NN所在的节点做了映射--><property> <name>dfs.nameservices</name> <value>mycluster</value></property><property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value></property><property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node01:8020</value></property><property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>node02:8020</value></property><property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>node01:50070</value></property><property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>node02:50070</value></property><!--指定JNN集群位置--><property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://node01:8485;node02:8485;node03:8485/mycluster</value></property><!--以下配置说明NN主从切换的方法--><property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value></property><property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value></property><property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/root/.ssh/id_dsa</value></property><!--以下配置指定NN主从切换为自动切换--><property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value></property></configuration> 8.修改core-site.xml123456789101112131415161718[root@node01 hadoop]# vi core-site.xml<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <!--配置zookeeper集群--> <property> <name>ha.zookeeper.quorum</name> <value>node02:2181,node03:2181,node04:2181</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/var/sxt/hadoop/ha</value> </property></configuration> 9.给node02生成公钥并分发12345678[root@node02 ~]# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa[root@node02 ~]# cd .ssh[root@node02 .ssh]# scp ./id_dsa.pub node01:/root/node02.pub[root@node02 .ssh]# scp ./id_dsa.pub node03:/root/node02.pub[root@node02 .ssh]# scp ./id_dsa.pub node04:/root/node02.pub[root@node01 ~]# cat ~/node02.pub >> ~/.ssh/authorized_keys[root@node03 ~]# cat ~/node02.pub >> ~/.ssh/authorized_keys[root@node04 ~]# cat ~/node02.pub >> ~/.ssh/authorized_keys 还有更简单的方法,在node01中,vi authorized_keys,复制文件中的内容,在末尾粘贴3份,只修改最后的节点名称为node02、node03、node04然后把.ssh文件夹分发给node02、node03、node04123[root@node01 .ssh]# scp ./* node02:`pwd`[root@node01 .ssh]# scp ./* node03:`pwd`[root@node01 .ssh]# scp ./* node04:`pwd` 10.将hadoop文件夹分发1234[root@node01 ~]# cd /opt/sxt[root@node01 sxt]# scp -r hadoop-2.6.5/ node02:`pwd`[root@node01 sxt]# scp -r hadoop-2.6.5/ node03:`pwd`[root@node01 sxt]# scp -r hadoop-2.6.5/ node04:`pwd` 11.启动JNN集群[root@node01 ~]# hadoop-daemon.sh start journalnode[root@node02 ~]# hadoop-daemon.sh start journalnode[root@node03 ~]# hadoop-daemon.sh start journalnode 12.在node01中执行格式化[root@node01 ~]# hdfs namenode –format[root@node01 ~]# hadoop-daemon.sh start namenode #手动启动namenode,只启动本机 13.在node02中执行如下命令[root@node02 ~]# hdfs namenode -bootstrapStandby 14.格式化zkfc[root@node01 ~]# hdfs zkfc –formatZK[root@node01 ~]# hadoop-daemon.sh start zkfc #手动启动zkfc 15.启动hdfs集群[root@node01 ~]# start-dfs.sh验证:杀死node02的namenode进程:12345678[root@node02 ~]# jps2284 Jps2090 JournalNode1951 NameNode1317 QuorumPeerMain2197 DFSZKFailoverController2012 DataNode[root@node02 ~]# kill -9 1951 SNN变成了ANN!恢复node02的NameNode进程:12345678[root@node02 ~]# hadoop-daemon.sh start namenode[root@node02 ~]# jps2407 Jps2090 JournalNode2330 NameNode1317 QuorumPeerMain2197 DFSZKFailoverController2012 DataNode node02并不会切换回ANN,而是成为了SNN关闭node01的namenode进程后,node02也会变为ANN命令行验证:12[root@node01 ~]# hdfs dfs -mkdir -p /user/root[root@node01 ~]# hdfs dfs -D dfs.blocksize=1048576 -put nginx Hadoop API参见我的另外两篇博客: HADOOP–mac下eclipse连接hadoop集群开发API HADOOP–mac下IDEA连接hadoop集群开发API MapReduce语义MapTask & ReduceTask相同的Key为一组,调用一次Reduce MapReduce原理 Map的数量=文件大小/split大小,split和block都有偏移量一切都是从最上方的user program开始的,user program链接了MapReduce库,实现了最基本的Map函数和Reduce函数。图中执行的顺序都用数字标记了。 MapReduce库先把user program的输入文件划分为M份(M为用户定义),每一份通常有16MB到64MB,如图左方所示分成了split0-4;然后使用fork将用户进程拷贝到集群内其它机器上。 user program的副本中有一个称为master,其余称为worker,master是负责调度的,为空闲worker分配作业(Map作业或者Reduce作业),worker的数量也是可以由用户指定的。 被分配了Map作业的worker,开始读取对应分片的输入数据,Map作业数量是由M决定的,和split一一对应;Map作业从输入数据中抽取出键值对,每一个键值对都作为参数传递给map函数,map函数产生的中间键值对被缓存在内存中。 缓存的中间键值对会被定期写入本地磁盘,而且被分为R个区,R的大小是由用户定义的,将来每个区会对应一个Reduce作业;这些中间键值对的位置会被通报给master,master负责将信息转发给Reduce worker。 master通知分配了Reduce作业的worker它负责的分区在什么位置(肯定不止一个地方,每个Map作业产生的中间键值对都可能映射到所有R个不同分区),当Reduce worker把所有它负责的中间键值对都读过来后,先对它们进行排序,使得相同键的键值对聚集在一起。因为不同的键可能会映射到同一个分区也就是同一个Reduce作业(谁让分区少呢),所以排序是必须的。 reduce worker遍历排序后的中间键值对,对于每个唯一的键,都将键与关联的值传递给reduce函数,reduce函数产生的输出会添加到这个分区的输出文件中。 当所有的Map和Reduce作业都完成了,master唤醒正版的user program,MapReduce函数调用返回user program的代码。 所有执行完毕后,MapReduce输出放在了R个分区的输出文件中(分别对应一个Reduce作业)。用户通常并不需要合并这R个文件,而是将其作为输入交给另一个MapReduce程序处理。整个过程中,输入数据是来自底层分布式文件系统(HDFS)的,中间数据是放在本地文件系统的,最终输出数据是写入底层分布式文件系统(HDFS)的。而且我们要注意Map/Reduce作业和map/reduce函数的区别:Map作业处理一个输入数据的分片,可能需要调用多次map函数来处理每个输入键值对;Reduce作业处理一个分区的中间键值对,期间要对每个不同的键调用一次reduce函数,Reduce作业最终也对应一个输出文件。 要点:Map:读懂数据、映射为KV模型、并行分布式、计算向数据移动Reduce:数据全量/分量加工、相同的Key汇聚到一个Reduce中、相同的Key调用一次reduce方法、Reduce中可以包含不同的key、排序实现key的汇聚K、V:使用自定义数据类型,作为参数传递,节省开发成本,提高程序自由度Writable序列化:分布式程序数据交互Comparable比较器:实现具体排序(字典序,数值序等) MapReduce任务中Shuffle和排序的过程流程分析: Map端: 每个输入分片会让一个map任务来处理,默认情况下,以HDFS的一个块的大小(默认为64M)为一个分片,当然我们也可以设置块的大小。map输出的结果会暂且放在一个环形内存缓冲区中(该缓冲区的大小默认为100M,由io.sort.mb属性控制),当该缓冲区快要溢出时(默认为缓冲区大小的80%,由io.sort.spill.percent属性控制),会在本地文件系统中创建一个溢出文件,将该缓冲区中的数据写入这个文件。 在写入磁盘之前,线程首先根据reduce任务的数目将数据划分为相同数目的分区,也就是一个reduce任务对应一个分区的数据。分区就是给数据打一个标签,让它被某个固定的reduce执行,这样做是为了避免有些reduce任务分配到大量数据,而有些reduce任务却分到很少数据,甚至没有分到数据的尴尬局面。其实分区就是对数据进行hash的过程。然后对每个分区中的数据进行排序,如果此时设置了Combiner,将排序后的结果进行combine操作,这样做的目的是让尽可能少的数据写入到磁盘。 当map任务输出最后一个记录时,可能会有很多的溢出文件,这时需要将这些文件合并。合并的过程中会不断地进行排序和combine操作,目的有两个:1)尽量减少每次写入磁盘的数据量;2)尽量减少下一复制阶段网络传输的数据量。最后合并成了一个已分区且已排序的文件。为了减少网络传输的数据量,这里可以将数据压缩,只要将mapred.compress.map.out设置为true就可以了。 将分区中的数据拷贝给相对应的reduce任务。分区中的数据怎么知道它对应的reduce是哪个呢?其实map任务一直和其父进程TaskTracker保持联系,而TaskTracker又一直和JobTracker保持心跳。所以JobTracker中保存了整个集群中的宏观信息。只要reduce任务向JobTracker获取对应的map输出位置就ok了哦。到这里,map端就分析完了。那到底什么是Shuffle呢?Shuffle的中文意思是“洗牌”,一个map产生的数据,结果通过hash过程分区却分配给了不同的reduce任务,就是一个对数据洗牌的过程。 Reduce端: Reduce会接收到不同map任务传来的数据,并且每个map传来的数据都是有序的。如果reduce端接受的数据量相当小,则直接存储在内存中(缓冲区大小由mapred.job.shuffle.input.buffer.percent属性控制,表示用作此用途的堆空间的百分比),如果数据量超过了该缓冲区大小的一定比例(由mapred.job.shuffle.merge.percent决定),则对数据合并后溢写到磁盘中。 随着溢写文件的增多,后台线程会将它们合并成一个更大的有序的文件,这样做是为了给后面的合并节省时间。其实不管在map端还是reduce端,MapReduce都是反复地执行排序,合并操作,现在终于明白了有些人为什么会说:排序是hadoop的灵魂。 合并的过程中会产生许多的中间文件(写入磁盘了),但MapReduce会让写入磁盘的数据尽可能地少,并且最后一次合并的结果并没有写入磁盘,而是直接输入到reduce函数。 MapReduce运行框架计算框架Mapper:计算框架Reducer: 在客户端启动一个作业。 向JobTracker请求一个Job ID。 将运行作业所需要的资源文件复制到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker专门为该作业创建的文件夹中。文件夹名为该作业的Job ID。JAR文件默认会有10个副本(mapred.submit.replication属性控制);输入划分信息告诉了JobTracker应该为这个作业启动多少个map任务等信息。 JobTracker接收到作业后,将其放在一个作业队列里,等待作业调度器对其进行调度(很像微机中的进程调度),当作业调度器根据自己的调度算法调度到该作业时,会根据输入划分信息为每个划分创建一个map任务,并将map任务分配给TaskTracker执行。对于map和reduce任务,TaskTracker根据主机核的数量和内存的大小有固定数量的map槽和reduce槽。这里需要强调的是:map任务不是随随便便地分配给某个TaskTracker的,这里有个概念叫:数据本地化(Data-Local)。意思是:将map任务分配给含有该map处理的数据块的TaskTracker上,同时将程序JAR包复制到该TaskTracker上来运行,这叫“计算移动,数据不移动”。而分配reduce任务时并不考虑数据本地化。 TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行,同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息。当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”。当JobClient查询状态时,它将得知任务已完成,便显示一条消息给用户。 以上是在客户端、JobTracker、TaskTracker的层次来分析MapReduce的工作原理的。 MR 1.x中:JobTracker核心、主、单点、调度所有的作业、监控整个集群的资源负载TaskTracker从,自身节点资源管理、和JobTracker心跳,汇报资源,获取TaskClient作业为单位、规划作业计算分布、提交作业资源到HDFS、最终提交作业到JobTracker弊端:JobTracker负载过重,单点故障;资源管理与计算调度强耦合,其他计算框架需要重复实现资源管理;不同框架对资源不能全局管理。 Yarn资源调度MR 2.x:On YARNYARN:解耦资源与计算ResourceManager:主,核心,集群节点资源管理NodeManager:与RM汇报资源,管理Container生命周期,计算框架中的角色都以Container表示Container:其中信息包括节点NM,CPU,MEM,I/O大小,启动命令等,默认NodeManager启动线程监控Container大小,超出申请资源额度,则kill,支持Linux内核的CgroupMR :MR-ApplicationMaster-Container,作业为单位,避免单点故障,负载到不同的节点,创建Task需要和RM申请资源(Container),Task:也是运行在Container中Client:RM-Client:请求资源创建AM,AM-Client:与AM交互 搭建基于yarn的mapreduce框架角色划分在HDFS 2.x HA的基础上进行配置 1.编辑mapred-site.xml123456789[root@node01 ~]# cd /opt/sxt/hadoop-2.6.5/etc/hadoop/[root@node01 hadoop]# cp mapred-site.xml.template mapred-site.xml[root@node01 hadoop]# vi mapred-site.xml<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration> 2.编辑yarn-site.xml12345678910111213141516171819202122232425262728293031323334353637[root@node01 hadoop]# vi yarn-site.xml<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <property> <name>yarn.resourcemanager.cluster-id</name> <value>cluster1</value> </property> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>node03</value> </property> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>node04</value> </property> <property> <name>yarn.resourcemanager.zk-address</name> <value>node02:2181,node03:2181,node04:2181</value> </property></configuration> 3.分发配置123456[root@node01 hadoop]# scp mapred-site.xml yarn-site.xml node02:`pwd` [root@node01 hadoop]# scp mapred-site.xml node02:`pwd`[root@node01 hadoop]# scp mapred-site.xml yarn-site.xml node03:`pwd` [root@node01 hadoop]# scp mapred-site.xml node03:`pwd`[root@node01 hadoop]# scp mapred-site.xml yarn-site.xml node04:`pwd`[root@node01 hadoop]# scp mapred-site.xml node04:`pwd` 4.启动yarn[root@node01 hadoop]# start-yarn.sh 5.手工启动ResourceManager[root@node03 ~]# yarn-daemon.sh start resourcemanager配置成功! 访问node04:8088,出现提示后跳转到node03(active) Kill node03的RM进程,node04的RM变为active,再次启动node03的RM进程,主从关系不变,即node04是active,node03是standby。 运行框架自带的例子程序12345678910111213141516171819202122232425262728293031323334353637[root@node01 ~]# cd pwd/opt/sxt/hadoop-2.6.5/share/hadoop/mapreduce[root@node01 mapreduce]# ll -htotal 4.8M-rw-r--r-- 1 root root 517K Jul 8 12:00 hadoop-mapreduce-client-app-2.6.5.jar-rw-r--r-- 1 root root 673K Jul 8 12:00 hadoop-mapreduce-client-common-2.6.5.jar-rw-r--r-- 1 root root 1.5M Jul 8 12:00 hadoop-mapreduce-client-core-2.6.5.jar-rw-r--r-- 1 root root 254K Jul 8 12:00 hadoop-mapreduce-client-hs-2.6.5.jar-rw-r--r-- 1 root root 27K Jul 8 12:00 hadoop-mapreduce-client-hs-plugins-2.6.5.Jar-rw-r--r-- 1 root root 60K Jul 8 12:00 hadoop-mapreduce-client-jobclient-2.6.5.jar-rw-r--r-- 1 root root 1.5M Jul 8 12:00 hadoop-mapreduce-client-jobclient-2.6.5-tests.jar-rw-r--r-- 1 root root 67K Jul 8 12:00 hadoop-mapreduce-client-shuffle-2.6.5.jar-rw-r--r-- 1 root root 287K Jul 8 12:00 hadoop-mapreduce-examples-2.6.5.jar[root@node01 mapreduce]# vi /tmp/hello.txtJava C C++ HadoopMySQL Oracle Java JavaC C C MySQL MySQL Spring SpringMVC JavaC++[root@node01 mapreduce]# hdfs dfs -mkdir /user/root/wordcount/input[root@node01 mapreduce]# hdfs dfs -put /tmp/hello.txt /user/root/wordcount/input/wordcount.txt[root@node01 mapreduce]# hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount /user/root/wordcount/input /user/root/wordcount/output[root@node01 mapreduce]# hdfs dfs -ls /user/root/wordcount/outputFound 2 items-rw-r--r-- 2 root supergroup 0 2017-07-10 16:18 /user/root/wordcount/output/_SUCCESS-rw-r--r-- 2 root supergroup 64 2017-07-10 16:18 /user/root/wordcount/output/part-r-00000[root@node01 mapreduce]# hdfs dfs -cat /user/root/wordcount/output/part-r-00000C 4C++ 2Hadoop 1Java 4MySQL 3Oracle 1Spring 1SpringMVC 1 Hadoop案例:WordCountJob类12345678910111213141516171819202122232425262728293031323334public class MyJob { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(true); Job job = Job.getInstance(conf); // Create a new Job //Job job = Job.getInstance(); job.setJarByClass(MyJob.class); // Specify various job-specific parameters job.setJobName("myjob"); //job.setInputPath(new Path("in")); //job.setOutputPath(new Path("out")); Path input = new Path("/user/root/wordcount/input"); FileInputFormat.addInputPath(job, input); Path output = new Path("/user/root/wordcount/output"); if ( output.getFileSystem(conf).exists(output)){ output.getFileSystem(conf).delete(output, true); } FileOutputFormat.setOutputPath(job, output); job.setMapperClass(MyMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(MyReducer.class); // Submit the job, then poll for progress until the job is complete job.waitForCompletion(true); }} Mapper类1234567891011121314public class MyMapper extends Mapper<Object, Text, Text, IntWritable>{ private final static IntWritable ONE = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, ONE); } }} Reducer类123456789101112131415public class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } 在集群中运行程序步骤 导出Jar包 把jar包上传到服务器 执行jar包 123456789101112131415[root@node01 ~]# hadoop jar /var/sxt/hadoop/jar/WordCount.jar com.sxt.hadoop.mapreduce.wordcount.MyJob[root@node01 ~]# hdfs dfs -ls /user/root/wordcount/outputFound 2 items-rw-r--r-- 2 root supergroup 0 2017-07-10 20:38 /user/root/wordcount/output/_SUCCESS-rw-r--r-- 2 root supergroup 64 2017-07-10 20:38 /user/root/wordcount/output/part-r-00000[root@node01 ~]# hdfs dfs -cat /user/root/wordcount/output/part-r-00000C 4C++ 2Hadoop 1Java 4MySQL 3Oracle 1Spring 1SpringMVC 1 MapReduce源码分析ClientwaitForCompletion()调用submit()123456789101112131415public boolean waitForCompletion(boolean verbose) throws IOException, InterruptedException, ClassNotFoundException { if (state == JobState.DEFINE) { //重点是提交的过程submit(); }if (verbose) { //监控并打印执行过程 monitorAndPrintJob(); } else { …… } return isSuccessful(); } submit()调用submitJobInternal()方法把作业提交到集群123456789101112131415161718public void submit() throws IOException, InterruptedException, ClassNotFoundException {ensureState(JobState.DEFINE);//判断使用的是hadoop 1.x还是2.x的jar包setUseNewAPI();//连接集群 connect(); final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(), cluster.getClient()); status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() { public JobStatus run() throws IOException, InterruptedException, ClassNotFoundException { //把作业提交到集群 return submitter.submitJobInternal(Job.this, cluster); } }); …… } submitJobInternal()方法详解123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209JobStatus submitJobInternal(Job job, Cluster cluster) throws ClassNotFoundException, InterruptedException, IOException {//validate the jobs output specs//Checking the input and output specifications of the job. 检查输入输出路径 checkSpecs(job); Configuration conf = job.getConfiguration(); addMRFrameworkToDistributedCache(conf); Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf); //configure the command line options correctly on the submitting dfs InetAddress ip = InetAddress.getLocalHost();if (ip != null) { //封装提交的信息 submitHostAddress = ip.getHostAddress(); submitHostName = ip.getHostName(); conf.set(MRJobConfig.JOB_SUBMITHOST,submitHostName); conf.set(MRJobConfig.JOB_SUBMITHOSTADDR,submitHostAddress); } JobID jobId = submitClient.getNewJobID();job.setJobID(jobId);//获得提交的目录 Path submitJobDir = new Path(jobStagingArea, jobId.toString()); JobStatus status = null; …… //copy配置文件 copyAndConfigureFiles(job, submitJobDir); Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir); // Create the splits for the job创建切片 LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir)); //创建切片的方法 int maps = writeSplits(job, submitJobDir);writeSplits()调用writeNewSplits()private int writeSplits(org.apache.hadoop.mapreduce.JobContext job, Path jobSubmitDir) throws IOException, InterruptedException, ClassNotFoundException { JobConf jConf = (JobConf)job.getConfiguration();int maps;//根据前面的信息选择使用1.x或者2.x的配置 if (jConf.getUseNewMapper()) { maps = writeNewSplits(job, jobSubmitDir); } else { maps = writeOldSplits(jConf, jobSubmitDir); } return maps;}writeNewSplits()private <T extends InputSplit> int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException, InterruptedException, ClassNotFoundException {Configuration conf = job.getConfiguration();//通过反射得到InputFormatClass InputFormat<?, ?> input = ReflectionUtils.newInstance(job.getInputFormatClass(), conf);getInputFormatClass() public Class<? extends InputFormat<?,?>> getInputFormatClass() throws ClassNotFoundException {return (Class<? extends InputFormat<?,?>>)//如果用户设置过InputFormat,//job.setInputFormatClass(cls);//就使用用户设置的//否则使用默认的Text conf.getClass(INPUT_FORMAT_CLASS_ATTR, TextInputFormat.class); }List<InputSplit> splits = input.getSplits(job);getSplits() public List<InputSplit> getSplits(JobContext job) throws IOException {Stopwatch sw = new Stopwatch().start();//在用户没有干预的情况下,值为1long minSize = Math.max(getFormatMinSplitSize(),getMinSplitSize(job));/*protected long getFormatMinSplitSize() { return 1; } public static long getMinSplitSize(JobContext job) {如果用户设置了,用用户设置的值,否则使用1//FileInputFormat.setMinInputSplitSize(job, size);return job.getConfiguration().getLong(SPLIT_MINSIZE, 1L); }*/long maxSize = getMaxSplitSize(job); /* 如果用户设置了,去用户的值,否则去一个无限大的值public static long getMaxSplitSize(JobContext context) { return context.getConfiguration().getLong(SPLIT_MAXSIZE,Long.MAX_VALUE); }*/ // generate splits List<InputSplit> splits = new ArrayList<InputSplit>();List<FileStatus> files = listStatus(job);//迭代用户给的目录下的所有文件,得到每个文件的//BlockLocations for (FileStatus file: files) { Path path = file.getPath(); long length = file.getLen(); if (length != 0) { BlockLocation[] blkLocations; if (file instanceof LocatedFileStatus) { blkLocations = ((LocatedFileStatus) file).getBlockLocations(); } else { FileSystem fs = path.getFileSystem(job.getConfiguration()); blkLocations = fs.getFileBlockLocations(file, 0, length); } if (isSplitable(job, path)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); /* 在用户没有干预的情况下 取maxSize和blockSize的最小值,默认情况下为blockSize 取blockSize和minSize的最大值,最后结果为blockSize protected long computeSplitSize(long blockSize, long minSize,long maxSize) {return Math.max(minSize, Math.min(maxSize, blockSize)); }*/ long bytesRemaining = length; while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { //计算切片属于哪个block int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); /*protected int getBlockIndex(BlockLocation[] blkLocations,long offset) { 判断offset在block块的偏移量的哪个范围 for (int i = 0 ; i < blkLocations.length; i++) { // is the offset inside this block? if ((blkLocations[i].getOffset() <= offset) && (offset < blkLocations[i].getOffset() + blkLocations[i].getLength())){ return i; } }BlockLocation last =blkLocations[blkLocations.length -1]; long fileLength = last.getOffset() + last.getLength() -1;throw new IllegalArgumentException("Offset " + offset +" is outside of file (0.." + fileLength + ")"); }*/ splits.add(makeSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts())); bytesRemaining -= splitSize; } if (bytesRemaining != 0) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); //创建切片 //切片信息包括文件名,偏移量,大小,位置信息 splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts())); } } else { // not splitable …… } } else { //Create empty hosts array for zero length files …… } } …… return splits; } ……conf.setInt(MRJobConfig.NUM_MAPS, maps); LOG.info("number of splits:" + maps); …… // Write job file to submit dir writeConf(conf, submitJobFile); // Now, actually submit the job (using the submit name) printTokens(jobId, job.getCredentials()); //之前都是提交前的准备,最终提交作业 status = submitClient.submitJob( jobId, submitJobDir.toString(), job.getCredentials()); ……} 总的来说,客户端做了以下几件事:配置完善检查路径计算split:maps资源提交到HDFS提交任务然后,AppMaster根据split列表信息向ResourceManager申请资源,RS创建container,然后AppMaster启动container,把MapReducer任务放进去。 总结图 Job类的继承关系123456789101112public class Job extends JobContextImpl implements JobContextpublic class JobContextImpl implements JobContextpublic interface JobContext extends MRJobConfig在interface MRJobConfig中定义了许多配置的默认值:MAPTASK内存默认1G(调优可以更改)public static final String MAP_MEMORY_MB = "mapreduce.map.memory.mb";public static final int DEFAULT_MAP_MEMORY_MB = 1024;Reduce内存默认1G,这个默认数值太小,应该调整public static final String REDUCE_MEMORY_MB = "mapreduce.reduce.memory.mb";public static final int DEFAULT_REDUCE_MEMORY_MB = 1024; Map-input123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218Map类最核心的方法就是run():public void run(Context context) throws IOException, InterruptedException { setup(context); try { while (context.nextKeyValue()) { map(context.getCurrentKey(), context.getCurrentValue(), context); } } finally { cleanup(context); }}context.nextKeyValue():map的输入context.write(k,v):map的输出hadoop-mapreduce-client-core-2.6.5.jarorg.apache.hadoop.mapred包MapTask类中是Map的源码,核心方法:run()@Override public void run(final JobConf job, final TaskUmbilicalProtocol umbilical) throws IOException, ClassNotFoundException, InterruptedException { this.umbilical = umbilical; if (isMapTask()) { // If there are no reducers then there won't be any sort. // Hence the map // phase will govern the entire attempt's progress. if (conf.getNumReduceTasks() == 0) { mapPhase = getProgress().addPhase("map", 1.0f); } else { // If there are reducers then the entire attempt's progress will be // split between the map phase (67%) and the sort phase (33%). // map阶段占67%,排序阶段占33% mapPhase = getProgress().addPhase("map", 0.667f); sortPhase = getProgress().addPhase("sort", 0.333f); } } TaskReporter reporter = startReporter(umbilical); boolean useNewApi = job.getUseNewMapper(); initialize(job, getJobID(), reporter, useNewApi); // check if it is a cleanupJobTask……// 如果使用了新版本的API if (useNewApi) { runNewMapper(job, splitMetaInfo, umbilical, reporter);private <INKEY,INVALUE,OUTKEY,OUTVALUE> void runNewMapper(final JobConf job, final TaskSplitIndex splitIndex, final TaskUmbilicalProtocol umbilical, TaskReporter reporter ) throws IOException, ClassNotFoundException, InterruptedException { // 1.make a task context so we can get the classes org.apache.hadoop.mapreduce.TaskAttemptContext taskContext = new org.apache.hadoop.mapreduce.task.TaskAttemptContextImpl(job, getTaskID(),reporter); // 2.make a mapperorg.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper =(org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>)ReflectionUtils.newInstance(taskContext.getMapperClass(), job);//如果用户设置了,就用用户的,否则用默认的//如果使用默认mapper,key直接输出,不进行任何处理 return (Class<? extends Mapper<?,?,?,?>>) conf.getClass(MAP_CLASS_ATTR, Mapper.class); // 3.make the input formatorg.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE> inputFormat =(org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE>)ReflectionUtils.newInstance(taskContext.getInputFormatClass(), job);//如果用户设置了,就用用户的,否则用默认的(TextInputFormat) // 4.rebuild the input split org.apache.hadoop.mapreduce.InputSplit split = null; split = getSplitDetails(new Path(splitIndex.getSplitLocation()), splitIndex.getStartOffset()); LOG.info("Processing split: " + split); org.apache.hadoop.mapreduce.RecordReader<INKEY,INVALUE> input = new NewTrackingRecordReader<INKEY,INVALUE> (split, inputFormat, reporter, taskContext);// inputFormat 创建了读取器this.real = inputFormat.createRecordReader(split, taskContext);//最终返回的是行读取器return new LineRecordReader(recordDelimiterBytes); job.setBoolean(JobContext.SKIP_RECORDS, isSkipping()); org.apache.hadoop.mapreduce.RecordWriter output = null; // 5.get an output object ……org.apache.hadoop.mapreduce.MapContext<INKEY, INVALUE, OUTKEY, OUTVALUE>mapContext =new MapContextImpl<INKEY, INVALUE, OUTKEY, OUTVALUE>(job, getTaskID(),input, output,committer,reporter, split);org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>.Context mapperContext = new WrappedMapper<INKEY, INVALUE, OUTKEY, OUTVALUE>().getMapContext( mapContext);public MapContextImpl(Configuration conf, TaskAttemptID taskid, //reader就是上面的input RecordReader<KEYIN,VALUEIN> reader, RecordWriter<KEYOUT,VALUEOUT> writer, OutputCommitter committer, StatusReporter reporter, InputSplit split) { super(conf, taskid, writer, committer, reporter); this.reader = reader; this.split = split; }@Override public boolean nextKeyValue() throws IOException, InterruptedException { return reader.nextKeyValue(); }//最终调用了LineRecordReader的nextKeyValue()public boolean nextKeyValue() throws IOException { if (key == null) { key = new LongWritable(); } key.set(pos); if (value == null) { value = new Text(); } int newSize = 0; // We always read one extra line, which lies outside the upper // split limit i.e. (end - 1) while (getFilePosition() <= end || in.needAdditionalRecordAfterSplit()) { if (pos == 0) { newSize = skipUtfByteOrderMark(); } else { newSize = in.readLine(value, maxLineLength, maxBytesToConsume(pos)); pos += newSize; } if ((newSize == 0) || (newSize < maxLineLength)) { break; } // line too long. try again LOG.info("Skipped line of size " + newSize + " at pos " + (pos - newSize)); } if (newSize == 0) { key = null; value = null; return false; } else { return true; } }try { // 6.输入初始化,准备输入流 input.initialize(split, mapperContext);real.initialize(split, context);FileSplit split = (FileSplit) genericSplit;Configuration job = context.getConfiguration();this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE);//偏移量start = split.getStart();end = start + split.getLength();final Path file = split.getPath();// open the file and seek to the start of the splitfinal FileSystem fs = file.getFileSystem(job);//开启流fileIn = fs.open(file);……//设置读取位置fileIn.seek(start);in = new UncompressedSplitLineReader(fileIn, job, this.recordDelimiterBytes, split.getLength());filePosition = fileIn;//If this is not the first split, we always throw away first record//because we always (except the last split) read one extra line in//next() method.//判断不是第一个切片时,抛弃第一行,防止数据被切割,第一行由上一个片读if (start != 0) { start += in.readLine(new Text(), 0, maxBytesToConsume(start));}this.pos = start; // 7.执行mapper的run() mapper.run(mapperContext); mapPhase.complete(); setPhase(TaskStatus.Phase.SORT); statusUpdate(umbilical); // 8.关闭input input.close(); input = null; output.close(mapperContext); output = null; } finally { closeQuietly(input); closeQuietly(output, mapperContext); } } } else { runOldMapper(job, splitMetaInfo, umbilical, reporter); } done(umbilical, reporter); } 总结反射mapper准备input来读数据 考虑本地读取,通过seek()设置考虑一行数据切割问题,非第一个split首行放弃,上一个split多处理一行 pos更新 执行map()input的nextKeyValue(),更新key和value通过getCurrentKey()和getCurrentValue()取到更新后的kv,传参给map() 总结图 Map-output环形缓冲区Map输出数据到磁盘时,会先写到内存,当内存中的数据占到一定比例,再一次性把这些数据溢写到磁盘,这个缓存区默认大小为100M,假设以下内存是Map的缓冲区实际上Map会把这段内存当作逻辑上的环形缓冲区来使用Map开始写数据到环形缓存区在真实的物理内存中,数据是这么分布的: MapTask类123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220// get an output objectif (job.getNumReduceTasks() == 0) {// 如果reduce数量为0//reduce数量可以由程序控制,有多个key设置多个reduce才有意义output = new NewDirectOutputCollector(taskContext, job, umbilical, reporter);} else { output = new NewOutputCollector(taskContext, job, umbilical, reporter);}@SuppressWarnings("unchecked")NewOutputCollector(org.apache.hadoop.mapreduce.JobContext jobContext, JobConf job, TaskUmbilicalProtocol umbilical, TaskReporter reporter ) throws IOException, ClassNotFoundException { // 创建一个容器 collector = createSortingCollector(job, reporter);@SuppressWarnings("unchecked") private <KEY, VALUE> MapOutputCollector<KEY, VALUE> createSortingCollector(JobConf job, TaskReporter reporter) throws IOException, ClassNotFoundException { MapOutputCollector.Context context = new MapOutputCollector.Context(this, job, reporter);Class<?>[] collectorClasses = job.getClasses( // 如果自定义一个输出类的逻辑,就使用自定义的 // 否则使用默认的MapOutputBuffer,即环形缓存区 JobContext.MAP_OUTPUT_COLLECTOR_CLASS_ATTR, MapOutputBuffer.class); …… // 初始化 collector.init(context);//sanity checks,都是调优点final float spillper =//进行溢写的阈值 job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);//环形缓冲区大小final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100);……// 溢写到磁盘,默认采用快速排序sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job);// 设置缓存区写数据和写索引之间的临界点(赤道)int maxMemUsage = sortmb << 20;maxMemUsage -= maxMemUsage % METASIZE;kvbuffer = new byte[maxMemUsage];bufvoid = kvbuffer.length;kvmeta = ByteBuffer.wrap(kvbuffer) .order(ByteOrder.nativeOrder()) .asIntBuffer();setEquator(0);bufstart = bufend = bufindex = equator;kvstart = kvend = kvindex;……// k/v serialization// 获得比较器comparator = job.getOutputKeyComparator();// 先取自定义的比较器,没有就取MapOutputKeyClass的K的比较器// 比如Text按照字典序排序// 如果有自定义的,必须实现RawComparator接口public RawComparator getOutputKeyComparator() { Class<? extends RawComparator> theClass = getClass( JobContext.KEY_COMPARATOR, null, RawComparator.class); if (theClass != null) return ReflectionUtils.newInstance(theClass, this);return WritableComparator.get(getMapOutputKeyClass().asSubclass(WritableComparable.class), this);}// combiner// 在map端对同一个key的内容进行合并// 环形缓存区的数据到达80%后,会开启新线程进行排序和溢写// 调优点:缓存区的数据溢写3次后生成3个小文件,会对这3个小文件再进行combiner,这个数字可以调整minSpillsForCombine = job.getInt(JobContext.MAP_COMBINE_MIN_SPILLS, 3);spillThread.setDaemon(true);spillThread.setName("SpillThread");spillLock.lock(); try {spillThread.start();sortAndSpill();// sort使用快速排序,然后combiner合并,然后溢写到磁盘,产生一个小文件sorter.sort(MapOutputBuffer.this, mstart, mend, reporter); while (!spillThreadRunning) { spillDone.await(); }…… return collector;……Collector初始化完毕 // collector创建成功 // 分区数量等于reduce数量 partitions = jobContext.getNumReduceTasks(); if (partitions > 1) { // 生成分区器,但还没有使用 partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>)ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);// 如果自定义了分区器的逻辑,就使用自定义的,否则使用HashPartitioner@SuppressWarnings("unchecked")public Class<? extends Partitioner<?,?>> getPartitionerClass() throws ClassNotFoundException {return (Class<? extends Partitioner<?,?>>)conf.getClass(PARTITIONER_CLASS_ATTR, HashPartitioner.class);}HashPartitioner:// 返回K的hashCode对reduce数量取模后的整数,// 弊端:可能造成数据倾斜// 所以partitioner是一个调优的地方public int getPartition(K key, V value, int numReduceTasks) { return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;}// 所以partitions = 1,返回0 } else { partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() { @Override public int getPartition(K key, V value, int numPartitions) { return partitions - 1; } }; }}// 分区器创建成功// output创建成功org.apache.hadoop.mapreduce.MapContext<INKEY, INVALUE, OUTKEY, OUTVALUE> mapContext = new MapContextImpl<INKEY, INVALUE, OUTKEY, OUTVALUE>(job, getTaskID(), input, output, committer, reporter, split);public MapContextImpl(Configuration conf, TaskAttemptID taskid, RecordReader<KEYIN,VALUEIN> reader, RecordWriter<KEYOUT,VALUEOUT> writer, OutputCommitter committer, StatusReporter reporter, InputSplit split) { super(conf, taskid, writer, committer, reporter); this.reader = reader; this.split = split;}MapContextImpl的父类:public TaskInputOutputContextImpl(Configuration conf, TaskAttemptID taskid,RecordWriter<KEYOUT,VALUEOUT> output,OutputCommitter committer,StatusReporter reporter) { super(conf, taskid, reporter); this.output = output; this.committer = committer;}public void write(KEYOUT key, VALUEOUT value ) throws IOException, InterruptedException { output.write(key, value);}// 这个write()调用了下面的方法: @Override public void write(K key, V value) throws IOException, InterruptedException { collector.collect(key, value, partitioner.getPartition(key, value, partitions));}// collector()就是执行collector的初始化过程// 关闭outputoutput.close(mapperContext);@Override public void close(TaskAttemptContext context ) throws IOException,InterruptedException { try { collector.flush(); } catch (ClassNotFoundException cnf) { throw new IOException("can't find class ", cnf); } collector.close();}// 把缓存区中没有达到阈值的数据也写到磁盘sortAndSpill();// 合并文件mergeParts();// 如果溢写次数大于3(可修改),触发combinerif (combinerRunner == null || numSpills < minSpillsForCombine) { Merger.writeFile(kvIter, writer, reporter, job); } else { combineCollector.setWriter(writer); combinerRunner.combine(kvIter, combineCollector);} 总结12345678910111213141516171819202122outputcollector = createSortingCollector(job, reporter); JobContext.MAP_OUTPUT_COLLECTOR_CLASS_ATTR, MapOutputBuffer.class); collector.init(context) sortmb = job.getInt(JobContext.IO_SORT_MB, 100);spillper = job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8)sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job);comparator = job.getOutputKeyComparator();combinerRunner = CombinerRunner.create(job, getTaskID(), combineInputCounter,reporter, null);minSpillsForCombine = job.getInt(JobContext.MAP_COMBINE_MIN_SPILLS, 3);spillThread.start();sortAndSpill();partitions = jobContext.getNumReduceTasks();partitioner = (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks 或者自定义output.write(key, value); collector.collect(key, value,partitioner.getPartition(key, value, partitions));output.close(mapperContext); collector.flush(); sortAndSpill(); mergeParts(); 总结图 ReduceReduceTask123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245// ======================run()方法作为入口=============================public void run(JobConf job, final TaskUmbilicalProtocol umbilical) throws IOException, InterruptedException, ClassNotFoundException { job.setBoolean(JobContext.SKIP_RECORDS, isSkipping());if (isMapOrReduce()) { // reduce 3 个阶段:copy、sort、reduce copyPhase = getProgress().addPhase("copy"); sortPhase = getProgress().addPhase("sort"); reducePhase = getProgress().addPhase("reduce");}……// =============以下代码是copy阶段(shuffle)=========================// Initialize the codec codec = initCodec(); RawKeyValueIterator rIter = null; ShuffleConsumerPlugin shuffleConsumerPlugin = null; Class combinerClass = conf.getCombinerClass(); CombineOutputCollector combineCollector = (null != combinerClass) ? new CombineOutputCollector(reduceCombineOutputCounter, reporter, conf) : null; Class<? extends ShuffleConsumerPlugin> clazz = job.getClass(MRConfig.SHUFFLE_CONSUMER_PLUGIN, Shuffle.class, ShuffleConsumerPlugin.class); shuffleConsumerPlugin = ReflectionUtils.newInstance(clazz, job); LOG.info("Using ShuffleConsumerPlugin: " + shuffleConsumerPlugin); ShuffleConsumerPlugin.Context shuffleContext = new ShuffleConsumerPlugin.Context(getTaskID(), job, FileSystem.getLocal(job), umbilical, super.lDirAlloc, reporter, codec, combinerClass, combineCollector, spilledRecordsCounter, reduceCombineInputCounter, shuffledMapsCounter, reduceShuffleBytes, failedShuffleCounter, mergedMapOutputsCounter, taskStatus, copyPhase, sortPhase, this, mapOutputFile, localMapFiles); shuffleConsumerPlugin.init(shuffleContext); // 得到迭代器rIter = shuffleConsumerPlugin.run();// ===========================copy阶段结束==========================//===========================准备比较器=============================RawComparator comparator = job.getOutputValueGroupingComparator();public RawComparator getOutputValueGroupingComparator() { Class<? extends RawComparator> theClass = getClass( JobContext.GROUP_COMPARATOR_CLASS, null, RawComparator.class);if (theClass == null) { // 如果没有自定义,调用这个方法 /*Map 端取排序比较器的顺序:1. 取自定义的排序比较器2. 取key自带的比较器Reduce端取比较器的顺序1. 取自定义的分组比较器2. 取自定义的排序比较器3. 取key自带的比较器*/ return getOutputKeyComparator(); } return ReflectionUtils.newInstance(theClass, this);}//===================== 准备比较器 结束=============================//===================== runNewReducer ============================= runNewReducer(job, umbilical, reporter, rIter, comparator, keyClass, valueClass); //创建reducerreducer = ReflectionUtils.newInstance(taskContext.getReducerClass(), job);//创建reducerContext(传入了迭代器和比较器)reducerContext = createReduceContext(reducer, job, getTaskID(),rIter, reduceInputKeyCounter,reduceInputValueCounter,trackedRW,committer,reporter, comparator,keyClass, valueClass);reduceContext = new ReduceContextImpl<INKEY, INVALUE, OUTKEY, OUTVALUE>(job, taskId,rIter,inputKeyCounter,inputValueCounter,output,committer,reporter,comparator,keyClass,valueClass);// 使用了ReduceContextImpl类其中有getCurrentKey(), context.getValues()等方法Reduce类调用的方法来自这里// =====================ReduceContextImpl start=================private KEYIN key; // current keyprivate VALUEIN value; // current valueprivate boolean firstValue = false; // first value in keyprivate boolean nextKeyIsSame = false; // more w/ this keyprivate boolean hasMore; // more in filepublic ReduceContextImpl(Configuration conf, TaskAttemptID taskid, RawKeyValueIterator input, Counter inputKeyCounter, Counter inputValueCounter, RecordWriter<KEYOUT,VALUEOUT> output, OutputCommitter committer, StatusReporter reporter, RawComparator<KEYIN> comparator, Class<KEYIN> keyClass, Class<VALUEIN> valueClass ) throws InterruptedException, IOException{ super(conf, taskid, output, committer, reporter); this.input = input; this.inputKeyCounter = inputKeyCounter; this.inputValueCounter = inputValueCounter; this.comparator = comparator; this.serializationFactory = new SerializationFactory(conf); this.keyDeserializer = serializationFactory.getDeserializer(keyClass); this.keyDeserializer.open(buffer); // 和buffer做了绑定 this.valueDeserializer = serializationFactory.getDeserializer(valueClass); this.valueDeserializer.open(buffer); hasMore = input.next(); this.keyClass = keyClass; this.valueClass = valueClass; this.conf = conf; this.taskid = taskid; }public boolean nextKey() throws IOException,InterruptedException { while (hasMore && nextKeyIsSame) { nextKeyValue(); } if (hasMore) { if (inputKeyCounter != null) { inputKeyCounter.increment(1); } return nextKeyValue(); } else { return false; }}public boolean nextKeyValue() throws IOException, InterruptedException { if (!hasMore) { key = null; value = null; return false; } firstValue = !nextKeyIsSame; DataInputBuffer nextKey = input.getKey(); currentRawKey.set(nextKey.getData(), nextKey.getPosition(), nextKey.getLength() - nextKey.getPosition()); buffer.reset(currentRawKey.getBytes(), 0, currentRawKey.getLength()); // 得到 Key/Value key = keyDeserializer.deserialize(key); DataInputBuffer nextVal = input.getValue(); buffer.reset(nextVal.getData(), nextVal.getPosition(), nextVal.getLength()- nextVal.getPosition()); value = valueDeserializer.deserialize(value); currentKeyLength = nextKey.getLength() - nextKey.getPosition(); currentValueLength = nextVal.getLength() - nextVal.getPosition(); if (isMarked) { backupStore.write(nextKey, nextVal); } hasMore = input.next(); if (hasMore) { nextKey = input.getKey(); // 判断下一个key与当前key是否相同 nextKeyIsSame = comparator.compare(currentRawKey.getBytes(), 0, currentRawKey.getLength(), nextKey.getData(), nextKey.getPosition(), nextKey.getLength() - nextKey.getPosition() ) == 0; } else { nextKeyIsSame = false; } inputValueCounter.increment(1); return true; }// 这个方法直接返回key,那就要求key是实时更新的,nextKeyValue()可以保证同时更新key和valuepublic KEYIN getCurrentKey() { return key;}// 通过内部类定义了迭代器供reduce使用protected class ValueIterable implements Iterable<VALUEIN> { private ValueIterator iterator = new ValueIterator(); @Override public Iterator<VALUEIN> iterator() { return iterator; }}// 定义了迭代器的通用方法protected class ValueIterator implements ReduceContext.ValueIterator<VALUEIN> {public boolean hasNext() { try { if (inReset && backupStore.hasNext()) { return true; } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("hasNext failed", e); } return firstValue || nextKeyIsSame;}@Overridepublic VALUEIN next() {......if (!nextKeyIsSame) { throw new NoSuchElementException("iterate past last value"); } // otherwise, go to the next key/value pair try { nextKeyValue(); return value; } catch (IOException ie) { ...... }}}// =====================ReduceContextImpl end=================reducer.run(reducerContext);// 1. setup()// 2. run()// 3. cleanup()public void run(Context context) throws IOException, InterruptedException { setup(context); try { while (context.nextKey()) {// 3. Map端是context.nextKeyValue() reduce(context.getCurrentKey(), context.getValues(), context); // If a back up store is used, reset it Iterator<VALUEIN> iter = context.getValues().iterator(); if(iter instanceof ReduceContext.ValueIterator) { ((ReduceContext.ValueIterator<VALUEIN>)iter).resetBackupStore(); } } } finally { cleanup(context); }}//===================== runNewReducer 结束========================== 总结Reduce有3个阶段 ShuffleshuffleConsumerPlugin.init(shuffleContext);rIter = shuffleConsumerPlugin.run(); Sort ReducerunNewReducer()创建reducer创建reducerContext, new ReduceContextImpl对象ReduceContextImpl getCurrentKey() nextKey()nextKeyValue() //同时更新key和value 内部类ValueIterableValueIterator iterator = new ValueIterator(); 内部类ValueIteratorhasNext() next() nextKeyValue()//同时更新key和value reducer.run(reducerContext); while (context.nextKey())reduce(context.getCurrentKey(), context.getValues(), context); Iterator<VALUEIN> iter = context.getValues().iterator(); 总结图Hadoop案例:Weather需求:找出每个月气温最高的2天,测试数据如下:12345678910111949-10-01 14:21:02 34c1949-10-01 19:21:02 38c1949-10-02 14:01:02 36c1950-01-01 11:21:02 32c1950-10-01 12:21:02 37c1951-12-01 12:21:02 23c1950-10-02 12:21:02 41c1950-10-03 12:21:02 27c1951-07-01 12:21:02 45c1951-07-02 12:21:02 46c1951-07-03 12:21:03 47c 自定义封装类12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970package com.sxt.hadoop.mapreduce.weather;import java.io.DataInput;import java.io.DataOutput;import java.io.IOException;import org.apache.hadoop.io.WritableComparable;public class Weather implements WritableComparable<Weather>{ private int year = 0; private int month = 0; private int day = 0; private int temperature = 0; public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public int getTemperature() { return temperature; } public void setTemperature(int temperature) { this.temperature = temperature; } @Override public void write(DataOutput out) throws IOException { out.writeInt(year); out.writeInt(month); out.writeInt(day); out.writeInt(temperature); } @Override public void readFields(DataInput in) throws IOException { this.year = in.readInt(); this.month = in.readInt(); this.day = in.readInt(); this.temperature = in.readInt(); } @Override public int compareTo(Weather w) { int c1 = Integer.compare(this.year, w.getYear()); if(c1 == 0) { int c2 = Integer.compare(this.month, w.getMonth()); if(c2 == 0) { int c3 = Integer.compare(this.day, w.getDay()); if(c3 == 0) { return Integer.compare(this.temperature, w.getTemperature()); }else { return c3; } }else { return c2; } } return c1; }} 自定义分区器12345678910111213141516171819202122232425262728293031323334353637383940package com.sxt.hadoop.mapreduce.weather;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Partitioner;public class WeatherPartitioner extends Partitioner<Weather, Text>{ public int getPartition(Weather key,Text value,int numReduceTasks) { return (key.getYear()) % numReduceTasks; }}自定义排序器package com.sxt.hadoop.mapreduce.weather;import org.apache.hadoop.io.WritableComparable;import org.apache.hadoop.io.WritableComparator;public class WeatherSort extends WritableComparator { public WeatherSort() { super(Weather.class,true); } @Override public int compare(WritableComparable a, WritableComparable b) { Weather w1 = (Weather) a; Weather w2 = (Weather) b; int c1 = Integer.compare(w1.getYear(), w2.getYear()); if(c1 == 0) { int c2 = Integer.compare(w1.getMonth(), w2.getMonth()); if(c2 == 0) { int c3 = Integer.compare(w1.getDay(), w2.getDay()); if(c3 == 0) { return - Integer.compare(w1.getTemperature(), w2.getTemperature()); }else { return c3; } }else { return c2; } } return c1; }} 自定义分组器1234567891011121314151617181920212223package com.sxt.hadoop.mapreduce.weather;import org.apache.hadoop.io.WritableComparable;import org.apache.hadoop.io.WritableComparator;public class WeatherGroup extends WritableComparator{ public WeatherGroup() { super(Weather.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { Weather w1 = (Weather) a; Weather w2 = (Weather) b; int c1 = Integer.compare(w1.getYear(), w2.getYear()); if(c1 == 0) { return Integer.compare(w1.getMonth(), w2.getMonth()); } return c1; }} Mapper类1234567891011121314151617181920212223242526272829303132333435363738394041package com.sxt.hadoop.mapreduce.weather;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.util.StringUtils;public class WeatherMapper extends Mapper<Object, Text, Weather, Text>{ Weather w = new Weather(); Text val = new Text(); @Override protected void map(Object key, Text value, Context context) throws IOException, InterruptedException { try { // 1.读懂数据 String[] strs = StringUtils.split(value.toString(), '\t'); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = null; Calendar cal = Calendar.getInstance(); date = sdf.parse(strs[0]); cal.setTime(date); // 2.映射K,V w.setYear(cal.get(Calendar.YEAR)); w.setMonth((cal.get(Calendar.MONTH))+ 1); w.setDay(cal.get(Calendar.DAY_OF_MONTH)); int tem = Integer.parseInt(strs[1].substring(0, strs[1].lastIndexOf("c"))); w.setTemperature(tem); val.set(tem+""); context.write(w, val); } catch (Exception e) { e.printStackTrace(); } }} Reducer类12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364package com.sxt.hadoop.mapreduce.weather;import java.io.IOException;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Reducer;public class WeatherReducer extends Reducer<Weather, Text, Text, IntWritable>{ Text kout = new Text(); IntWritable vout = new IntWritable(); @Override protected void reduce(Weather key, Iterable<Text> vals, Context context) throws IOException, InterruptedException { int wd1 = 0; int wd2 = 0; int d1 = 0; int d2 = 0; int day = 0; int flg = 0; for (Text val : vals) { //首次 if (flg == 0){ day = key.getDay(); d1 = day; wd1 = key.getTemperature(); flg++; } //第二次 if(day != key.getDay()) { //是不是第三次之后 if(flg==1){ d2=key.getDay(); wd2 = key.getTemperature(); flg++; }else{ //先判断,温度有没有比缓存大的 if ( wd1 > wd2 ){ if (key.getTemperature()> wd2){ wd2=key.getTemperature(); d2=key.getDay(); } }else{ if (key.getTemperature()> wd1){ wd1=key.getTemperature(); d1=key.getDay(); } } } } } kout.set(key.getYear()+"-"+key.getMonth()+"-"+d1); vout.set(wd1); context.write(kout, vout); if (flg == 2){ kout.set(key.getYear()+"-"+key.getMonth()+"-"+d2); vout.set(wd2); context.write(kout, vout); } }} Job类1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253package com.sxt.hadoop.mapreduce.weather;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;public class WeatherJob { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(true); /* * 本地模拟设置以下配置 * conf.set("mapreduce.app-submission.cross-platform", "true"); * conf.set("mapreduce.framework.name", "local"); * job.setJar("path/xxx.jar"); */ conf.set("mapreduce.app-submission.cross-platform", "true"); conf.set("mapreduce.framework.name", "local"); Job job = Job.getInstance(conf); job.setJar("E:\\package\\Weather.jar"); job.setJarByClass(WeatherJob.class); job.setMapperClass(WeatherMapper.class); // 使用自定义数据类型包装Key(年、月、日、温度),默认正序 job.setMapOutputKeyClass(Weather.class); job.setMapOutputValueClass(Text.class); job.setNumReduceTasks(2); // 分区算法保证相同的Key为一组 // 目的是将不同的Key分发到不同的Reduce // 按(年,月)计算分区号 // 类似分组比较器,可以收敛计算宽度 // 按(年)计算分区号 job.setPartitionerClass(WeatherPartitioner.class); // 自定义排序比较器,倒序 job.setSortComparatorClass(WeatherSort.class); // 自定义分组边界,按年、月进行分组 job.setGroupingComparatorClass(WeatherGroup.class); job.setReducerClass(WeatherReducer.class); Path input = new Path("/user/weather/input/weather.data"); FileInputFormat.addInputPath(job, input); Path output = new Path("/user/weather/output"); if (output.getFileSystem(conf).exists(output)) { output.getFileSystem(conf).delete(output, true); } FileOutputFormat.setOutputPath(job, output); job.waitForCompletion(true); } } Hadoop案例:好友推荐 本人 好友 Tom Cat Hadoop Hello Cat Tom Hive Hadoop Tom Hive World World Hadoop Hive Hello Hive Hadoop Cat World MR Hello Hello Tom World Hive MR MR Hello Hive 文件friend的内容如下:Tom Hello Hadoop CatWorld Hadoop Hello HiveCat Tom HiveMR Hive HelloHive Cat Hadoop World Hello MRHadoop Tom Hive WorldHello Tom World Hive MR 代码思路:split后两两组合为key(例:Tom_Hello,这里还需要注意Tom_Hello与Hello_Tom,明显他们应该是相同的组合,但是hadoop可不会智能的将其识别为相同的key,所以组合之前先比较大小再按顺序组合,保证他们之间是相同的组合,即都是Helllo_Tom),直接好友value为0,间接好友value为1,reduce中,出现1,则跳过这次计算。 Job类1234567891011121314151617181920212223242526272829303132333435363738class FoF { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(true); //在Windows环境中run mapreduce需要此条配置 //除了GUN/Linux运行客户端都需要设置为true conf.set("mapreduce.app-submission.cross-platform", "true"); //本地模拟需要此条配置,配置文件(mapred-site.xml)的值为 yarn conf.set("mapreduce.framework.name", "local"); Job job = Job.getInstance(conf); //jar包是集群模式运行不可缺少的 job.setJarByClass(FoF.class); //本地多线程模拟不需要打jar包 //job.setJar("E:\\package\\FoF.jar"); job.setMapperClass(FMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(FReducer.class); Path input = new Path("/user/friends/input"); FileInputFormat.addInputPath(job, input); Path output = new Path("/user/friends/output"); if(output.getFileSystem(conf).exists(output)) { output.getFileSystem(conf).delete(output, true); /* true的意义: * if path is a directory and set to true * the directory is deleted else throws an exception. * In case of a file the value can be set to eithertrue or false */ } FileOutputFormat.setOutputPath(job, output); job.waitForCompletion(true); }} Mapper类12345678910111213141516171819202122232425262728293031323334353637383940public class FMapper extends Mapper<LongWritable, Text, Text, IntWritable>{ Text mkey = new Text(); IntWritable mvalue = new IntWritable(); @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException { //Tom Cat Hadoop Hello//输入一行数据,迭代后两两输出,输出时标注关系,直接好友:0,间接好友:1 String[] friends = StringUtils.split(value.toString(), ' '); for(int i=0; i<friends.length; i++) { mkey.set(compare(friends[0], friends[i])); mvalue.set(0); /* * Tom-Tom 0 * Cat-Tom 0 * Hadoop-Tom 0 * Hello-Tom 0 */ context.write(mkey, mvalue); for(int j = i+1; j < friends.length-1; j++) { mkey.set(compare(friends[i+1], friends[j+1])); mvalue.set(1); /* * Cat-Hadoop 1 * Cat-Hello 1 * Hadoop-Hello 1 */ context.write(mkey, mvalue);} } } public static String compare(String s1, String s2) { if(s1.compareTo(s2) > 0) { return s2 + "-" + s1; }else { return s1 + "-" + s2; } } } Reducer类123456789101112131415161718192021222324public class FReducer extends Reducer<Text, IntWritable, Text, Text>{ Text rvalue = new Text(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, Text>.Context context) throws IOException, InterruptedException { //相同的key为一组,调用一次reduce int sum = 0; int flag = 0; for(IntWritable i : values) { sum += i.get(); //如果是直接好友,不再计算 if(i.get() == 0) { flag = 1; break; } } if(flag == 0){ rvalue.set(sum + ""); context.write(key, rvalue); } } } 服务器集群运行的两种方式 在Linux中运行1) 打jar包,把jar发送到服务器,hadoop jar方式2) 直接运行client代码conf.set(“mapreduce.app-submission.cross-platform”, “true”);job.setJar(“path”); 本地多线程模拟运行conf.set(“mapreduce.framework.name”, “local”);当Configuration conf = new Configuration(true);true值设置为false时,不需要配置”local”,因为Configuration conf = new Configuration(false)默认选择local模式。当Configuration conf = new Configuration(false)时,还需要读取配置文件的信息,需要手工设置,conf.set(“”, “”); Hadoop案例:ItemCF协同过滤商品推荐思考:购买成功后:购买了该商品的其他用户购买了以下商品根据用户的实时行为搜索成功后:您可能感兴趣的以下商品根据用户的主观意识主页或广告:您可能感兴趣的以下商品根据用户的特征向量 推荐系统是基于协同过滤(Collaborative Filtering)算法来实现的,CF算法包括两种: 基于用户的系统过滤:UserCF基于用户的协同过滤,通过不同用户对物品的评分来评测用户之间的相似性,基于用户之间的相似性做出推荐。简单来讲就是:给用户推荐和他兴趣相似的其他用户喜欢的物品。 基于物品的协同过滤:ItemCF 基于item的协同过滤,通过用户对不同item的评分来评测item之间的相似性,基于item之间的相似性做出推荐。简单来讲就是:给用户推荐和他之前喜欢的物品相似的物品。 算法原理Co-occurrence Matrix(同现矩阵)和User Preference Vector(用户评分向量)相乘得到的这个Recommended Vector(推荐向量)1.基于全量数据的统计,产生同现矩阵体现商品间的关联性每件商品都有自己对其他全部商品的关联性(每件商品的特征)解释:通过历史订单交易记录,计算得出每一件商品相对其他商品同时出现在同一订单的次数,所以每件商品都有自己相对全部商品的同现列表2.用户评分向量体现的是用户对一些商品的评分解释:用户会对部分商品有过加入购物车,购买等实际操作,经过计算会得到用户对这部分商品的评分向量列表3.任一商品需要用户评分向量乘以基于该商品的其他商品关联值,求和得出针对该商品的推荐向量解释:使用用户评分向量列表中的分值,依次乘以每一件商品同现列表中该分值的代表物品的同现值,求和便是该物品的推荐向量,排序取TopN即可 求同现矩阵的方法: 求推荐向量(R值)方法: MR思路 代码略……]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>hadoop原理及其搭建</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hive--DDL和DML练习]]></title>
<url>%2F2016%2F10%2F13%2FHive-DDl%2F</url>
<content type="text"><![CDATA[hive的三种模式简介及其搭建测试。 语法参考HIve官网 DDL(Hive Data Definition Language)准备测试数据人员表:id、name、likes爱好、address地址123456781,xiaoming1,book-lol-lanqiu,beijing:xisanqi-shanghai:lujiazui2,xiaoming2,book-lol-lanqiu,beijing:xisanqi-shanghai:lujiazui-huoxing:xx3,xiaoming3,book-lol-lanqiu,beijing:xisanqi-huoxing:xx4,xiaoming4,book-lol-lanqiu,beijing:xisanqi-shanghai:lujiazui-huoxing:xx5,xiaoming5,booklanqiu,beijing:xisanqi-shanghai:lujiazui-huoxing:xx6,xiaoming6,book-lol-lanqiu,beijing:xisanqi-shanghai:lujiazui-huoxing:xx7,xiaoming7,book-lol-lanqiu,beijing:xisanqi-shanghai:lujiazui-huoxing:xx8,xiaoming8,book-lol,beijing:xisanqi-shanghai:lujiazui-huoxing:xx 默认为内部表/MANAGED删除内部表,数据就会从hdfs删除。123456789101112create table psn0 (id int,name string,likes ARRAY<string>,address MAP<string,string>)ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY '-' MAP KEYS TERMINATED BY ':';-- 加载数据到表LOAD DATA LOCAL inpath '/root/data' INTO TABLE psn0; 外部表/EXTERNAL删除外部表只删除metastore的元数据,不删除hdfs中的表数据。因为外部表由hdfs管理,hive可以增删改查,但是无法将底层数据删除。1234567891011create external table psn1(id int,name string,likes ARRAY<string>,address MAP<string,string>)ROW FORMAT DELIMITED FIELDS TERMINATED BY ','COLLECTION ITEMS TERMINATED BY '-'MAP KEYS TERMINATED BY ':'LOCATION '/psn1'; 分区表分区表的意义在于优化查询。查询时尽量利用分区字段。如果不使用分区字段,就会全部扫描创建分区表(顺便说,sql不区分大小写)分区就相当于分目录,后面还会有分桶,分桶相当于分文件。分区的顺序即创建时给定分区的顺序,也就是说下面的语句会创建一个sex目录,sex目录下创建age目录。删除sex就会把age也删除掉。1234567891011121314151617181920212223242526272829CREATE TABLE psn3( id int, name string, likes ARRAY<string>, address MAP<string,string>)PARTITIONED BY (sex string, age int)ROW format delimitedfields TERMINATED BY ','COLLECTION items TERMINATED BY '-'map keys terminated BY ':';-- 查询表的分区信息SHOW PARTITIONS psn3;-- 加载数据到分区表(静态分区)当数据被加载至表中时,不会对数据进行任何转换。Load操作只是将数据复制至Hive表对应的位置。数据加载时在表下自动创建一个目录-- 这里会在数据末尾再添加sex和age字段且全都是male和90LOAD DATA LOCAL inpath '/root/data' INTO TABLE psn3 PARTITION (sex='male', age=90);-- 分区必须都赋值,否则会报错hive> load data local inpath '/root/data' into table psn3 partition (sex='girl');-- FAILED: SemanticException [Error 10006]: Line 1:63 Partition not found ''girl''-- 修改分区测试-- 添加分区,即添加一个空目录ALTER TABLE psn3 add PARTITION (sex='male',age=1);-- 删除分区ALTER TABLE psn3 DROP PARTITION (sex = 'male', age = 1);-- 删除分区就相当于删除目录ALTER TABLE psn3 DROP PARTITION (sex = 'male');-- 当表中只剩sex=male,age=1的数据时,删除age会将male目录也删除。ALTER TABLE psn3 drop PARTITION (age=1); 在外部表中删除分区,数据是否会丢失?聪明的你应该想到了,答案是不会。1234567891011121314151617create external table psn4(id int,name string,likes ARRAY<string>,address MAP<string,string>)PARTITIONED BY (sex string, age int)ROW FORMAT DELIMITED FIELDS TERMINATED BY ','COLLECTION ITEMS TERMINATED BY '-'MAP KEYS TERMINATED BY ':'LOCATION '/psn4';-- 下面的sql会将psn1下的data文件移到psn4表中,导致psn1中无数据。LOAD DATA inpath '/psn1/data' INTO TABLE psn4 PARTITION (sex='male', age=40);LOAD DATA inpath '/user/hive/warehouse/psn0/data' INTO TABLE psn4 PARTITION (sex='female', age=28);-- 删除外部表分区,发现依旧只是删除对应的metastore的元数据,hdfs中的数据不会删除。ALTER TABLE psn4 DROP PARTITION (sex = 'male'); 另外两种建表方式12345678CREATE TABLE psn5 like psn0;CREATE TABLE psn6 AS SELECT id, likes FROM psn4;CREATE TABLE res2( countLine int);FROM psn4 INSERT INTO TABLE res SELECT count(*); 由于删除外部表不会删除hdfs中的数据,如果再次执行建表语句,原来的数据自然就导入到表里了。也就是说如果你的hdfs中原本有一张表,那么可以用创建外部表的方式将其直接导入hive。上传相同文件或同名文件data3,hdfs中存储的文件会复制一份,不会覆盖。 DML]]></content>
<categories>
<category>Bigdata</category>
<category>Hive</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>Hive</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hive--安装测试]]></title>
<url>%2F2016%2F10%2F11%2FHive%E5%AE%89%E8%A3%85%2F</url>
<content type="text"><![CDATA[hive的三种模式简介及其搭建测试。 Hive的三种模式local模式。此模式连接到一个In-memory 的数据库Derby,一般用于Unit Test。 Hive单用户模式通过网络连接到一个数据库中,是最经常使用到的模式。 Pre: 启动集群规划node01:MySQLnode01:Hive 客户端(其实本来该用node02,一时失误,但是不影响练习)123456789101112131415161.启动Zookeeper[root@node02 ~]# zkServer.sh start[root@node03 ~]# zkServer.sh start[root@node03 ~]# zkServer.sh statusJMX enabled by defaultUsing config: /opt/sxt/zookeeper-3.4.6/bin/../conf/zoo.cfgMode: leader# zookeeper的过半机制[root@node04 ~]# zkServer.sh start2.启动Hdfs[root@node01 ~]# start-dfs.sh3.启动 Yarn[root@node01 ~]# start-yarn.sh单独启动Resourcemanager[root@node03 ~]# yarn-daemon.sh start resourcemanager[root@node04 ~]# yarn-daemon.sh start resourcemanager 1.安装MySQL server12345[root@node01 ~]# yum install mysql-server启动mysql[root@node01 ~]# service mysqld start开机启动[root@node01 ~]# chkconfig mysqld on 2.MySQL配置登录mysql修改权限12345678910111213141516171819202122232425262728293031321)进入到mysql的console:[root@node01 ~]# mysqlmysql> show databases;+--------------------+| Database |+--------------------+| information_schema || mysql || test |+--------------------+3 rows in set (0.00 sec)2)选择mysql数据库mysql> use mysqlmysql> show tables; mysql> select host, user from user;3)把所有库`*`下的所有表`.*`的权限都给了root用户,可以从任何地方访问`%`,密码是123。mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123' WITH GRANT OPTION;4)定删除多余会对权限造成影响的数据mysql> delete from mysql.user where host != '%';mysql> select user, host, password from mysql.user;+------+------+-------------------------------------------+| user | host | password |+------+------+-------------------------------------------+| root | % | *23AE809DDACAF96AF0FD78ED04B6A265E05AA257 |+------+------+-------------------------------------------+1 row in set (0.00 sec)5)刷新权限并退出mysql> flush privileges;mysql> exit6)使用密码重新登录[root@node01 ~]# mysql -u root -pEnter password:这里输入密码 3.hive部署上传hive部署包并解压123$ scp /Users/Chant/Documents/大数据0627/hive/apache-hive-1.2.1-bin.tar.gz root@node01:/root[root@node01 ~]# tar -zxvf apache-hive-1.2.1-bin.tar.gz -C /opt/[root@node01 opt]# mv apache-hive-1.2.1-bin/ hive-1.2.1 配置环境变量并生效1234[root@node01 hive-1.2.1]# vi /etc/profileexport HIVE_HOME=/opt/hive-1.2.1export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin:$HIVE_HOME/bin[root@node01 hive-1.2.1]# . /etc/profile 1234[root@node01 hive-1.2.1]# cd conf/[root@node01 conf]# mv hive-default.xml.template hive-site.xml[root@node01 conf]# vi hive-site.xml用:.,$-1d删除默认的配置 写入以下配置1234567891011121314151617181920212223242526272829<property> <name>hive.metastore.warehouse.dir</name> <value>/user/hive_remote/warehouse</value> </property> <property> <name>hive.metastore.local</name> <value>true</value> </property> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://localhost/hive_remote?createDatabaseIfNotExist=true</value> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>com.mysql.jdbc.Driver</value> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>root</value> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>123</value> </property> 上传mysql驱动并放到hive的lib目录下12$ scp /Users/Chant/Documents/大数据0627/hive/mysql-connector-java-5.1.32-bin.jar root@node01:/root[root@node01 ~]# mv mysql-connector-java-5.1.32-bin.jar /opt/hive-1.2.1/lib/ 此时启动hive的话会报错,jline包不兼容,可以去看一下hadoop下的jline包与hive的jline包版本不一致,所以需要用高版本替换低版本。 1234[root@node01 lib]# cd /opt/sxt/hadoop-2.6.5/share/hadoop/yarn/lib[root@node01 lib]# rm -f jline-0.9.94.jar[root@node01 lib]# cp /opt/hive-1.2.1/lib/jline-2.12.jar ./[root@node01 lib]# ls jline-2.12.jar 接着启动hive成功。可以做一些练习测试。12345678hive> show tables;hive> create table tbl (id int, name string);hive> show tables;大部分语句都会转成MapReduce来运行。hive> insert into tbl values (1,'xiaoming');hive> select count(*) from tbl;select * from不会转成MapReduce,因为它只需要读取,没有计算。hive> select * from tbl; 访问node01:50070可以看到之前配置文件中配置的<name>hive.metastore.warehouse.dir</name> <value>/user/hive_remote/warehouse</value> 在hdfs中创建了这个目录,并用于存储我们创建的表数据。 单用户模式,元数据存储在mysql中,可以连接数据库查看一下。 Hive多用户模式远程服务器模式。用于非Java客户端访问元数据库,在服务器端启动MetaStoreServer,客户端利用Thrift协议通过MetaStoreServer访问元数据库。 规划node01:MySQLnode03:MetaStore Servernode04:Hive 客户端 Pre上传hive部署包到node03和node04并解压1234$ scp /Users/Chant/Documents/大数据0627/hive/apache-hive-1.2.1-bin.tar.gz root@node03:/root$ scp /Users/Chant/Documents/大数据0627/hive/apache-hive-1.2.1-bin.tar.gz root@node04:/rootroot@node03 ~]# tar -zxvf apache-hive-1.2.1-bin.tar.gz -C /opt/[root@node04 ~]# tar -zxvf apache-hive-1.2.1-bin.tar.gz -C /opt/ 两台上都修改环境变量并生效1234[root@node03 hive-1.2.1]# vi /etc/profileexport HIVE_HOME=/opt/hive-1.2.1export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin:$ZOOKEEPER_PREFIX/bin:$HIVE_HOME/bin[root@node03 hive-1.2.1]# source /etc/profile 服务端配置修改Hive配置 123[root@node03 hive-1.2.1]# cd conf[root@node03 conf]# cp hive-default.xml.template hive-site.xml[root@node03 conf]# vi hive-site.xml 删除原有的configuration,修改为如下内容123456789101112131415161718192021222324<property> <name>hive.metastore.warehouse.dir</name> <value>/user/hive/warehouse</value> </property> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://node01:3306/hive?createDatabaseIfNotExist=true</value> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>com.mysql.jdbc.Driver</value> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>root</value> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>123</value> </property> 上传驱动到node03并放到hive的lib目录下12$ scp /Users/Chant/Documents/大数据0627/hive/mysql-connector-java-5.1.32-bin.jar root@node03:/root[root@node03 conf]# cp /root/mysql-connector-java-5.1.32-bin.jar ../lib/ 启动hive服务端1[root@node03 lib]# hive --service metastore 可以看到服务端的9083端口已经启动。 客户端配置12[root@node04 conf]# mv hive-default.xml.template hive-site.xml[root@node04 conf]# vi hive-site.xml .,$-1d删除原有的configuration,修改为如下内容 1234567891011121314<property> <name>hive.metastore.warehouse.dir</name> <value>/user/hive/warehouse</value> </property> <property> <name>hive.metastore.local</name> <value>false</value> </property> <property> <name>hive.metastore.uris</name> <value>thrift://node03:9083</value> </property> 同样的,此时如果启动客户端会报错java.lang.IncompatibleClassChangeError: Found class jline.Terminal,所以去替换hadoop的jline。 123[root@node04 lib]# cp jline-2.12.jar /opt/sxt/hadoop-2.6.5/share/hadoop/yarn/lib/[root@node04 lib]# cd /opt/sxt/hadoop-2.6.5/share/hadoop/yarn/lib/[root@node04 lib]# rm -f jline-0.9.94.jar 这时候就可以启动hive了,做点简单的练习测试。 123456[root@node04 lib]# hivehive> show tables;hive> create table tbl (id int, name string);hive> desc tbl;hive> insert into tbl values (1,'zhangsan');hive> select * from tbl; 连接数据库,刷新,因为我们配置的<name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://node01:3306/hive?createDatabaseIfNotExist=true</value>所以能看到多了一个hive数据库,内容和单用户模式下一样,能找到TBLS和COLUMNS_V2。访问http://node01:50070/ 可以看到表数据存在hdfs中。]]></content>
<categories>
<category>Bigdata</category>
<category>Hive</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>Hive</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--MapReduce深入源码-Mapper]]></title>
<url>%2F2016%2F09%2F08%2FHadoop-mapper%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[MapReduce Mapper源码解析。 概览鼠标移到Mapper上,查看文档,先看个大概,有个大概的逻辑框架后再去看源码。 点击Mapper,Command+F12查看文件结构,找到run方法。 setup开启服务连接(比如连接数据库,打开某个文件)中间循环传一行执行一次map,最后cleanup关闭连接,释放资源。setup和cleanup中除了一行注释外,并无操作,所以是需要我们自己来实现。可以看出map的输入来自context.nextKeyValue(),输出通过context.write()。即:context.nextKeyValue()–>Mapper.map()–>context.write()随便找个地方输入MapTask,然后点击,进入到org.apache.hadoop.mapred下的MapTask,选中MapTask,Command+F12打开文件结构,键盘敲击run找到run方法并进入。从注释看出,运行任务时,打印出的map百分之几的那些进度信息是在这里定义的。往下翻找到runNewMapper并点击(因为我们用的是新的API)。看注释,找到// get an output object,里面有一段try代码块,可以看到先初始化,然后调用了mapper.run(),之前看过的这个mapper.run()就是通过循环一行行去读数据并执行map方法。 反射Mapper往上翻,回到runNewMapper的第二个注释// make a mapper(第一个注释是准备创建上下文,所以就略过了,主要看计算框架)。打开getMapperClass()的继承关系,找到JobContextImpl可以看到,它的默认取得是Mapper.class而Mapper中的map是默认直接输出,开发时的业务运算逻辑就没写进去,所以这个MAP_CLASS_ATTR肯定是要由用户配置的,Mapper类由开发人员自己实现(比如WordCount中的MyMapper)。所以最终// make a mapper阶段就是将用户所写的Mapper反射出来成一个对象。 输入input// make the input format阶段也是同理,上一篇客户端源码中已经讲过,默认取TextInputFormat.class,可通过job.setInputFormatClass()来设置INPUT_FORMAT_CLASS_ATTR。// rebuild the input split阶段,一个split对应一个map,客户端会生成split列表,这个split列表支撑了计算向数据移动,这里是其中的一个计算map任务,把split重新构造了一下。往下继续,点击NewTrackingRecordReader, 可见,input = new NewTrackingRecordReader,NewTrackingRecordReader底层有一个real,而这个real是由createRecordReader返回的一个LineRecordReader。 所以翻到try代码块中的Initialize初始化阶段,打开实现关系,input是NewTrackingRecordReader,所以这里应该选择NewTrackingRecordReader发现里面调用的是real的初始化打开实现关系,real是LineRecordReader,所以选择LineRecordReader。LineRecordReader中的initialize首先准备好了split的开始和结束位置,然后通过文件路径拿到文件。接着看注释// open the file and seek to the start of the split,其实注释说得很清楚了,先open开启文件的IO输入流,如果这里从头开始读,一定会读到别的block块或者别的split去了,所以一定要用seek方法,将你这个计算要读的split的起始位置的偏移量给这个流设置进去。流开启后,通过in将输入流fileIn绑定到SplitLineReader,注意看注释及那段代码,通过in将第一行抛弃(里面new了一个匿名对象Text,匿名对象用完就被销毁了,以此达到抛弃第一行的目的,同时将偏移量start移到了下一行),避免出现文件切割造成的乱码和单词被切开的问题。初始化的主要功能就是这个。计算向数据移动是由客户端来支撑的,本地化数据读取就是由初始化的这段代码来完成的。 初始化后调用run方法,run方法循环迭代context.nextKeyValue()同时调用map方法,这个context来自于mapper.run(mapperContext)调用时传入的mapperContext,mapperContext来自于mapContext,mapContext中传的参数有输入input和输出output。点击MapContextImpl,其输入reader就是之前的input它的nextKeyValue()调用的reader的nextKeyValue(),也就是input的nextKeyValue(),而input是NewTrackingRecorder。所以,打开继承关系,找到NewTrackingRecorder并进入。里面实际调用的是real的nextKeyValue,而real是LineReacordReader,所以打开实现关系,找到LineReacordReader进入。可以看到,nextKeyValue主要做了四件事: set方法设置pos 给Key和Value赋值 pos更新 返回布尔型 而getCurrentKey()和getCurrentValue()就只是直接返回Key和Value就好了。整理一下整个流程:用逻辑语言描述一遍: MapReduce中InputFormat和InputSplit解读Hadoop的Mapreduce是一个分布并行处理大数据的程序框架,一个Map/Reduce作业(job)通常会把指定要处理(在job的配置中由我们来指定)数据集切分为若干独立的数据块(这个工作由mapreduce计算框架自动去完成),然后再由 map任务(task)以完全并行的方式处理它们。 MapReduce(job)要操作的数据保存在文件系统HDFS上,InputFormat接口定义的方法就是如何读取文件和分割文件以提供分片给mapper,TextInputFormat文本格式输入是InputFormat的默认实现类。通过使用InputFormat,MapReduce框架可以做到:1.验证作业的输入的正确性;2.把输入文件切分成多个逻辑InputSplits,并把每一个InputSplit分别分发给一个单独的MapperTask;3.提供RecordReader的实现,这个RecordReader从指定的InputSplit中正确读出一条一条的K-V对(K是偏移量,V是每行数据),这些K-V对将由我们写的Mapper方法处理。详细解读:(1)InputFormat和InputSplitInputSplit是Hadoop定义的用来传送给每个单独的map的数据,InputSplit存储的并非数据本身,而是一个分片长度和一个记录数据位置的数组。生成InputSplit的方法可以通过InputFormat()来设置。 当数据传送给map时,map会将输入分片传送到InputFormat,InputFormat则调用方法CreateRecordReader()生成RecordReader,RecordReader再通过getCurrentKey()、getCurrentValue()方法创建可供map处理的一个一个的对。简而言之,InputFormat()方法是用来生成可供map处理的对的。Hadoop预定义了多种方法将不同类型的输入数据转化为map能够处理的对,它们都继承自InputFormat,分别是:1234567891011121314InputFormat | |---BaileyBorweinPlouffe.BbpInputFormat |---ComposableInputFormat |---CompositeInputFormat |---DBInputFormat |---DistSum.Machine.AbstractInputFormat |---FileInputFormat |---CombineFileInputFormat |---KeyValueTextInputFormat |---NLineInputFormat |---SequenceFileInputFormat |---TeraInputFormat |---TextInputFormat 其中TextInputFormat是Hadoop默认的输入方法,在TextInputFormat中,每个文件(或其一部分)都会单独地作为map的输入,而这个是继承自FileInputFormat的。 之后,每行数据都会生成一条记录,每条记录则表示成形式:key值是每个数据的记录在数据分片中字节偏移量,数据类型是LongWritable;value值是每行的内容,数据类型是Text。注:1.在写MapReduce程序的时候,我们写的Mapper方法都要继承API提供的Mapper类:通常我们会重写map()方法,map()每次接受一个K-V对,然后我们对这个K-V对进行处理,再分发出处理后的数据。我们也可能重写setup()以对这个map task进行一些预处理,比如创建一个List之类的;我们也可能重写cleanup()方法对做一些处理后的工作,当然我们也可能在cleanup()中写出K-V对。举个例子就是:InputSplit的数据是一些整数,然后我们要在mapper中算出它们的和。我们就可以在先设置个sum属性,然后map()函数处理一个K-V对就是将其加到sum上,最后在cleanup()函数中调用context.write(key,value);最后我们看看Mapper类中的run()方法,它相当于map task的驱动,我们可以看到run()方法首先调用setup()进行初始操作,然后对每个context.nextKeyValue()获取的K-V对,就调用map()函数进行处理,最后调用cleanup()做最后的处理。2.事实上,从context.nextKeyValue()就是调用了相应的RecordReader来获取K-V对的。RecordReader:是用来从一个输入分片inputSplit中读取一个一个的K -V 对的抽象类,我们可以将其看作是在InputSplit上的迭代器。最主要的方法就是nextKeyvalue()方法,由它获取分片上的下一个K-V 对。 (2)OutputFormat每一种输入格式都有一种输出格式与其对应。默认的输出格式是 TextOutputFormat ,这种输出方式与输入类似,会将每条记录以一行的形式存入文本文件。不过,它的键和值可以是任意形式的,因为程序内容会调用toString()方法将键和值转换为String类型再输出。 输出ouput看完了输入,接下来看输出。回到runNewMapper(),找到注释// get an output object所以reduce是可以设置的,使用语句job.setNumReduceTasks(n)来设置recude数量,key多的话就多设置reduce,因为多个reduce是并行计算的。点击NewOutputCollector,可见分区数量等于reduce数量。而分区器partitioner是通过反射得到的。打开getPartitionerClass的实现关系,找到JobContextImpl进入。可以看到用户可以设置partitioner,不设置的话,其默认值是HashPartitioner通过语句job.setPartitionerClass(cls);来设置partitioner,自己实现这个partitioner类。点击HashPartitioner,可以看到其实它和简单,就是将Key的哈希值和Reduce数取模,返回余数,打标签,让Reducer知道这条数据该去哪个Reduce。这里是一个调优的重点。因为HashPartioner并不清楚数据具体情况,有可能发生数据倾斜(比如,大部分数据取模后都等于1,都到同一个Reduce里去了),这里可以根据具体场景自己实现Partitioner。举个栗子:比如数据是乱序的数字,需求是全排序。那么可以在自己实现的Partioner中写个判断,1~100返回0,100~200返回1,这样来尽量让他们均匀的分布到每个Reduce中去。回到NewOutputCollector,如果partions等于1,那就直接返回0,所有数据去到同一个Reduce。点击createSortingCollector 先找到return collector,再一步步往上看。collector反射于subClazz,subClazz来自于clazz,clazz是for循环传入的collectorClasses,collectorClasses来自用户配置或默认的MapOutputBuffer。所以这个collector默认是MapOutputBuffer(下图中按数字顺序查看)。而这个MapOutputBuffer就是环形缓冲区(后面再讲,这个环形是个挺机智的机制😂)。可以点进去看一下,它有1109行代码,如果你能写一个更好的环形缓冲区,那就可以上天了。关于环形缓冲区源码详细解析请戳MapReduce之mapOutputBuffer解析。 我们来看一下这个环形缓冲区的初始化,找到colletor.init()打开实现关系,找到MapOutputBuffer进入。MAP_SORT_SPILL_PERCENT就是环形缓冲区溢写的阈值,默认为80%。IO_SORT_MB就是环形缓冲区的大小,默认100M。这是两个调优点,如果数据一行就好几百兆,那么显然要调大IO_SORT_MB。如果map运算速度比较快,剩下的20%不足以满足map的输出(比如:80%被锁住正在溢写,而剩下20%由于map计算较快,也已经也已经写满,就阻塞了),那么应该调小MAP_SORT_SPILL_PERCENT,可以直接调成50%,看看有没有提升,再去微调,不要一点点的调。在默认情况下,block大小hadoop1.0是64M,hadoop2.0是128M,而split大小默认等于block大小,所以map会读取128M,输出的时候也就差不多100M左右,最多溢写两次,所以IO_SORT_MB默认情况下还是可以的。 hadoop在执行MapReduce任务时,在map阶段,map函数产生的输出,并不是直接写入磁盘的。为了提高效率,它将输出结果先写入到内存中(即环形内存缓冲区,默认大小100M),再从缓冲区(溢)写入磁盘。缓存为什么要设计成环形的?有什么好处?答:使输入输出并行工作,即“写缓冲”可以和“溢写”并行。“溢写”工作由单独的线程来做。 往下看,可以看到默认的排序器用的是快速排序QuickSorter,这里也是一个调优点,可以自行配置排序器。继续往下,K/V序列化这里,看到有一个比较器。点击getOutputComparator,经鼠标放到getMapOutputKeyClass上,可以看到这里默认取的是map输出的key的比较器,你也可以用job.setSortComparatorClass(cls);配置自己实现的比较器,这就允许你map输出时用的比较器和放到环形缓冲区后最终输出时的比较器可以不同。我们可以找个简单的类型比如IntWritable来看一下它的comparator继续看MapOutputBuffer的init(),往下翻,可以看到// combiner,这个combiner在一开始的概览里有描述 Users can optionally specify a combiner, via Job.setCombinerClass(Class), to perform local aggregation of the intermediate outputs, which helps to cut down the amount of data transferred from the Mapper to the Reducer. 就是可以调用combiner在map端合并数据,这样就可以减少要传输的数据量和reduce端的压力。但是要注意的是,不是什么数据都适合简单的合并,比如平均数,简单的合并后是不对的(比如:$(\frac{a+b}{2}+\frac{c+d}{2})/2\neq\frac{a+b+c+d}{4}$),这时候需要注意书写正确的合并方法。往下找到try代码块中的spillThread,shift+Command+B打开声明类型找到try代码块中sortAndSpill,看名字就知道这方法是负责排序和溢写。可以看到排序用的sorter,而之前已经看到过sorter的默认排序方法是快速排序。 参考资料以下是环形缓冲区的一些源码解读和排序算法的文章: MapReduce之mapOutputBuffer解析–详细 MapReduce源码解析–环形缓冲区–详细 map的环形内存缓冲区–概览 常用排序算法总结 MapReduce篇之InputFormat MapReduce中InputFormat和InputSplit解读 自定义InputFormat]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>API</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--MapReduce深入源码-Client]]></title>
<url>%2F2016%2F09%2F05%2FHadoop-client%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[MapReduce客户端提交过程的源码,以及手写wordCount测试。 一入源码深似海,源码深深深几许 点击submit 点击submitJobInternal 往下翻,看到客户端启动后先创建切片列表由于我使用的是新的API,所以点击writeNewSplites这里不是直接点,而是command+option+B打开继承关系,选择JobContextImpl。这里用户如果设置了InputFormat.class则取用户自己实现的,否则取默认值TextInputFormat.class也就是说可以在自己的代码中设置InputFormat.class,自己去实现ooxx.class这个类,这样就可以控制它是按行还是按一块数据来输入。setInputFormatClass里面设置的就是INPUT_FORMAT_CLASS_ATTR,而getInputFormatClass里get的也是这个INPUT_FORMAT_CLASS_ATTR。 这里顺带说一下,进入Job.java,点击JobContext–>MRJobConfig可以看到很多常量属性,这与你的hadoop配置文件有关,搜索Default,找到DEFAULT_MAP_MEMORY_MB,可以看到他有默认配置且为1G。这里是一个调优点。当你的数据每一行都是好几百兆时,1G的内存会导致频繁的磁盘IO???,或者内存溢出,当你确定计算没有问题但是提交计算总是报内存溢出,可以试试将它调大。DEFAULT_REDUCE_MEMORY_MB只有1G,这导致你的buffer会很小,会频繁地触发磁盘IO(比如一次读3G数据到内存后写入磁盘一次,和读3次1G后写3次到磁盘。IO调用次数越多,开销越大)。所以DEFAULT_REDUCE_MEMORY_MB基本上是必然要调的。回到TextInputFormat,Ctrl+H打开继承层级,发现它继承自FileInputFormat,对比我们创建输入路径时的语句,可见TextInputFormat是通过继承来获取到父类中的Job配置和input路径。 接下来,看getSplits。打开实现关系,选择FileInputFormat。点击getFormatMinSplitSize,可见其固定返回1。 点击getMinSplitSize,可见其默认值为1,所以long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); 计算出的默认值也是1,且用户可以干预调节。通过FileInputFormat这个类来调节SplitSize。点击getMaxSplitSize,可以看到MaxSplitSize的默认值是long类型的最大值。返回getSplits接着往下翻。判断是否为本地文件,否则读取集群的blockLocations。标红的代码在之前的测试中使用过,用来取回所有的block位置信息。从computeSplitSize中可以看出,切片大小splitSize的默认值就是block块大小blockSize。如果想让切片的在block中,应该设置MaxSize,令其小于blockSize;如果想让切片大于block块,应该设置MinSize,令其大于blockSize。鼠标放到makeSplit上,可以看出split列表中的信息就是makeSplit的参数,文件,偏移量,split长度,节点主机名。再点击上面的getBloclIndex向上翻,找到// Create the splits for the job的地方,不知道你还记不记得,刚才看的那么多源码是writeSplits的实现,这里的maps就是writeSplits计算出的需要多少个map(一个block中的splits数量)。可以重新追溯着复盘一下,maps<–writeSplits<–writeNewSplits(返回array.length<–splits.toarray)<–getSplits(返回splits<–for循环迭代files过程中splits.add())下面的就看着注释走马观花吧,到最后,终于submit了。谢天谢地,我快要疯了。从源码可以看出客户端主要做了以下事情,计算向数据移动,本地化数据读取: 配置完善:个性化 检查路径 计算Split:mapsblocklocation:位置信息file1.txt,0,1048576,node02,node03,node04file1.txt,1048576,1048576,node01,node02,node03 资源提交到HDFS 提交任务接下来由集群中AppMaster向RM申请资源(参照split)。 测试理解写一个WordCount程序,在Mapper中添加一行Thread.sleep(999999);如下: 12345while (itr.hasMoreTokens()) { word.set(itr.nextToken()); Thread.sleep(999999); context.write(word, one);} 打jar包拷贝到你的HDFS主节点,运行。看到map 0% reduce 0%后,新开一个ssh窗口,查看hdfs的根目录,发现多了一个tmp目录,一步步深入其中。可以将这些文件get到本地,然后发现这里面的job.jar和一开始上传的testHadoop-1.0-SNAPSHOT.jar大小一样,其实内容也是一样的,集群中任一节点启动MapTask任务后,就会把这个jar包拿过去,然后反射出里面的MyMapper。然后任一节点启动ReduceTask任务后,也会从hdfs中把这个jar包拿过去,反射出里面的MyReducer,这样就可以分布式地计算了。12-rw-r--r-- 1 root root 13K Jul 7 03:44 testHadoop-1.0-SNAPSHOT.jar-rw-r--r-- 1 root root 13K Jul 7 03:50 job.jar 查看job.split,发现里面的乱码,这部分乱码之前源码中看到的makeSplit的参数,即split列表中的信息:文件,偏移量,split长度(大小 ),节点主机名。12[root@node01 tmp]# vi job.splitSPL^@^@^@^A/org.apache.hadoop.mapreduce.lib.input.FileSplit*hdfs://mycluster/sxt/mr/wc/input/hello.txt^@^@^@^@^@^@^@^@^@^@^@^@^@%ú? WordCount示例将Job,Mapper,Reducer的example里的代码拼起来就刚好是一个WordCount程序,自己稍作修改就行。 Client类12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package com.chant.mr.wc;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.lib.db.DBInputFormat;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;/** * Created by Chant on 2017/7/8. */public class MyJob { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration conf = new Configuration(true); Job job = Job.getInstance(conf); // Create a new Job// Job job = Job.getInstance(); job.setJarByClass(MyJob.class); // Specify various job-specific parameters job.setJobName("myjob");// job.setInputPath(new Path("in"));// job.setOutputPath(new Path("out"));// 上面的是示例里的传统方法,现在用以下方法,因为还可以从数据库或者其他地方拿数据。 Path input = new Path("/sxt/mr/wc/input"); FileInputFormat.addInputPath(job,input);// DBInputFormat.setInput(); Path output = new Path("/sxt/mr/wc/output"); if (output.getFileSystem(conf).exists(output))//也可以用FileSystem fs = FileSystem.get(conf);然后fs.exist(output) { output.getFileSystem(conf).delete(output); } FileOutputFormat.setOutputPath(job,output); job.setMapperClass(MyMapper.class);// map的输出结果到buffer环里,会序列化。需要告诉reduce你这个反序列化后是什么类型,reduce会将反序列化后的类型强转为对应类型。 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(MyReducer.class); // Submit the job, then poll for progress until the job is complete job.waitForCompletion(true); }} Mapper类1234567891011121314151617181920212223242526package com.chant.mr.wc;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Mapper;import java.io.IOException;import java.util.StringTokenizer;/** * Created by Chant on 2017/7/8. */public class MyMapper extends Mapper<Object, Text, Text, IntWritable> {// 为什么要把word和one定义到map之外呢? 因为map端可能读入很多行,有10万行就调用十万次map方法,如果word和one定义到map里面,就会创建10万个word和one对象,gc就会很忙,程序阻塞。 private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); Thread.sleep(999999); context.write(word, one); } }} Reduceer类123456789101112131415161718192021222324package com.chant.mr.wc;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Reducer;import java.io.IOException;/** * Created by Chant on 2017/7/8. */public class MyReducer extends Reducer<Text,IntWritable, Text,IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); }}]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>API</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java阻塞与非阻塞的同步异步,以及回调]]></title>
<url>%2F2016%2F08%2F19%2Fjava%E5%9B%9E%E8%B0%83%2F</url>
<content type="text"><![CDATA[怎样理解阻塞非阻塞与同步异步的区别,回调又是什么鬼? 阻塞非阻塞与同步异步“阻塞”与”非阻塞”与”同步”与“异步”不能简单的从字面理解,提供一个从分布式系统角度的回答。 1.同步与异步 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。 而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。典型的异步编程模型比如Node.js 举个通俗的例子: 你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。 而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。 2.阻塞与非阻塞 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。 同步异步的经典例子老张爱喝茶,废话不说,煮开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。 所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。 所谓阻塞非阻塞,仅仅对于老张而言。立等的老张,阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。 回调的经典例子回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法,这样子说你是不是有点晕晕的,其实我刚开始也是这样不理解,看了人家说比较经典的回调方式: • Class A实现接口CallBack callback——背景1 • class A中包含一个class B的引用b ——背景2 • class B有一个参数为callback的方法f(CallBack callback) ——背景3 • A的对象a调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C • 然后b就可以在f(CallBack callback)方法中调用A的方法 ——B类调用A类的某个方法D 回调代码示例告诉他干活的结果。这个例子其实是一个回调+异步的例子,再举一个例子,A程序员写了一段程序a,其中预留了回调函数接口,并封装好了该程序,程序员B让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法,到这里你可能似懂非懂了。 首先创建一个回调接口,让老板得告知干完活如何找到他的方式:留下老板办公室地址: 123456789101112package net.easyway.test;/** * 此接口为联系的方式,不论是电话号码还是联系地址,作为 * 老板都必须要实现此接口 * @author Administrator * */public interface CallBackInterface { public void execute();} 创建回调对象,就是老板本人,因为员工干完活后要给他打电话,因此老板必须实现回调接口,不然员工去哪里找老板? 12345678910111213141516package net.easyway.test;/** * 老板是作为上层应用身份出现的,下层应用(员工)是不知道 * 有哪些方法,因此他想被下层应用(员工)调用必须实现此接口 * @author Administrator * */public class Boss implements CallBackInterface { @Override public void execute() { System.out.println("收到了!!" + System.currentTimeMillis()); }} 创建控制类,也就是员工对象,他必须持有老板的地址(回调接口),即使老板换了一茬又一茬,办公室不变,总能找到对应的老板。 123456789101112131415161718192021222324252627package net.easyway.test;/** * 员工类,必须要记住,这是一个底层类,底层是不了解上层服务的 * @author Administrator * */public class Employee { private CallBackInterface callBack = null; //告诉员工老板的联系方式,也就是注册 public void setCallBack(CallBackInterface callBack){ this.callBack = callBack; } //工人干活 public void doSome(){ //1.开始干活了 for(int i=0;i<10;i++){ System.out.println("第【" + i + "】事情干完了!"); } //2.告诉老板干完了 callBack.execute(); }} 测试类代码: 123456789101112131415package net.easyway.test;public class Client { public static void main(String[] args) { Employee emp = new Employee(); //将回调对象(上层对象)传入,注册 emp.setCallBack(new Boss()); //开启控制器对象运行 emp.doSome(); }} 回调总结要明确的一点是,首先要搞清回调函数出现的原因,也就是适用场景,才能搞清楚回调机制,不然事倍功半。 最后,再举一例,为了使我们写的函数接近完美,就把一部分功能外包给别人,让别人个性化定制,至于别人怎么实现不管,我唯一要做的就是定义好相关接口,这一设计允许了底层代码调用高层定义的子程序,增强程序灵活性,和反射有着异曲同工之妙,这才是回调的真正原因! 用一段话来总结下回调:上层模块封装时,很难预料下层模块会如何实现,因此,上层模块只需定义好自己需要但不能预料的接口(也就是回调接口),当下层模块调用上层模块时,根据当前需要的实现回调接口,并通过注册或参数方式传入上层模块即可,这样就实现下层调用上层,并且上层还能根据传入的引用来调用下层的具体实现,将程序的灵活性大大的增加了。 参考文章 Java回调机制解析–代码 经典的回调方式 怎样理解阻塞非阻塞与同步异步的区别?]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[IDEA中文乱码问题]]></title>
<url>%2F2016%2F08%2F17%2FIDEA%E4%B8%AD%E6%96%87%E4%B9%B1%E7%A0%81%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[导入eclipse项目时,出现中文乱码的所有解决办法。 1.导入eclipse项目出现中文乱码这个情况,通常都是因为编码为GBK导致的。第一步是点击右下角,有一个显示你当前编码的地方,然后选择GBK(由于我的项目之前是GBK编码,所以在这里我要是选择的GBK)。另外提醒一点,如果你的idea右下角没有这个按钮,请在你的编码界面中随意右键,然后选择“File Encoding”,效果一样。 第二步会出现如下提示,这里很重要,不要选错,先选择“Reload”,这里请严格按照我说的来,文章后面会告诉你如果选错会有什么后果 接着你就会看到乱码已经变成中文了,但是这对我还没结束,由于我将页面改成了GBK编码,但这并不是我想要的,我想要的是utf8的编码格式。 第三步,就是再次点击设置编码地方,然后选择UTF-8格式,这次就是选择Convert,这就结束了。 相信有些朋友已经有点头绪了。这个“Reload”选择后不会改变文件和内容的编码格式,而是将IDE本身的解码格式由我原先的UTF-8换成了 GBK,由GBK的解码格式解GBK的文件就不会再看到乱码。而“Convert”是将GBK格式的文件内容转换成了UTF-8,同时将IDE的解码格式 也换成UTF-8。所以之前说的,如果你第一次选择了“Convert”那么就会由原来的乱码弄成另一种乱码,反正我是没弄回来过。 控制台输出是乱码比如:System.out.println(“中文”);执行这句话控制台输出乱码,这个问题在Run->Edit configurations中的VM options里加上-Dfile.encoding=UTF-8,就好了,这种问题是操作系统不是中文环境导致的。 所有配置都没问题但仍然输出乱码这个时候只有一种解释:IDEA把你的字体编码弄错了,但是在哪里弄错的呢。经过多次排查寻找,终于,在项目的目录下有个.idea的文件夹,这个文件夹里有个encodings.xml的文件,里面记录了你某些文件对应的特殊编码,为什么会有这种编码呢,因为之前我无意中点了右下角的编码,改了一下,就被idea记录到 encodings.xml中,当你再次访问的时候,它就会用那种编码。我说IDEA你那么智能你妈知道吗?只要把encodings.xml里面的除了UTF-8的都删了就好啦(我的所有字体都是UTF-8)! 参考文章 idea中的汉语注释出现乱码的解决方案 小技巧!两分钟解决IntelliJ IDEA中文乱码问题]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>IDEA</tag>
</tags>
</entry>
<entry>
<title><![CDATA[zookeeper详解]]></title>
<url>%2F2016%2F08%2F06%2Fzookeeper%2F</url>
<content type="text"><![CDATA[zookeeper学习笔记。 Zookeeper介绍在Zookeeper的官网上有这么一句话:ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. 即:1.配置管理,2.名字服务,3.提供分布式同步4.以及集群管理 Zookeeper是Google的Chubby一个开源的实现,是 Hadoop 的分布式协调服务。它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。 Zookeeper特点大部分分布式应用需要一个主控、协调器或控制器来管理物理分布的子进程(如资源、任务分配等),目前,大部分应用需要开发私有的协调程序(JournalNode、Sentinel等),缺乏一个通用的机制,协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器,ZooKeeper提供通用的分布式锁服务,用以协调分布式应用,相比于Keepalived,后者采用优先级监控,监控节点不好管理,没有协同工作,功能单一,可扩展性差 Zookeeper应用Hadoop使用Zookeeper的事件处理确保整个集群只有一个NameNode,存储配置信息等。HBase使用Zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机,存储访问控制列表等。 Zookeeper安装配置解压,配置环境变量 node02上解压zookeeper包12[root@node02 software]# tar xf zookeeper-3.4.6.tar.gz[root@node02 software]# mv zookeeper-3.4.6 /opt/sxt/ 2.修改/etc/profile文件,配置zookeeper路径1234567[root@node02 sxt]# vi + /etc/profileexport JAVA_HOME=/usr/java/jdk1.7.0_67export HADOOP_PREFIX=/opt/sxt/hadoop-2.6.5export ZOOKEEPER_PREFIX=/opt/sxt/zookeeper-3.4.6export PATH=$JAVA_HOME/bin:$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin:$ZOOKEEPER_PREFIX/bin[root@node02 sxt]# . /etc/profile 3. 配置zookeeper12345678910111213141516171819202122232425262728293031[root@node02 sxt]# cd zookeeper-3.4.6/conf[root@node02 conf]# cp zoo_sample.cfg zoo.cfg[root@node02 conf]# vi zoo.cfg# zookeeper数据存放目录dataDir=/var/sxt/zookeeper#发送心跳的间隔时间,单位:毫秒tickTime=2000#日志存放位置dataLogDir=/var/sxt/zookeeper/log#客户端连接Zookeeper服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求clientPort=2181#这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper服务器的客户端,而是 Zookeeper 服务器集群中连接到Leader的Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过5个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表 明这个客户端连接失败。总的时间长度就是 5*2000=10秒initLimit=5#这个配置项标识Leader与Follower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime的时间长度,总的时间长度就是2*2000=4秒syncLimit=2# 在文件末尾追加以下内容server.A=B:C:DA是一个数字,表示这个是第几号服务器;B是这个服务器的ip地址;C表示的是这个服务器与集群中的Leader服务器交换信息的端口;D表示的是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于B都是一样的,所以不同的Zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号选举leader有几个影响因素:1. 集群的投票2. 谁的缓存中数据最多3. 靠ID来区分server.1=node02:2888:3888server.2=node03:2888:3888server.3=node04:2888:3888[root@node02 conf]# mkdir /var/sxt/zookeeper[root@node02 conf]# cd /var/sxt/zookeeper/[root@node02 zookeeper]# echo 1 > myid 4. 配置node03和node04的zookeeper1234567891011[root@node02 sxt]# cd /opt/sxt[root@node02 sxt]# scp -r zookeeper-3.4.6 node03:`pwd`[root@node02 sxt]# scp -r zookeeper-3.4.6 node04:`pwd`[root@node02 sxt]# scp /etc/profile node03:/etc[root@node02 sxt]# scp /etc/profile node04:/etc[root@node03 ~]# . /etc/profile[root@node03 ~]# mkdir /var/sxt/zookeeper[root@node03 ~]# echo 2 > /var/sxt/zookeeper/myid[root@node04 ~]# . /etc/profile[root@node04 ~]# mkdir /var/sxt/zookeeper[root@node04 ~]# echo 3 > /var/sxt/zookeeper/myid 5. 启动zookeeper集群12345678910[root@node02 ~]# zkServer.sh start[root@node03 ~]# zkServer.sh start[root@node04 ~]# zkServer.sh start# 当正常启动服务的集群过半(我们这里过半是两台)时,zookeeper就可以决策出主从关系,当启动的服务不过半(我们这里是只启动一台)时,zkServer的状态是报错状态,id最大的为leader,node04的zookeeper的id为3,所以node04为leader[root@node02 ~]# zkServer.sh statusMode: follower[root@node03 ~]# zkServer.sh statusMode: follower[root@node04 ~]# zkServer.sh statusMode: leader Zookeeper角色领导者(leader),负责进行投票的发起和决议,更新系统状态学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票,Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度客户端(client),请求发起方 Zookeeper选举如何在zookeeper集群中选举出一个leader,zookeeper使用了三种算法:LeaderElectionAuthFastLeaderElectionFastLeaderElection具体使用哪种算法,在配置文件中是可以配置的,对应的配置项是”electionAlg”,其中1对应的是LeaderElection算法,2对应的是AuthFastLeaderElection算法,3对应的是FastLeaderElection算法。默认使用FastLeaderElection算法。我们分析它的选举机制。 选择机制中的概念服务器IDserver.1=node02:2888:3888server.2=node03:2888:3888server.3=node04:2888:3888这里的1、2、3就是服务器的ID,ID越大在选择算法中的权重越大。数据ID每个在zookeeper服务器先读取当前保存在磁盘的数据,zookeeper中的每份数据,都有一个对应的id值,这个值是依次递增的,换言之,越新的数据,对应的ID值就越大,在选举算法中数据越新权重越大。逻辑时钟或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。选举状态LOOKING,竞选状态。FOLLOWING,随从状态,同步leader状态,参与投票。OBSERVING,观察状态,同步leader状态,不参与投票。LEADING,领导者状态。选举消息内容在读取数据完毕之后,每个zookeeper服务器发送自己选举的leader(首次选自己),这个协议中包含了以下几部分的数据: 所选举leader的id(就是配置文件中写好的每个服务器的id)),在初始阶段,每台服务器的这个值都是自己服务器的id,也就是它们都选举自己为leader 服务器最大数据的id,这个值大的服务器,说明存放了更新的数据。 逻辑时钟的值,这个值从0开始递增,每次选举对应一个值,也就是说:如果在同一次选举中,那么这个值应该是一致的。逻辑时钟值越大,说明这一次选举leader的进程更新。 本机在当前选举过程中的状态,有以下几种:LOOKING,FOLLOWING,OBSERVING,LEADING。 选举流程简述1.从磁盘读取数据,更新数据ID2.向其他节点发送投票值,包括服务器ID、数据ID、逻辑时钟、选举状态3.接受来自其他节点的数据 每台服务器将自己服务器的以上数据发送到集群中的其他服务器之后,同样的也需要接收来自其他服务器的数据,它将做以下的处理:(1)如果所接收数据中服务器的状态还是在选举阶段(LOOKING 状态),那么首先判断逻辑时钟值,又分为以下三种情况: a) 如果发送过来的逻辑时钟大于目前的逻辑时钟,那么说明这是更新的一次选举,此时需要更新一下本机的逻辑时钟值,同时将之前收集到的来自其他服务器的选举清空,因为这些数据已经不再有效了,然后判断是否需要更新当前自己的选举情况,在这里是根据选举leader id,保存的最大数据id来进行判断,这两种数据之间对这个选举结果的影响的权重关系是:首先看数据id,数据id大者胜出;其次再判断leader id,leader id大者胜出。然后再将自身最新的选举结果(也就是上面提到的四种数据)广播给其他服务器。 b) 发送过来数据的逻辑时钟小于本机的逻辑时钟,说明对方在一个相对较早的选举进程中,这里只需要将本机的数据发送过去 c) 两边的逻辑时钟相同,此时也只是调用totalOrderPredicate函数判断是否需要更新本机的数据,如果更新了再将自己最新的选举结果广播出去(2)然后再处理两种情况: a) 服务器判断是不是已经收集到了所有服务器的选举状态,如果是,那么这台服务器选举的leader就定下来了,然后根据选举结果设置自己的角色(FOLLOWING还是LEADER),然后退出选举过程 b) 即使没有收集到所有服务器的选举状态,也可以根据该节点上选择的最新的leader是不是得到了超过半数以上服务器的支持,如果是,那么当前线程将被阻塞等待一段时间(这个时间在finalizeWait定义)看看是不是还会收到当前leader的数据更优的leader,如果经过一段时间还没有这个新的leader提出来,那么这台服务器最终的leader就确定了,否则进行下一次选举(3) 如果所接收服务器不在选举状态,也就是在FOLLOWING或者LEADING状态做以下两个判断: a) 如果逻辑时钟相同,将该数据保存到recvset,如果所接收服务器宣称自己是leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态,退出选举过程b) 否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程 集群启动leader选举的过程假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的,假设这些服务器依序启动,来看看会发生什么.1) 服务器1启动,此时只有它一台服务器启动了,它发出去的信息没有任何响应,所以它的选举状态一直是LOOKING状态2) 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态3) 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader4) 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接受当小弟的命了5) 服务器5启动,同4一样,当小弟 Leader down掉之后的选举 Zookeeper的一些概念假死在分布式系统中,每个节点上都存在一个监控者来监控本机的状态,但是监控者很难判定其他的节点的状态,心跳是一种可靠的途径,Zookeeper就是使用心跳来判断客户端是否仍然活着。使用ZooKeeper来做master HA基本都是同样的方式,每个节点都尝试注册一个象征master的临时节点,没有注册成功的成为slave,并且通过watch机制监控着master所创建的临时节点,Zookeeper通过内部心跳机制来确定master的状态,一旦master出现意外,Zookeeper能很快获悉并且通知其他的slave,其他slaver在之后作出相关反应。这样就完成了一个切换。这种模式也是比较通用的模式,基本大部分都是这样实现的,但是,心跳出现超时可能是master挂了,但是也可能是master,zookeeper之间网络出现了问题,我们把因为master和zookeeper之间的网络问题造成的两者之间不能通信这种情况称作假死。 网络分区/脑裂lit-brain” mean? “Split brain” is a condition whereby two or more computers or groups of computers lose contact with one another but still act as if the cluster were intact. This is like having two governments trying to rule the same country. If multiple computers are allowed to write to the same file system without knowledge of what the other nodes are doing, it will quickly lead to data corruption and other serious problems. Split-brain is prevented by enforcing quorum rules (which say that no group of nodes may operate unless they are in contact with a majority of all nodes) and fencing (which makes sure nodes outside of the quorum are prevented from interfering with the cluster).在“双机热备”高可用(HA)系统中,当联系2个节点的“心跳线”断开时,本来为一整体、动作协调的HA系统,就分裂成为2个独立的个体。由于相互失去了联系,都以为是对方出了故障,2个节点上的HA软件像“裂脑人”一样,“本能”地争抢“共享资源”、争起“应用服务”,就会发生严重后果:或者共享资源被瓜分、2边“服务”都起不来了;或者2边“服务”都起来了,但同时读写“共享存储”,导致数据损坏(常见如数据库轮询着的联机日志出错)。即:过半机制解决了脑裂问题。 半数机制在zookeeper的选举在zookeeper的选举在zookeeper的选举在zookeeper的选举机制中,只要有过半的机器已经选好了leader,leader就被确认了,而不会询问每台服务器的意见,这就是半数机制。 顺序性客户端的更新顺序与它们被发送的顺序相一致。客户端发送给leader的请求,会被leader记录一个序列号,达到请求有序的效果,leader与所有flower之间会建立消息队列,请求的请求会放到follower队列中,达到有序的处理请求的效果。 可用性Zookeeper保证了可Zookeeper保证了可用性,数据总是可用的,没有锁。并且有一大半的节点所拥有的数据是最新的,实时的。如果想保证取得是数据一定是最新的,需要手工调用Sync() 原子性更新操作要么成功要么失败,没有第三种结果。 一致性一致性是指从系统外部读取系统内部的数据时,在一定约束条件下相同,即数据变动在系统内部各节点应该是同步的。根据一致性的强弱程度不同,可以将一致性级别分为如下几种: ①强一致性(strong consistency)。任何时刻,任何用户都能读取到最近一次成功更新的数据。 ②单调一致性(monotonic consistency)。任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也就是说,可获取的数据顺序必是单调递增的。③会话一致性(session consistency)。任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这值更旧的值,会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不同用户或同一用户不同会话间则没有保障。④ 最终一致性(eventual consistency)。用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。⑥弱一致性(weak consistency)。用户无法在确定时间内读到最新更新的值。 Zookeeper保证了最终一致性,原因: 1. 假设有2n+1个server,在同步流程中,leader向follower同步数据,当同步完成的follower数量大于 n+1时同步流程结束,系统可接受client的连接请求。如果client连接的并非同步完成的follower,那么得到的并非最新数据,但在一段时间后,所有数据都会同步。 2. follower接收写请求后,转发给leader处理;leader完成两阶段提交的机制。向所有server发起提案,当提案获得超过半数(n+1)的server认同后,将对整个集群进行同步,超过半数(n+1)的server同步完成后,该写请求完成。如果client连接的并非同步完成follower,那么得到的并非最新数据,但在一段时间后数据都会同步。 Session客户端与集群节点建立TCP连接后获得一个session,如果连接的Server出现问题,在没有超过Timeout时间时,可以连接其他节点,同一session期内的特性不变 Zookeeper数据模型ZnodeZookeeper的结构是目录型结构,便于管理逻辑关系,节点znode不是文件file,Znode中的信息包含最大1MB的数据信息和Zxid等元数据信息节点类型Znode有两种类型,短暂的(ephemeral)和持久的(persistent),短暂znode的客户端会话结束时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点,持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除,Znode的类型在创建时确定并且之后不能再修改,Znode有四种形式的目录节点:PERSISTENTEPHEMERALPERSISTENT_SEQUENTIAL(有序)EPHEMERAL_SEQUENTIAL(有序) 事件监听WatcherWatcher是ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应。可以设置观察的操作:exists,getChildren,getData可以触发观察的操作:create,delete,setData 原子消息广播协议ZABZookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式和广播模式。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server的完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和server具有相同的系统状态广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,低32位是个递增计数。 Zookeeper全解析—Paxos作为灵魂(摘抄)ZK Server最基础的东西是什么呢?我想应该是Paxos了。所以本文会介绍Paxos以及它在ZK Server中对应的实现。 先说Paxos,它是一个基于消息传递的一致性算法,Leslie Lamport在1990年提出,近几年被广泛应用于分布式计算中,Google的Chubby,Apache的Zookeeper都是基于它的理论来实现的,Paxos还被认为是到目前为止唯一的分布式一致性算法,其它的算法都是Paxos的改进或简化。有个问题要提一下,Paxos有一个前提:没有拜占庭将军问题。就是说Paxos只有在一个可信的计算环境中才能成立,这个环境是不会被入侵所破坏的。 关于Paxos的具体描述可以在Wiki中找到:http://zh.wikipedia.org/zh-cn/Paxos算法。网上关于Paxos分析的文章也很多。这里希望用最简单的方式加以描述并建立起Paxos和ZK Server的对应关系。 Paxos描述了这样一个场景,有一个叫做Paxos的小岛(Island)上面住了一批居民,岛上面所有的事情由一些特殊的人决定,他们叫做议员(Senator)。议员的总数(Senator Count)是确定的,不能更改。岛上每次环境事务的变更都需要通过一个提议(Proposal),每个提议都有一个编号(PID),这个编号是一直增长的,不能倒退。每个提议都需要超过半数((Senator Count)/2 +1)的议员同意才能生效。每个议员只会同意大于当前编号的提议,包括已生效的和未生效的。如果议员收到小于等于当前编号的提议,他会拒绝,并告知对方:你的提议已经有人提过了。这里的当前编号是每个议员在自己记事本上面记录的编号,他不断更新这个编号。整个议会不能保证所有议员记事本上的编号总是相同的。现在议会有一个目标:保证所有的议员对于提议都能达成一致的看法。 好,现在议会开始运作,所有议员一开始记事本上面记录的编号都是0。有一个议员发了一个提议:将电费设定为1元/度。他首先看了一下记事本,嗯,当前提议编号是0,那么我的这个提议的编号就是1,于是他给所有议员发消息:1号提议,设定电费1元/度。其他议员收到消息以后查了一下记事本,哦,当前提议编号是0,这个提议可接受,于是他记录下这个提议并回复:我接受你的1号提议,同时他在记事本上记录:当前提议编号为1。发起提议的议员收到了超过半数的回复,立即给所有人发通知:1号提议生效!收到的议员会修改他的记事本,将1好提议由记录改成正式的法令,当有人问他电费为多少时,他会查看法令并告诉对方:1元/度。 现在看冲突的解决:假设总共有三个议员S1-S3,S1和S2同时发起了一个提议:1号提议,设定电费。S1想设为1元/度, S2想设为2元/度。结果S3先收到了S1的提议,于是他做了和前面同样的操作。紧接着他又收到了S2的提议,结果他一查记事本,咦,这个提议的编号小于等于我的当前编号1,于是他拒绝了这个提议:对不起,这个提议先前提过了。于是S2的提议被拒绝,S1正式发布了提议: 1号提议生效。S2向S1或者S3打听并更新了1号法令的内容,然后他可以选择继续发起2号提议。 好,我觉得Paxos的精华就这么多内容。现在让我们来对号入座,看看在ZK Server里面Paxos是如何得以贯彻实施的。 小岛(Island)——ZK Server Cluster 议员(Senator)——ZK Server 提议(Proposal)——ZNode Change(Create/Delete/SetData…) 提议编号(PID)——Zxid(ZooKeeper Transaction Id) 正式法令——所有ZNode及其数据 貌似关键的概念都能一一对应上,但是等一下,Paxos岛上的议员应该是人人平等的吧,而ZK Server好像有一个Leader的概念。没错,其实Leader的概念也应该属于Paxos范畴的。如果议员人人平等,在某种情况下会由于提议的冲突而产生一个“活锁”(所谓活锁我的理解是大家都没有死,都在动,但是一直解决不了冲突问题)。Paxos的作者Lamport在他的文章”The Part-Time Parliament“中阐述了这个问题并给出了解决方案——在所有议员中设立一个总统,只有总统有权发出提议,如果议员有自己的提议,必须发给总统并由总统来提出。好,我们又多了一个角色:总统。 总统——ZK Server Leader 又一个问题产生了,总统怎么选出来的?oh, my god! It’s a long story. 在淘宝核心系统团队的Blog上面有一篇文章是介绍如何选出总统的,有兴趣的可以去看看:http://rdc.taobao.com/blog/cs/?p=162 现在我们假设总统已经选好了,下面看看ZK Server是怎么实施的。 情况一: 屁民甲(Client)到某个议员(ZK Server)那里询问(Get)某条法令的情况(ZNode的数据),议员毫不犹豫的拿出他的记事本(local storage),查阅法令并告诉他结果,同时声明:我的数据不一定是最新的。你想要最新的数据?没问题,等着,等我找总统Sync一下再告诉你。 情况二: 屁民乙(Client)到某个议员(ZK Server)那里要求政府归还欠他的一万元钱,议员让他在办公室等着,自己将问题反映给了总统,总统询问所有议员的意见,多数议员表示欠屁民的钱一定要还,于是总统发表声明,从国库中拿出一万元还债,国库总资产由100万变成99万。屁民乙拿到钱回去了(Client函数返回)。 情况三: 总统突然挂了,议员接二连三的发现联系不上总统,于是各自发表声明,推选新的总统,总统大选期间政府停业,拒绝屁民的请求。 呵呵,到此为止吧,当然还有很多其他的情况,但这些情况总是能在Paxos的算法中找到原型并加以解决。这也正是我们认为Paxos是Zookeeper的灵魂的原因。当然ZK Server还有很多属于自己特性的东西:Session, Watcher,Version等等等等,需要我们花更多的时间去研究和学习。 Zookeeper客户端1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495#启动服务[root@node02 ~]# zkServer.sh start#进入客户端[root@node02 ~]# zkCli.sh#查看zookeeper有哪些命令[zk: localhost:2181(CONNECTED) 0] helpZooKeeper -server host:port cmd args connect host:port get path [watch] ls path [watch] set path data [version] rmr path delquota [-n|-b] path quit printwatches on|off create [-s] [-e] path data acl stat path [watch] close ls2 path [watch] history listquota path setAcl path acl getAcl path sync path redo cmdno addauth scheme auth delete path [version] setquota -n|-b val path[zk: localhost:2181(CONNECTED) 1] ls /[hbase, yarn-leader-election, hadoop-ha, zookeeper]#创建节点[zk: localhost:2181(CONNECTED) 2] create /sxt01 "hello"Created /sxt01[zk: localhost:2181(CONNECTED) 3] ls /[sxt01, hbase, yarn-leader-election, hadoop-ha, zookeeper][zk: localhost:2181(CONNECTED) 4] get /sxt01"hello"cZxid = 0x1800000003ctime = Wed Jul 26 05:15:08 CST 2017mZxid = 0x1800000003mtime = Wed Jul 26 05:15:08 CST 2017pZxid = 0x1800000003cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 7numChildren = 0[zk: localhost:2181(CONNECTED) 5] set /sxt01 "byebye"cZxid = 0x1800000003ctime = Wed Jul 26 05:15:08 CST 2017mZxid = 0x1800000004mtime = Wed Jul 26 05:17:53 CST 2017pZxid = 0x1800000003cversion = 0dataVersion = 1aclVersion = 0ephemeralOwner = 0x0dataLength = 8numChildren = 0#监控节点[zk: localhost:2181(CONNECTED) 6] get /sxt01 true"byebye"……[zk: localhost:2181(CONNECTED) 7] set /sxt01 "world"WATCHER::#这里只是打印出监控的节点变化了,代码中可以自定义功能WatchedEvent state:SyncConnected type:NodeDataChanged path:/sxt01cZxid = 0x1800000003……#创建带序列号的节点(序列号单步递增)[zk: localhost:2181(CONNECTED) 8] create -s /sxt01 ""Created /sxt010000000007[zk: localhost:2181(CONNECTED) 9] create -s /sxt01 ""Created /sxt010000000008#创建临时节点[zk: localhost:2181(CONNECTED) 10] create -e /sxt02 "sxt02"Created /sxt02……#再开一个客户端查看这个节点,有临时节点标志[zk: localhost:2181(CONNECTED) 2] get /sxt02"sxt02"……ephemeralOwner = 0x15d7b9833480000 #写明了持有者dataLength = 7numChildren = 0#关闭创建临时节点的客户端后,别的客户端就不能访问这个临时节点了[zk: localhost:2181(CONNECTED) 2] get /sxt02Node does not exist: /sxt02 参考资料 Zookeeper可以干什么 ZooKeeper常见问题 Zookeeper编程]]></content>
<categories>
<category>Bigdata</category>
<category>Zookeeper</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>Zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--yarn详解及配置测试]]></title>
<url>%2F2016%2F07%2F06%2FHadoop-yarnTest%2F</url>
<content type="text"><![CDATA[YARN:Yet Another Resource Negotiator Client通过RS(ResourceManager)来提交作业,RS选择一个节点创建AppMaster进程,AppMaster启动后向RS申请资源(申请Container),然后RS去通知NodeManager来启动Container进程,然后AppMaster向Container分发任务(比如让它反射哪个类)。即任务调度(任务运行的先后顺序,重试机制)、任务监控和容错等由AppMaster来完成,资源(每个节点硬件资源,Container也是资源)管理全部交给RS管理。 图中的客户端提交了两个作业,每个作业都有自己的AppMaster,其中一个AppMaster死了不会影响另一个AppMaster,任务调度不是单点运行,一个任务故障不会影响另一个任务,其实,AppMaster也是一种Container,其容错机制为:如果AppMaster挂了,MR会另起一个AppMaster。解决了1.0里面的jobTracker负载过重,单点故障问题。下图是spark提交作业到yarn流程图,给以后的spark笔记做个铺垫吧。 搭建yarn1234567[root@node01 hadoop]# vi mapred-site.xml<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration> 12345678910111213141516171819202122232425262728293031323334[root@node01 hadoop]# vi yarn-site.xml<configuration><!-- Site specific YARN configuration properties --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property><property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <property> <name>yarn.resourcemanager.cluster-id</name> <value>cluster1</value> </property> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>node03</value> </property> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>node04</value> </property> <property> <name>yarn.resourcemanager.zk-address</name> <value>node02:2181,node03:2181,node04:2181</value> </property></configuration> 分发配置到集群。12root@node01 hadoop]# scp mapred-site.xml yarn-site.xml node02:`pwd`#node03和node04同理 node01上启动yarn。nodemanager会启动。1234567[root@node01 hadoop]# start-yarn.shstarting yarn daemonsstarting resourcemanager, logging to /opt/sxt/hadoop-2.6.5/logs/yarn-root-resourcemanager-node01.outnode04: starting nodemanager, logging to /opt/sxt/hadoop-2.6.5/logs/yarn-root-nodemanager-node04.outnode02: starting nodemanager, logging to /opt/sxt/hadoop-2.6.5/logs/yarn-root-nodemanager-node02.outnode03: starting nodemanager, logging to /opt/sxt/hadoop-2.6.5/logs/yarn-root-nodemanager-node03.out[root@node01 hadoop]# 但是yarnmanager不会,需要zai node03和node04上单独启动。记得jps验证一下。12345678910[root@node03 etc]# yarn-daemon.sh start resourcemanagerstarting resourcemanager, logging to /opt/sxt/hadoop-2.6.5/logs/yarn-root-resourcemanager-node03.out[root@node03 etc]# jps1703 QuorumPeerMain3316 ResourceManager3540 Jps2748 JournalNode3178 NodeManager2668 DataNode# node04同理 访问http://node03:8088/cluster和http://node04:8088/cluster。讲道理,node04会发生跳转到node03。但是我的出错了。clusterID都不一致了。 12[root@node03 logs]# cd /opt/sxt/hadoop-2.6.5/logs[root@node03 logs]# vi yarn-root-resourcemanager-node03.log 发现是hosts文件配置错误 修改hosts文件后再启动resourcemanager,jps查看进程启动成功,访问node03:8088发生跳转。 WordCount测试/opt/sxt/hadoop-2.6.5/share/hadoop/mapreduce文件夹下执行example包中的wordcount程序,然后查看输出是否正确。在任务执行过程中,可以去其它节点jps查看下进程,node02会多了一个YarncChild,node03会多了个MRAppMaster,node04多了两个YarnChild。作业结束,这些进程也就消失了。1[root@node01 mapreduce]# hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount /user/root /output ResourceManager和NodeManager是yarn框架的常服务,提交任务后,会在node03启动MRAppMaster并和ResourceManager去通信,然后申请Container, 也就是那三个YarncChild,这三个Container中运行map task 和reduce task。]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>yarn</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--mac下eclipse连接hadoop集群开发API]]></title>
<url>%2F2016%2F07%2F05%2FHadoop-eclipseConnectHadoop%2F</url>
<content type="text"><![CDATA[mac下eclipse连接hadoop集群开发API 下载安装EclipseEclipse IDE for Java Developers 配置Eclipse下载Hadoop-Eclipse-Plugin可下载 Github 上的 hadoop2x-eclipse-plugin(选择下载hadoop-eclipse-plugin-2.6.0.jar) 安装Hadoop-Eclipse-Plugin在Applications中找个Eclise, 右键, Show Package Contents 将插件复制到plugins目录下然后重新打开Eclipse,添加插件。 连接Hadoop集群配置Hadoop安装目录1)mkdir /Users/Chant/opt将源码包和部署包都解压到这里。2)配置环境变量vim ~/.zshrc 12export HADOOP_PREFIX=/Users/Chant/opt/hadoop-2.6.5export PATH="$ANACONDA_HOME/bin:/usr/local/bin:$PATH:$HADOOP_HOME/bin" 3)在Eclipse中指向该安装目录(Eclipse–>偏好设置)需要改Location name, Location name 这里就是起个名字,随便起。Port填的是8020,这个由你的集群配置决定(去web页面可以查看端口)。 ( 勾选Use M/R的话,需要在hosts文件中添加192.168.14.11 Master # 添加Master的IP,Master会引用Mac中的hosts配置的IP ) Map/Reduce(V2) Master Host 这里就是虚拟机里hadoop master对应的IP地址,下面的端口对应 hdfs-site.xml里dfs.datanode.ipc.address属性所指定的端口,默认端口50020。 DFS Master Port: 这里的端口,对应core-site.xml里fs.defaultFS所指定的端口 最后的user name要跟虚拟机里运行hadoop的用户名一致,比如用zkpk身份安装运行hadoop 2.5.2的,所以这里填写zkpk,如果你是用root安装的,相应的改成root,但是改成root也没用,且看权限设置。 权限设置(Permisssion deny的三种解决办法)1.由于客户端与服务器的权限问题,对输入目录等需要赋予授权123456# 假设Mac的用户名为hadoopgroupadd supergroup # 添加supergroup组useradd -g supergroup hadoop # 添加hadoop用户到supergroup组# 修改hadoop集群中hdfs文件的组权限, 使属于supergroup组的所有用户都有读写权限hadoop fs -chmod 777 / 2.或者hdfs-site.xml里添加,并重启hdfs。1234<property> <name>dfs.permissions.enabled</name> <value>false</value></property> 3.或者在环境变量中设置HADOOP_USER_NAME值为root,就不用改系统用户名了(windows下是可以修改电脑用户名为root来解决)。ps:记得重启一下eclipse。 查看HDFS点击展开左上角的DFS Location, 查看是否可以直接访问HDFS。 添加jar包使用命令将部署包中(其实可以不要kms和httpfs下的jar包)的jar包都拷贝到同一目录下,方便添加。12[root@node01 share]# hadoop-2.6.5/share[root@node01 share]# \cp $(find hadoop -name *.jar) hadoop-2.6.5-lib.test 添加测试单元在项目下新建conf(HA,full分别用于不同的模式)文件夹,用于存储hdfs集群配置 配置Hadoop参数复制集群的配置文件。1$ scp root@node02:/opt/sxt/hadoop-2.6.5/etc/hadoop/core-site.xml root@node02:/opt/sxt/hadoop-2.6.5/etc/hadoop/hdfs-site.xml ./ 拷贝到HA文件夹下面,并将HA目录设置为源目录,这样就能通过相对路径引用它了。效果如下: 链接源码随便搜索一个类(command+shift+T,搜索NameNode),发现没有源码,点击Attach Source -> External location -> External Floder 测试创建测试类HDFS-API开发测试123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384package com.chant.hadoop.hdfs.test;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.BlockLocation;import org.apache.hadoop.fs.FSDataInputStream;import org.apache.hadoop.fs.FSDataOutputStream;import org.apache.hadoop.fs.FileStatus;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import org.junit.After;import org.junit.Before;import org.junit.Test;public class TestHDFS { Configuration conf = null; FileSystem fs = null; @Before public void conn() throws IOException{ conf = new Configuration(false); //默认即为true,去bin目录下寻找配置文件并读取 conf.set("fs.defaultFS", "hdfs://node01:8020"); fs = FileSystem.get(conf); } @After public void close() throws IOException{ fs.close(); } @Test public void testConn(){ System.out.print(conf.get("fs.defaultFS")); } @Test public void mkdir() throws IOException{ Path tmp = new Path("/temp"); if(!fs.exists(tmp)){ fs.mkdirs(tmp); } } //上传文件 @Test public void uploadFile() throws IOException{ Path file = new Path("/temp/sxt.txt"); FSDataOutputStream output = fs.create(file); InputStream input = new BufferedInputStream(new FileInputStream(new File("/Users/Chant/core-site.xml"))); IOUtils.copyBytes(input, output, conf, true); } //下载文件 @Test public void download() throws IOException{ Path file = new Path("/temp/sxt.txt"); FSDataInputStream input = fs.open(file); OutputStream output = new BufferedOutputStream(new FileOutputStream(new File("/Users/Chant/sxtTest"))); IOUtils.copyBytes(input, output, conf, true); }// 获取blockLocations,偏移量,计算向数据移动就靠这个来支撑 @Test public void bl() throws IOException{ Path f = new Path("/user/root/hello.txt"); FileStatus file = fs.getFileStatus(f); BlockLocation[] fileBlockLocations = fs.getFileBlockLocations(file, 0, file.getLen()); for (BlockLocation blockLocation : fileBlockLocations) { System.out.println(blockLocation); } //读取指定block中的数据 FSDataInputStream input = fs.open(f); input.seek(1068577);//修改读取的偏移量,计算向数据移动 System.out.println((char)input.readByte()); }} 运行,取到了配置信息。 mkdir测试 获取blockLocations获取blockLocations(偏移量,位置信息),计算向数据移动就靠这个来支撑。访问http://node01:50070/找到文件的block信息,然后去对应节点查看文件,再测试代码,看读取是否正确。 1234567[root@node03 logs]# cd /var/sxt/hadoop/ha/dfs/data/current/BP-570581829-192.168.14.11-1499209010319/current/finalized/subdir0/subdir0/[root@node03 subdir0]# ls *1073741826*blk_1073741826 blk_1073741826_1002.meta[root@node03 subdir0]# head blk_1073741826hant hadoop 42388hello Chant hadoop 42389hello Chant hadoop 42390 参考资料 Mac下Eclipse提交任务到Hadoop集群]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>API</tag>
<tag>hadoop</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP--mac下IDEA连接hadoop集群开发API]]></title>
<url>%2F2016%2F07%2F05%2FHadoop-IDEAConnectHadoop%2F</url>
<content type="text"><![CDATA[mac下IDEA连接hadoop集群开发API 创建项目 pom.xml文件如下 1234567891011121314151617181920212223242526272829303132333435363738394041<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.Chant.hadoop</groupId> <artifactId>testHadoop</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <hadoop.version>2.6.5</hadoop.version> </properties> <dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>${hadoop.version}</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>${hadoop.version}</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>${hadoop.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies></project> 添加依赖的Libary引用项目和是哪个右击open module stting或者command+向下键。使用命令将部署包中share/hadoop下(其实可以不要kms和httpfs下的jar包)的jar包都拷贝到同一目录下,方便添加。12[root@node01 share]# hadoop-2.6.5/share[root@node01 share]# \cp $(find hadoop -name *.jar) hadoop-2.6.5-lib.test 将所有jar包添加进来,导入的libary可以起个名称,比如hadoop2.6.5。 设置运行参数hdfs://node01:9000/chant/words/input/test.txthdfs://node01:9000/chant/words/output大家参考这个改一下(主要是把IP换成自己虚拟机里的IP),注意的是,如果input/test.txt文件没有,请先手动上传,然后/output/ 必须是不存在的,否则程序运行到最后,发现目标目录存在,也会报错, 这里IP和端口要看集群配置,比如我的core-site.xml里dfs.DefaultFS配的是hdfs://mycluster,而hdfs-site.xml里rpc配的node01:8020,所以写node01:8020。12345678910# core-site.xml<property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value></property># hdfs-site.xml<property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node01:8020</value></property> working directory:指向你本地的hadoop安装目录。 WordCount测试代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133package com.chant.mr;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import org.apache.hadoop.io.IntWritable;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.GenericOptionsParser;import java.io.IOException;import java.io.InputStream;/** * Created by Chant on 2017/7/6. *//** * 这是统计单词个数的例子 * <p> * Created by zhangws on 16/7/31. */public class WordsCount { public static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable> { public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] strings = value.toString().split(" "); for (String s : strings) { //将文本行放入key context.write(new Text(s), new IntWritable(1)); } } } public static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> { public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int count = 0; for (IntWritable v : values) { count += v.get(); } //输出key context.write(key, new IntWritable(count)); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); if (otherArgs.length < 2) { System.err.println("Usage: wordcount <in> [<in>...] <out>"); System.exit(2); } //先删除output目录 rmr(conf, otherArgs[otherArgs.length - 1]); Job job = Job.getInstance(conf, "WordsCount"); job.setJarByClass(WordsCount.class); job.setMapperClass(MyMapper.class); job.setCombinerClass(MyReducer.class); job.setReducerClass(MyReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); if (job.waitForCompletion(true)) { cat(conf, otherArgs[1] + "/part-r-00000"); System.out.println("success"); } else { System.out.println("fail"); } } /** * 删除指定目录 * * @param conf * @param dirPath * @throws IOException */ private static void rmr(Configuration conf, String dirPath) throws IOException { boolean delResult = false;// FileSystem fs = FileSystem.get(conf); Path targetPath = new Path(dirPath); FileSystem fs = targetPath.getFileSystem(conf); if (fs.exists(targetPath)) { delResult = fs.delete(targetPath, true); if (delResult) { System.out.println(targetPath + " has been deleted sucessfullly."); } else { System.out.println(targetPath + " deletion failed."); } }// return delResult; } /** * 输出指定文件内容 * * @param conf HDFS配置 * @param filePath 文件路径 * @return 文件内容 * @throws IOException */ public static void cat(Configuration conf, String filePath) throws IOException {// FileSystem fileSystem = FileSystem.get(conf); InputStream in = null; Path file = new Path(filePath); FileSystem fileSystem = file.getFileSystem(conf); try { in = fileSystem.open(file); IOUtils.copyBytes(in, System.out, 4096, true); } finally { if (in != null) { IOUtils.closeStream(in); } } }} 参考文章 Macbook Intellij idea与Eclipse远程调试Hadoop应用程序 eclipse/intellij idea 远程调试hadoop 2.6.0]]></content>
<categories>
<category>Bigdata</category>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>API</tag>
</tags>
</entry>
<entry>
<title><![CDATA[LVS+Keepalived+Nginx实战]]></title>
<url>%2F2016%2F06%2F16%2FLVS%2BKeepalived%2BNginx%20%2F</url>
<content type="text"><![CDATA[LVS+Keepalived+Nginx实战,从背景介绍到搭建测试。 大型网站架构演进历程初始阶段应用程序、数据库、文件等所有资源在一台服务器上。典型架构Linux+Apache+Mysql+PHP,简称LAMP。 应用服务和数据服务分离随着网站业务的发展,一台服务器逐渐不能满足需求:越来越多的用户访问导致性能越来越差,越来越多的数据导致存储空间不足。这时就需要将应用和数据分离。这三台服务器对硬件资源的要求各不相同,应用服务器要处理大量业务逻辑,因此需要更快更强大的CPU;数据库服务器需要磁盘检索和数据缓存,因此需要更大的硬盘和内存,文件服务器需要存储大量用户上传的文件,因此需要更大的硬盘。 使用缓存改善网站性能网站访问特点和现实世界的财富分配一样遵循二八定律:80%的业务访问集中在20%的数据上。例如淘宝买家浏览的商品集中在少部分成交数多、评价良好的商品上;百度搜索关键词集中在少部分热门词汇上。网站使用的缓存分为两种:缓存在应用服务器上的本地缓存和缓存在专门的分布式缓存服务器的远程缓存。本地缓存的速度更快一些,但是受应用服务器内存限制,其缓存数量有限,而且会出现与应用程序争用内存的情况。远程分布式缓存可以使用集群的方式,部署大内存的服务器作为专门的缓存服务器,可以在理论上做到不受内存容器限制的缓存服务。 使用应用服务器集群改善网站并发处理处理能力使用集群是网站解决高并发、海量数据问题的常用手段。通过使用应用服务器集群,改善负载压力,实现系统的可伸缩性。通过负载均衡调度服务器,可将来自用户浏览器的访问请求分发到应用服务器集群中的任何一台服务器。当有更多的用户时,在集群中加入更多的应用服务器即可。当一台服务器的处理能力、存储空间不足时,不要企图去更换更强大的服务器,对答应网站而言,不管多么强大的服务器,都满足不了网站持续增长的业务需求,这种情况下,跟恰当的做法是增加一台服务器分担缘由服务器的访问及存储压力。 数据库读写分离网站在使用缓存后,是绝大部分数据读操作访问都可以不通过数据库就能完成,但是仍有一部分读操作(缓存访问不命中、缓存过期)和全部的写操作需要访问数据库,在网站的规模达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈。目前大部分的主流数据库都提供主从热备的功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。网站利用数据库的这一功能,实现数据库读写分离,从而改善数据库负载压力。 使用反向代理和CDN加速网站响应使用网站业务不断发展,用户规模越来越大,由于中国复杂的网络环境,不同地区的用户访问网站时,速度差别也极大。使用CDN和反向代理的目的都是早返回数据给用户。一方面加快用户访问速度,另一方面也减轻后端服务器的负载压力。CDN和反向代理的基本原理都是缓存,区别在于CDN部署在网络提供商的机房,使用户在请求网站服务时,可以从距离自己最近的网络提供商机房提取数据;而反向代理则部署在网站的中心机房,当用户请求到达中心机房后,首先访问的服务器是反向代理服务器,如果反向代理服务器缓存着用户请求的资源,就将其直接返回给用户。 使用分布式文件系统和分布式数据库系统分布式数据库是网站数据库拆分的最后手段,只有在单表数据规模非常庞大时才使用。网站最长使用的数据库拆分手段是业务分库,将不同业务的数据库部署在不同的物理服务器上。 业务拆分大型网站为了应付日益复杂的业务场景,通过使用分而治之的手段将整个网站业务分为不同的产品线,如大型电商网站会将首页、商铺、订单、买家、卖家等拆分成不同的产品线,分归不同的业务团队负责。将一个网站拆分成不同的应用,每个应用独立部署维护。应用之间可以通过超链接建立联系,也可以通过消息队列进行数据转发,也可通过同一个数据库系统构建一个关联的完整系统。 使用NoSQL和搜索引擎随着网站业务越来越复杂,对数据存储和检索的需求也越来越复杂,网站需要采用一些非数据库技术如NoSQL和非数据库查询技术如搜索引擎。源自互联网的技术手段,对可伸缩的分布式特性具有更好的支持。应用服务器则通过一个统一的数据模块访问各种数据,减轻应用程序管理诸多数据的麻烦。 分布式服务随着业务拆分越来越小,存储系统越来越庞大,应用系统的整体复杂度呈指数级增加,部署维护越来越难。由于所有应用要和所有数据库系统连接,在数万台服务器规模的网站中,这些连接的数目是服务器规模的平方,导致数据库连接资源不足,拒绝服务。各个应用系统需要执行很多相同的业务操作,比如用户管理、商品管理等。可以将这些共用业务提取出来,独立部署。应用系统只需要通过调用共用业务服务完成具体业务操作。 大型网站架构核心技术分布式缓存系统分布式缓存概述分布式缓存系统是进化的产物。本地缓存 -> 集群缓存 -> 分布式缓存(数据网格)。性能:访问缓存中的一个对象比直接访问远端数据存储引擎(例如数据库)要快很多。直接访问一个已经存在的对象比从数据创建一个对象要快。数据网格支持一些性能调优特性可能不被集群缓存所支持,例如,应用程序可以根据数据之间关联关系的紧密程度来确保相互关联的对象被保存在相同的缓存节点。一致性:本地缓存只有在应用程序被部署到单一的应用服务器上的时候才有意义,如果它被部署到了多台应用服务器上的话, 那么本地缓存一点意义都没有,问题出在过期数据。集群缓存是通过复制和让缓存数据失效来解决这个问题的。除了支持JTA事物之外,分布式缓存还支持XA(分布式)和两阶段提交事物。可伸缩性:集群缓存和数据网格的区别就在于可伸缩性。数据网格是可伸缩的。缓存数据是通过动态的分区被分发的。结果就是,增加一个缓存节点既提高了吞吐量也提高了容量。独立性:允许分布式缓存能够独立于应用服务器而被独立的扩展. 也让其服务器能够被指派与应用服务器不同的资源。这样的架构也让数据网格的基础架构能够独立于应用服务器的惯例和调整。能够独立于应用而被升级,应用的重新部署也不会对数据网格本身产生任何影响。基础架构:使用在基础架构中作为顶级系统的独立数据网格服务。 使用分布式缓存的好处是它的可扩展性和独立性,并且,作为顶级基础设施组件,它能够同时提供本地缓存和集群缓存所能够提供的性能和一致性。 典型应用场景页面缓存: 用来缓存Web页面的内容片段,包括HTML、CSS和图片等,多应用于社交网站等。应用对象缓存: 缓存系统作为ORM框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问。状态缓存: 缓存包括Session会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群。并行处理: 通常涉及大量中间计算结果需要共享。事件处理: 分布式缓存提供了针对事件流的连续查询(continuous query)处理技术,满足实时性需求。 Memcached分布式缓存系统Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached是基于一个存储键/值对的HashMap。其守护进程是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。 Memcached的存储方式: 为了提高性能,Memcached中保存的数据都存储在Memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启Memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used,近期最少使用)算法自动删除不使用的缓存。Memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。 Redis分布式缓存系统Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与Memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。Redis支持语言:Redis的存储方式:Redis的存储分为:内存存储、磁盘存储和log文件三部分,配置文件中有三个参数对其进行配置。save seconds updates,save配置,指出在多长时间内,有多少次更新操作,就将数据同步到数据文件。这个可以多个条件配合,比如默认配置文件中的设置,就设置了三个条件。appendonly yes/no ,appendonly配置,指出是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面的save条件来同步的,所以有的数据会在一段时间内只存在于内存中。appendfsync no/always/everysec ,appendfsync配置,no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次。 两种分布式缓存系统比较存储数据位置:Redis中,并不是所有的数据都一直存储在内存中的,这是和Memcached相比一个最大的区别。数据类型支持:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。对数据备份的支持: Redis支持数据的备份,即master-slave模式的数据备份。数据持久化:Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 负载均衡负载均衡概述负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。负载均衡,英文名称为Load Balance,其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。服务器负载均衡从字面意义来讲可以理解为,单台服务器不能称之为均衡,只有多个服务器才能称之为均衡,也就是说:多个服务器组成的这样一个系统,我们称之为服务器均衡系统。负载均衡组成的方式:负载均衡的服务器(管理器)被均衡的服务器集群(客户机)负载均衡管理器,是整个负载均衡的控制服务器(DR),所有用户的请求都要先经过这台服务器,然后由此服务器根据各个实际处理服务器状态将请求具体分配到某个实际处理服务器中,用户是感觉不到后端的服务器的,他们只看到当前这台DR服务器。DR服务器只负责转发和安装相应的管理软件,所以一般企业负载均衡服务器非常重要,但是资源使用非常少,所以不必用非常高的配置来担当负载均衡管理器。 负载均衡分类:软/硬件软件负载均衡解决方案是指在一台或多台服务器相应的操作系统上安装一个或多个附加软件来实现负载均衡,它的优点是基于特定环境,配置简单,使用灵活,成本低廉,可以满足一般的负载均衡需求。软件解决方案缺点也较多,因为每台服务器上安装额外的软件运行会消耗系统不定量的资源,越是功能强大的模块,消耗得越多,所以当连接请求特别大的时候,软件本身会成为服务器工作成败的一个关键;软件可扩展性并不是很好,受到操作系统的限制;由于操作系统本身的Bug,往往会引起安全问题。 硬件负载均衡解决方案是直接在服务器和外部网络间安装负载均衡设备,这种设备通常称之为负载均衡器,由于专门的设备完成专门的任务,独立于操作系统,整体性能得到大量提高,加上多样化的负载均衡策略,智能化的流量管理,可达到最佳的负载均衡需求。负载均衡器有多种多样的形式,除了作为独立意义上的负载均衡器外,有些负载均衡器集成在交换设备中,置于服务器与Internet链接之间,有些则以两块网络适配器将这一功能集成到PC中,一块连接到Internet上,一块连接到后端服务器群的内部网络上。一般而言,硬件负载均衡在功能、性能上优于软件方式,不过成本昂贵。 部署方式:路由模式负载均衡有三种部署方式:路由模式、桥接模式、服务直接返回模式。路由模式部署灵活,约60%的用户采用这种方式部署;桥接模式不改变现有的网络架构;服务直接返回(DSR)比较适合吞吐量大特别是内容分发的网络应用。约30%的用户采用这种模式。 服务器的网关必须设置成负载均衡机的LAN口地址,且与WAN口分属不同的逻辑网络。因此所有返回的流量也都经过负载均衡。这种方式对网络的改动小,能均衡任何下行流量。 应用场景在当业务系统服务器无法支撑当前的业务量时,用户可以选择更高性能的服务器。但更为合理的做法是通过在既有业务服务器基础上,增量的方式增加相同功能的服务器,将计算任务分摊到后台多台较低配置的服务器处理,每台服务器都可以响应服务请求。实现合理安排客户请求并加快了请求相应速度,来提高用户体验。而用户仅感受到是一台高性能服务器在提供服务。 负载均衡硬件 负载均衡软件 消息队列消息队列概述介绍:消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。常用消息队列系统:目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。 应用场景异步处理:流量削峰:应用解耦:日志处理:消息通讯—点到点方式:消息通讯—发布订阅方式: 开源消息队列软件RabbitMQRabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。AMQP当中有四个概念非常重要: 虚拟主机(virtual host),交换机(exchange),队列(queue)和绑定(binding)。一个虚拟主机持有一组交换机、队列和绑定。 页面静态化技术页面静态化概述静态页面:最早的时候,网站内容是通过在主机空间中放置大量的静态网页实现的。为了方便对这些分散在不同目录的静态网页的管理,(一般是通过FTP),象frontpage/dreamweaver这样软件甚至直接提供了向主页空间以FTP方式直接访问文件的功能。以静态网页为主的网站最大的困难在于对网页的管理,在这种框架里,网页框架和网页中的内容混杂在一起,很大程度地加大了内容管理的难度。为了减轻这种管理的成本,发展出了一系列的技术,甚至连css本身,原本也是针对这种乱七八糟的网页维护而设计的,目的就是把网页表达的框架和内容本身抽象分离出来。A.静态网页的内容稳定,页面加载速度快。B.静态网页的没有数据库支持,在网站制作和维护方面的工作量较大。C.静态网页的交互性差,有很大的局限性。 动态页面:通过执行asp、php、jsp和.net等程序生成客户端网页代码的网页。通常可以通过网站后台管理系统对网站的内容进行更新管理。发布新闻,发布公司产品,交流互动,博客,网上调查等,这都是动态网站的一些功能。也是我们常见的。 常见的扩展名有:.asp、php、jsp、cgi和aspx 等。 注意:动态页面的“动态”是网站与客户端用户互动的意思,而非网页上有动画的就是动态页面。A.交互性好。 B.动态网页的信息都需要从数据库中读取,每打开一个一面就需要去获取一次数据库,如果访问人数很多,也就会对服务器增加很大的荷载,从而影响这个网站的运行速度。其实大家都知道,效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。为什么需要动态页面静态化:1) 搜索引擎的优化尽管搜索机器人有点讨厌,各个网站不但不会再象从前一样把它封起来,反而热情无比地搞SEO,所谓的面向搜索引擎的优化,其中就包括访问地址的改写,令动态网页看上去是静态网页,以便更多更大量地被搜索引擎收录,从而最大限度地提高自已的内容被目标接收的机会。但是,在完全以动态技术开发的网站,转眼中要求变换成静态网页提供,同时,无论如何,动态网页的内容管理功能也是必须保留的;就如同一辆飞驶的奔驰忽然要求180度转弯,要付出的成本代价是非常大的,是否真的值得,也确实让人怀疑。2) 提高程序性能很多大型网站,进去的时候看它很复杂的页面,但是加载也没有耗费多长时间,除了其它必要原因以外,静态化也是其中必需考虑的技术之一。先于用户获取资源或数据库数据进而通过静态化处理,生成静态页面,所有人都访问这一个静态页面,而静态化处理的页面本身的访问速度要较动态页面快很多倍,因此程序性能会有大大的提升。静态化在页面上的体现为:访问速度加快,用户体验性明显提升;在后台体现为:访问脱离数据库,减轻了数据库访问压力。 FreeMarker实现页面静态化FreeMarker是什么:FreeMarker是一个基于Java的开发包和类库的一种将模板和数据进行整合并输出文本的通用工具,FreeMarker实现页面静态化的原理是:将页面中所需要的样式写入到FreeMarker模板文件中,然后将页面所需要的数据进行动态绑定并放入到Map中,然后通过FreeMarker的模板解析类process()方法完成静态页面的生成。模板引擎:一种基于模板的、用来生成输出文本的通用工具;基于Java的开发包和类库。FreeMarker能做什么:MVC框架中的View层组件、html页面静态化、代码生成工具和CMS模板引擎。为什么要用FreeMarker:程序逻辑(Java 程序)和页面设计(FreeMarker模板)分离; 分层清晰,利于分工合作; 主流Web框架良好的集成(struts2,springmvc); 简单易学、功能强大;免费开源。 Velocity实现页面静态化Velocity是什么: Velocity是一个基于Java的模板引擎(template engine)。它允许任何人仅仅使用简单的模板语言(template language)来引用由Java代码定义的对象。Velocity的应用: 当Velocity应用于Web开发时,界面设计人员可以和Java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计人员可以只关注页面的显示效果,而由Java程序开发人员关注业务逻辑编码。Velocity将Java代码从web页面中分离出来,这样为web站点的长期维护提供了便利,同时也为我们在JSP和PHP之外又提供了一种可选的方案。 分布式数据库中间件分布式数据库中间件概述虽然云计算时代,传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。传统数据库系统随着规模的增大,遇到了主要问题:单个表数据量太大;单个库数据量太大;单台数据量服务器压力很大;读写速度遇到瓶颈。当面临以上问题时,我们会想到的第一种解决方式就是 向上扩展(scale up) 简单来说就是不断增加硬件性能。此时我们不得不依赖于第二种方式: 水平扩展 。 直接增加机器,把数据库放到不同服务器上,在应用到数据库之间加一个proxy进行路由,这样就可以解决上面的问题了。 分布式数据库中间件比较 分布式数据库中间件MyCatMyCat是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信。其核心功能是分表分库,即将一个大表水平切分为N个小表,存储在后端MySQL服务器里或者其他数据库里。 MyCat使用手册:http://mycat.org.cn/document/Mycat_V1.6.0.pdfMyCat的技术原理:MyCat技术原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。MyCat的核心功能:1. 支持水平切分2. 支持垂直切分 3. 支持读写分离4. 支持全局表5. 支持独创的ER关系分片,解决E-R分片难处理问题存在关联关系的父子表在数据插入的过程中,子表会被MyCat路由到其相关父表记录的节点上,从而父子表的Join查询可以下推到各个数据库节点上完成,这是最高效的跨节点Join处理技术 LVSLVS使用的三种模式:NAT、TUN、DR NAT模式-网络地址转换Virtual Server via Network Address Translation(VS/NAT) 通过网路地址转换,调度器重写请求报文的目标地址,根据预设的调度算法,将请求分派给后端的真实服务器,真实服务器的响应报文通过调度器时,报文的源地址被重写,再返回给客户,完成整个负载调度过程。NAT模式特点:1. NAT技术请求和响应的报文都必须经过调度器地址重写然后再转发,返回时再改写2. 只需要在LB(调度器)上配置WAN IP即可,也要有LAN IP和内部通信,内部RS只需要配置LAN IP3. 每台内部节点RS的网关要配成LB的LAN物理网卡地址,这样才能确保数据返回时仍能经过LB4. 由于请求与回传数据都必须经过负载均衡器,因此访问量大时LB有瓶颈5. 支持对IP及端口进行转换 TUN模式-隧道模式Virtual Server via IP Tunneling(VS/TUN) 为了解决NAT模式的瓶颈问题,调度器把请求报文通过IP隧道转发至真实服务器,而真实服务器将响应直接返回给客户,这样调度器只处理请求报文,由于一般网络服务应答数据比请求数据大很多,采用TUN模式后,集群系统的吞吐量可以提高10倍。 TUN的连接调度和管理与NAT一样,只是转发报文的方法不同,调度器根据各个服务器的负载情况,动态地选择一台服务器,将请求报文封装在另一个IP报文中,再将封装后的报文转发给选出的服务器,服务器收到报文后,先将报文解封获得来自目标地址为VIP的报文,服务器发现VIP地址被配置在本地的IP隧道设备上,所以就处理这个请求,然后根据路由表将响应报文直接返回给客户。TUN模式的特点:1. 负载均衡器把请求的报文通过IP隧道的方式(不改写请求报文的地址,而是直接封装成另外的IP报文)转发给真实服务器,真实服务器处理请求后把响应直接返回给客户端。2. 由于真实服务器把响应后的报文直接返回给客户端,因此虽然理论上只要能出网,无需外网IP地址,但RS最好有一个外网IP地址,这样效率高。3. 由于调度器只处理入站报文,因此集群系统的效率会提高10倍以上,但是隧道模式会带来一定的开销,它适合LAN/WAN。4. LAN环境下不如DR模式效率高,有的系统还需要考虑IP隧道的支持问题,还需要绑定VIP,配置复杂。5. LAN下多采用DR,WAN环境下可以用TUN模式,但是在WAN下更多的被DNS+haproxy/nginx等代理取代,因此TUN模式在国内公司已经使用的很少。 DR模式-直接路由Virtual Server via Direct Routing(VS/DR)VS/DR模式是通过改写请求报文的目标MAC地址,将请求发给真实服务器的,而真实服务器将响应后的处理结果直接返回给客户端用户。同VS/TUN技术一样,VS/DR技术可极大地提高集群系统的伸缩性。而且,这种方法没有IP隧道的开销,对集群中的真实服务器也没有必须支持IP隧道协议的要求,但是要求调度器与真实服务器都有一块网卡连在同一物理网段上。在LVS-DR配置中,Dierctor将所有入站请求转发给集群内部节点,但集群内部节点直接将它们的回复发送给客户端计算机(没有通过Director回来)。如图所示:VS/DR模式是互联网使用的最多的一种模式。VS/DR模式的工作流程如下图7所示。它的连接调度和管理与VS/NAT和VS/TUN中的一样,它的报文转发方法和前两种又有不同,将报文直接路由给目标服务器。在VS/DR中,调度器根据各个服务器的负载情况、连接数等因素,动态的选择一台服务器,不修改目的IP地址和目的端口,也不封装IP报文,而是将请求的数据帧的MAC地址改为选出服务器的MAC地址,再将修改后的数据帧在服务器组的局域网上发送。因为请求数据帧的MAC地址是选出的真实服务器,所以服务器肯定可以收到这个数据帧,从中可以获得该IP报文。当真实服务器发现报文的目标地址VIP是在本地的网络设备上,服务器就会处理这个报文,然后根据路由表将响应报文直接返回给用户。DR模式的特点:1. 通过在调度器LB上修改数据包的目标MAC地址实现转发。注意,源地址仍然是CIP地址,目标地址仍然是VIP。2. 由于只有请求的报文经过负载均衡器,而回传的报文无需经过负载均衡器,因此,访问量大的时候效率很高(和NAT相比)。3. 因DR模式是通过MAC地址改写机制实现的转发,因此,所有RS节点和调度器LB只能在一个LAN中。(小缺点)4. 需要注意RS节点的VIP的绑定(lo:VIP)和ARP抑制问题。强调,RS节点的默认网关不需要是LB的IP,而是出口路由器的IP。由于仅进行了MAC地址的改写,因此,LB无法改变请求的目标端口(和NAT主要区别)。LB几乎支持所有的UNIX,Linux系统,目前无Windows版,但是RS节点可以是windows。5. 总体来说DR模式效率很高,但是配置也比较麻烦,访问量不是特别大的企业可以用haproxy/nginx取代它。直接对外的访问业务,例如:web服务做RS节点,最好用公网IP地址,不直接对外的业务,例如MySQL,存储系统RS节点,最好用内部IP地址。 3种模式的对比 在LINUX环境下搭建DR模式原理图 一些概念VIP: 虚拟服务器地址DIP: 转发的网络地址,有两个作用:和RIP通信:ARP协议,获取Real Server的RIP:MAC地址转发Client的数据包到RIP上(隐藏的VIP)RIP: 后端真实主机(后端服务器)CIP: 客户端IP地址VIP: 虚拟主机IP 步骤1.准备3台Linux机器并配置它们的eth0网卡的IP(在一个网段)node001:LVS,IP:192.168.9.101node002:Real Server,IP:192.168.9.102node003:Real Server,IP:192.168.9.103 2.在LVS的eth0:0接口上配置VIP12343. [root@node001 ~]# ifconfig eth0:0 192.168.9.100/24[root@node001 ~]# ifconfigeth0 inet addr:192.168.9.101 Bcast:192.168.9.255 Mask:255.255.255.0 eth0:0 inet addr:192.168.9.100 Bcast:192.168.9.255 Mask:255.255.255.0 24代表该IP的子网掩码为255.255.255.0注意,要想让配置永久生效,应修改配置文件vi /etc/sysconfig/network-scripts/ifcfg-eth0:0DEVICE=eth0:0IPADDR=192.168.9.100NETMASK=255.255.255.0 3. 修改node002和node003的内核ARP通告和响应级别(隐藏VIP)arp_ignore: 定义接收到ARP请求时的响应级别0:只要本地配置的有相应地址,就给予响应1:仅在请求的目标(MAC)地址配置请求到达的接口上的时候,才给予响应;arp_announce:定义将自己地址向外通告时的通告级别0:将本地任何接口上的任何地址向外通告1:试图仅向目标网络通告与其网络匹配的地址2:仅向与本地接口上地址匹配的网络进行通告 123456789[root@node002 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore [root@node002 ~]# echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce#后两步可选,为保险起见,防止再配置的网卡进行通告,可以这么设置[root@node002 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore [root@node002 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce[root@node003 ~]# echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore [root@node003 ~]# echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce[root@node003 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore [root@node003 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce 4. 在node002和node003上配置VIP12[root@node002 ~]# ifconfig lo:0 192.168.9.100 netmask 255.255.255.255[root@node003 ~]# ifconfig lo:0 192.168.9.100 netmask 255.255.255.255 注意,要想让配置永久生效,应修改配置文件vi /etc/sysconfig/network-scripts/ifcfg-lo:0DEVICE=lo:0IPADDR=192.168.9.100NETMASK=255.255.255.255 5. 在node002和node003上安装httpd(apache静态web server,默认端口号80)服务并启动 12345678910111213[root@node002 ~]# yum install httpd –y[root@node002 ~]# cd /var/www/html[root@node002 html]# vi index.html<h1>From 192.168.9.102</h1>[root@node002 html]# service httpd start[root@node002 html]# chkconfig httpd on 设置httpd服务开机启动[root@node003 ~]# yum install httpd –y[root@node003 ~]# cd /var/www/html[root@node003 html]# vi index.html<h1>From 192.168.9.103</h1>[root@node002 html]# service httpd start[root@node002 html]# chkconfig httpd on 浏览器访问node002和node003,验证一下:但此时还不能通过LVS访问RS 6. 在LVS上安装ipvsadm客户端1234567891011121314151617181920[root@node001 network-scripts]# yum install ipvsadm –y添加监听的IP[root@node001 network-scripts]# ipvsadm -A -t 192.168.9.100:80 -s rr查看是否设置成功[root@node001 network-scripts]# ipvsadm -lnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 192.168.9.100:80 rr添加后台主机[root@node001 network-scripts]# ipvsadm -a -t 192.168.9.100:80 -r 192.168.9.102 -g[root@node001 network-scripts]# ipvsadm -a -t 192.168.9.100:80 -r 192.168.9.103 -g验证[root@node001 network-scripts]# ipvsadm -lnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 192.168.9.100:80 rr -> 192.168.9.102:80 Route 1 0 0 -> 192.168.9.103:80 Route 1 0 0 去页面验证:在浏览器输入192.168.9.100并反复刷新,页面交替显示node002和node003的主页,说明LVS集群配置成功。 也可以通过ipvsadm –lnc命令查看LVS调度状态。说明ipvsadm -A -t 192.168.9.100:80 -s rr命令中,rr指定了LVS调度RS的算法,rr代表轮询,即依次循环的调用所有的RS节点,其他算法还有wrr、dh、sh等。-t: TCP协议的集群-u: UDP协议的集群-f: FWM: 防火墙标记添加:-A修改:-E删除:-D -t|u|f service-address LVS+Keepalived搭建高可用集群Keepalived介绍LVS的弊端: 后端:需要镜像服务器 后端:没有健康检查机制 自身:单点故障(LVS出现故障后,整个集群都不能正常使用)keepalived是集群管理中保证集群高可用(High Available)的服务软件,对LVS进行了改进: 使用心跳机制探测后端RS是否提供服务。a) 探测网卡接口是否down,如果是,从LVS中删除该RSb) 检测到down的机器又up,则从LVS中再次添加该RS LVS DR,使用主从(HA)模式,即LVS有备用的机器Keepalived结构图: LINUX中搭建LINUX中搭建LVS+Keepalived集群准备工作需要修改集群中RS的内核ARP通告和响应级别,配置好VIP,并启动httpd服务,LVS不需要做任何配置 1. 在node001和node004上安装keepalived12[root@node001 ~]# yum install keepalived –y[root@node004 ~]# yum install keepalived –y 2. 修改keepalived的配置文件123[root@node001 ~]# cd /etc/keepalived/[root@node001 keepalived]# cp keepalived.conf keepalived.conf.bak[root@node001 keepalived]# vi keepalived.conf 对keepalived.conf配置文件做说明: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667! Configuration File for keepalived<!-- global_defs start-->global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_DEVEL}<!-- global_defs end--><!-- vrrp_instance VI_1 start-->vrrp_instance VI_1 { state MASTER //指定本机是主机还是从机,node004改为BACKUP interface eth0 virtual_router_id 51 priority 100 //从机的改为其一半 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.9.100/24 dev eth0 label eth0:0 //指定监听的接口 }}//可以添加多台LVSvirtual_server 192.168.9.100 80 { delay_loop 6 lb_algo rr //LVS调度RS的算法 lb_kind DR //LVS的模式 nat_mask 255.255.255.0 //LVS的子网掩码 persistence_timeout 0 protocol TCP real_server 192.168.9.102 80 { //指定RS的IP和端口 weight 1 HTTP_GET { //监听的协议 url { path / status_code 200 //根据状态码检测RS是否运行正常 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } //另一台RS,可以添加多台 real_server 192.168.9.103 80 { weight 1 HTTP_GET { url { path / status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }}<!-- vrrp_instance VI_1 end-> 修改配置文件用到一些VI命令:dG:从当前行到最后一行全部删除:.,$-1y:从当前行到倒数第二行全部复制 3. 修改node004的keepalived配置文件123456789101112[root@node004 ~]# cd /etc/keepalived/[root@node004 keepalived]# mv keepalived.conf keepalived.conf.bak[root@node001 keepalived]# scp keepalived.conf root@node004:`pwd`[root@node004 keepalived]# vi keepalived.confvrrp_instance VI_1 {state BACKUPinterface eth0virtual_router_id 51priority 50 //是主机的一半……} 4. 启动keepalived服务12[root@node001 keepalived]# service keepalived start[root@node004 keepalived]# service keepalived start 5. 配置完成,进行测试LVS功能正常破坏掉主机的eth0网卡[root@node001 keepalived]# ifconfig eth0 down发现VIP已经漂移到node00412345[root@node004 keepalived]# ifconfigeth0 inet addr:192.168.9.104 Bcast:192.168.9.255 Mask:255.255.255.0 eth0:0 Link encap:Ethernet HWaddr 00:0C:29:54:A1:77 inet addr:192.168.9.100 Bcast:0.0.0.0 Mask:255.255.255.0UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 浏览器也不受影响再恢复node001的eth0网卡,VIP再次漂移回node001123456789[root@node001 keepalived]# ifconfig eth0 down[root@node001 keepalived]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:67:1D:65 inet addr:192.168.9.101 Bcast:192.168.9.255 Mask:255.255.255.0 eth0:0 Link encap:Ethernet HWaddr 00:0C:29:67:1D:65 inet addr:192.168.9.100 Bcast:0.0.0.0 Mask:255.255.255.0[root@node004 keepalived]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:54:A1:77 inet addr:192.168.9.104 Bcast:192.168.9.255 Mask:255.255.255.0 杀掉keepalived进程1234567891011[root@node001 ~]# ps –efroot 1136 1 0 21:26 ? 00:00:00 /usr/sbin/keepalived -Droot 1138 1136 0 21:26 ? 00:00:00 /usr/sbin/keepalived -Droot 1139 1136 0 21:26 ? 00:00:00 /usr/sbin/keepalived –D[root@node001 ~]# kill -9 1136[root@node004 keepalived]# ps –efroot 1145 1 0 21:27 ? 00:00:00 /usr/sbin/keepalived -Droot 1146 1145 0 21:27 ? 00:00:00 /usr/sbin/keepalived -Droot 1147 1145 0 21:27 ? 00:00:00 /usr/sbin/keepalived –D[root@node004 keepalived]# kill -9 1145多次查看进程,确保杀死所有keepalived进程 此时主机和从机都拥有了VIP12345678910[root@node001 ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:67:1D:65 inet addr:192.168.9.101 Bcast:192.168.9.255 Mask:255.255.255.0eth0:0 Link encap:Ethernet HWaddr 00:0C:29:67:1D:65 inet addr:192.168.9.100 Bcast:0.0.0.0 Mask:255.255.255.0[root@node004 keepalived]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:54:A1:77 inet addr:192.168.9.104 Bcast:192.168.9.255 Mask:255.255.255.0eth0:0 Link encap:Ethernet HWaddr 00:0C:29:54:A1:77 inet addr:192.168.9.100 Bcast:0.0.0.0 Mask:255.255.255.0 这种情况说明keepalived会出现问题,在互联网中出现这种情况后,会导致发送的数据包同时发送到主机和从机,需要使用zookeeper来解决这个问题。 NginxNginx简介Nginx(”engine x”)是一个高性能的HTTP和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。官方测试nginx能够支撑5万并发链接,并且cpu、内存等资源消耗却非常低,运行非常稳定。2011年6月1日,nginx 1.0.4发布。Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发,其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:新浪、网易、腾讯等。功能:web服务器、web reverse proxy、smtp reverse proxy Nginx和apache的比较 nginx相对于apache的优点:轻量级,同样起web服务,比apache占用更少的内存及资源 ;抗并发,nginx处理请求是异步非阻塞的,而apache则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能 ;高度模块化的设计,编写模块相对简单 ;社区活跃,各种高性能模块出品迅速。 apache 相对于nginx 的优点:rewrite ,比nginx的rewrite强大 ;模块超多,基本想到的都可以找到 ;少bug,nginx的bug相对较多。 Nginx配置简洁,Apache复杂 。 最核心的区别在于apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程。单个tomcat支持的最高并发解决高并发和单个服务器过载问题Tengine安装Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。 1. 解压安装包[root@node001 local]# tar xf tengine-2.1.0.tar.gz 2. 进入tengine目录[root@node001 local]# cd tengine-2.1.0/ 3. 查看README文件,找到安装方法To install Tengine, just follow these three steps: $ ./configure $ make # make install 4. 执行configure文件,指定安装目录12345678910[root@node001 tengine-2.1.0]# ./configure --prefix=/usr/local/nginx使用如下命令查看更多安装选项[root@node01 tengine-2.1.0]# ./configure --help --help print this message --prefix=PATH set installation prefix --sbin-path=PATH set nginx binary pathname --conf-path=PATH set nginx.conf pathname --error-log-path=PATH set error log pathname …… 5.报错:./configure: error: C compiler cc is not found 6.安装gcc,再执行configure文件 [root@node001 tengine-2.1.0]# yum install gcc 7. 报错:./configure: error: the HTTP rewrite module requires the PCRE library. 8. 查看PCRE有哪些版本123456[root@node001 tengine-2.1.0]# yum search pcrepcre-devel.i686 : Development files for pcrepcre-devel.x86_64 : Development files for pcrepcre-static.x86_64 : Static library for pcrepcre.x86_64 : Perl-compatible regular expression librarypcre.i686 : Perl-compatible regular expression library 9. 选择安装开发版,系统自动识别安装什么位数的软件[root@node001 tengine-2.1.0]# yum install pcre-devel 10. 再执行configure文件[root@node001 tengine-2.1.0]# ./configure --prefix=/usr/local/nginx 11. 报错:./configure: error: SSL modules require the OpenSSL library. 12. 根据pcre的经验,安装OpenSSL开发版[root@node001 tengine-2.1.0]# yum install openssl-devel 13. 再执行configure文件[root@node001 tengine-2.1.0]# ./configure --prefix=/usr/local/nginx看到如下信息说明configure文件执行成功: 14. 执行make命令[root@node001 tengine-2.1.0]# make 15. 执行make install命令[root@node001 tengine-2.1.0]# make install 16. 将nginx文件放到/etc/init.d目录下,并做修改123[root@node001 tengine-2.1.0]# vi /etc/init.d/nginxnginx="/usr/local/nginx/sbin/nginx"NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf" 17. 给nginx文件赋予执行权限[root@node001 tengine-2.1.0]# chmod +x nginx 18. 启动服务[root@node001 sbin]# service nginx start 19. 验证是否启动12[root@node001 sbin]# service nginx statusnginx (pid 6510 6508) is running... 20.去网页验证,看到如下页面说明nginx安装成功! Nginx配置解析nginx.conf配置文件的结构……events{……}http {…… server{……}server{……}} 全局的配置user nobody; #定义Nginx运行的用户和用户组说明:1234567[root@node01 tengine-2.1.0]# ps -fe | grep nginxroot 1367 1335 0 13:18 pts/1 00:00:00 vi nginx.confroot 1608 1 0 14:32 ? 00:00:00 nginx: master process /opt/sxt/nginx/sbin/nginx –c /opt/sxt/nginx/conf/nginx.confnobody 1610 1608 0 14:32 ? 00:00:00 nginx: worker process root 1626 1097 0 14:45 pts/0 00:00:00 grep nginx[root@node01 tengine-2.1.0]# service nginx statusnginx (pid 1610 1608) 正在运行... master process不负责处理客户端连接请求,负责对worker process的监管,而worker process负责处理客户端请求。Nginx支持热加载和热升级,比如更新了配置文件后执行reload命令,master会开出一个新进程去读取更新过的配置文件,而worker进程继续保持从旧请求的连接,直到旧进程死亡,新进程会与新请求连接。master process由root启动,worker process由nobody启动,权限较小。123456worker_processes 1; #nginx进程数,建议设置为等于虚拟机CPU总核心数error_log logs/error.log;error_log logs/error.log notice;error_log logs/error.log info;#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]pid logs/nginx.pid; #进程文件 event下的一些配置及其意义12345678910111213141516use epoll;#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ];#epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型#如果跑在FreeBSD上面,就用kqueue模型。worker_connections 1024; #单个进程最大连接数(最大连接数=连接数*进程数) #假设worker_processes为8#系统可以打开的最大文件数和内存大小成正比#查看自己的系统可以打开的最大文件数 cat /proc/sys/fs/file-max :97318#并发连接总数小于系统可以打开的文件总数,这样就在操作系统可以承受的范围之内#选择最大连接数为80000#在设置了反向代理的情况下,根据经验,最大连接数应该再除以4,就是20000#所以单个进程最大连接数为20000/8 = 2500#同时应该调整系统的单个进程可以打开的文件数#使用ulimit –a查看到open file =1024#应该调整系统的单个进程最大打开文件数(该数值x进程数<=97318)#ulimit -SHn 10000 http下的一些配置及其意义12345678910111213include mime.types; #文件扩展名与文件类型映射表default_type application/octet-stream; #默认文件类型log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #日志格式access_log logs/access.log main; #日志文件位置sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off.原理,比如Nginx接受用户对某文件的请求,nginx不能直接读取磁盘的内容,需要经过内核的调用,nginx告诉内核需要读取x文件,内核会读取x文件到内核的内存中,在把这个文件copy到nginx的内存堆中,nginx得知数据已经准备好,会再把这个文件发给内核,内核切片之后发送给用户。当并发数过大时太耗费资源,所以这个选项的存在是为了减少文件在两个内存之间的copy,提高效率。keepalive_timeout 0; #长连接超时时间,单位是秒(与keeplived软件无关)#gzip on; #开启gzip压缩输出 server下的一些配置及其意义12345678listen 80; #监听的IP和地址server_name www.bbb.com; #主机名 location / { root /opt; #网页文件存放的目录index index.html index.htm;#默认首页文件,顺序从小到右,如果找不到index.html,则index.htm为首页 autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。 } 虚拟主机虚拟主机是一种特殊的软硬件技术,它可以将网络上的每一台计算机分成多个虚拟主机,每个虚拟主机可以独立对外提供www服务,这样就可以实现一台主机对外提供多个web服务,每个虚拟主机之间是独立的,互不影响的。 基于IP的虚拟主机1. 查看服务器的IP地址12345[root@node01 ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:A6:C6:18 inet addr:192.168.9.11 Bcast:192.168.9.255 Mask:255.255.255.0lo Link encap:Local Loopback ine###### inet addr:127.0.0.1 Mask:255.0.0.0 在eth0网卡设备上添加两个IP别名192.168.9.98和192.168.9.991234[root@node01 ~]# ifconfig eth0:1 192.168.9.98 broadcast 192.168.9.255 netmask 255.255.255.0 up[root@node01 ~]# route add -host 192.168.9.98 dev eth0:1[root@node01 ~]# ifconfig eth0:2 192.168.9.99 broadcast 192.168.9.255 netmask 255.255.255.0 up[root@node01 ~]# route add -host 192.168.9.99 dev eth0:2 注意:以上配置只是临时生效,想让它们永久生效,可以将这两条ifconfig和route命令添加到/etc/rc.local文件中,让系统开机时自动运行。在文件末尾增加以下内容,保存退出即可。123456789101112#!/bin/sh## This script will be executed *after* all the other init scripts.# You can put your own initialization stuff in here if you don't# want to do the full Sys V style init stuff.touch /var/lock/subsys/localifconfig eth0:1 192.168.9.98 broadcast 192.168.9.255 netmask 255.255.255.0 uproute add -host 192.168.9.98 dev eth0:1ifconfig eth0:2 192.168.9.99 broadcast 192.168.9.255 netmask 255.255.255.0 uproute add -host 192.168.9.99 dev eth0:2 3. 验证是否配置成功123456789[root@node01 ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:0C:29:A6:C6:18 inet addr:192.168.9.11 Bcast:192.168.9.255 Mask:255.255.255.0eth0:1 Link encap:Ethernet HWaddr 00:0C:29:A6:C6:18 inet addr:192.168.9.98 Bcast:192.168.9.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1eth0:2 Link encap:Ethernet HWaddr 00:0C:29:A6:C6:18 inet addr:192.168.9.99 Bcast:192.168.9.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 4. 配置基于IP的虚拟主机1234567891011121314151617181920212223242526272829303132[root@node01 ~]# vi /opt/sxt/nginx/conf/nginx.confhttp {server { listen 192.168.9.11:80; server_name 192.168.9.11; access_log logs/host.access.log main; location / { root /var/www/server1; index index.html index.htm; }}server { listen 192.168.9.98:80; listen 192.168.9.98; access_log logs/host.access.log main; location / { root /var/www/server2; index index.html index.htm; }}server { listen 192.168.9.99:80; server_name 192.168.9.99; access_log logs/host.access.log main; location / { root /var/www/server3; index index.html index.htm; }}} 5.在/var/www/下创建3个文件夹server1、server2、server3,在其中分别定义三个虚拟主机的首页index.html1234567[root@node01 ~]# mkdir -p /var/www/server{1,2,3}[root@node01 ~]# vi /var/www/server1/index.html<h1>From Virtual Host 1:192.168.9.11</h1>[root@node01 ~]# vi /var/www/server2/index.html<h1>From Virtual Host 2:192.168.9.98</h1>[root@node01 ~]# vi /var/www/server3/index.html<h1>From Virtual Host 3:192.168.9.99</h1> 6. 开启nginx服务(如果已经启动则重启nginx服务)12[root@node01 ~]# service nginx start[root@node01 ~]# service nginx reload 7. 在网页查看效果 基于域名的虚拟主机1.在nginx.conf文件中配置123456789101112131415161718http {server { listen 80; server_name www.aaa.com; location / { root /var; autoindex on; } } server { listen 80; server_name www.bbb.com; location / { root /opt; autoindex on; }}} 2. 修改Windows的hosts文件192.168.9.11 node01 www.aaa.com www.bbb.com 3.重启服务[root@node01 ###### [root@node01 ~]# service nginx reload 4.去网页验证123456789101112131415161718[root@node01 ~]# ll /var总用量 60drwxr-xr-x. 6 root root 4096 6月 29 11:50 cachedrwxr-xr-x. 3 root root 4096 5月 22 12:05 dbdrwxr-xr-x. 3 root root 4096 5月 22 12:05 emptydrwxr-xr-x. 2 root root 4096 9月 23 2011 gamesdrwxr-xr-x. 17 root root 4096 6月 29 11:50 libdrwxr-xr-x. 2 root root 4096 9月 23 2011 localdrwxrwxr-x. 5 root lock 4096 7月 3 07:50 lockdrwxr-xr-x. 4 root root 4096 7月 3 12:39 loglrwxrwxrwx. 1 root root 10 5月 22 12:04 mail -> spool/maildrwxr-xr-x. 2 root root 4096 9月 23 2011 nisdrwxr-xr-x. 2 root root 4096 9月 23 2011 optdrwxr-xr-x. 2 root root 4096 9月 23 2011 preservedrwxr-xr-x. 13 root root 4096 7月 3 12:39 rundrwxr-xr-x. 8 root root 4096 5月 22 12:05 spooldrwxrwxrwt. 2 root root 4096 6月 30 09:41 tmpdrwxr-xr-x. 2 root root 4096 9月 23 2011 yp 123[root@node01 ~]# ll /opt总用量 4drwxr-xr-x 3 root root 4096 6月 29 09:46 sxt 注意:确认配置没有问题的情况下如果报错,可尝试service nginx restart(当reload不能解决问题时) location映射解析location [ = | ~ | ~ | ^~ ] uri { … } location URI {}: 对当前路径及子路径下的所有对象都生效; location = URI {}:注意URL最好为具体路径。精确匹配指定的路径,不包括子路径,因此,只对当前资源生效; location ~ URI {}与location ~ URI {}: 模式匹配URI,此处的URI可使用正则表达式,~区分字符大小写,~不区分字符大小写; location ^~ URI {}: 不使用正则表达式 优先级:= > ^~ > ~|~ > /|/dir/ 精确匹配优先级最高,如果找到,停止搜索12345678910111213server { listen 192.168.9.11:80; server_name 192.168.9.11; location = /images/test.png { return 601; } location /images/test { return 602; } location /images { return 603; }} 所有剩下的常规字符串,最长的匹配 如果这个匹配使用^〜前缀,搜索停止123456location /images/test { return 602;}location ^~ /images { return 604;} 正则表达式按照顺序匹配123456location ~ login { return 605;}location ~ (.*)\.html$ { return 606;} 123456location ~ (.*)\.html$ { return 606;}location ~ login { return 605;} 说明 “普通 location” 的匹配规则是“最大前缀”,因此“普通 location ”的确与 location 编辑顺序无关; “正则 location” 的匹配规则是“顺序匹配,且只要匹配到第一个就停止后面的匹配”; “普通location ”与“正则 location ”之间的匹配顺序是先匹配普通 location ,再“考虑”匹配正则 location ;也就是说匹配完“普通 location ”后,有的时候需要继续匹配“正则 location ”,有的时候则不需要继续匹配“正则 location ”。两种情况下,不需要继续匹配正则 location :1) 当普通 location 前面指定了“ ^~ ”,特别告诉 Nginx 本条普通 location 一旦匹配上,则不需要继续正则匹配;2) 当普通location 恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则总结location匹配顺序:先普通顺序无关最大前缀被打断^~完全匹配再正则不完全匹配有顺序先匹配,先应用,即时退出匹配反向代理12345678server { listen 192.168.9.11:80; server_name 192.168.9.11; location / { root html; index index.html index.htm; }} 12location /mp3 { proxy_pass http://192 前提:node03安装了httpd123location /baidu { proxy_pass http://www.baidu.com/;} 客户端发生了跳转!http://www.baidu.com会被百度转换为https://www.baidu.com再返回123location /baidu { proxy_pass https://www.baidu.com/;} Nginx负载均衡1234567891011upstream httpd { server 192.168.9.12:80; server 192.168.9.13:80;} server { listen 192.168.9.11:80; server_name 192.168.9.11; location /mp3 { proxy_pass http://httpd/; }} Nginx的session一致性问题http协议是无状态的,即你连续访问某个网页100次和访问1次对服务器来说是没有区别对待的,因为它记不住你。那么,在一些场合,确实需要服务器记住当前用户怎么办?比如用户登录邮箱后,接下来要收邮件、写邮件,总不能每次操作都让用户输入用户名和密码吧,为了解决这个问题,session的方案就被提了出来,事实上它并不是什么新技术,而且也不能脱离http协议以及任何现有的web技术。session的常见实现形式是会话cookie(session cookie),即未设置过期时间的cookie,这个cookie的默认生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。实现机制是当用户发起一个请求的时候,服务器会检查该请求中是否包含sessionid,如果未包含,则系统会创造一个名为JSESSIONID的输出 cookie返回给浏览器(只放入内存,并不存在硬盘中),并将其以HashTable的形式写到服务器的内存里面;当已经包含sessionid是,服务端会检查找到与该session相匹配的信息,如果存在则直接使用该sessionid,若不存在则重新生成新的 session。这里需要注意的是session始终是有服务端创建的,并非浏览器自己生成的。但是浏览器的cookie被禁止后session就需要用get方法的URL重写的机制或使用POST方法提交隐藏表单的形式来实现。session共享:首先我们应该明白,为什么要实现共享,如果你的网站是存放在一个机器上,那么是不存在这个问题的,因为会话数据就在这台机器,但是如果你使用了负载均衡把请求分发到不同的机器呢?这个时候会话id在客户端是没有问题的,但是如果用户的两次请求到了两台不同的机器,而它的session数据可能存在其中一台机器,这个时候就会出现取不到session数据的情况,于是session的共享就成了一个问题。1234567891011121314151617[root@node02 software]# rpm -i jdk-7u67-linux-x64.rpm[root@node02 software]# tar xf apache-tomcat-7.0.61.tar.gz[root@node02 software]# vi /etc/profileexport JAVA_HOME=/usr/java/jdk1.7.0_67export PATH=$PATH:$JAVA_HOME/bin[root@node02 software]# . /etc/profile[root@node02 software]# java -versionjava version "1.7.0_67"Java(TM) SE Runtime Environment (build 1.7.0_67-b01)Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)[root@node02 software]# jps1309 Jps[root@node02 software]# cd apache-tomcat-7.0.61/webapps/ROOT/[root@node02 ROOT]# cp index.jsp index.jsp.bak[root@node02 ROOT]# vi index.jspFrom 192.168.9.12<br>session=<%= session.getId() %> 对node03做相同的配置,index.jsp改为From 192.168.9.13启动node02和node03的tomcat12345678[root@node02 ~]# cd /usr/local/software/apache-tomcat-7.0.61/bin/[root@node02 bin]# ./startup.sh Using CATALINA_BASE: /usr/local/software/apache-tomcat-7.0.61Using CATALINA_HOME: /usr/local/software/apache-tomcat-7.0.61Using CATALINA_TMPDIR: /usr/local/software/apache-tomcat-7.0.61/tempUsing JRE_HOME: /usr/java/jdk1.7.0_67Using CLASSPATH: /usr/local/software/apache-tomcat-7.0.61/bin/bootstrap.jar:/usr/local/software/apache-tomcat-7.0.61/bin/tomcat-juli.jarTomcat started. 在两个页面交替访问192.168.9.12:8080和192.168.9.13:8080,sessionID不变 在nginx上配置,让请求转到node02和node03123456789101112upstream tom { server 192.168.9.12:8080; server 192.168.9.13:8080;}server { listen 192.168.9.11:80; server_name 192.168.9.11; location /cat { proxy_pass http://tom/; }}[root@node01 software]# service nginx reload 随着页面刷新,sessionId会一直改变!解决这个问题:memcached(一个内存数据库) 1.安装memcached[root@node01 software]# yum install memcached -y 2. 启动memcached[root@node01 software]# memcached -d -m 128m -p 11211 -l 192.168.9.11 -u root -P /tmp/ 3. 让node02和node03的tomcat使用memcached1234567891011[root@node02 bin]# cd /usr/local/software/apache-tomcat-7.0.61/conf/[root@node02 conf]# vi context.xml加入如下配置:<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:192.168.9.11:11211" sticky="false" lockingMode="auto" sessionBackupAsync="false" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" sessionBackupTimeout="1000" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> 4. 把需要的jar包放到两台机器的tomcat的lib目录下 5.重启node02和node03的tomcat服务12[root@node03 bin]# ./shutdown.sh[root@node03 bin]# ./startup.sh 此时刷新页面,sessionId不会再改变,session在集群中的一致性问题得到解决,但还存在一种比memcached更优秀的内存数据库——redis.]]></content>
<categories>
<category>Bigdata</category>
<category>loadBalance</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>LVS+Keepalived+Nginx</tag>
<tag>loadBalance</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java远程调用shell脚本]]></title>
<url>%2F2016%2F04%2F04%2Fjava%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8shell%E8%84%9A%E6%9C%AC%2F</url>
<content type="text"><![CDATA[目的:java程序调用远程服务器的shell脚本来实现服务的起、停、重启。 前言(废话):由于知道我经验不足,领导给新同事分配任务时说:“这个java远程调用shell脚本重启服务,他两可能搞不出来,你研究下吧。”被人鄙视了一把,于是自己搭虚拟机,查资料,试一把,突然发现,好多事情其实没那么难嘛。 正文:Step1.准备脚本重启的关键在于关闭,要关闭程序,那就要先找到程序pid,然后kill。找到核心命令(获取pid): 1ID=`ps -ef | grep "$NAME" | grep -v "grep" | grep -v kill | awk '{print $2}'` 于是初期博主调试后,写出了这样的shell 如果你的虚机是root用户,kill命令会很强大,所以一定判断参数是否为空或数字,否则,分分钟把你的所有进程都kill,虚机瞬间爆炸😂,重头再来。 12345678910111213141516171819202122232425#!/bin/shNAME=$1##检查参数,不能为空或纯数字,否则会kill几乎所有进程,直接死机。非root用户的话,应该不会死机,会报permission denied.a=`echo "$NAME" | grep [^0-9] >/dev/null && echo 0 || echo 1` #判断参数是否为数字,是数字则返回1,不是则返回0if [ "$NAME" == "" ]; then #$NAME一定要加引号,不然$NAME为空的时候就成了if[ == "" ],会报错:unary operatorecho "未输入要kill的进程名"elif [ $a = 1 ]; thenecho "进程名不能为纯数字"else##重启进程echo "-----------------------"echo -e "pNmae\t=\t$NAME"ID=`ps -ef | grep "$NAME" | grep -v "grep" | grep -v "restart" | awk '{print $2}'`echo -e "pid\t=\t$ID"echo "-----------------------"for id in $IDdokill -9 $idecho "killed $id"doneecho "-----------------------"echo "restarted" $NAME./$NAME 1>/dev/null 2>&1 &#./$NAMEfi 然后领导交于博主一份专业的shell脚本,瞬间把博主的三脚猫脚本秒成渣了。于是博主认真研读,添加注释,并稍作修改得到以下脚本。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123#!/bin/sh# -*- coding: utf-8 -*-#Filename: server.sh.Chant#Author: Chant#Email: statuschuan@gmail.com#Date: 2017-06-03#Desc:#用途:该脚本用于停止、启动服务#使用说明:#启动该脚本时需要两个参数,参数均不能为空#第一个参数为:程序入口名称#第二个参数为:参数名称#注意事项:#远程调用时,请将脚本环境变量配置到/etc/bashrc或者用户目录下的.bashrcprint_usage(){ echo "Usage: $0 COMMAND" echo "where COMMAND is one of:" echo " help Help print this usage message" echo " start <server_name> <server_param> Start" echo " stop <server_name> <server_param> Stop" echo " restart <server_name> <server_param> Restart"}start(){ #echo "start not suport now." cmd=$@ $cmd 1>/dev/null 2>&1 & #这里一定要写1>/dev/null 2>&1 不能只写&,否则远程调用时,会等待cmd的返回结果(stdOut),就么法愉快地玩耍了。}stop(){ #get arguments SERVER_NAME=$1 SERVER_PARAM=$2 PROCESS_NAME="$1 $2" #echo $PROCESS_NAME if [ "$1" = "" ]; then echo "第二个参数不能为空" exit 0; fi if [ -z "$2" ]; then echo "第三个参数不能为空" exit 0; fi #get process's pids pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"|awk '{print $2}'` #Chant:使用以下命令可以过滤掉脚本本身的pid,就不用写后面的判断语句了。 #但是其实用$$获取当前脚本pid在逻辑上更严密,否则,万一你的脚本名和要操作的程序名有相同部分就会出问题, #eg: 脚本名为:ser.sh 而程序名为:poser.sh,那么由于grep -v "$0" 就取不到其pid了 #pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"| grep -v "$0" |awk '{print $2}'` # 为basename指定一个路径,basename命令会删掉所有的前缀包括最后一个slash(‘/’)字符,然后将字符串显示出来。 #pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"| grep -v "$(basename $0)" |awk '{print $2}'` current_pid=$$ echo process pids is $pids. #kill process if [ -n "$pids" ]; #判断pids是否为空,引号必须加。"$pids" == ""等效 then for pid in $pids do #current shell pid shoud not kill. if [ $pid -ne $current_pid ]; then #check $pid is exist or not check=`ps -p $pid` if [ $? -eq 0 ]; then echo kill $pid start. kill -9 $pid #judge result if [ $? -eq 0 ]; then echo kill $pid success. else echo kill $pid fail. fi fi fi done else echo "$PROCESS_NAME does not exist." fi}# get command argumentsCOMMAND=$1shift# support help commandscase $COMMAND in --help|-help|-h|help) print_usage exit 0 ;; "") print_usage exit 0 ;; "start") start $@ echo "$@ started" exit 0 ;; "stop") stop $@ exit 0 ;; "restart") start and stop的参数需要一致才可以,如果不一致则需要调整参数传入方式 stop $@ eep 3 tart $@ echo "$@ restarted" exit 0 ;;esac Step2.java程序远程调用shell 1.导入需要依赖的jar包。Java远程调用Shell脚本这个程序需要ganymed-ssh2-build210.jar包。里面还有example包,方便学习。为了调试方便,可以将\ganymed-ssh2-build210\src下的代码直接拷贝到我们的工程里,此源码的好处就是没有依赖很多其他的包,拷贝过来干干净净。2.导入commons-io包,里面的IOUtils会经常使用。 123456789101112131415<dependencies> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> <!-- https://mvnrepository.com/artifact/ch.ethz.ganymed/ganymed-ssh2 --> <dependency> <groupId>ch.ethz.ganymed</groupId> <artifactId>ganymed-ssh2</artifactId> <version>build210</version> </dependency></dependencies> 3、编写RemoteShellExecutor工具类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125package RemoteShell;import java.io.*;import java.nio.charset.Charset;import org.apache.commons.io.IOUtils;import ch.ethz.ssh2.ChannelCondition;import ch.ethz.ssh2.Connection;import ch.ethz.ssh2.Session;import ch.ethz.ssh2.StreamGobbler;/** * Created by Chant on 2017/5/27. * 远程调用脚本重启服务 */public class RemoteShellExecutor { private Connection conn; /** 远程机器IP */ private String ip; /** 用户名 */ private String osUsername; /** 密码 */ private String password; private String charset = Charset.defaultCharset().toString(); private static final int TIME_OUT = 1000 * 5 * 60; /** * 构造函数 * @param ip * @param usr * @param pasword */ public RemoteShellExecutor(String ip, String usr, String pasword) { this.ip = ip; this.osUsername = usr; this.password = pasword;// System.out.println(charset); } /** * 登录 * @return * @throws IOException */ private boolean login() throws IOException { conn = new Connection(ip); conn.connect(); return conn.authenticateWithPassword(osUsername, password); } /** * 执行脚本 * * @param cmds * @return * @throws Exception */ public int exec(String cmds) throws Exception { InputStream stdOut = null; InputStream stdErr = null; String outStr = ""; String outErr = ""; int ret = -1; try { if (login()) { // Open a new {@link Session} on this connection Session session = conn.openSession(); // Execute a command on the remote machine. session.execCommand(cmds); stdOut = new StreamGobbler(session.getStdout()); outStr = processStream(stdOut, charset); stdErr = new StreamGobbler(session.getStderr()); outErr = processStream(stdErr, charset); session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT); System.out.println("outStr=" +"\n"+ outStr); System.out.println("outErr=" +"\n"+ outErr); ret = session.getExitStatus(); } else { throw new Exception("登录远程机器失败" + ip); // 自定义异常类 实现略 } } finally { if (conn != null) { conn.close(); } IOUtils.closeQuietly(stdOut); IOUtils.closeQuietly(stdErr); } return ret; } /** * @param in * @param charset * @return * @throws IOException * @throws UnsupportedEncodingException */// private String processStream(InputStream in, String charset) throws Exception {// byte[] buf = new byte[1024];// StringBuilder sb = new StringBuilder();// while (in.read(buf) != -1) {// sb.append(new String(buf, charset));// }// return sb.toString();// } private String processStream(InputStream in, String charset)throws Exception { StringBuilder sb = new StringBuilder(); BufferedReader bufr = new BufferedReader(new InputStreamReader(in,charset)); String line = null; while((line = bufr.readLine()) != null){ sb.append(line); sb.append("\n");//??换行符是依赖平台的 } return sb.toString(); }} 4、Java程序调用远程Shell 1234567public static void main(String args[]) throws Exception { RemoteShellExecutor("10.10.10.100", "root", "123123"); String proName = "entranceMain.sh"; String para = "1"; System.out.println(executor.exec("server.sh.Chant start " + proName +" "+ para));} 将start换为restart,stop,测试结果如下,注意pid发生变化,说明重启成功。 参考资料:Java程序调用远程Shell脚本]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>shell</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HIVE和HBASE区别]]></title>
<url>%2F2016%2F03%2F06%2FHIVE%E5%92%8CHBASE%E5%8C%BA%E5%88%AB%2F</url>
<content type="text"><![CDATA[从架构图中不难看出,Hive是运行在MapReduce之上的,而Hbase则是在更底层的HDFS之上,他们的读写和查询特点也因此而迥然不同。 如果英语不错的话,请直接跳到英文原版 1. 两者分别是什么? Apache Hive 是一个构建在Hadoop基础设施之上的数据仓库。通过Hive可以使用HQL语言查询存放在HDFS上的数据。HQL是一种类SQL语言,这种语言最终被转化为Map/Reduce. 虽然Hive提供了SQL查询功能,但是Hive不能够进行交互查询–因为它只能够在Haoop上批量的执行Hadoop。 Apache HBase 是一种Key/Value系统,一种运行于HDFS顶层的NoSQL(=Not Only SQL,泛指非关系型的数据库)数据库系统。区别于Hive,HBase具备随即读写功能,是一种面向列的数据库。和Hive不一样,Hbase的能够在它的数据库上实时运行,而不是运行MapReduce任务。 Hbase被分区为表格,表格又被进一步分割为列簇。列簇必须使用schema定义,列簇将某一类型列集合起来(列不要求schema定义)。例如,“message”列簇可能包含:“to”, ”from” “date”, “subject”, 和”body”. 每一个 key/value对在Hbase中被定义为一个cell,每一个key由row-key,列簇、列和时间戳组成。 在Hbase中,行是key/value映射的集合,这个映射通过row-key来唯一标识。Hbase利用Hadoop的基础设施,可以利用通用的设备进行水平的扩展。 2. 两者的特点Hive帮助熟悉SQL的人运行MapReduce任务。因为它是JDBC兼容的,同时,它也能够和现存的SQL工具整合在一起。**运行Hive查询会花费很长时间,因为它会默认遍历表中所有的数据。** 虽然有这样的缺点,一次遍历的数据量可以通过Hive的分区机制来控制。分区允许在数据集上运行过滤查询,这些数据集存储在不同的文件夹内,查询的时候只遍历指定文件夹(分区)中的数据。这种机制可以用来,例如,只处理在某一个时间范围内的文件,只要这些文件名中包括了时间格式。 HBase通过存储key/value来工作。它支持四种主要的操作: • 添加或更新数据行 • 扫描获取某范围内的cells • 为某一具体数据行返回对应的cells • 从数据表中删除数据行/列,或列的描述信息 • 列信息可用于获取数据变动前的取值(透过HBase压缩策略可以删除列信息历史记录来释放存储空间)。 虽然HBase包括表格,但是schema仅仅被表格和列簇所要求,列不需要schema。Hbase的表格包括增加/计数功能。 3. 限制Hive不支持常规的SQL更新语句,如:数据插入,更新,删除。因为其对数据的操作是针对整个数据表的。另外,由于hive在hadoop上运行批量操作,它需要花费很长的时间,通常是几分钟到几个小时才可以获取到查询的结果。Hive必须提供预先定义好的schema将文件和目录映射到列,并且Hive与ACID不兼容。HBase查询是通过特定的语言来编写的,这种语言需要重新学习。类SQL的功能可以通过Apache Phonenix实现,但这是以必须提供schema为代价的。另外,Hbase也并不是兼容所有的ACID特性,虽然它支持某些特性。最后但是最重要的–为了运行Hbase,Zookeeper是必须的,zookeeper是一个用来进行分布式协调的服务,这些服务包括配置服务,维护元信息和命名空间服务。 4. 应用场景Hive适合用来 对一段时间内的数据进行分析查询,适用于网络日志等数据量大、静态的数据查询 。例如,用来计算趋势或者网站的日志。Hive不应该用来进行实时的查询。因为它需要很长时间才可以返回结果。 Hbase非常适合用来 进行大数据的实时查询。Facebook用Hbase进行消息和实时的分析。它也可以用来统计Facebook的连接数。 5. 总结Hive和Hbase是两种基于Hadoop的不同技术–Hive是一种类SQL的引擎,并且运行MapReduce任务,Hbase是一种在Hadoop之上的NoSQL 的Key/vale数据库。当然,这两种工具是可以同时使用的。就像用Google来搜索,用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到Hbase,设置再从Hbase写回Hive。例如:利用Hive处理静态离线数据,利用HBase进行联机实时查询,而后对两者间的结果集进行整合归并,从而使得数据完整且永葆青春,为进一步的商业分析提供良好支持。 6.英文原版万一你英语不错却翻不了墙,那多尴尬啊。😂给你准备了英文原版,请享用。 Hive vs. HBase By Saggi Neumann Big Data May 26, 2014 Comparing Hive with HBase is like comparing Google with Facebook - although they compete over the same turf (our private information), they don’t provide the same functionality. But things can get confusing for the Big Data beginner when trying to understand what Hive and HBase do and when to use each one of them. Let’s try and clear it up. What They DoApache Hive is a data warehouse infrastructure built on top of Hadoop. It allows for querying data stored on HDFS for analysis via HQL, an SQL-like language that gets translated to MapReduce jobs. Despite providing SQL functionality, Hive does not provide interactive querying yet - it only runs batch processes on Hadoop.Apache HBase is a NoSQL key/value store which runs on top of HDFS. Unlike Hive, HBase operations run in real-time on its database rather than MapReduce jobs. HBase is partitioned to tables, and tables are further split into column families. Column families, which must be declared in the schema, group together a certain set of columns (columns don’t require schema definition). For example, the “message” column family may include the columns: “to”, “from”, “date”, “subject”, and “body”. Each key/value pair in HBase is defined as a cell, and each key consists of row-key, column family, column, and time-stamp. A row in HBase is a grouping of key/value mappings identified by the row-key. HBase enjoys Hadoop’s infrastructure and scales horizontally using off the shelf servers. FeaturesHive can help the SQL savvy to run MapReduce jobs. Since it’s JDBC compliant, it also integrates with existing SQL based tools. Running Hive queries could take a while since they go over all of the data in the table by default. Nonetheless, the amount of data can be limited via Hive’s partitioning feature. Partitioning allows running a filter query over data that is stored in separate folders, and only read the data which matches the query. It could be used, for example, to only process files created between certain dates, if the files include the date format as part of their name.HBase works by storing data as key/value. It supports four primary operations: put to add or update rows, scan to retrieve a range of cells, get to return cells for a specified row, and delete to remove rows, columns or column versions from the table. Versioning is available so that previous values of the data can be fetched (the history can be deleted every now and then to clear space via HBase compactions). Although HBase includes tables, a schema is only required for tables and column families, but not for columns, and it includes increment/counter functionality. LimitationsHive does not currently support update statements. Additionally, since it runs batch processing on Hadoop, it can take minutes or even hours to get back results for queries. Hive must also be provided with a predefined schema to map files and directories into columns and it is not ACID compliant.HBase queries are written in a custom language that needs to be learned. SQL-like functionality can be achieved via Apache Phoenix, though it comes at the price of maintaining a schema. Furthermore, HBase isn’t fully ACID compliant, although it does support certain properties. Last but not least - in order to run HBase, ZooKeeper is required - a server for distributed coordination such as configuration, maintenance, and naming. Use CasesHive should be used for analytical querying of data collected over a period of time - for instance, to calculate trends or website logs. Hive should not be used for real-time querying since it could take a while before any results are returned.HBase is perfect for real-time querying of Big Data. Facebook use it for messaging and real-time analytics. They may even be using it to count Facebook likes. SummaryHive and HBase are two different Hadoop based technologies - Hive is an SQL-like engine that runs MapReduce jobs, and HBase is a NoSQL key/value database on Hadoop. But hey, why not use them both? Just like Google can be used for search and Facebook for social networking, Hive can be used for analytical queries while HBase for real-time querying. Data can even be read and written from Hive to HBase and back again. 参考资料:主要源于:HIVE和HBASE区别 汉化于 原文出处部分源于:浅谈Hive vs. HBase 区别在哪里]]></content>
<categories>
<category>Bigdata</category>
</categories>
<tags>
<tag>Bigdata</tag>
<tag>Hive</tag>
<tag>Hbase</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java多线程]]></title>
<url>%2F2015%2F11%2F21%2Fjava%E5%A4%9A%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[多线程大总结 并行与并发 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。 Java线程具有五种基本状态 网上有很多图,但是感觉做好的还是这张图(虽然里面interrupt写错成interupt,但是总体架构却是最清晰完整的)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种: 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态; 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态; 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 Object的方法synchronized, wait, notify 是属于Object的方法。 它们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入(也就是很多人说的锁)。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。 wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行(也就是说wait会释放锁,而sleep是属于Thread的方法,不会释放锁)。 当某代码并不持有监视器的使用权时(即已经脱离同步块)去使用wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。 synchronized单独使用: 同步代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容public 12345678public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } }} 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。 12345public class Thread1 implements Runnable { public synchronized void run() { ..do something }} 生产者消费者问题(Producer-consumer problem)synchronized, wait, notify结合:典型场景生产者消费者问题 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980public class TestProduce { public static void main(String[] args) { SyncStack sStack = new SyncStack(); Shengchan sc = new Shengchan(sStack); Xiaofei xf = new Xiaofei(sStack); sc.start(); xf.start(); }}class Mantou { int id; Mantou(int id){ this.id=id; }}class SyncStack{ int index=0; Mantou[] ms = new Mantou[10]; public synchronized void push(Mantou m){ while(index==ms.length){ try { this.wait(); //wait后,线程会将持有的锁释放。sleep是即使睡着也持有互斥锁。 } catch (InterruptedException e) { e.printStackTrace(); } } this.notify(); //唤醒在当前对象等待池中等待的第一个线程。notifyAll叫醒所有在当前对象等待池中等待的所有线程。 //如果不唤醒的话。以后这两个线程都会进入等待线程,没有人唤醒。 ms[index]=m; index++; } public synchronized Mantou pop(){ while(index==0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.notify(); index--; return ms[index]; }}class produce extends Thread{ SyncStack ss = null; public Shengchan(SyncStack ss) { this.ss=ss; } @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("造馒头:"+i); Mantou m = new Mantou(i); ss.push(m); } }}class consume extends Thread{ SyncStack ss = null; public Xiaofei(SyncStack ss) { this.ss=ss; } @Override public void run() { for (int i = 0; i < 20; i++) { Mantou m = ss.pop(); System.out.println("吃馒头:"+i); } }} volatile多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。 针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。 基本线程类基本线程类指的是Thread类,Runnable接口,Callable接口。 ####Thread类,Runnable接口继承Thread类(Thread类本身就是继承了Runnable接口的),实现Runnable接口都可以创建线程。如果Thread和Runnable都复写了run()方法,最终会运行哪一个run()方法呢?12345678910111213141516171819202122232425262728293031323334353637383940public class ThreadTest { public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 30) { Runnable myRunnable = new MyRunnable(); Thread thread = new MyThread(myRunnable); thread.start(); } } }}class MyRunnable implements Runnable { private int i = 0; @Override public void run() { System.out.println("in MyRunnable run"); for (i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } }}class MyThread extends Thread { private int i = 0; public MyThread(Runnable runnable){super(runnable);} @Override public void run() { System.out.println("in MyThread run"); for (i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } }} 同样的,与实现Runnable接口创建线程方式相似,不同的地方在于 1Thread thread = new MyThread(myRunnable); 通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。我们看一下Thread类中对Runnable接口中run()方法的实现:123456@Override public void run() { if (target != null) { target.run(); } } 也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。 Callablefuture模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态。这是Thread和Runnable做不到的 12345ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.Future future = e.submit(new myCallable());future.isDone() //return true,false 无阻塞future.get() // return 返回值,阻塞直到该线程运行结束 后台线程(Daemon Thread)概念/目的:后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡(其实这个也很好理解,因为后台线程存在的目的在于为前台线程服务的,既然所有的前台线程都死亡了,那它自己还留着有什么用…伟大啊 ! !)。设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。 Thread类相关方法:12345678//当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)public static Thread.yield() //暂停一段时间public static Thread.sleep() //在一个线程中调用other.join(),将等待other执行完后才继续本线程。 public join()//后两个函数皆可以被打断public interrupt() 关于中断它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。中断只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。Thread.interrupted()检查当前线程是否发生中断,返回boolean类型值。synchronized在获锁的过程中是不能被中断的。 中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()),则同样可以在中断后离开代码体。 Thread类最佳实践写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。 Java多线程的就绪、运行和死亡状态以及停止线程 就绪状态转换为运行状态:当此线程得到处理器资源; 运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。 运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。 此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。 由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:12345678910111213141516171819202122232425262728293031323334public class ThreadTest { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 30) { thread.start(); } if(i == 40){ myRunnable.stopThread(); } } }}class MyRunnable implements Runnable { private boolean stop; @Override public void run() { for (int i = 0; i < 100 && !stop; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } } public void stopThread() { this.stop = true; }} 高级多线程控制类以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。 Lock类 lock: 在java.util.concurrent包内。共有三个实现: • ReentrantLock • ReentrantReadWriteLock.ReadLock • ReentrantReadWriteLock.WriteLock主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。区别如下: 1. lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序) 2. 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。 3. 本质上和监视器锁(即synchronized是一样的) 4. 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。 5. 和Condition类的结合。 6. 性能更高,对比如下图: ReentrantLock可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。使用方法是:1.先new一个实例1static ReentrantLock r=new ReentrantLock(); 2.加锁1r.lock()或r.lockInterruptibly(); 此处也是个不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception 或catch)3.释放锁1r.unlock() 必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。 ReentrantReadWriteLock可重入读写锁(读写锁的一个实现123ReentrantReadWriteLock lock = new ReentrantReadWriteLock()ReadLock r = lock.readLock();WriteLock w = lock.writeLock(); 两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码 管理类管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBeanThreadPoolExecutor如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:12345678ExecutorService e = Executors.newCachedThreadPool();ExecutorService e = Executors.newSingleThreadExecutor();ExecutorService e = Executors.newFixedThreadPool(3);// 第一种是可变大小线程池,按照任务数来分配线程,// 第二种是单线程池,相当于FixedThreadPool(1)// 第三种是固定大小线程池。// 然后运行e.execute(new MyRunnableImpl()); 该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc: corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。maximumPoolSize:线程最大值,线程的增长始终不会超过该值。keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDSworkQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。 参考文章 Java中的多线程你只要看这一篇就够了 Java总结篇系列:Java多线程(一) Java总结篇系列:Java多线程(二) 尚学堂java300集]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[在hexo博客中插入图片,音乐,视频,公式]]></title>
<url>%2F2015%2F11%2F04%2F%E5%9C%A8hexo%E5%8D%9A%E5%AE%A2%E4%B8%AD%E6%8F%92%E5%85%A5%E5%9B%BE%E7%89%87%EF%BC%8C%E9%9F%B3%E4%B9%90%EF%BC%8C%E8%A7%86%E5%B1%8F%EF%BC%8C%E5%85%AC%E5%BC%8F%2F</url>
<content type="text"><![CDATA[摘要:如何简洁优雅地在hexo博客中插入图片,音乐,视频,公式.这篇博客本来是拿来测试图片音乐的插入的,但是测着测着,忽然灵光一现,为什么不直接写成一篇博客呢? 页内跳转添加一则小技巧,实现markdown页内跳转。html标签实现 1. 定义一个锚(id):<span id="jump">跳转到的地方</span> 2. 使用markdown语法:[点击跳转](#jump) Example:我在本文参考文献前加了一行<span id="jump"> </span>,然后在此处编写[点击跳转](#jump)点击跳转 图片关于插入图片,网上的图床(注册麻烦,使用麻烦,需要上传,网速不行咋办?类似Lightshot Screenshot,上传基本30秒,如果你要写一份需要详细截图的安装文档,那估计一天的时间都花在上传图片上了),hexo官方的方法(将图片放在source/image/下,这样在编辑器中并不能实时预览,而且你还需要记住图片的名字。)都令人觉得插入图片简直是个噩梦。知道我遇到了MWeb,一个可以直接把图片拖入即可完成图片插入的Markdown编辑器,或者使用微信截图Ctrl+command+a保存(记得是点下载按钮,不是点那个小红勾)后,直接Command+v,图片就插入了。而且其低调简洁的界面,简单实用的快捷键,完美的诠释了什么叫优雅的写作方式(一些bug和程序崩溃除外)。当然如此美腻而又强大,必然不便宜的,官方价格¥98。不要桑心,他还有免费的lite版,两个版本的区别主要是以下几点: 文档库文档限制在 10 个以内;(然鹅,写hexo一直都是用外部模式,压根不需要文档库)外部模式可引入的文件夹限制在 1 个;(然鹅,一个文件下可以有无数个文件夹)支持的发布服务只可增加 1 个;(然鹅,写hexo博客,不需要编辑器来帮我发布) 当然,MWeb不只是拿来写hexo博客的,更多信息请戳MWeb官网以及帮助文档。 1![](/media/14967219636093.jpg) 当然,markdown是支持GIF动图的,使用方法和图片一致,只要你放的链接是一张动图的链接就行。 Eaxmple: 1![](http://upload-images.jianshu.io/upload_images/291600-3b00271942fef854.gif?imageMogr2/auto-orient/strip) 音乐1.使用网易云音乐的外链播放器比如你右键某首歌,复制链接的到链接为http://music.163.com/#/m/song?id=2919622只需要其中的id=2919622就够了。 那么在文章中使用ifname标签如下,记得将其中的id改为你想要的音乐id。 1<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=430 height=86 src="//music.163.com/outchain/player?type=2&id=2919622&auto=0&height=66"></iframe> 2.使用 Hexo 插件插入音乐/音频hexo-tag-aplayer:https://github.com/grzhan/hexo-tag-aplayer官方简介:Embed APlayer(https://github.com/DIYgod/APlayer) in Hexo posts/pages.很明显,除了posts,还可以在pages页面使用。 Installation: 1npm install --save hexo-tag-aplayer Usage:1{% aplayer title author url [picture_url, narrow, autoplay, width:xxx, lrc:xxx] %} 参数说明及详细信息,请戳github,还有加入歌词和播放例表等强大功能等你探索。Example:1{% aplayer "她的睫毛" "周杰伦" "http://home.ustc.edu.cn/~mmmwhy/%d6%dc%bd%dc%c2%d7%20-%20%cb%fd%b5%c4%bd%de%c3%ab.mp3" "http://home.ustc.edu.cn/~mmmwhy/jay.jpg" "autoplay=false" %} new APlayer({ element: document.getElementById("aplayer0"), narrow: false, autoplay: false, showlrc: 0, music: { title: "她的睫毛", author: "周杰伦", url: "http://home.ustc.edu.cn/~mmmwhy/%d6%dc%bd%dc%c2%d7%20-%20%cb%fd%b5%c4%bd%de%c3%ab.mp3", pic: "http://home.ustc.edu.cn/~mmmwhy/jay.jpg", } }); 使用唱吧录制并上传音频,用浏览器打开分享链接,右键显示网页源代码 搜索mp3,找到你的音频链接。 1{% aplayer "Job or Education" "Chant" "http://lzscuw.changba.com/899486104.mp3" "http://aliimg.changba.com/cache/photo/735572112_640_640.jpg" "autoplay=false" %} new APlayer({ element: document.getElementById("aplayer1"), narrow: false, autoplay: false, showlrc: 0, music: { title: "Job or Education", author: "Chant", url: "http://lzscuw.changba.com/899486104.mp3", pic: "http://aliimg.changba.com/cache/photo/735572112_640_640.jpg", } }); 视频1使用标签插入视频1.1使用iframe标签插入视频一般的国内网站,获取嵌入代码的方法如下图: Youtube,右键视频,复制嵌入代码,直接将嵌入代码粘贴进你的markdown文章就OK啦。 此方法在嵌入Youtube视频时,其成败与hexo主题有关,在本主题下加载嵌入失败,在别的主题下,比如maupassant成功。1<iframe width="854" height="480" src="https://www.youtube.com/embed/xqf2DJgucsU" frameborder="0" allowfullscreen></iframe> 但腾讯视频的引用嵌入是没有问题的。1<iframe frameborder="0" width="640" height="498" src="https://v.qq.com/iframe/player.html?vid=g0512cgb51w&tiny=0&auto=0" allowfullscreen></iframe> 1.2使用embed标签插入视屏在优酷,腾讯等等网站都是一样的方法,如图:然后将代码直接粘贴到你的markdown就OK了。1<embed src="https://imgcache.qq.com/tencentvideo_v1/playerv3/TPout.swf?max_age=86400&v=20161117&vid=g0512cgb51w&auto=0" allowFullScreen="true" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" type="application/x-shockwave-flash"></embed> 2.使用hexo插件插入视频hexo-tag-dplayer:https://github.com/NextMoe/hexo-tag-dplayer与aplayer类似,不过它是用来插入视频。 Installation: 1npm install hexo-tag-dplayer --save Usage: 1{% dplayer key=value ... %} 参数说明及详细信息,请戳githubExample:首先,也是小白最难的一步,找到视频源 以下代码中的url即为你刚才复制的视频源链接12{% dplayer "url=http://ugcydzd.qq.com/flv/92/216/g0512cgb51w.p712.1.mp4?sdtfrom=v1010&guid=72ffbc53bc13455246dcec4efd2c2b02&vkey=0CE5FC72FB6FA6D97FD1077E0449F0AB0ADDF71FFE82014D6FA31F80237EA5C3A40C048E207507FA283E0EB1C1C3E10188B0D7CAD66E072FF8AB6BBC2D2E8E9E34631C122081535D7168D0D2723548E25E94E04EC20FD1A10848CDB66FBE45E35E6F7D1D0C3C0520AFA5331498386C8D" "pic=/media/14972328758364.jpg" "loop=yes" "theme=#FADFA3" "autoplay=false" "token=tokendemo" %} 效果如下:var dplayer1 = new DPlayer({"element":document.getElementById("dplayer1"),"autoplay":false,"theme":"#FADFA3","loop":true,"video":{"url":"http://ugcydzd.qq.com/flv/92/216/g0512cgb51w.p712.1.mp4?sdtfrom=v1010&guid=72ffbc53bc13455246dcec4efd2c2b02&vkey=0CE5FC72FB6FA6D97FD1077E0449F0AB0ADDF71FFE82014D6FA31F80237EA5C3A40C048E207507FA283E0EB1C1C3E10188B0D7CAD66E072FF8AB6BBC2D2E8E9E34631C122081535D7168D0D2723548E25E94E04EC20FD1A10848CDB66FBE45E35E6F7D1D0C3C0520AFA5331498386C8D","pic":"/media/14972328758364.jpg"}}); 此方法youtube视频的引用还在探索中。 var dplayer2 = new DPlayer({"element":document.getElementById("dplayer2"),"video":{"url":"https://www.youtube.com/embed/xqf2DJgucsU"}}); 以下是官方示例,点击设置按钮还有弹幕、速度、洗脑循环等选项。 1{% dplayer "url=http://devtest.qiniudn.com/若能绽放光芒.mp4" "addition=https://dplayer.daoapp.io/bilibili?aid=4157142" "api=http://dplayer.daoapp.io" "pic=http://devtest.qiniudn.com/若能绽放光芒.png" "id=9E2E3368B56CDBB4" "loop=yes" "theme=#FADFA3" "autoplay=false" "token=tokendemo" %} var dplayer3 = new DPlayer({"element":document.getElementById("dplayer3"),"autoplay":false,"theme":"#FADFA3","loop":true,"video":{"url":"http://devtest.qiniudn.com/若能绽放光芒.mp4","pic":"http://devtest.qiniudn.com/若能绽放光芒.png"},"danmaku":{"api":"http://dplayer.daoapp.io","id":"9E2E3368B56CDBB4","token":"tokendemo","addition":["https://dplayer.daoapp.io/bilibili?aid=4157142"]}}); 参考资料:1.Hexo博客中插入音乐/视频2.markdown语法及编辑器推荐,都是踩过的坑3.页内跳转]]></content>
<categories>
<category>Hexo</category>
</categories>
<tags>
<tag>教程</tag>
<tag>Hexo</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java设计模式--六大原则]]></title>
<url>%2F2015%2F09%2F19%2Fjava%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[传说Java有六大心法,23种武功招式。分别就是Java设计模式六大原则和常用的23种设计模式了。传说掌握心法再配合招式,便能独步天下,称霸武林。这里先来学习下这6大心法。 1.单一职责原则(Single Responsibility Principle)一个类只负责一项职责,不要存在多余一个职责导致类的变更。 比如:类A负责两个不同的职责,b,c职责。由于b职责需求发生变化而需要改变A类,原本运行正常的c职责出现故障。这就违背了单一职责原则。 2.里氏替换原则(Liskov Substitution Principle) 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。 子类中可以增加自己特有的方法。 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。 总之尽量不要重写父类已经实现的方法,可以用接口其它方法绕过去。关于第三点的宽松和严格,一开始我也不理解,还是用代码解释一下吧。父类能够存在的地方,子类就能存在,并且不会对运行结果有变动。反之则不行。父类say()里面的参数是HashMap类型,是Map类型的子类型。(因为子类的范围应该比父类大)123456789101112131415161718192021222324252627282930313233343536373839404142package LSP;import java.util.Collection;import java.util.HashMap;import java.util.Map;/** * 里氏置换原则(Liskov Substitution Principle),简称LSP。 * Created by Chant on 2017/6/19. *///场景类public class LspTest { public static void main(String args[]) { invoke(); } public static void invoke() { //父类存在的地方,子类就应该能够存在 Father f = new Father(); Son s = new Son(); HashMap map = new HashMap(); f.say(map); s.say(map); }}//父类class Father { public Collection say(HashMap map) { System.out.println("父类被执行..."); return map.values(); }}//子类class Son extends Father { //方法输入参数类型 public Collection say(Map map) { System.out.println("子类被执行..."); return map.values(); }} 无论是用父类还是子类调用say方法,得到的结果都是 父类被执行… 但是,如果将上面Father里的say参数改为Map,子类Son里的say参数改为HashMap,得到的结果就变成了 f.say(map)结果:父类被执行… s.say(map)结果: 子类被执行… 这样会造成逻辑混乱。所以子类中方法的前置条件必须与父类中被覆写的前置条件相同或者更宽。 3.依赖倒置原则(Dependence Inversion Principle) 高层模块不应该依赖底层模块,都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。 总之:多用抽象的接口来描述要做的动作,降低实现这个动作的事务之间的耦合度。(各自拥有各自的接口,不要放在一起使用,降低耦合性) 4.接口隔离原则(Interface Segregation Principle)客户端不应该依赖它不需要的接口;一个类对另一个类的依赖建立在最小的接口上。总之就是一个接口尽量完功能的单一,不要让一个接口承担过多的责任。 5.迪米特法则(law of Demeter)通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。只与自己的成员变量和参数打交道,不与其它打交道。 6.开闭原则(Open Close Principle)对扩展开放,修改关闭。尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。 最后,是丑得不行的思维导图,怕辣眼睛就别看了😂。 参考文章 Java设计模式六大原则–详细解说版 Java六大原则–精简版 Java六大原则–代码版]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>设计模式</tag>
</tags>
</entry>
</search>