-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.xml
1612 lines (1200 loc) · 84.4 KB
/
index.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" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Grey Times</title>
<link>http://kangkona.github.io/</link>
<description>Recent content on Grey Times</description>
<generator>Hugo -- gohugo.io</generator>
<language>zh-cn</language>
<lastBuildDate>Tue, 01 Dec 2015 00:00:00 +0000</lastBuildDate>
<atom:link href="http://kangkona.github.io/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>日落北京城</title>
<link>http://kangkona.github.io/sundown-at-peking/</link>
<pubDate>Tue, 01 Dec 2015 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/sundown-at-peking/</guid>
<description><p>我想象中的北京,差不多一半是火海炼狱,一半是婀娜天堂,满满都是魔幻现实主义气息。</p>
<p>不过北京应该是我听说次数最多的一个城市,甚至比我自己的故乡还多。这一点倒不奇怪,好比牛肉面在兰州绝不会叫兰州牛肉面,加州也找不到一家旅馆叫california hotel。从小看到大的新闻联播每天在说北京,人们谈论这个国家时也用北京代指,关键的时刻在北京定格,重要的事情在北京发生,权力的手掌在北京挥动。任何和北京有关的东西都会打上北京烙印:北京时间,北京烤鸭,北京牌香烟,老北京方便面,北京布鞋。我有个在北京出生的表妹叫燕京,大抵也是因为北京做女孩名不好听的缘故。北京更多时候则是一种文化符号:北京人在纽约,北京故事,北京杂种,北京北京,One Night In 北京,北京的冬天,北京情人,北京小妞,我爱北京天安门,北京的金山上放光芒。</p>
<p>当然了,还有北京欢迎你。关于这个还有一个颇有意味的小品,说是1990年在北京开亚运会的时候,在街上有一个广告牌,上面写着“北京欢迎您”,等亚运会开完了,“您”下面的“心”掉了,变成了“北京欢迎你”,又过了些日子,“你”字的“亻”又没了,变成“北京欢迎尔”了。政客,商人,艺术家,知识分子,跑龙套的,异装者,农民工,职业杀手,午夜牛郎,卖周黑鸭的小夫妻。。。不管你是什么身份,不管北京怎么称呼你,总之还是来者不拒的。各种各样的人在这片神奇的土地上贩卖着关于自己的一切,大多数没有立足之地,生活还很艰难,但好在每个努力打拼的人都还有自己的故事。这些故事都是北京不可或缺的,人们常常讲哪个城市是有底蕴的,我觉得底蕴就是故事,故事就是几百倍几千倍于正在发生的真实。所以北京是真实的,让人又爱又恨的真实。</p>
<p>虽然我没有到过北京,我爸倒是去北京打过一阵工。他每年都给我带回一些好玩的东西,印象最深刻的是个电子琴。这是我第一次接触这么精巧的东西,周围也没有人会摆弄。我只会按上面一个设置好的按钮,一按就会从头到尾播放一遍《世上只有妈妈好》,在当时给我带来了很大的成就感。我这厢还没把手捂热,我哥就屁颠屁颠把琴拿去学校炫耀。正如大多数故事情节一样,绑在自行车座上面摔了个啪唧烂,然后那个电子琴就从我的记忆里漏掉了。还有一年周围人都在玩小霸王学习机,我爸问我需不需要学习机来学习,我当然说要。等到年关回来,他就说忘了这个事儿,然后我爸再没有去过北京了。</p>
<p>所以北京还欠我一个小霸王学习机。</p>
</description>
</item>
<item>
<title>Go语言中的面向对象</title>
<link>http://kangkona.github.io/oo-in-golang/</link>
<pubDate>Mon, 08 Jun 2015 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/oo-in-golang/</guid>
<description>
<p>最近在思考Go语言中面向对象实现,感觉最初的设计者真是掐准了软件工程的命脉,优雅与实用恰到好处的结合,使得这门语言于平凡处见深刻。</p>
<p>下面来剖析一下其中的一些设计点。</p>
<h1 id="类与类型">类与类型</h1>
<p>golang中没有class关键字,却引入了type,二者不是简单的替换那么简单,type表达的涵义远比class要广。 主流的面向对象语言(C++, Java)不太强调类与类型的区别,本着一切皆对象的原则,类被设计成了一个对象生成器。这些语言中的类型是以类为基础的,即通过类来定义类型,类是这类语言的根基。与之不同,golang中更强调类型,你在这门语言中根本看不到类的影子。实现上述传统语言的class只是type功能的一部分:</p>
<pre><code class="language-golang"> type Mutex struct {
state int32
sema uint32
}
</code></pre>
<p>此外,type还可以扩展已经定义的类型:</p>
<pre><code class="language-golang"> type Num int32
func (num Num) IsBigger(otherNum Num) bool {
return num &gt; otherNum
}
</code></pre>
<p>这种灵活的定义方式可以很好地提高程序的可扩展性,通过重命名原有类型,也可以做到一定程序上的解耦。</p>
<p>传统对象型语言由于设计之初追求面向对象的彻底性,使得后来加入函数式对象时不得不Hack一把:C++很鸡贼地重载 <code>()</code> 实现:</p>
<pre><code class="language-cpp">class Adder{
public:
int operator() (int a, int b) {
return a+b;
}
};
int add(int a, int b, Adder&amp; adder) {
return adder(a,b);
}
add(1, 3, new Adder);
</code></pre>
<p>Java则憋了很久才憋出 <code>FunctionalInterface</code> (只有一个抽象方法的接口)可以无缝地与历史包袱兼容:</p>
<pre><code class="language-java">public interface Displayer {
void display();
}
//Test class to implement above interface
public class FunctionInterfaceTestImpl {
public static void main(String[] args) {
//Old way using anonymous inner class
Displayer oldWay = new Displayer(){
public void display(){
System.out.println(&quot;Display from old way&quot;);
}};
OldWay.display();//outputs: Display from old way
//Using lambda expression
Displayer newWay = () -&gt; {System.out.println(&quot;Display from new Lambda Expression&quot;);}
newWay.display();//outputs : Display from new Lambda Expression
}
}
</code></pre>
<p>而在golang中,借助type的威力,定义函数式类型和定义一般类型并无区别:</p>
<pre><code>type Traveser func(ele interface{})
type Filter func(ele interface{}) bool
</code></pre>
<h1 id="方法放在哪里">方法放在哪里</h1>
<p>golang与传统对象式语言的另一个不同是方法并不在类的定义范围之内,而是通过把类作为接收器(receiver)与方法进行绑定:</p>
<pre><code class="language-golang">// Once is an object that will perform exactly one action.
type Once struct {
m Mutex
done uint32
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&amp;o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&amp;o.done, 1)
f()
}
}
</code></pre>
<p>看起来仅仅是放置位置的不同,其实是设计理念的不同。将方法放在类定义里面,意味着方法是类不可分割的一部分,类的最小单位就是数据成员和当前定义的所有操作,你要认识这个类,必须一次性认识这个类中定义的所有的东西。相反,先定义类的数据结构,然后像搭积木一样将目前需要的方法一个一个地进行绑定,你便可以根据需求对类进行扩展。传统的类定义是你必须一开始便想好这个类有哪些操作,一旦类定义好了,类就成了你定义的样子,再无其他可能。golang的这种开放式扩展定义方式,使得类更加具有生命力,你不必一开始就设计好一切(往往也很难做到),类会随着你的实现思路逐渐成长为你想要的那个样子。</p>
<h1 id="组合还是继承">组合还是继承</h1>
<p>继承是面向对象鼓吹的三大特性之一,但经过多年的实践,业界普遍认识到继承带来的弊端:</p>
<ul>
<li>破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性<br /></li>
<li>对扩展支持不好,往往以增加系统结构的复杂度为代价<br /></li>
<li>不支持动态继承。在运行时,子类无法选择不同的父类<br /></li>
<li>子类不能改变父类的接口<br /></li>
<li>对具体类的重载,重写会破会里氏替换原则<br /></li>
</ul>
<p>golang的设计者意识到了继承的这些问题,在语言设计之初便拿掉了继承。其实也不能说golang里面没有继承,只不过继承是用匿名组合实现的,没有传统的的继承关系链(父类和子类完全是不同类型), 同时还能重用父类的方法与成员。</p>
<pre><code class="language-golang">type Base struct{
}
func (b Base)Show(){
println(&quot;Bazinga!&quot;)
}
type Child struct{
Base
}
func main() {
child := Child{}
child.Show()
}
</code></pre>
<p>这种用Has-A代替Is-A的模拟实现,既解决了一些软件工程问题,同时甩掉了很多困扰程序员的心智包袱。</p>
<h1 id="非侵入式接口">非侵入式接口</h1>
<p>学CS到现在,感觉计算机科学的精髓其实就两个字:<code>abstract</code> 和 <code>tradeoff</code>。 golang中的非侵入式接口便很好地体现了这两点。传统对象式语言里面有一堆与接口相关的东西:抽象类,抽象接口,虚函数,纯虚函数等等。概念虽多,说起来不过是在不同<code>abstrct</code>层面上进行<code>tradeoff</code>而已。 golang的接口很彻底,就是一系列操作定义的集合,根本不允许进行实现,而且也不能定义变量或者常量这些东东:</p>
<pre><code class="language-golang"> type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
</code></pre>
<p>记得有位大学老师把Java中的接口比作资格证书,你要去考资格证书,并达到资格证书中的所有要求,才算具有某些资质。golang的接口则不太一样,只要你能做到资格证书中规定的那些事情,不管你去不去考这个资格证,都认为你具有了资质。其实这种想法在一些动态语言中实现过,且有诗为证:</p>
<blockquote>
<p>如果一个人看起来像鸭子,走起来像鸭子,叫起来像鸭子,那么他就是个基佬。</p>
</blockquote>
<p>这种非侵入式的设计方式,很大程度上也是为了解耦。接口和类本就是不同的东西:类是为了把数据和代码包装在一起,是为了对内实现;接口则更像是一种契约,是为了对外展示。基于这种抽象层面的接口进行编程,很容易达到设计模式中的依赖倒置,接口隔离以及迪米特法则几个原则。</p>
<p>编程语言的进化固然可以带来一些工程上的好处, 但千万不要忘了那句古训:</p>
<blockquote>
<p>There is no silver bullet.</p>
</blockquote>
</description>
</item>
<item>
<title>PL Meets AI</title>
<link>http://kangkona.github.io/pl-meets-ai/</link>
<pubDate>Thu, 04 Jun 2015 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/pl-meets-ai/</guid>
<description><p>编程语言是很善变的,一旦用其编程,它便不再是你头脑中的那个语言。</p>
<p>上面这句话是我瞎掰的,不过确实是最近的一些体会。编程语言是高度形式化的产物,一旦用于实际生产,便免不了受到现实条件的制约。比如下面这个条件语句</p>
<pre><code class="language-python"> if c1:
s1
elif c2:
s2
else c3:
s3
</code></pre>
<p>三个分支在逻辑上是平行的,本来无所谓先后,但在现在语言实现大都是从前往后依次判断每个分支。如果c1, c2, c3实际发生的概率为1%,1%,98%,很显然这种写法给每次执行都增加了不必要的判断。</p>
<p>下面这种代码片段也是随处可见的:</p>
<pre><code class="language-python">for i in range(n):
if i == 0:
coldStart
else:
doSth
</code></pre>
<p>程序员为了保持代码的优雅性,会把一些初始启动的代码放在循环体的内部,有时保持这种优雅性也挺好,但遇到性能要求较高的场景时,这种代码积少成多变化成为瓶颈。</p>
<p>举上面两个例子,是说明在实际编码过程中,会人为地产生一些顺序性和代码结构上的依赖关系。老程序猿会告诉刚入职场的新手说,你应该把最先发生的事情放在最开始,或者说先针对一般场景编程,然后再处理特许情况。这种编程策略一定程度上可行,但对程序员的依赖较高,在具体的行业里,很大程度就要看对业务的理解程度是否深刻。“人肉优化”往往周期较长,缺乏系统性,而且需要大量的试错成本。</p>
<p>那么问题来了,我们能否先写一个一般的代码,然后采集一些执行期间的状态数据,然后让程序自动进化呢?PL和AI是计算机科学的两大学科分支,如果将AI的方法引入PL领域,应该会带来一些根本性的变革。我设想的一种结合方式如下:<br />
- 给编译器增加一个监控模块,以代码块为单位,记录每个代码块的执行情况;<br />
- 再增加一个调整模块,结合代码块之间的结构关系,去做一些块间结构调整,调整过程中唯一的不变式是程序的执行语义。</p>
<p>由于这些模块本身也是具有开销的,所以可以做成可插拔的,等程序进化得足够好时,便可以关掉这些模块。整个执行流程如下:</p>
<p><img src="../images/plmeetsai-1.png" alt="plmeetsai-1" />
</p>
<p>其中,markBlock作用是标记代码块,可能会在原来程序基础上增加一些类似于脚手架的东西。Monitor和Interexchanger即上面提到的两个模块,将其作为Runtime的一部分编译进源程序。 在程序运行期,Monitor对程序状态进行监控,Interexchanger会在达到某些条件时对程序结构进行微调。当程序被优化得足够好(例如很长时间没有发生调整),便可以拆掉BlockMark脚手架,卸去Monitor和Interexchanger两大器械。</p>
<p><img src="../images/plmeetsai-2.png" alt="plmeetsai-2" />
</p>
<p>我创建了<a href="http://www.github.com/kangkona/ProPro">ProPro</a>来实践这个想法,希望通过写写画画,思路可以越来越清晰。等到有些眉目的时候,再进行一次新的总结。</p>
</description>
</item>
<item>
<title>JCommander:Java外部参数解析利器</title>
<link>http://kangkona.github.io/jcommander-using-example/</link>
<pubDate>Fri, 13 Mar 2015 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/jcommander-using-example/</guid>
<description><p>最近需要把项目交给别人进行运维,为了不让接手之人涉及太多繁琐细节,我把一些定义在final类中的不可变量抽取出来,把项目变成可外部配置的。用配置文件可以达到这个目的,但由于配置之间有相互依赖关系,比如:</p>
<pre><code>public static boolean local = false;
public static String host = (local) ? &quot;127.0.0.1&quot; : &quot;172.16.3.142&quot;;
public static int port = (local) ? 6379 : 6380;
</code></pre>
<p>原本只需要改变local, 用配置文件的话与local值有依赖关系的地方都要面临修改。</p>
<p>后来打算用命令行参数实现可外部动态配置,如果自己动手实现完善的命令行参数解析,可不是一项little job。 比较了几款开源的工具,还是选择了<a href="http://www.jcommander.org/">JCommander</a>。主要原因是被它官网的slogan打动了:</p>
<pre><code>Because life is too short to parse command line parameters.
</code></pre>
<p>项目是maven构建的,使用JCommander的方式十分简单:</p>
<pre><code>&lt;dependency&gt;
&lt;groupId&gt;com.beust&lt;/groupId&gt;
&lt;artifactId&gt;jcommander&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>使用JCommander的方式也很简单,给需要外部传参的变量加Parameter标注:</p>
<pre><code> @Parameter(names = { &quot;-topologyName&quot;}, description = &quot;Topology name.&quot;)
private static String TOP_NAME = &quot;sz-train&quot;;
</code></pre>
<p>一般类型参数后面都要跟值,JCommander会根据对应变量做类型检查和转换,不合法时会抛出异常错误。</p>
<p>boolean类型有点特殊,后面不需要跟一个值,输入<code>-local</code>之后,local值即为true:</p>
<pre><code>@Parameter(names = { &quot;-local&quot;}, description = &quot;Local model, default cluster Model.&quot;)
public static boolean LOCAL_MODE = false;
</code></pre>
<p>如果一个boolean变量的默认值为true,而想通过参数设置为false,可以指定元数:</p>
<pre><code>@Parameter(names = { &quot;-log&quot;, &quot;-verbose&quot;}, description = &quot; Wheather to write system log.&quot;, arity = 1)
public static boolean LOG = true;
</code></pre>
<p>默认情况下,参数是可选的,如果要求必须指定参数,可以设置required:</p>
<pre><code>@Parameter(names = &quot;-operator&quot;, required = true)
private String operator;
</code></pre>
<p>有时仅仅依靠JCommander的类型检查还不够,还需要自定义检查器提前发现不合法的输入:</p>
<pre><code>@Parameter(names = { &quot;-redisPort&quot;}, description = &quot;Redis port.&quot;, validateWith = PortValidator.class)
public static int REDIS_PORT=(LOCAL_MODE) ? 6379:6380;
public static class PortValidator implements IParameterValidator {
public void validate(String name, String value)
throws ParameterException {
Pattern pattern = Pattern.compile(&quot;[1-9]\\d*&quot;);
Matcher matcher = pattern.matcher(value);
if (matcher.matches()) {
int n = Integer.parseInt(value);
if (n &lt; 65536) {
return;
}
}
throw new ParameterException(&quot;Parameter &quot; + name
+ &quot; should be a number(0~65535) (found &quot; + value +&quot;)&quot;);
}
}
</code></pre>
<p>如果是一个写日志的目录,可以提前发现该目录是否可写,这是配置文件无法做到的:</p>
<pre><code>@Parameter(names = { &quot;-logDir&quot;}, description = &quot;Dir to write log file.&quot;,
validateWith = DirValidator.class)
public static String LOG_DIR = &quot;/logs/your_project/&quot;;
public static class DirValidator implements IParameterValidator {
public void validate(String name, String value)
throws ParameterException {
File file = new File(value);
if (!file.isDirectory() || !file.canWrite()) {
throw new ParameterException(&quot;Parameter &quot; + name
+ &quot; should be a writable folder(found &quot; + value +&quot;)&quot;);
}
}
}
</code></pre>
<p>对于IP地址,不仅可以通过正则表达式进行匹配,还可以进行简单的网络连通性探测等。相比基于文本的配置文件,JCommander显示出了强大的优势。</p>
<p>但更多的项目可能还是更适合用配置文件的方式进行外部配置,如果配置文件如果可以吸收JCommander的特点,那就perfect了。我理想中的配置文件应该有如下特性:<br />
- 配置文件本身是programmable<br />
- 可以进行上下文联系<br />
- 自动类型检查和转换<br />
- 可以自定义语义检查器<br />
- 所使用的弱语言可以方便嵌入</p>
</description>
</item>
<item>
<title>Go实践之并发初体验</title>
<link>http://kangkona.github.io/concurrent-in-go/</link>
<pubDate>Fri, 28 Nov 2014 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/concurrent-in-go/</guid>
<description>
<p>golang的一大卖点是并发模型基于CPS(continuation passing style),使用起来比较简单。刚开始我也觉得比较简单,但使用之后发现,你必须很清楚golang的并发机制才能使用自如,在需要同步尤甚,一不小心就会陷入罪恶的渊薮。</p>
<h2 id="sharing-vs-communicating">Sharing VS Communicating</h2>
<p>如何我们想从1顺序打印到10000,可能会<a href="http://play.golang.org/p/ZG5EihF4PU">这样</a>写:</p>
<pre><code class="language-golang">package main
import (
&quot;fmt&quot;
)
var messages chan int
var done chan bool
func main() {
messages = make(chan int)
done = make(chan bool)
times := 10000
for i := 0; i &lt; times; i++ {
go func() {
messages &lt;- i
done &lt;- true
}()
}
go func() {
for i := range messages {
fmt.Println(i)
}
}()
for i := 0; i &lt; times; i++ {
&lt;-done
}
}
</code></pre>
<p>但事实上,打印的结果的是</p>
<pre><code>10000
10000
....
</code></pre>
<p>由于10000个for循环执行地相当快, 很可能在for已经循环结束, i的值增长到10000时, gorountines的调度才完成,i此时作为逃逸变量为goroutines共享,所以gorountines看到的i都是10000,打印的也都是10000。</p>
<p>以上共享变量的方式我们称之为Share,要解决这个问题,只能把main的i通过消息传递的方式Communicate给每个gorountines <a href="http://play.golang.org/p/lppU7mFsRh">例子</a>:</p>
<pre><code> for i := 0; i &lt; times; i++ {
go func(v int) {
messages &lt;- v
done &lt;- true
}(i)
}
</code></pre>
<p>运行一下,结果是正确的。这时候才真正体会到那句话的奥妙:</p>
<pre><code>Don’t communicate by shared memory. Instead, share memory by communicating.
—— Rob Pike
</code></pre>
<h2 id="synchronization">Synchronization</h2>
<p>golang没有join,无法直接在程序中设置等待点。实现同步通常有两种做法:</p>
<h3 id="使用channel">使用channel</h3>
<p>由于channel分为阻塞和非阻塞的,使用阻塞的channel时就能达到同步的目的。上面两个例子都使用了channel进行同步。</p>
<h3 id="使用waitgroup">使用WaitGroup</h3>
<p><a href="http://play.golang.org/p/k440dqN3Ai">示例如下</a></p>
<pre><code>func main() {
var wg sync.WaitGroup
wg.Add(1000)
for i := 0; i &lt; 1000; i++ {
go func(v int) {
defer wg.Done()
fmt.Println(v)
}(i)
}
wg.Wait()
fmt.Println(&quot;exit&quot;)
}
</code></pre>
<p>Add操作设置要等待的gorountine个数,每执行一次Done,waitgroup就会减1, 执行Wait的地方就相当于join。</p>
<p>两种操作的本质都是设置一个计数器,每个gorountine执行完都会通知一下主程序,直到所以gorountine完全dead,main才会往下走。</p>
<p>当不清楚gorountines的数目时,在<a href="http://segmentfault.com/q/1010000000487990">网上</a>看到的一种做法是可以设置一个远大于gorountines的数目的<a href="http://play.golang.org/p/jb1wJaQJZ0">阈值</a>:</p>
<pre><code>L: for {
select {
case &lt;- done:
i++
if i &gt; 10000 {
break L
}
}
}
</code></pre>
<h2 id="concurrent-data-structure">Concurrent Data Structure</h2>
<p>在并发环境下,数据结构往往也需要设计成并发的,比如一个并发的Map可以这样设计:</p>
<pre><code>type CorrMap struct {
line2BusId map[string][]string
sync.RWMutex
}
func NewCorrMap() *CorrMap {
return &amp;CorrMap{line2BusId : map[string][]string{}}
}
func (c *CorrMap) Add(line string, busId string) {
c.Lock()
defer c.Unlock()
busIds, exists := c.line2BusId[line]
if exists {
c.line2BusId[line] = append(busIds, busId)
} else {
var tmp []string
c.line2BusId[line] = append(tmp, busId)
}
}
func (c CorrMap) Size() int {
count := 0
for _, v := range c.line2BusId {
count+= len(v)
}
return count
}
</code></pre>
<p>不过利用Lock可能没有充分发挥golang的优势,更好的方法可以参考<a href="http://se77en.cc/2014/04/08/share-by-communicating-the-concurrency-slogan-in-golang/">这篇博客</a>。</p>
</description>
</item>
<item>
<title>Go实践之JSON解析</title>
<link>http://kangkona.github.io/json-and-go/</link>
<pubDate>Fri, 28 Nov 2014 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/json-and-go/</guid>
<description>
<p>当我们开发了一个新版本的软件时,通常需要和稳定版进行对比,进行QoS评估。对实时性网络服务而言,比较客观的做法是同时请求相同的服务,然后对比返回结果。</p>
<p>首先,我们对一些常用的函数进行包裹,简化操作:</p>
<h2 id="发送http请求">发送Http请求</h2>
<pre><code class="language-golang">func sendHttpRequest(url string) string {
response, _ := http.Get(url)
defer response.Body.Close()
var bodystr string
if response.StatusCode == 200 {
body, _ := ioutil.ReadAll(response.Body)
bodystr = string(body)
} else {
return &quot;&quot;
}
return bodystr
}
</code></pre>
<h2 id="获取json数据">获取Json数据</h2>
<p>如果你知道返回的json数据的格式,可以事先定义好对应的数据结构。例如json返回内容为</p>
<pre><code class="language-json">{
&quot;name&quot;:&quot;Monica&quot;,
&quot;age&quot;: 10,
&quot;hobby&quot; : [&quot;music&quot;, &quot;dance&quot;]
}
</code></pre>
<p>对应的数据结构和操作:</p>
<pre><code>type Person struct {
name string `json:&quot;name&quot;`
age string `json:&quot;age&quot;`
hobby []string `json:&quot;hobby&quot;`
}
bodystr := sendHttpRequest(url)
var person Person
err := json.Unmarshal([]byte(bodystr), &amp;person)
</code></pre>
<p>这样json内容就会变成一个实例化的Person对象,所有的json成员都会对应类型化。</p>
<p>如果我们不知道json数据格式怎么办? json的最外层肯定是一个map, 所以可以把json数据反序列化为一个通用的 <code>interface{}</code> :</p>
<pre><code>func getJson(url string) map[string]interface{} {
bodystr := sendHttpRequest(url)
var object interface{}
err := json.Unmarshal([]byte(bodystr), &amp;object)
if err != nil {
return nil
} else {
return object.(map[string]interface{})
}
}
</code></pre>
<p>好处是通用,可以解析任意的json,但由于不知内层数据的类型,因此只作了最外层map的解析,返回数据的类型为<code>map[string]interface{}</code>。之后可能会根据需要进行类型断言,转换等。</p>
<p>感觉json目前已经成了网络数据传输格式的事实标准,有很多人总结过<a href="http://www.cnblogs.com/SanMaoSpace/p/3139186.html">JSON与XML的区别比较</a>,我觉得json最大的优势是:程序员友好。json中的map, array, set, string, num都能完美地对应到某一编程语言中,即表示的不仅仅是文本,还是带类型的数据。而xml本质还是文本,要借助于xslt,scheme,属性等一堆东西实现json的类型化,表示成本高,解析成本也高。可能的好处是比较体系化,有成套的解决方案,用户可视化方便。</p>
</description>
</item>
<item>
<title>Storm集群的安装与配置</title>
<link>http://kangkona.github.io/storm-cluster-install-config/</link>
<pubDate>Sun, 02 Nov 2014 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/storm-cluster-install-config/</guid>
<description>
<h2 id="1-infrastructure">1. Infrastructure</h2>
<p>3台流处理Server<br />
- CPU : 3 Core<br />
- Memory : 3G<br />
- Disk : 300G<br />
- OS : Ubuntu Server 12.04 64bit</p>
<h2 id="2-depended-software">2. Depended Software</h2>
<ul>
<li>built-essential<br /></li>
<li>Python 2.7<br /></li>
<li>Java 1.7.0_71(最低要求1.6)<br /></li>
<li>Zookeeeper 3.4.6<br /></li>
<li>ZeroMQ 4.0.5<br /></li>
<li>jzmq github-master<br /></li>
<li>Storm 0.9.1-incubating<br /></li>
</ul>
<h2 id="3-install-config">3. Install &amp;&amp; Config</h2>
<p>几乎所有软件对集群中的机器来说,安装过程都是完全一致的,所有没有必要在每台机器重复安装过程,可以使用Puppet或者Docker这些工具来提高效率。但由于操作的集群网络环境受限,最终利用Xshell可以一次向多个会话发送命令的功能,做到了完全同步的安装和配置。</p>
<h3 id="3-1-修改-etc-hosts">3.1 修改/etc/hosts</h3>
<p>使用名称来代表机器会带来很多方便,在/etc/hosts中追加如下内容:</p>
<pre><code>172.21.1.168 master
172.21.1.169 node1
172.21.1.170 node2
</code></pre>
<h3 id="3-2-build-essential">3.2 build-essential</h3>
<p><a href="http://packages.ubuntu.com/lucid/amd64/build-essential/filelist">build-essential</a>作用是提供编译程序必须软件包的列表信息, 编译程序有了这个软件包, 才知道 头文件和库函数的位置,还会下载依赖的软件包,组成一个基本的开发环境</p>
<pre><code> $ sudo apt-get install build-essential
</code></pre>
<h3 id="3-3-安装jdk">3.3 安装JDK</h3>
<pre><code>$ tar zxvf jdk-7u71-linux-x86.tar.gz
# mv jdk1.7.0_71 /usr/lib/jvm
# sudo vim /etc/profile
</code></pre>
<p>追加内容:</p>
<pre><code>export JAVA_HOME=/usr/lib/jvm/jdk1.7.0_71
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin:$PATH
</code></pre>
<pre><code>$source /etc/profile
</code></pre>
<h3 id="3-4-安装zookeeper-3-4-6">3.4 安装Zookeeper 3.4.6</h3>
<p>详细内容可以参考 <a href="http://www.iteblog.com/archives/904">Zookeeper 3.4.5分布式安装手册</a> 这篇文章, 这里简要描述:</p>
<pre><code class="language-shell">$ tar -zxvf zookeeper-3.4.6.tar.gz
$ cd zookeeper-3.4.6/conf/
$ cp zoo_sample.cfg zoo.cfg
$ vim zoo.cfg
</code></pre>
<ul>
<li>将里面的默认配置修改为如下(具体配置可以根据你机器来定):<br /></li>
</ul>
<pre><code>tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/logs/zookeeper
# the port at which the clients will connect
clientPort=2181
server.1 = master:2888:3888
server.2 = node1:2888:3888
server.3 = node2:2888:3888
</code></pre>
<ul>
<li>在刚刚zoo.cfg文件中dataDir属性指定的目录(本文中为/logs/zookeeper)下创建一个myid,在里面添加你指定的server编号,因为这台机器是master,而zoo.cfg中master编号为1(server.1=master:2888:3888),所以myid内容只需要为1即可。<br /></li>
</ul>
<pre><code>$ mkdir -p /logs/zookeeper
$ echo 1 &gt; /logs/zookeeper/myid
</code></pre>
<ul>
<li><p>这样就在master机器上配置好Zookeeper,接下来只需要将master配置好的Zookeeper整个目录打包分发到node1、node2机器中,解压到安装位置。</p>
<p><strong>不要忘记在node1的/logs/zookeeper/myid文件中添加2。node2的/logs/zookeeper/myid文件中添加3。</strong></p></li>
<li><p>将每个机器的zookeeper的路径添加到Path<br />
在/etc/profile追加如下内容:</p></li>
</ul>
<pre><code># zookeeper
export ZOOKEEPER_HOME=/packages/zookeeper-3.4.6
export PATH=${ZOOKEEPER_HOME}/bin:$PATH
</code></pre>
<p><strong>不要忘记在三台机器 <code>source /etc/profile</code> 使之生效。</strong></p>
<ul>
<li>分别在master、node1、node2机器上启动Zookeeper相关服务:<br /></li>
</ul>
<pre><code>$ZOOKEEPER_HOME/bin/zkServer.sh start
JMX enabled by default
Using config: /home/wyp/Downloads/zookeeper-3.4.5/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
</code></pre>
<ul>
<li>测试Zookeeper是否安装成功:<br />
参见 <a href="http://www.iteblog.com/archives/904">Zookeeper 3.4.5分布式安装手册</a> 第7点 。<br /></li>
</ul>
<h3 id="3-5-安装zmq">3.5 安装ZMQ</h3>
<p>默认安装在/usr/local/lib位置,后面会比较省劲:</p>
<pre><code>$ tar -xzf zeromq-4.0.5.tar.gz
$ cd zeromq-4.0.5
$ ./configure
$ make
$ sudo make install
</code></pre>
<h3 id="3-6-安装-jzmq">3.6 安装 jzmq</h3>
<p>最开始装<a href="https://github.com/nathanmarz/jzmq">nathanmarz</a>的分支,一直无法make,最后下载了zeromq的<a href="https://github.com/zeromq/jzmq">master</a>分支。 make过程中提示安装libtool, pkg-config, autoconf几个工具:</p>
<pre><code>$ sudo apt-get install libtool pkg-config autoconf
$ git clone https://github.com/zeromq/jzmq
$ cd jzmq
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
</code></pre>
<h3 id="3-7-storm-install-config">3.7 Storm install &amp;&amp; config</h3>
<ul>
<li>安装之前的调研<br /></li>
</ul>
<p>在官网下载了storm-0.9.1-incubating 版本解压到安装位置。 该版本的一大亮点是采用了Netty做消息传输层,在以前的版本里,Storm只能依赖ZeroMQ做消息的传输,但其实并不适合, <a href="http://www.cnblogs.com/alephsoul-alephsoul/p/3467651.html">理由</a>:</p>
<ul>
<li>ZeroMQ是一个本地化的消息库,它过度依赖操作系统环境;<br /></li>
<li>安装起来比较麻烦;(有了Netty可以不要ZeroMQ和jzmq)<br /></li>
<li>ZeroMQ的稳定性在不同版本之间差异巨大,并且目前只有2.1.7版本的ZeroMQ能与Storm协调的工作(写文档才注意到这句话。。。后面需要测试一下)。<br /></li>
</ul>
<p>引入Netty的原因是:<br />
- 平台隔离,Netty是一个纯Java实现的消息队列,可以帮助Storm实现更好的跨平台特性,同时基于JVM的实现可以让我们对消息有更好的控制;<br />
- 高性能,Netty的性能要比ZeroMQ快两倍左右,<a href="http://yahooeng.tumblr.com/post/64758709722/making-storm-fly-with-netty">让Storm飞</a> 专门比较了ZeroMQ和Netty的性能。<br />
- 安全性认证,使得我们将来要做的 worker 进程之间的认证授权机制成为可能。</p>
<p>主要参考<a href="http://www.cnblogs.com/panfeng412/archive/2012/11/30/how-to-install-and-deploy-storm-cluster.html">Storm集群安装部署步骤【详细版】</a> 和其他集群的经验对conf/storm.yaml进行如下配置:</p>
<ul>
<li>storm.zookeeper.servers: Storm集群使用的Zookeeper集群地址,其格式如下:<br /></li>
</ul>
<pre><code>storm.zookeeper.servers:
- &quot;172.21.1.168&quot;
- &quot;172.21.1.169&quot;
- &quot;172.21.1.170&quot;
</code></pre>
<p>如果Zookeeper集群使用的不是默认端口,那么还需要storm.zookeeper.port选项。</p>
<ul>
<li>storm.local.dir: Nimbus和Supervisor进程用于存储少量状态,如jars、confs等的本地磁盘目录,需要提前创建该目录并给以足够的访问权限。然后在storm.yaml中配置该目录,如:<br /></li>
</ul>
<pre><code>storm.local.dir: &quot;/logs/storm/workdir&quot;
</code></pre>
<ul>
<li><p>java.library.path: Storm使用的本地库(ZMQ和JZMQ)加载路径,默认为&raquo;/usr/local/lib:/opt/local/lib:/usr/lib&raquo;,一般来说ZMQ和JZMQ默认安装在/usr/local/lib 下,因此不需要配置即可。</p></li>
<li><p>nimbus.host: Storm集群Nimbus机器地址,各个Supervisor工作节点需要知道哪个机器是Nimbus,以便下载Topologies的jars、confs等文件,如:</p></li>
</ul>
<pre><code>nimbus.host: &quot;172.21.1.168&quot;
</code></pre>
<ul>
<li>supervisor.slots.ports: 对于每个Supervisor工作节点,需要配置该工作节点可以运行的worker数量。每个worker占用一个单独的端口用于接收消息,该配置选项即用于定义哪些端口是可被worker使用的。默认情况下,每个节点上可运行4个workers,分别在6700、6701、6702和6703端口,我配置了80个:<br /></li>
</ul>
<pre><code>supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
......
- 6780
</code></pre>
<ul>
<li>storm UI端口<br /></li>
</ul>
<pre><code>ui.port: 8088
</code></pre>
<ul>
<li>如果要在Storm里使用Netty做传输层,只需要简单的把下面的内容加入到storm.yaml中,并根据你的实际情况调整参数即可:<br /></li>
</ul>
<pre><code>storm.messaging.transport: &quot;backtype.storm.messaging.netty.Context&quot;
storm.messaging.netty.server_worker_threads: 1
storm.messaging.netty.client_worker_threads: 1
storm.messaging.netty.buffer_size: 5242880
storm.messaging.netty.max_retries: 100
storm.messaging.netty.max_wait_ms: 1000
storm.messaging.netty.min_wait_ms: 100
</code></pre>
<ul>
<li>为了方便使用,可以将Storm位置加入到系统环境变量中<br /></li>
</ul>
<p>在/etc/profile追加如下内容:</p>
<pre><code># Storm
export STORM_HOME=/packages/zookeeper-3.4.6
export PATH=${STORM_HOME}/bin:$PATH
</code></pre>
<p><strong>不要忘记在三台机器 <code>source /etc/profile</code> 使之生效。</strong></p>
<ul>
<li>启动Storm各个后台进程<br /></li>
</ul>
<p>最后一步,启动Storm的所有后台进程。和Zookeeper一样,Storm也是快速失败(fail-fast)的系统,这样Storm才能在任意时刻被停止,并且当进程重启后被正确地恢复执行。这也是为什么Storm不在进程内保存状态的原因,即使Nimbus或 Supervisors被重启,运行中的Topologies不会受到影响。</p>
<p>以下是启动Storm各个后台进程的方式:<br />
- Nimbus: 在Storm主控节点上运行&raquo;bin/storm nimbus &gt;/dev/null 2&gt;&amp;1 &amp;&laquo;启动Nimbus后台程序,并放到后台执行;<br />
- Supervisor: 在Storm各个工作节点上运行&raquo;bin/storm supervisor &gt;/dev/null 2&gt;&amp;1 &amp;&laquo;启动Supervisor后台程序,并放到后台执行;<br />
- UI: 在Storm主控节点上运行&raquo;bin/storm ui &gt;/dev/null 2&gt;&amp;1 &amp;&laquo;启动UI后台程序,并放到后台执行,启动后可以通过http://{nimbus host}:8080观察集群的worker资源使用情况、Topologies的运行状态等信息。</p>
<p><strong>注意事项</strong>:<br />
- Storm后台进程被启动后,将在Storm安装部署目录下的logs/子目录下生成各个进程的日志文件。<br />
- 经测试,Storm UI必须和Storm Nimbus部署在同一台机器上,否则UI无法正常工作,因为UI进程会检查本机是否存在Nimbus链接。</p>
<p>至此,Storm集群已经部署、配置完毕,可以向集群提交拓扑运行了。</p>
<ul>
<li><p>向集群提交任务</p>
<ul>
<li>启动Storm Topology:<br /></li>
</ul></li>
</ul>
<p><code>storm jar allmycode.jar org.me.MyTopology arg1 arg2 arg3</code><br />
- 停止Storm Topology:</p>
<p><code>storm kill {toponame}</code></p>
</description>
</item>
<item>
<title>人性化排序</title>
<link>http://kangkona.github.io/friendly-sort/</link>
<pubDate>Mon, 27 Oct 2014 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/friendly-sort/</guid>
<description><p>对于大部分产品来说,搜索功能是必不可少的。有搜索的地方,就有排序。对文本信息的排序没有数值排序来的那么直观,对搜索到的信息,通常的展示策略有三种:</p>
<pre><code> 1. 按自然顺序排列;
2. 按相似度由高至低排列;
3. 按信息的活性(热度)进行排列。
</code></pre>
<p>当然,最糟糕也最常见的是下面这种方式:完全不做任何排序。</p>
<p>最近碰到的一个应用场景让我对此进行了反思。我们的实时公交查询系统为其他App开发者提供了这样的一个API:根据用户的搜索内容返回线路名称。现在不妨假设用户输入了一个 &laquo;1&raquo;,后台进行查询,找到所有与 &laquo;1&raquo; 有关的线路,最开始我们的输出是无序的:</p>
<pre><code> 81, 17, 201路, 快线1号, 11, 1路, 168 , 高快巴士1号线 ....
</code></pre>
<p>结果竟然返回了221个与1有关的线路,这样的展示结果显然是会让用户骂人的。后来决定先返回所有 &laquo;1&raquo; 开头的线路,其余自然排序:</p>
<pre><code> 11, 17, 168, 1路,201路, 81, 高快巴士1号线,快线1号 ...
</code></pre>
<p>这次结果好了很多,但是1路竟然排在168后面,仍然会让人不爽,所以按照相似度进行排序:</p>
<pre><code> 11,17,81,1路,168,201路,.... 快线1号,高快巴士1号线
</code></pre>
<p>这次达到了预想的效果,但把快线1号,高快巴士1号线这样比较难记,难输入的线路放到了最后,实际上用户如果只输入 &laquo;1&raquo; 进行查询,很可能要找的就是 “快线1号” 或者 “高快巴士1号线”,所以把这种输入成本高的线路挪到了前面:</p>
<pre><code> 11, 17, 1路,168,快线1号,高快巴士1号线,201路,81 ....
</code></pre>
<p>实现代码:</p>
<pre><code class="language-Java"> Arrays.sort(sortedLineNames, new Comparator&lt;Object&gt;(){
@Override
public int compare(Object b1, Object b2) {
String s1 = (String)b1;
String s2 = (String)b2;
if (s1.startsWith(searchString) &amp;&amp; !s2.startsWith(searchString)) {
return -1;
}
else if(!s1.startsWith(searchString) &amp;&amp; s2.startsWith(searchString)) {
return 1;
}
else if (! s1.startsWith(searchString) &amp;&amp; !s2.startsWith(searchString)) {
return s2.compareTo(s1);
}
else {
return s1.compareTo(s2);
}
}
});
</code></pre>
<p>当然,Java 8引入lambda之后,可以简写为:</p>
<pre><code> Arrays.sort(sortedLineNames,
(s1, s2) -&gt;
s1.startsWith(searchString) &amp;&amp; !s2.startsWith(searchString) ? -1 :
!s1.startsWith(searchString) &amp;&amp; s2.startsWith(searchString) ? 1 :
!s1.startsWith(searchString) &amp;&amp; !s2.startsWith(searchString) ? s2.compareTo(s1) :
s1.compareTo(s2));
</code></pre>
<p>在多数情况下,这些策略已经可以组合出不错的效果了,但这就足够了么? 用户搜索的目的是希望找到对自己有价值的信息,而排序最终是为了讨好用户:在信息呈现之前,猜测一下用户的心理,认为用户最想要什么,就把什么放在前面,让用户用最少的时间成本获取最有价值的信息。所以为了更懂用户,可能还需要对日志进行分析,统计每条线路的搜索频率,得出线路的活跃程度,每次尽可能把活性比较大的线路放置在返回结果的前面。更进一步,可以看看线路活性的分布情况和规律,得出更有指导性的结论。</p>
<p>写到这里,突然感觉排序分明就是一种人工智能的东东,这也无怪乎那些做搜索的公司都铆足了劲儿搞深度学习。 在这样一个时代,不懂人心估计就得死吧。</p>
</description>
</item>
<item>
<title>借力数据结构</title>
<link>http://kangkona.github.io/data-struct-powerful/</link>
<pubDate>Thu, 18 Sep 2014 00:00:00 +0000</pubDate>
<guid>http://kangkona.github.io/data-struct-powerful/</guid>
<description>
<p>做实时公交查询服务时,最重要的能够实时跟踪每一辆车的时空信息,并结合静态数据,准确刻画出一个城市每一时刻所有公交线路的状态。由于基础数据只有线路,站点这样的信息,做时间预测,位置计算便只能依赖这些信息。比较关键的思路大致如下:</p>
<h2 id="位置计算">位置计算</h2>
<pre><code>S1 S2 S3 S4 S5 S6 S7
P
</code></pre>
<p>为了得到P的位置,需要利用一个评价公式: 假设P到前一站距离Pre, 到后一站距离Next,两站之间距离Between,有如下公式:</p>
<pre><code> Cost = Pre + Next - Between
</code></pre>
<p>我们认为使Cost取值最小的两个站即为P所在位置的前后站。</p>
<h2 id="时间预测">时间预测</h2>
<p>比如我们要估算P到S7站点的到站时间,可以取该车次所有车最近5趟S3到S7的时间 T_AVG,所以预估时间公式为:</p>
<pre><code>T_ESTIMATE = Distance(P-&gt;S4-&gt; ... -&gt;S7) / Distance(S3-&gt;S4-&gt; .... -&gt; S7) * T_AVG
</code></pre>
<p>BUT,现在通过历史数据分析,提取出了线路的轨迹信息,就是除了站点以外,还有很多有序的轨迹点可以代表线路。这些点带来的好处是: 两个站之间的轨迹是曲线(甚至环形都是很常见的)的话,如果只有站点,连出的轨迹就是一条直线段,模拟效果很差;而点多了以后,几乎就可以还原出真实的线路轨迹。</p>
<p>在考虑如何利用这些点的时候,就碰到一个设计权衡的问题:</p>
<h2 id="1-不把站点插入轨迹点集里面">1. 不把站点插入轨迹点集里面</h2>
<p>得到的轨迹点集合已经可以很好模拟真实情况了,而站点信息(主要是经纬度)由于是人工采集的,会存在一些偏差,把站点插入轨迹点集之后的模拟效果反而会变差(抽样发现会出现迂回的情况)。但是仍然需要记录每个轨迹点的位置信息(位于哪两站),设计轨迹点数据结构如下:</p>
<pre><code>type GeoPoint struct {
lat float
lng float
}
type TrackPoint struct {
GeoPoint
preStationIndex int //前一站
nextStationIndex int //后一站
index int //在点集中的次序
}
</code></pre>
<p>这时进行位置计算,就需要用轨迹点去计算: 先算出在哪两个点之间,然后根据前后点的关系,计算出在哪两个站点之间,这时计算比较复杂,可以分成如下情况:</p>
<ul>
<li><p>前后点没有跨站</p>