-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
2911 lines (2331 loc) · 186 KB
/
atom.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
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Chouqin & Laoqi</title>
<link href="http://username.github.com/atom.xml" rel="self"/>
<link href="http://username.github.com"/>
<updated>2013-10-16T20:22:12+08:00</updated>
<id>http://username.github.com</id>
<author>
<name>Qiping Li</name>
<email>[email protected]</email>
</author>
<entry>
<title>MapReduce学习心得</title>
<link href="http://username.github.com/bigdata/2013/10/16/mapreduce"/>
<updated>2013-10-16T00:00:00+08:00</updated>
<id>http://username.github.com/bigdata/2013/10/16/mapreduce</id>
<content type="html">
<h2 id="section">简介</h2>
<p>对于计算机系的同学来说,MapReduce这个词应该并不陌生,
现在是所谓的“大数据时代”,“大数据”这个词被炒得非常热。
何为“大数据”?随着互联网的发展,现在的数据越来越多,
给原先的技术带来了两方面的挑战,一是<strong>存储</strong>,
如何存储这些PB级别的数据,
二是<strong>分析</strong>, 如何对这么大的数据进行分析,
从中提取出有用的信息。</p>
<p>MapReduce就是一个对大数据进行分析的框架。
使用MapReduce,用户只需要定义自己的<code>map</code>函数和<code>reduce</code>函数,
然后MapReduce就能把这些函数分配到不同的机器上去并行的执行,
MapReduce帮你解决好调度,容错,节点交互的问题。
这样,一个没有分布式系统编程经验的人也可以利用MapReduce把自己的程序放到几千台机器上去执行。</p>
<p><code>map</code>和<code>reduce</code>都是来自于函数式编程的概念,<code>map</code>函数接受一条条的纪录作为输入,
然后输出一个个<code>&lt;key, value&gt;</code>对,<code>reduce</code>函数接受<code>&lt;key, values&gt;</code>对,
(其中<code>values</code>是每一个<code>key</code>对应的所有的<code>value</code>),通过对这些<code>values</code>进行一个“总结”,
得到一个<code>&lt;key, reduce_value&gt;</code>。</p>
<p>比如拿经典的<strong>WordCount</strong>例子来说,对于文本中的每一个单词<code>word</code>,
<code>map</code>都会生成一个<code>&lt;word, 1&gt;</code>对(注意如果一个文本中一个单词出现多次就生成多个这样的对),
<code>reduce</code>函数就会收到<code>&lt;word, 1,...&gt;</code>这样的输入,它的工作就是把所有的<code>1</code>都加起来,
生成一个<code>&lt;word, sum&gt;</code>。</p>
<p>MapReduce函数基于键值对进行处理,看起来很简单,
那很多的分析任务不仅仅只是简单的Wordcount而已,能够使用这两个函数来实现吗?
幸运的是,很多的大规模数据分析任务都能通过MapReduce来表达,
这也是为什么MapReduce能够作为一个框架被提出的原因,
在最后一个部分中会给出一些更加复杂的使用MapReduce的例子。</p>
<h2 id="section-1">架构</h2>
<p>在大概了解了MapReduce之后,我们来看一下它到底是怎么实现的。
我们首先看一下一个MapReduce任务执行完需要经过那些流程,
然后再看一下在实现MapReduce的时候需要考虑的几个因素。</p>
<h3 id="section-2">基本流程</h3>
<p><img src="/assets/themes/../images/map_reduce_execution.png" alt="" /></p>
<p>我们首先假定把一个任务分成M个<code>map</code>Task和R个<code>reduce</code>task,
如上图,
整个任务的执行过程如下:</p>
<ol>
<li>
<p>首先根据<code>map</code>的数量把原来的数据分成M个splits,每一个split对应一个<code>map</code>task。</p>
</li>
<li>
<p>在集群的节点上启动<code>master</code>,M个<code>map</code>task和N个<code>reduce</code>task,
<code>master</code>把split分配给相应的<code>map</code>task。需要注意的是,
一个集群的节点(又称作一个worker)上可能有多个<code>map</code>task,
也有可能<code>map</code>task和<code>reduce</code>task在同一个worker。</p>
</li>
<li>
<p>每一个<code>map</code>task读取自己的split,根据用户定义的<code>map</code>函数生成<code>&lt;key value&gt;</code>对,
把结果保存在<strong>本地文件</strong>中,
根据<code>key</code>的不一样,结果被写入到R个不同的文件。
这些文件的位置会告知给<code>master</code>,然后<code>master</code>再告知给相应的<code>reduce</code>task。</p>
</li>
<li>
<p>当一个<code>reduce</code>task被告知这些文件的位置时,它通过远程调用读取这些文件的内容,
当和这个<code>reduce</code>task相关的所有文件都被读到之后,它把这些内容按照<code>key</code>进行一个排序,
然后就能保证同一个<code>key</code>的所有<code>values</code>同时被传给<code>reduce</code>。</p>
</li>
<li>
<p><code>reduce</code>task使用用户定义的<code>reduce</code>函数处理上述排序好的数据,
将最终的结果保存到一个<strong>Global File System</strong>(比如GFS),
这是为了保证数据的可靠性。</p>
</li>
</ol>
<h3 id="section-3">文件的保存</h3>
<p>在上述的过程中,我们看到<code>map</code>task的结果被保存在本地,
而把<code>reduce</code>task的结果保存在具有可靠性保证的文件系统上。
这是因为<code>map</code>task产生的是中间结果,当这些结果被<code>reduce</code>之后,
就可以被扔掉,不需要备份,这样可以节约磁盘空间。
而<code>reduce</code>task产生的是最终结果,需要一定的可靠性保证。</p>
<h3 id="split">split的粒度</h3>
<p>在对一个任务进行划分时,需要考虑split的粒度:</p>
<ul>
<li>
<p>如果split太小,M就会很大,
<code>master</code>需要纪录的数据就会很多,
就会消耗很多<code>master</code>的内存。</p>
</li>
<li>
<p>如果split太大,一方面调度不具有灵活性,
因为调度是以split为单位的,一个较大的task无法被分割放到其他空闲的worker上去执行。
另一方面无法利用<code>locality</code>进行调度,
因为<code>map</code>task的输入文件一般保存在分布式文件系统上,
<code>master</code>在调度时尽量把一个split分配到较近的节点上去执行,
如果split太大超过了一个文件block的大小,
这样可能两个block在不同的节点上,甚至跨了不同的机架,
这样无法利用<code>locality</code>了。</p>
</li>
</ul>
<p>所以,在实际应用中,split的大小为一个block。</p>
<h3 id="section-4">容错</h3>
<p>由于MapReduce被分布到上千台机器上去执行,
错误是不可避免的。
MapReduc需要在节点发生故障时进行处理。</p>
<p>当一个节点发生故障,
在这个节点上的所有的<code>map</code>task都需要重新执行,
因为<code>map</code>task的结果是保存在节点本地的,
节点发生故障之后,这些结果就不可用了。
而成功执行的<code>reduce</code>task就不需要重新执行了,
因为它的结果是保存在分布式文件系统上,
可靠性是可以保证的。</p>
<h2 id="section-5">常见应用</h2>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<h3 id="section-6">矩阵向量乘法</h3>
<p>假设有一个<script type="math/tex">m \times n</script>的矩阵M,
它和一个n维列向量v的乘积是一个m维的列向量x,有</p>
<script type="math/tex; mode=display">
x_i = \sum_{j=1}^{n} m_{ij}v_j
</script>
<p>可以根据j,把M按列分成k块,把v也对应分成k块,
每个M块和对应的v块被分给一个<code>map</code>task,
<code>map</code>task生成的结果为<script type="math/tex">(i, m_{ij}v_j)</script>对,
<code>reduce</code>task把每一个i对应的所有<script type="math/tex">m_{ij}v_j</script>加起来。</p>
<h3 id="section-7">关系代数运算</h3>
<p>关系数据库表的Join,Selection,Projection, Union, Intersection,
Difference,Group And Aggregation等操作都可以使用MapReduce来实现。</p>
<p>值得一提的是,对于Join运算,比如链接关系R(a, b)和关系S(b, c),
在生成以b为键的键值对时,需要指定来自于哪一个关系,
比如关系R生成的键值对的形式为<code>&lt;b, (R, a)&gt;</code>,
这样<code>reduce</code>时就可以根据这个信息进行组合。</p>
<h3 id="section-8">矩阵乘法</h3>
<p>假设有一个<script type="math/tex">m \times n</script>的矩阵M,
和一个<script type="math/tex">n \times p</script>的矩阵N,
它们的乘积是一个<script type="math/tex">m \times p</script>的矩阵P,
有:</p>
<script type="math/tex; mode=display">
p_{ik} = \sum_{j=1}^{n} m_{ij}n_{jk}
</script>
<p>矩阵M和矩阵N的乘法可以看成是关系M(I, J, V)和关系N(J, K, W)先进行一次Join,
再进行一次Group And Aggregation之后的结果,
因此可以直接通过两次MapReduce进行矩阵的乘法运算。</p>
<p>如果想要一次MapReduce就得到结果,可以在<code>map</code>时以(i, k)为键生成键值对,
同样的,需要指明来自于矩阵M还是矩阵N,因此,相应的键值对的格式分别为
<script type="math/tex">((i, k), (M, j, m_{ij}))</script>(对于矩阵M),<script type="math/tex">((i, k), (N, j, n_{jk}))</script>(对于矩阵N)。
<code>reduce</code>时进行相应的组合。</p>
</content>
</entry>
<entry>
<title>GFS学习心得</title>
<link href="http://username.github.com/bigdata/2013/10/10/gfs"/>
<updated>2013-10-10T00:00:00+08:00</updated>
<id>http://username.github.com/bigdata/2013/10/10/gfs</id>
<content type="html">
<p>又好久没写博客了,这几个月来零零散散做了一些事情,
学到的东西很杂,一直都没有形成系统,也没有在某些方面有很深的体会。
有些东西刚刚深入进去看了一点(比如<a href="redis.io">redis</a>,看过一些代码,下次一定要写点心得出来),
还没来得及总结,就被一些其他的事情把时间挤走了。而现在,
时间相对来说比较空闲,可以认真研究一些技术,学得一些东西了。</p>
<p>这几天一直在看<a href="http://research.google.com/archive/gfs.html">The Google File System</a>这篇论文,
看得很慢,一天才能看几页,有些地方还要反反复复看几遍才能“理解”,
但也确实学得了不少东西,包括一些分布式系统设计的基本思想,
以及如何根据应用的具体场景来做设计决策。
诚然,对于一个这么大的系统,要想弄明白它的全部细节是比较困难的,
能够把它的整个过程捋顺就可以了。本文中,
我把我对这篇论文印象比较深的内容用我自己的理解讲出来,
希望能够给对GFS感兴趣的同学一点帮助。</p>
<h2 id="section">特殊的应用场景</h2>
<p>GFS作为一个分布式的文件系统,
除了要满足一般的文件系统的需求之外,
还根据一些特殊的应用场景(原文反复提到的<code>application workloads and technological environment</code>),
来完成整个系统的设计。</p>
<h3 id="section-1">分布式文件系统的要求</h3>
<p>一般的分布式文件系统需要满足以下四个要求:</p>
<ul>
<li>Performance:高性能,较低的响应时间,较高的吞吐量</li>
<li>Scalability: 易于扩展,可以简单地通过增加机器来增大容量</li>
<li>Reliability: 可靠性,系统尽量不出错误</li>
<li>Availability: 可用性,系统尽量保持可用</li>
</ul>
<p>(注:关于reliability和availability的区别,
请参考<a href="http://unfolding-mirror.blogspot.com/2009/06/reliability-vs-availability.html">这篇</a>)</p>
<h3 id="gfs">GFS基于的假设</h3>
<p>基于对实际应用场景的研究,GFS对它的使用场景做出了如下假设:</p>
<ol>
<li>
<p>GFS运行在成千上万台便宜的机器上,这意味着节点的故障会经常发生。
必须有一定的容错的机制来应对这些故障。</p>
</li>
<li>
<p>系统要存储的文件通常都比较大,每个文件大约100MB或者更大,
GB级别的文件也很常见。必须能够有效地处理这样的大文件,
基于这样的大文件进行系统优化。</p>
</li>
<li>
<p>workloads的读操作主要有两种:</p>
<ul>
<li>
<p>大规模的流式读取,通常一次读取数百KB的数据,
更常见的是一次读取1MB甚至更多的数据。
来自同一个client的连续操作通常是读取同一个文件中连续的一个区域。</p>
</li>
<li>
<p>小规模的随机读取,通常是在文件某个随机的位置读取
几个KB数据。
对于性能敏感的应用通常把一批随机读任务进行排序然后按照顺序批量读取,
这样能够避免在通过一个文件来回移动位置。(后面我们将看到,
这样能够减少获取metadata的次数,也就减少了和master的交互)</p>
</li>
</ul>
</li>
<li>
<p>workloads的写操作主要由大规模的,顺序的append操作构成。
一个文件一旦写好之后,就很少进行改动。因此随机的写操作是很少的,
所以GFS主要针对于append进行优化。</p>
</li>
<li>
<p>系统必须有合理的机制来处理多个client并发写同一个文件的情况。
文件经常被用于生产者-消费者队列,需要高效地处理多个client的竞争。
正是基于这种特殊的应用场景,GFS实现了一个无锁并发append。</p>
</li>
<li>
<p>利用高带宽比低延迟更加重要。基于这个假设,
可以把读写的任务分布到各个节点,
尽量保证每个节点的负载均衡,
尽管这样会造成一些请求的延迟。</p>
</li>
</ol>
<!--more-->
<h2 id="section-2">架构</h2>
<p>下面我们来具体看一下GFS的整个架构。</p>
<p><img src="/assets/themes/../images/gfs-arch.png" alt="" /></p>
<p>可以看到GFS由三个不同的部分组成,分别是<code>master</code>,<code>client</code>, <code>chunkserver</code>。
<code>master</code>负责管理整个系统(包括管理metadata,垃圾回收等),一个系统只有一个<code>master</code>。
<code>chunkserver</code>负责保存数据,一个系统有多个<code>chunkserver</code>。
<code>client</code>负责接受应用程序的请求,通过请求<code>master</code>和<code>chunkserver</code>来完成读写等操作。
由于系统只有一个<code>master</code>,<code>client</code>对<code>master</code>请求只涉及metadata,
数据的交互直接与<code>chunkserver</code>进行,这样减小了<code>master</code>的压力。</p>
<p>一个文件由多个chunk组成,一个chunk会在多个<code>chunkserver</code>上存在多个replica。
对于新建文件,目录等操作,只是更改了metadata,
只需要和<code>master</code>交互就可以了。注意,与linux的文件系统不同,
目录不再以一个inode的形式保存,也就是它不会作为data被保存在<code>chunkserver</code>。
如果要读写文件的文件的内容,就需要<code>chunkserver</code>的参与,
<code>client</code>根据需要操作文件的偏移量转化为相应的<code>chunk index</code>,
向<code>master</code>发出请求,<code>master</code>根据文件名和<code>chunk index</code>,得到一个全局的<code>chunk handle</code>,
一个chunk由唯一的一个<code>chunk handle</code>所标识,
<code>master</code>返回这个<code>chunk handle</code>以及拥有这个chunk的<code>chunkserver</code>的位置。
(不止一个,一个chunk有多个replica,分布在不同的<code>chunkserver</code>。
必要的时候,<code>master</code>可能会新建chunk,
并在<code>chunkserver</code>准备好了这个chunk的replica之后,才返回)
<code>client</code>拿到<code>chunk handle</code>和<code>chunkserver</code>列表之后,
先把这个信息用文件名和<code>chunk index</code>作为key缓存起来,
然后对相应的<code>chunkserver</code>发出数据的读写请求。
这只是一个大概的流程,对于具体的操作过程,下面会做分析。</p>
<h3 id="chunk">Chunk大小</h3>
<p>Chunk的大小是一个值得考虑的问题。在GFS中,chunk的大小是64MB。
这比普通文件系统的block大小要大很多。
在<code>chunkserver</code>上,一个chunk的replica保存成一个文件,
这样,它只占用它所需要的空间,防止空间的浪费。</p>
<p>Chunk拥有较大的大小由如下几个好处:</p>
<ul>
<li>它减少了<code>client</code>和<code>master</code>交互的次数。</li>
<li>减少了网络的开销,由于一个客户端可能对同一个chunk进行操作,
这样可以与<code>chunkserver</code>维护一个长TCP连接。</li>
<li>chunk数目少了,metadata的大小也就小了,这样节省了<code>master</code>的内存。</li>
</ul>
<p>大的chunk size也会带来一个问题,一个小文件可能就只占用一个chunk,
那么如果多个<code>client</code>同时操作这个文件的话,就会变成操作同一个chunk,
保存这个chunk的<code>chunkserver</code>就会称为一个hotspot。
这样的问题对于小的chunk并不存在,因为如果是小的chunk的话,
一个文件拥有多个chunk,操作同一个文件被分布到多个<code>chunkserver</code>.
虽然在实践中,可以通过错开应用的启动的时间来减小同时操作一个文件的可能性。</p>
<h3 id="metadata">Metadata</h3>
<p>GFS的<code>master</code>保存三种metadata:</p>
<ol>
<li>文件和chunk的namespace</li>
<li>文件到chunk的映射</li>
<li>每一个chunk的具体位置</li>
</ol>
<p>metadata保存在内存中,可以很快地获取。
前面两种metadata会通过operation log来持久化。
第3种信息不用持久化,因为在<code>master</code>启动时,
它会问<code>chunkserver</code>要chunk的位置信息。
而且chunk的位置也会不断的变化,比如新的<code>chunkserver</code>加入。
这些新的位置信息会通过日常的<code>HeartBeat</code>消息由<code>chunkserver</code>传给<code>master</code>。</p>
<p>将metadata保存在内存中能够保证在<code>master</code>的日常处理中很快的获取metadata,
为了保证系统的正常运行,<code>master</code>必须定时地做一些维护工作,比如清除被删除的chunk,
转移或备份chunk等,这些操作都需要获取metadata。
metadata保存在内存中有一个不好的地方就是能保存的metadata受限于<code>master</code>的内存,
不过足够大的chunk size和使用前缀压缩,能够保证metadata占用很少的空间。</p>
<p>对metadata进行修改时,使用锁来控制并发。需要注意的是,对于目录,
获取锁的方式和linux的文件系统有点不太一样。在目录下新建文件,
只获取对这个目录的读锁,而对目录进行snapshot,却对这个目录获取一个写锁。
同时,如果涉及到某个文件,那么要获取所有它的所有上层目录的读锁。
这样的锁有一个好的地方是可以在通过一个目录下同时新建两个文件而不会冲突,
因为它们都是获得对这个目录的读锁。</p>
<h3 id="operation-log">Operation Log</h3>
<p>Operation log用于持久化存储前两种metadata,这样<code>master</code>启动时,
能够根据operation log恢复metadata。同时,可以通过operation log知道metadata修改的顺序,
对于重现并发操作非常有帮助。因此,必须可靠地存储operation log,
只有当operation log已经存储好之后才向<code>client</code>返回。
而且,operation log不仅仅只保存在<code>master</code>的本地,而且在远程的机器上有备份,
这样,即使<code>master</code>出现故障,也可以使用其他的机器做为<code>master</code>。</p>
<p>从operation log恢复状态是一个比较耗时的过程,因此,使用checkpoint来减小operation log的大小。
每次恢复时,从checkpoint开始恢复,只处理checkpoint只有的operation log。
在做checkpoint时,新开一个线程进行checkpoint,原来的线程继续处理metadata的修改请求,
此时把operation log保存在另外一个文件里。</p>
<h3 id="section-3">一致性模型</h3>
<p>关于一致性,先看几个定义,对于一个file region,存在以下几个状态:</p>
<ul>
<li>consistent。如果任何replica, 包含的都是同样的data。</li>
<li>defined。defined一定是consistent,而且能够看到一次修改造成的结果。</li>
<li>undefined。undefined一定是consistent,是多个修改混合在一块。举个例子,
修改a想给文件添加A1,A2,修改b想给文件添加B1,B2,如果最后的结果是A1,A2,B1,B2,
那么就是defined,如果是A1,B1,A2,B2,就是undefined。</li>
<li>inconsitent。对于不同的replica,包含的是不同的data。</li>
</ul>
<p>在GFS中,不同的修改可能会出现不同的状态。对于文件的append操作(是GFS中的主要写操作),
通过放松一定的一致性,更好地支持并发,在下面的具体操作时再讲述具体的过程。</p>
<h3 id="lease">Lease机制</h3>
<p><code>master</code>通过lease机制把控制权交给<code>chunkserver</code>,当写一个chunk时,
<code>master</code>指定一个包含这个chunk的replica的<code>chunkserver</code>作为<code>primary replica</code>,
由它来控制对这个chunk的写操作。一个lease的过期时间是60秒,如果写操作没有完成,
<code>primary replica</code>可以延长这个lease。<code>primary replica</code>通过一个序列号控制对这个chunk的写的顺序,
这样能够保证所有的replica都是按同样的顺序执行同样的操作,也就保证了一致性。</p>
<h3 id="section-4">版本号</h3>
<p>对于每一个chunk的修改,chunk都会赋予一个新的版本号。
这样,如果有的replica没有被正常的修改(比如修改的时候当前的<code>chunkserver</code>挂了),
那么这个replica就被<code>stale replica</code>,当<code>client</code>请求一个chuck时,<code>stale replica</code>会被<code>master</code>忽略,
在<code>master</code>的定时管理过程中,会把<code>stale replica</code>删除。</p>
<h3 id="section-5">负载均衡</h3>
<p>为了尽量保证所有<code>chunkserver</code>都承受差不多的负载,
<code>master</code>通过以下机制来完成:</p>
<ul>
<li>首先,在新建一个chunk或者是复制一个chunk的replica时,
尽量保证负载均衡。</li>
<li>当一个chunk的replica数量低于某个值时,尝试给这个chuck复制replica</li>
<li>扫描整个系统的分布情况,如果不够平衡,则通过移动一些replica来达到负责均衡的目的。</li>
</ul>
<p>注意,<code>master</code>不仅考虑了<code>chunkserver</code>的负载均衡,也考虑了机架的负载均衡。</p>
<h2 id="section-6">基本操作</h2>
<h3 id="read">Read</h3>
<p>Read操作其实已经在上面的Figure 1中描述得很明白了,有如下几个过程:</p>
<ol>
<li>
<p><code>client</code>根据chunk size的大小,把<code>(filename,byte offset)</code>转化为<code>(filename,chunk index)</code>,
发送<code>(filename,chunk index)</code>给<code>master</code></p>
</li>
<li>
<p><code>master</code> 返回<code>(chunk handle,所有正常replica的位置)</code>,
<code>client</code>以<code>(filename,chunk index)</code>作为key缓存这个信息</p>
</li>
<li>
<p><code>client</code>发<code>(chunk handle,byte range)</code>给其中一个<code>chunkserver</code>,通常是最近的一个。</p>
</li>
<li>
<p><code>chunkserver</code>返回chunk data</p>
</li>
</ol>
<h3 id="overwrite">Overwrite</h3>
<p>直接假设<code>client</code>已经知道了要写的chunk,如Figure 2,具体过程如下:</p>
<p><img src="/assets/themes/../images/gfs-write.png" alt="" /></p>
<ol>
<li><code>client</code>向<code>master</code>询问拥有这个chunk的lease的<code>primary replica</code>,如果当前没有<code>primary replica</code>,
<code>master</code>把lease给其中的replica</li>
<li><code>master</code>把<code>primary replica</code>的位置和其他的拥有这个chunk的replica的<code>chunkserver</code>(<code>secondary replica</code>)的位置返回,
<code>client</code>缓存这个信息。</li>
<li><code>client</code>把数据以流水线的方式发送到所有的replica,流水线是一种最高效利用的带宽的方法,
每一个replica把数据用LRU buffer保存起来,并向<code>client</code>发送接受到的信息。</li>
<li><code>client</code>向<code>primary replica</code>发送write请求,<code>primary replica</code>根据请求的顺序赋予一个序列号</li>
<li><code>primary replica</code>根据序列号修改replica和请求其他的<code>secondary replica</code>修改replica,
这个统一的序列号保证了所有的replica都是按照统一的顺序来执行修改操作。</li>
<li>当所有的<code>secondary replica</code>修改完成之后,返回修改完成的信号给<code>primary replica</code></li>
<li><code>primary replica</code>向<code>client</code>返回修改完成的信号,如果有任何的<code>secondary replica</code>修改失败,
信息也会被发给<code>client</code>,<code>client</code>然后重新尝试修改,重新执行步骤3-7。</li>
</ol>
<p>如果一个修改很大或者到了chuck的边界,那么client会把它分成两个写操作,
这样就有可能发生在两个写操作之间有其他的写操作,所以这时会出现undefined的情况。</p>
<h3 id="record-append">Record Append</h3>
<p>Record Append的过程相对于Overwrite的不同在于它的错误处理不同,
当写操作没有成功时,<code>client</code>会尝试再次操作,由于它不知道offset,
所以只能再次append,这就会导致在一些replica有重复的记录,
而且不同的replica拥有不同的数据。</p>
<p>为了应对这种情况的发生,应用程序必须通过一定的校验手段来确保数据的正确性,
如果对于生产者-消费者队列,消费者可以通过唯一的id过滤掉重复的记录。</p>
<h3 id="snapshot">Snapshot</h3>
<p>Snapshot是对文件或者一个目录的“快照”操作,快速地复制一个文件或者目录。
GFS使用<em>Copy-on-Write</em>实现snapshot,首先<code>master</code>revoke所有相关chunk的lease,
这样所有的修改文件的操作都需要和<code>master</code>联系,
然后复制相关的metadata,复制的文件跟原来的文件指向同样的chunck,
但是chuck的reference count大于1。</p>
<p>当有<code>client</code>需要写某个相关的chunck C时,<code>master</code>会发现它的reference count大于1,
<code>master</code>推迟回复给<code>client</code>,先新建一个<code>chunk handle</code>C’,
然后让所有拥有C的replica的<code>chunkserver</code>在本地新建一个同样的C‘的replica,
然后赋予C’的一个replica一个lease,把C’返回给<code>client</code>用于修改。</p>
<h3 id="delete">Delete</h3>
<p>当<code>client</code>请求删除文件时,GFS并不立即回收这个文件的空间。
也就是说,文件相关的metadata还在,
文件相关的chunk也没有从<code>chunkserver</code>上删除。
GFS只是简单的把文件删除的operation log记下,
然后把文件重新命名为一个hidden name, 里面包含了它的删除时间。
在<code>master</code>的日常维护工作时,
它会把删除时间删除时间超过3天的文件从metadata中删除,
同时删除相应chunk的metadata,
这样这些chunk就变成了orphan chunk,
它们会在<code>chunkserver</code>和<code>master</code>进行<code>Heartbeat</code>交互时从<code>chunkserver</code>删除。</p>
<p>这样推迟删除(原文叫垃圾回收)的好处有:</p>
<ul>
<li>对于分布式系统而言,要确保一个动作正确执行是很难的,
所以如果当场要删除一个chunk的所有replica需要复杂的验错,重试。
如果采用这种推迟删除的方法,只要metadata被正确的处理,最后的replica就一定会被删除,
非常简单</li>
<li>把这些删除操作放在<code>master</code>的日常处理中,可以使用批处理这些操作,
平摊下来的开销就小了</li>
<li>可以防止意外删除的可能,类似于回收站</li>
</ul>
<p>这样推迟删除的不好在于浪费空间,如果空间吃紧的话,<code>client</code>可以强制删除,
或者指定某些目录下面的文件直接删除。</p>
<h2 id="section-7">后记</h2>
<p>GFS,MapReduce,BigTable并称为Google的“三架马车”,
既然看了GFS,怎么能不看另外两篇?
欲知Mapreduce,BigTable到底是怎么一回事,
请静候我接下来的博文。</p>
</content>
</entry>
<entry>
<title>使用markdown和reStructuredText生成文档</title>
<link href="http://username.github.com/tools/2013/04/20/markup-lang"/>
<updated>2013-04-20T00:00:00+08:00</updated>
<id>http://username.github.com/tools/2013/04/20/markup-lang</id>
<content type="html"><script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<h3 id="section">基本介绍</h3>
<p><a href="http://daringfireball.net/projects/markdown/">Markdown</a>和
<a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>(下面简称rst)
是现在比较流行的<a href="http://en.wikipedia.org/wiki/Lightweight_markup_language">轻量级标注语言</a>,
这些语言拥有比较强大的表现力,可以通过简单的书写代码就可以写出包含代码,图片,数学公式等各种格式的文档。
这样,我们不用把心思花在各种格式的调节,而只需要专注于文档的内容就行了。
我们通过标记语言写成的文本文件能够被转化为html, tex, pdf,epub, word等各种格式,
我们只需要利用标记语言提供的语法把文本文件写好,相应的转换器会为你转换为特定的文档格式。
我的博客其实都是使用markdown一个特定版本<a href="http://kramdown.rubyforge.org/">kramdown</a>写的,
然后转换为html来发布。</p>
<p>学习这些标记语言其实很简单,远远比学习latex简单,把它提供的一些语法都写一遍,比如说怎么写标题,
怎么写列表,怎么插入代码,怎么插入数学符号等等。知道怎么写了之后,
就多练习,写得多了,熟悉之后就会发现,确实能够省去你控制格式的很多烦恼。</p>
<h3 id="pandoc">使用pandoc</h3>
<p>Markdown有很多超集,比如上面提到的kramdown,这些超集在markdown的基础之上有提供了一些功能,
比如说原生的markdown是不支持数学公式的,但kramdown支持,
它可以把你写在两个在源文件中的这样一段markdown代码:</p>
<pre><code>$$
O(g(n)) = \{f(n): \exists c,n, \forall n \geq n_0, 0 \leq f(n) \leq cg(n)\}
$$
</code></pre>
<p>转化为这样一段html:</p>
<div class="highlight"><pre><code class="html"><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">&quot;math/tex; mode=display&quot;</span><span class="nt">&gt;</span>
<span class="nx">O</span><span class="p">(</span><span class="nx">g</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="o">=</span> <span class="err">\</span><span class="p">{</span><span class="nx">f</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="o">:</span> <span class="err">\</span><span class="nx">exists</span> <span class="nx">c</span><span class="p">,</span><span class="nx">n</span><span class="err">,</span> <span class="err">\</span><span class="nx">forall</span> <span class="nx">n</span> <span class="err">\</span><span class="nx">geq</span> <span class="nx">n_0</span><span class="p">,</span> <span class="mi">0</span> <span class="err">\</span><span class="nx">leq</span> <span class="nx">f</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="err">\</span><span class="nx">leq</span> <span class="nx">cg</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="err">\</span><span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div>
<p>包含这个的html<strong>如果里面包含<a href="http://www.mathjax.org/">MathJax</a>这个js库</strong>的话,
MathJax就会把上述的一段html转换为如下的数学公式呈现给你。</p>
<script type="math/tex; mode=display">
O(g(n)) = \{f(n): \exists c,n, \forall n \geq n_0, 0 \leq f(n) \leq cg(n)\}
</script>
<!--more-->
<p>如果你使用了kramdown去处理一个包含数学公式的markdown,却发现没有转化为数学公式,
那么极有可能是没有把MathJax的库包含到这个markdown中,需要通过下面这一行代码引用:</p>
<div class="highlight"><pre><code class="html"><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span> <span class="na">src=</span><span class="s">&quot;http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div>
<p>下面我介绍一个转换模板语言的神器——<a href="http://johnmacfarlane.net/pandoc/">Pandoc</a>,
它能够在各种格式的文本之间进行转换,而且支持的格式非常多。
我觉得使用pandoc比较适用的场景是你要写一个几页的文档,这个文档里包含了代码,数学公式等。
在这种情况下,你先写成一个markdown的文本文件,然后利用pandoc转换成需要的格式(pdf, html等)。
而如果写比较大型的文档,或者是写书,我觉得待会我要介绍的Sphinx可能更加合适。
当然,也有很多人使用markdown来写书。</p>
<p>使用这些标记语言,包括我下面提到的rst, 转换为pdf,
最大的不好就是如果里面包含了中文,就需要一些比较麻烦的处理。
因为这些标记语言在转换为pdf时,都是先转换为latex,而latex对中文的支持又不是那么的好。
对于pandoc,如果需要转换为中文的话,需要另外提供一个模板,
这个模板引入了一些中文需要的包,还声明了一些中文的字体。
需要注意两点:</p>
<ol>
<li>模板里面的声明的字体,一定要是系统已经安装好的字体。
可以通过<code>fc-list :lang=zh</code>查看已经安装的中文字体。</li>
<li>使用<code>xelatex</code>作为latex引擎。</li>
</ol>
<p>我<a href="https://gist.github.com/chouqin/5412396">这里</a>有一个我自己写的一个用于转化简单pdf的模板。
记得修改成你已经安装好的字体。这样就能通过下面的命令生成pdf:</p>
<pre><code>pandoc -o solution.pdf solution.md --latex-engine=xelatex\
--template=mytemplate.tex
</code></pre>
<h3 id="sphinx">使用Sphinx来制作文档</h3>
<p><a href="http://sphinx-doc.org/">Sphinx</a>是一个很强大的用于制作文档的工具,
几乎所有的Python文档都是用Sphinx这个工具生成的,
在Python代码里面的注释能够很方便的转换为相应的API文档。
但是它并不仅仅局限于Python,它现在也能够支持C/C++,
而且在朝着更多的语言发展。除去和语言相关的成分,
它本身就十分适合写文档。</p>
<p>Sphinx使用rst作为它的标记语言,而且扩充了一些rst的模块,
用于更方便的书写文档,比如说<code>toctree</code>。
而且它还有很多的html主题可以使用,能够让你生成的html比较美观。</p>
<p>很多人拿Sphinx用来写书,同样的,如果要生成pdf,还是会面临中文问题,
好在还是有方法可以解决一些比较简单的问题,
在<a href="http://hyry.dip.jp:8000/pydoc/pydoc_write_tools.html">这个文档</a>中,
作者比较详细地介绍了一些它在写这本书时遇到的一些问题,
虽然还有些许问题没有解决,但已经能解决大部分的问题了,
从作者生成书籍的质量可以看出。</p>
<p>我其实就是打算用Sphinx来记录一些学习的笔记,
暂时还没有出书的打算,因此针对于我的需求,
我使用这两个步骤达到生成pdf的目的:</p>
<ol>
<li>在conf.py中,设置<code>latex_elements.preamble</code>:</li>
</ol>
<div class="highlight"><pre><code class="python"><span class="s">&#39;preamble&#39;</span> <span class="p">:</span> <span class="s">r&quot;&quot;&quot;</span>
<span class="s"> \usepackage{float}</span>
<span class="s"> \usepackage{xeCJK}</span>
<span class="s"> \usepackage{indentfirst}</span>
<span class="s"> \setlength{\parindent}{2em}</span>
<span class="s"> \textwidth 6.5in</span>
<span class="s"> \oddsidemargin -0.2in</span>
<span class="s"> \evensidemargin -0.2in</span>
<span class="s"> \usepackage{ccaption}</span>
<span class="s"> \usepackage{fontspec,xunicode,xltxtra}</span>
<span class="s"> \setmonofont[Mapping={}]{Source Code Pro}</span>
<span class="s"> \setCJKmainfont[ItalicFont={Adobe Kaiti Std R}, BoldFont={Adobe Heiti Std}]{Adobe Song Std}</span>
<span class="s"> \setCJKmonofont[BoldFont={WenQuanYi Zen Hei Mono}]{Adobe Fangsong Std}</span>
<span class="s"> \setCJKsansfont[ItalicFont={Adobe Kaiti Std R},BoldFont={Adobe Heiti Std}]{Adobe Song Std}</span>
<span class="s"> \XeTeXlinebreaklocale &quot;zh&quot;</span>
<span class="s"> \XeTeXlinebreakskip = 0pt plus 1pt</span>
<span class="s"> \renewcommand{\baselinestretch}{1.3}</span>
<span class="s"> \captiontitlefont{\small\sffamily}</span>
<span class="s"> \captiondelim{ - }</span>
<span class="s"> \renewcommand\today{\number\year年\number\month月\number\day日}</span>
<span class="s"> \makeatletter</span>
<span class="s"> \renewcommand*\l@subsection{\@dottedtocline{2}{2.0em}{4.0em}}</span>
<span class="s"> \renewcommand*\l@subsubsection{\@dottedtocline{3}{3em}{5em}}</span>
<span class="s"> \makeatother</span>
<span class="s"> \titleformat{\chapter}[display]</span>
<span class="s"> {\bfseries\Huge}</span>
<span class="s"> {\filleft \Huge 第 \hspace{2 mm} \thechapter \hspace{4 mm} 章}</span>
<span class="s"> {4ex}</span>
<span class="s"> {\titlerule</span>
<span class="s"> \vspace{2ex}%</span>
<span class="s"> \filright}</span>
<span class="s"> [\vspace{2ex}%</span>
<span class="s"> \titlerule]</span>
<span class="s"> %\definecolor{VerbatimBorderColor}{rgb}{0.2,0.2,0.2}</span>
<span class="s"> \definecolor{VerbatimColor}{rgb}{0.95,0.95,0.95}</span>
<span class="s">&quot;&quot;&quot;</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s">&quot;utf-8&quot;</span><span class="p">)</span>
</code></pre></div>
<ol>
<li>在index.rst的开始加上:</li>
</ol>
<div class="highlight"><pre><code class="rst"><span class="p">..</span> <span class="ow">raw</span><span class="p">::</span> latex
\renewcommand\partname{部分}
\renewcommand{\chaptermark}[1]{\markboth{第 \thechapter\ 章 \hspace{4mm} #1}{}}
\fancyhead[LE,RO]{学习笔记}
\renewcommand{\figurename}{\textsc{图}}
\renewcommand\contentsname{目 录}
</code></pre></div>
<p>这样就能生成看上去还比较不错的pdf了。
注意不能直接通过<code>make latexpdf</code>去生成pdf,
而是需要先通过<code>make latex</code> 生成tex文件,
再使用<code>xelatex</code>去编译tex文件生成pdf.</p>
<h3 id="section-1">总结</h3>
<p>我在这边博客中主要总结了一些编译中文pdf的经验,
至于这些标记语言的语法,应该不难,
多写写,就会熟练的,我就不在这赘述了。</p>
</content>
</entry>
<entry>
<title>毕业论文点滴</title>
<link href="http://username.github.com/feelings/2013/04/04/bachelor-thesis"/>
<updated>2013-04-04T00:00:00+08:00</updated>
<id>http://username.github.com/feelings/2013/04/04/bachelor-thesis</id>
<content type="html">
<p>已经好久没写博客了,因为寒假一个多月都没看书,
而一回到学校,又要忙着毕业论文的事,所以也无暇顾及这个。
大部分牛人的博客,都是记载着自己正在研究的方面,
在博客中也是向读者传达着一些知识,我们总是可以从其中学到某种东西。
而我,要达到这样的程度要有很长的一段路需要走,
由于技术水平较低,现在的博客记录的仍然还是自己在<strong>学习</strong>过程中的一些心得和体会,
很大程度上是描述自己的坎坷经历,希望能给某些人一些借鉴,少走一些弯路。</p>
<p>好了,言归正传,这一个月,都在忙着做毕业设计,
包括看论文,写代码,找数据,做实验。一个月的时间,
就完成了这么一件事,效率确实不算高,
主要原因在于对于搞科研还是新手,
很多情况下都是在尝试了各种可能之后才找到方法,
有时一个人的蛮干还不如直接通过其他方式获得出路。
哎,科研就是要耐得住寂寞,不停地调参数,不停地跑数据,
把各种可能性都尝试一遍,之后才有好的结果。</p>
<p>先简单介绍一下我这个论文的要求,论文的题目叫
<code>信息缺失情况下的社区挖掘</code>,其实就是一个聚类算法,
根据节点的某些属性和节点之间的边的关系,把图中的比较接近的节点聚到一起,
形成一个社区。而信息缺失,是指节点中的某些边的关系并不知道,
在这样的情况下进行聚类。采用的办法是首先利用节点之间的属性和边的关系,
通过机器学习方法获取所有节点之间的距离,然后再根据这个距离对节点进行聚类。
下面我来说一下整个的过程。</p>
<p>首先是准备数据。一开始准备爬微博的数据,
所以就去网上搜各种微博的爬虫,
然后终于找到了这个<a href="https://bitbucket.org/chineking/weibocrawler">一个简单的分布式新浪微博爬虫</a>,
在这个基础上进行了一点小修改,弄了一个简单的单节点爬虫。
这个爬虫可以抓取用户的微博,用户的个人资料以及用户之间的关注关系。
然后,又使用<a href="http://ictclas.nlpir.org/">NLPIR</a>对微博进行分词,
提取出关键字作为用户的属性,同时以用户之间的关注关系作为边,这样就开始实验。
可是,不知道是实验室的网络不稳定还是新浪微博的限制,这个爬虫很难稳定地抓取微博的数据,
最后实在没办法,学长建议我去网上直接找别人爬取到的数据集。
于是,我先后使用了Google+, Facebook,
Twitter(这三个数据集都是在<a href="http://snap.stanford.edu/data/">Stanford Large Network Dataset Collection</a>上找到的),
和Flixter数据集做实验。其中使用Flixter数据集做出来的结果也还可以,可是师兄说一定要有<strong>Ground Truth</strong>用于验证,
所以我又只好去找其他的数据集,
最后终于<a href="http://dmml.asu.edu/users/xufei/datasets.html">在这</a>找到了可以用于实验的数据集。</p>
<p>然后是实现代码去做实验。这个代码其实两部分,第一部分要利用机器学习去学习一个距离,
第二部分是基于距离进行聚类。为了学习距离,需要求解一个最优化函数,
而这里面涉及的数学的公式好复杂,实现起来也非常困难,
找到了一份和这个论文比较相似的论文的源代码,却发现里面错误好多,有些地方也不知道从哪里开始改。
大概修改了一下就把这个距离拿到第二步去聚类,发现结果非常差,
然后也不知道是第一步的问题还是第二步的问题,但是我就只会改第二步的代码:P。
终于在第二步实在找不出什么错误然后第一步的结果又不好之后,我去问老师,
老师让我使用谱聚类算法来验证距离是不是正确的,
因为谱聚类算法也是基于距离的聚类算法,如果谱聚类算法得不到正确的结果,那就是距离的问题了。
多好的主意啊,我当时怎么就没想到呢,因为我当时还没听说过谱聚类算法。
于是我就拿谱聚类算法去验证,果然是距离没学对,而除了代码没写对之外,
有一个重要的步骤没有做,<strong>对Feature做Normalize</strong>,
这个步骤对于使用节点的属性来说非常重要,它保证了所有属性的作用是均等的。
而后,我又在<a href="http://www.cs.cmu.edu/~liuy/distlearn.htm">这里</a>找到了一些其他的学习距离的算法,
我使用了其中的DCA算法来作为我的第一步,相当靠谱。
通过谱聚类算法验证之后,我发现我的第二步算法就几乎没有什么问题了,
跑出的结果也比较让人满意。</p>
<p>大概就是这么一个比较纠结的过程,总结出一些经验吧:</p>
<pre><code>&gt;&gt;&gt;import this
</code></pre>
<ol>
<li>
<p>写代码还是要多检查,一个bug除了导致几个小时的结果无效,还要浪费更多的时间去找到它。</p>
</li>
<li>
<p>在一个步骤保证正确之前,不要急于开展下一步,这样只会增加更多的复杂性。</p>
</li>
<li>
<p>沉下心来研究论文,尽量采用和别人一样的方法,得到的结果总是好些,虽然不知道为什么好。</p>
</li>
<li>
<p>不要畏惧实验和失败,说不定下一次就是成功的那一次。</p>
</li>
<li>
<p>真爱生命,远离科研。</p>
</li>
</ol>
</content>
</entry>
<entry>
<title>最短路径与最大流</title>
<link href="http://username.github.com/clrs/2013/01/06/clrs-24-26"/>
<updated>2013-01-06T00:00:00+08:00</updated>
<id>http://username.github.com/clrs/2013/01/06/clrs-24-26</id>
<content type="html"><!--<script type="text/javascript" src="/assets/custom/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>-->
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<h2 id="section">最短路径</h2>
<p>在最短路径问题中,
希望在一个图中找出从一个节点到另外一个节点的最短路径,
可能要找出从某一个特定节点到其他节点的最短路径,
也可能是找出所有节点对之间的最短路径,
图中边的权重可能为负值,甚至包含负的回路,
因此,在不同的情况下,有不同的算法适合求解问题,
关于求解所有节点之间的最短路径问题已经在动态规划时详细地解释过Floyd算法,
现在就谈谈两个处理单源最短路径的算法, Dijkstra算法和Bellman-Ford算法。</p>
<h3 id="dijkstra">Dijkstra算法</h3>
<p>Dijkstra算法用于求解所有边的权重为非负值时的单源最短路径问题,
它与Prim算法很相似,利用一个集合S保存已经求出最短路径的节点集合,
开始是S只包含源节点s,
每次从V-S中挑选出一个距离S中节点最近的节点u放入S,同时正确地设置u的邻居节点的路径长度,
直到S为V。</p>
<p>对于Dijkstra算法的正确性,只需要递归地证明下面的性质成立:</p>
<pre><code>当节点u被加入到S时,u到s的最短路径已经被正确的设置, 而且u到s的最短路径上的前趋节点w已经被添加进S。
</code></pre>
<ul>
<li>初始化:一开始时,这个性质显然成立</li>
<li>保持:假设当u被添加到S时,u到s的最短路径已经被正确的设置,对于u的任何一个邻接节点v,
如果s到v的最短路径是<script type="math/tex"> s \xrightarrow{p} u \xrightarrow{} v </script>,那么s到v的路径能够被正确的设置,
而且从v到s最短路径上的前趋节点u已经被添加进S, 这样当v被添加进S时,上述的性质都是能够保持的。</li>
</ul>
<h3 id="bellman-ford">Bellman-Ford算法</h3>
<p>Bellman-Ford算法用于求解权重可以为负值时的单源最短路径问题,
而且它还可以用于判断图中是否存在s可达的负的回路。
Bellman-Ford的执行过程就是运行|V|-1次更新操作,
每次更新操作遍历每一条边(u, v)更新dist[v],伪代码如下:</p>
<div class="highlight"><pre><code class="pascal"> <span class="k">procedure</span> <span class="nf">update</span><span class="p">((</span><span class="n">u</span><span class="o">,</span> <span class="n">v</span><span class="p">))</span><span class="o">:</span>
<span class="n">dist</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">dist</span><span class="p">[</span><span class="n">v</span><span class="p">]</span><span class="o">,</span> <span class="n">dist</span><span class="p">[</span><span class="n">u</span><span class="p">]</span><span class="o">+</span><span class="n">w</span><span class="p">(</span><span class="n">u</span><span class="o">,</span> <span class="n">v</span><span class="p">))</span>
<span class="k">for</span> <span class="n">k</span> <span class="o">:=</span> <span class="mi">1</span> <span class="k">to</span> <span class="err">|</span><span class="n">V</span><span class="err">|</span><span class="o">-</span><span class="mi">1</span>
<span class="k">for</span> <span class="p">(</span><span class="n">u</span><span class="o">,</span> <span class="n">v</span><span class="p">)</span> <span class="k">in</span> <span class="n">E</span><span class="o">:</span>
<span class="n">update</span><span class="p">((</span><span class="n">u</span><span class="o">,</span> <span class="n">v</span><span class="p">))</span>
</code></pre></div>
<!--more-->
<p>要证明这个算法的正确性,先说明它的两个性质:</p>
<ol>
<li>如果调用update((u, v))时,dist[u]已经被正确的设置,
那么dist[v]也将会被正确地设置。</li>
<li>如果dist[v]已经被正确的设置,
调用任意的update((u, v))都不会改变dist[v]的值,
也就是说多次地执行update是安全的。</li>
</ol>
<p>有了这个性质,对于任意节点v,
通过对v到s的最短路径的长度做一个简单的归纳,
就能够说明当算法结束时,dist[v]能够被正确的设置。</p>
<p>在我看来,Bellman-Ford算法的价值并不在于这个算法本身,
它给出求解一些问题的普遍思路。对于有着相互依赖的k个元素<script type="math/tex"> \{n_1, n_2, ..., n_k\} </script>,
如果它们之间的关系很复杂,比如如果<script type="math/tex"> n_1 </script>和<script type="math/tex"> n_2 </script>之间有关系,那么<script type="math/tex"> n_1 </script>和<script type="math/tex"> n_3 </script>之间可能就会有关系,
而且这种关系可能构成一个循环,如果每次找到了两个元素的关系就去更新其他元素的关系就会陷入一个死循环,
但如果利用Bellman-Ford算法的思想,就能明白对于任意两个元素<script type="math/tex"> n_i </script>和<script type="math/tex"> n_j </script>,它们之间的关系最多隔着k-2个中间元素,
第1次更新的时候可以把没有隔中间元素的两个元素的关系确立好,第2次更新的时候可以把只隔1个元素的两个元素确定好,
依次类推,当k-1次更新的时候,所有元素的关系都能确定好。就不需要一确定两个元素的关系就去考虑要不要更新相关的元素,
思路显得清晰。</p>
<h2 id="section-1">最大流问题</h2>
<p>最大流问题是图论中一类很重要的问题,因为它和线性规划也有着很强的关联,
所以它的应用也十分广泛。在最大流问题中,对于图G,有两个特殊的节点s,t,
它的任何一条边都有一个容量c,对每条边的一个特定赋值称为一个流f,
流必须满足两个性质:</p>
<ol>
<li>对于任意一条边e,<script type="math/tex"> 0 \leq f_e \leq c_e </script></li>
<li>对于除了s,t之外的任意节点u, 入流等于出流, 即: <script type="math/tex"> \sum\limits_{(w,u) \in E} f_{wu} = \sum\limits_{(u, z) \in E} f_{uz} </script></li>
</ol>
<p>最大流是想找出一个f,使得<script type="math/tex"> \sum\limits_{s,u \in E} f_{su} </script>最大。</p>
<p>求解最大流的算法非常直观:</p>
<ol>
<li>从零流量开始</li>
<li>每次从f的残留网络中选择一条从s到t的路径,在这条路径上增加流,
重复这个过程直到残留网络中不存在从s到t的路径为止</li>
</ol>
<p>要证明这个算法的正确性,需要了解图的(s,t)-割,
以及割的容量,一个图的(s,t)-割把图分成互不交叉的两个组L和R,
使得s在L中,t在R中。该割的容量就是横跨L和R两个集合的所有边的容量之和,
有如下性质成立:</p>
<p>对于任意流f,任意(s,t)-割(L, R), <script type="math/tex"> size(f) \leq capacity(L, R) </script></p>
<p>说明割的容量是任何流的大小的上限。</p>
<p>下面说明最大流算法的正确性。当程序终止时,残留网络<script type="math/tex"> G^f </script>中不存在由s到t的路径,
那么令L为<script type="math/tex"> G^f </script>中s可达的所有节点,R为其他的节点,那么此时size(f) = capacity(L, R)。
这是因为对于任何从L到R的边e, 有</p>
<script type="math/tex; mode=display">
f_e = c_e 且 f_{e'} = 0 其中f_{e'}表示从e的反向流量
</script>
<p>因为这其中任何一个违反都会导致e的终止节点在<script type="math/tex"> G^f </script>中从s可达。所以此时size(f) = capacity(L, R)。
所以对于任意流f’, 都有<script type="math/tex"> size(f') \leq size(f) </script>,意味这f是一个最大流。</p>
<p>关于最大流还有两个很重要的东西:</p>
<ol>
<li>如果采用BFS在残留网络中寻找从s到t的最短路径,会使得迭代的次数不会超过O(VE)</li>
<li>最大流可用于二部图的匹配,关键是要证明容量为整数的图中找出的最大流给任意边的赋值都是整数。</li>
</ol>
</content>
</entry>
<entry>
<title>再见2012,你好2013</title>
<link href="http://username.github.com/feelings/2013/01/01/new-2013"/>
<updated>2013-01-01T00:00:00+08:00</updated>
<id>http://username.github.com/feelings/2013/01/01/new-2013</id>
<content type="html">
<p>看着网络上各路牛人的年末总结,也想跟风自己来写一个,
只可惜拖的时间有点长,到现在才开始发表。</p>
<p>2012年对我来说绝对是不一样的一年,求职,面试,外推,实习,
以前对我来说很遥远的事情在这一年真真切切的发生。
有成功,也有失败,有欢笑,也有泪水,有面临抉择时的犹豫不决,
也有选择之后的淡定从容。这些经历,大多是艰辛的,
却没有将我击垮,能让自己觉得人生的充实。世界末日都照样过了,
还有什么挺不过呢?</p>
<p>在学习方面,2012年学习了很多新技术,新的语言,
对于算法,也有了更深入的理解。总的来说,还是广度有余,深度不足。
多多接触一些新的东西当然是有必要的,无论是对于开阔视野还是为了选择一个自己感兴趣的方向,
可是一个人必须要有所专长,在一个方面成为专家,这样才能体现个人的价值,
我在这方面仍然有所欠缺,希望在未来几年能够再某一个方面深入研究下去。
为什么是几年,因为我觉得有所建树,必须经过几年的积累。</p>
<p>在其他方面,相对来说就做得太少了一点,生活显得略微单调了一些。
空闲时间除了打打球,打打dota,似乎就没有干过别的什么事。更让我觉得愧疚的事,
很少看专业方面的其他书,思考的方式也越来越朝着计算机那个非0即1的方向去转了。
现在的情商好像是在下降,在2013年中一定要强迫自己多读书,多读与专业无关的书籍。</p>
<p>今年最好用的工具还是微博,在这上面可以很好地打发时间,
同时可以获取很多很有意思的信息。虽然说能学到的东西很少,
但它确实能很大程度地开阔视野,你能了解到很多你在现实生活中很难碰到的一些人和事。
喜欢微博,喜欢它带给我的快节奏信息。</p>
<p>今年最深的几点感触:</p>
<ol>
<li>在面临选择时,不要过分地去权衡利弊,在做出选择之后坚定地超前走才是最重要的。</li>
<li>不要固执地去争论一个观点的正确性,这并不是一个非黑即白的世界</li>
<li>严于律己,宽以待人</li>
<li>多看书,多思考,思考和学习一个都不能落下</li>
<li>珍惜别人给自己的每一条建议</li>
</ol>
<p>2013 To-Do-List:</p>
<ol>
<li>多看书,多感受生活,开阔自己的胸襟</li>
<li>学习一门新的编程语言,可能是go或者erlang</li>
<li>学完完有关数据挖掘的基础知识</li>
<li>为github上的一个库提交代码</li>
</ol>
<p>祝愿我的亲人和朋友身体健康,考研的同学考上好的学校,
工作的同学工作顺心。</p>
</content>
</entry>
<entry>
<title>图的基本算法</title>
<link href="http://username.github.com/clrs/2012/12/30/clrs-22-23"/>
<updated>2012-12-30T00:00:00+08:00</updated>
<id>http://username.github.com/clrs/2012/12/30/clrs-22-23</id>
<content type="html">
<h2 id="section">广度优先遍历</h2>
<p>对于广度优先遍历,在每次遍历时,都试图把同一个级别的所有节点先遍历,
再遍历下一个级别的节点。它的实现方式是通过一个队列保存需要去遍历的节点,
在每次遍历到一个节点时,都把它的邻接节点放入队列(如果这个节点没有被遍历过的话)等待着被遍历,
这样能保证更深层次的节点会遍历在它的任何邻接节点的后面,因为它的邻接节点更先进入队列。</p>
<p>广度优先遍历可以用来求解无权图中的单源最短路径问题,
对于节点s,在它上面执行一次广度优先遍历就能求出各个节点和s相距的节点数,
这也就是它们到达节点s所需的距离。对于广度优先遍历,
算法首先会发现和s距离为k的所有节点,然后再去发现和s距离为k+1的节点。
这其实和Dijkstra算法是采用同样的思想,先找出和s距离更近的节点,
在这基础上再找出更远的节点,因为这样的话当确定好了更远节点的路径时,