-
Notifications
You must be signed in to change notification settings - Fork 5
/
search.xml
1453 lines (697 loc) · 854 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
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"?>
<search>
<entry>
<title>中国近些年的人口情况分析</title>
<link href="/2022-10-30-population-crisis/"/>
<url>/2022-10-30-population-crisis/</url>
<content type="html"><![CDATA[<p>本文主要基于第七次人口普查数据作为基础,站在2022年的时间节点上对人口问题进行一些分析和未来预测</p><p>我将根据六普数据,七普数据,官方给出的人口数据,倒推到每一年的出生人口数量,计算出当前时间节点每一个年龄的人口数量,分析问题和预测都将基于某一个年龄段的人口数量来进行</p><blockquote><p>计算标准:年龄计算采用简单的 (当前年份-出生年份 = 年龄),不考虑虚岁周岁<br>定义:60岁以上算是老年人, 人均寿命78岁</p></blockquote><h2 id="数据明细"><a href="#数据明细" class="headerlink" title="数据明细"></a>数据明细</h2><p>近年人口的详细图表<br><img src="https://github.com/leriou/imgs/blob/main/20221030/people_data.png?raw=true"></p><h2 id="老龄化问题"><a href="#老龄化问题" class="headerlink" title="老龄化问题"></a>老龄化问题</h2><p>中国的老龄化问题很严重,这个是人人都知道的事情,但是到底严重到了什么程度,未来会有多大的压力,我认为很多人对此认识不足</p><p>我们来看一组数据</p><ol><li>过去10年的老年人增长的趋势和未来10年的老年人增长趋势</li></ol><p>过去的10年的新增的老年人(1952年-1961年出生)我们用六普的数据(2010年)能得到大约一共是 1.57 亿(红色线)<br>未来10年的新增老年人(1962年-1971年出生)我们用七普的数据(2020年)能得到大概是 2.3 亿(蓝色线)<br><img src="https://github.com/leriou/imgs/blob/main/20221030/trend_oldpeople_incr.png?raw=true"></p><p>从图中可以看得出来,过去的10年,由于1952-1961当时的出生人口数量并不如后面第一次婴儿潮时期,同时经历了58-61年的人口减员,导致过去10年间新增的老年人数量不到1.57亿,平均每年新增1500万</p><p>但是随后就迎来了新中国的第一波婴儿潮,10年间出生人口有2.3亿,即便算上后续的人口减员也不会低于2.1亿,新增老年人从 1.5亿到2.1亿 至少增加了30%</p><p>如果按照过去5年和未来5年来看, 过去5年新增老年人口7000万, 未来5年新增老年人1.16亿, 增长了65%<br><img src="https://github.com/leriou/imgs/blob/main/20221030/10_year_old_people.png?raw=true"></p><p>社会劳动力人口(25-60岁)的变化<br><img src="https://github.com/leriou/imgs/blob/main/20221030/25_60_total_peoples.png?raw=true"></p><p>过去5年有7000万的老人退出劳动力市场, 但是有8300万的新增25岁以上社会劳动力(今年25-29岁)<br>但是未来5年将有1.16亿的人退出劳动力市场,新补充的人口却只有7200万人(今年20-24岁)<br>也就是说未来5年对比现在至少有 4400万人的劳动力缺口,现在的很多工作招不到人未来恐怕会比现在更难招人</p><p>随着劳动力缺口,养老金亏空问题会比现在严重的多得多</p><ol start="2"><li>对老龄化加速的误判<br>历年死亡人口统计<br><img src="https://github.com/leriou/imgs/blob/main/20221030/people_death.png?raw=true"><br>从上面的死亡人口数据可以看到过去的5年死亡人口基本在960万-1100万以内,每年死亡的人口里面有90%是老人<br>也就是说过去5年现有的60岁以上的老年人口每年增长绝对值约为 (7000万新增-4500万死亡=2500万)<br>但是未来5年这个数字可能会巨幅增长,我们假设未来5年平均每年死亡人口1100万,其中90%是老人,则净增老人数量为(1.16亿-5000万=6600万),远远高于过去5年的净增数量,给养老金带来的压力极大</li></ol><h2 id="对未来的出生人口预测"><a href="#对未来的出生人口预测" class="headerlink" title="对未来的出生人口预测"></a>对未来的出生人口预测</h2><p>结婚人口近15年变化趋势情况<br><img src="https://github.com/leriou/imgs/blob/main/20221030/married_peoples.png?raw=true"></p><p>结婚人口和出生人口的关系<br><img src="https://github.com/leriou/imgs/blob/main/20221030/married_peoples_and_born_peoples.png?raw=true"></p><p>我们可以明显的看到随着结婚人数的下降,出生人口在结婚人口下降之后也采用近乎相同的趋势开始下降</p><h2 id="房价和购房人口"><a href="#房价和购房人口" class="headerlink" title="房价和购房人口"></a>房价和购房人口</h2><p>房子是一种商品,在市场化机制下商品的价格由供需关系决定,所以要研究房子价格我们就要研究一下供需关系</p><p>房子的提供方是土地+房产建设公司+服务公司,</p><p>房子的主要的需求方是 1. 进入城市的大学生因为结婚需要购买婚房的人口 2. 养老买房人口 3. 投资客</p><p>我们先分析进入城市的大学城和因为结婚需要买婚房的人口,用七普的数据来进行研究</p><p>我们这里我们假设学生到27左右开始有婚姻需求和购房能力, 那购房的主力大约就是27岁-37岁的人群</p><p>过去10年和未来几年的购房人口变化情况<br><img src="https://github.com/leriou/imgs/blob/main/20221030/peoples_age27_37.png?raw=true"></p><p>从上面的图中也可以看到, 未来几年随着结婚人数的减少, 刚需婚房的需求在未来可能会有较大的变化,这个变化可能并不明显但是一定会发生</p><h2 id="统计误差和其他"><a href="#统计误差和其他" class="headerlink" title="统计误差和其他"></a>统计误差和其他</h2><p>七普数据与官方的出生人口数据误差情况</p><p><img src="https://github.com/leriou/imgs/blob/main/20221030/07_normal_diff.png?raw=true"></p><p>七普六普的人口差值<br><img src="https://github.com/leriou/imgs/blob/main/20221030/07_06_diff.png?raw=true"></p><p>从七普数据和往年官方公布的出生人口情况来看存在较大误差,我们不去分析误差产生的原因,我们基本认为最近一次的七普人口数据普查数据最准确。但是我们也能看到,过去的官方出生人口和六普统计人口,在某些年龄段的人口数量上都有比较大的低估,这会让我们以往对人口的判断有比较大的风险,我们需要根据最新的七普人口数据重新调整对未来的人口预期</p><p>我坚定地认为未来几年的养老医疗产业是最具投资价值的领域</p>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
<tags>
<tag> 思考 </tag>
</tags>
</entry>
<entry>
<title>回顾2022</title>
<link href="/2022-02-03-review-2022/"/>
<url>/2022-02-03-review-2022/</url>
<content type="html"><![CDATA[<p>好久好久没有好好的写过一篇东西了</p><p>借这个机会正好记录一下自己这一年的所感和所得</p><p>如果用一个词描述2022这一年的感受,我想那就是</p><p>迷茫</p><p>随着年纪越来越大,渐渐的不知道自己</p>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
<tags>
<tag> 回顾2022 </tag>
</tags>
</entry>
<entry>
<title>2021年读书记录</title>
<link href="/2021-01-01-records/"/>
<url>/2021-01-01-records/</url>
<content type="html"><![CDATA[<h1 id="2021"><a href="#2021" class="headerlink" title="2021"></a>2021</h1><h1 id="1月"><a href="#1月" class="headerlink" title="1月"></a>1月</h1><p>看书:</p><ul><li><input disabled="" type="checkbox"> 影响力</li><li><input disabled="" type="checkbox"> 文明的冲突</li></ul><h1 id="9月"><a href="#9月" class="headerlink" title="9月"></a>9月</h1><p>看书</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input checked="" disabled="" type="checkbox"> 深度学习推荐系统</li></ul>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
</entry>
<entry>
<title>用神经网络帮妹子找对象</title>
<link href="/2020-07-08-neural-network-demo/"/>
<url>/2020-07-08-neural-network-demo/</url>
<content type="html"><![CDATA[<h1 id="机器学习和深度学习入门"><a href="#机器学习和深度学习入门" class="headerlink" title="机器学习和深度学习入门"></a>机器学习和深度学习入门</h1><p>神经网络这个名词已经被人广为知道了,但是由于机器学习还是有一定的门槛。所以好多人想学而不得入其门,大部分的入门文章会给你解释一大堆专有名词的含义</p><p>我自己17年接触机器学习,后面几年没有太过深入研究。直到19年做推荐系统的需求才重新开始关注这方面的学习。目前人工智能已经是公认的未来10-20年的重点发展方向,所以我认为大部分人都可以了解一下什么是机器学习,以及机器学习到底能做什么。</p><p>我写这篇文章的目的其实就是希望能通过一个简单的例子来解答比较玄学的神经网络是怎么工作的</p><p>为什么我要选神经网络来做例子而不用其他的支持向量机或者卷积神经网络呢</p><p>因为<strong>神经网络算是传统机器学习和深度学习等更高级机器学习算法的一个交叉点</strong></p><h2 id="机器学习和深度学习"><a href="#机器学习和深度学习" class="headerlink" title="机器学习和深度学习"></a>机器学习和深度学习</h2><p>机器学习是一种对机器从数据中获得能力的算法的统称,深度学习是机器学习的一个子类。深度学习一定是机器学习,机器学习不一定是深度学习。</p><p>一般把基于决策树,支持向量机,近邻算法等这种传统数学的算法的叫做传统机器学习</p><p>而像卷积神经网络,循环神经网络等这种叫做深度学习。但是传统的神经网络则普遍被归类于机器学习的范畴,神经网络就成了从机器学习走向深度学习的一个很好的拐点,所以我认为学习神经网络具有比较高的性价比</p><h2 id="从一个例子说起"><a href="#从一个例子说起" class="headerlink" title="从一个例子说起"></a>从一个例子说起</h2><p>假设现在有一个妹子去相亲,遇到了四个候选人,四个候选人分别在高,富,帅三个维度上的指标如下, 妹子对四人是否愿意交往的态度我们也做了调研, 现在想问的是如果有一个新的候选人小李, 我们想预测妹子对小李会是什么态度? 这个问题很有价值吧,我们现在就试图用神经网络解决这个问题</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"> 高 富 帅 是否愿意交往</span><br><span class="line">------------------------------------------- </span><br><span class="line">小明 否 是 是 是</span><br><span class="line">小张 是 是 是 是 </span><br><span class="line">小刘 是 否 是 否</span><br><span class="line">小赵 否 否 是 否</span><br><span class="line">-------------------------------------------</span><br><span class="line">小李 是 是 否 ? </span><br></pre></td></tr></table></figure><p>我们先假设妹子一定是喜欢男孩拥有的某些特征, 所以才愿意交往的,我们有男孩子的3个属性的特征(高,富,帅)。我们先做几个基本假设</p><ol><li>假设妹子对这三个特征的喜欢程度分别是 x, y, z, 而且喜欢程度跟名字无关</li><li>妹子是否同意跟男孩子交往是根据整体印象决定的,整体印象跟高富帅三个方面都有关系,妹子有自己的一套方法根据三个人的高富帅特征来评估整体印象</li></ol><p>我们可以用如下伪代码来描述整个妹子评价男孩子的方法</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">妹子的评估方法( [高, 富, 帅], 妹子对不同特征的喜好程度) => 整体印象</span><br></pre></td></tr></table></figure><ol start="3"><li>妹子如果觉得整体印象合格就会同意试试交往</li></ol><p>根据我们上面的假设我们可以得到如下流程</p><p><code>妹子观察一个人的特征 --> 在心里进行整体印象评估 -->根据评估结果决定是否同意交往</code></p><p>我们如果用程序描述以上流程就是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 获取男孩子的特征 比如小明的特征可以转换为 </span><br><span class="line">a = [0, 1, 1] </span><br><span class="line"># 根据个人特征给出整体印象评估分</span><br><span class="line">score = sig(a, 妹子对不同特征的喜好程度)</span><br><span class="line"># 根据分数决定是否同意交往</span><br><span class="line">befriend(res) ? 愿意 : 不愿意 </span><br><span class="line">由于我们已经知道妹子对小明,小张,小刘,小赵,的印象如何,我们可以得出如下结论</span><br><span class="line">小明 befriend(sig([0,1,1])) -> 是(1)</span><br><span class="line">小张 befriend(sig([1,1,1])) -> 是(1)</span><br><span class="line">小刘 befriend(sig([1,0,1])) -> 否(0)</span><br><span class="line">小赵 befriend(sig([0,0,1])) -> 否(0)</span><br></pre></td></tr></table></figure><h3 id="用程序来模拟妹子的整个评估过程"><a href="#用程序来模拟妹子的整个评估过程" class="headerlink" title="用程序来模拟妹子的整个评估过程"></a>用程序来模拟妹子的整个评估过程</h3><p>我们假设妹子对不同特征的偏好程序分别为 [x,y, z], 我们使用程序随机生成这个值</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="comment"># 给个随机种子,随机数生成顺序就固定了,方便调试</span></span><br><span class="line">np.random.seed(<span class="number">1</span>) </span><br><span class="line"><span class="comment"># 因为妹子喜欢的特征有三个所以是个(3,1)的向量</span></span><br><span class="line">w = np.random.random((<span class="number">3</span>,<span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(w)</span><br></pre></td></tr></table></figure><p>以上w的结果是我们猜测的妹子对不同特征的喜好程度,这个喜欢程度肯定是不准的,但是不要紧,我们先看看根据我们猜的权重进行流程会是什么结果。我们要模拟妹子的评估过程还需要知道妹子怎么根据最终得分来判断是否愿意交往。</p><p>我们假设: 妹子是根据综合得分,综合得分超过一个阈值就同意交往。 但是我们也能猜到肯定是得分越高交往概率越大,得分越低交往概率越小,所以我们可以使用如下函数来作为妹子的整体印象评估函数: $y=\cfrac{1}{1+e^{-x}}$</p><p>妹子的评估函数形状大概如下图</p><p><img src="https://i.loli.net/2020/08/08/dJeXBcqKVHiFYLN.png" alt="1-image-20200808134204732.png"></p><p>如此我们就可以写代码模拟过程了</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 小刘的特征</span></span><br><span class="line">liu = np.array([[<span class="number">0</span>,<span class="number">1</span>,<span class="number">1</span>]])</span><br><span class="line"><span class="comment"># 妹子的评估方法</span></span><br><span class="line">sig = <span class="keyword">lambda</span> x: <span class="number">1</span>/ (<span class="number">1</span>+ np.exp(-x))</span><br><span class="line"><span class="comment"># 使用特征w来评估小明 [0,1,1]的特征</span></span><br><span class="line">sig(np.dot(liu, w)) <span class="comment"># 0.6727</span></span><br><span class="line"><span class="comment"># 评估其他三个人,使用矩阵可以同时计算多组数据,这就是为啥机器学习要用矩阵的原因</span></span><br><span class="line">others = np.array([[<span class="number">1</span>,<span class="number">1</span>,<span class="number">1</span>],[<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>],[<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>]])</span><br><span class="line">sig(np.dot(others, w)) <span class="comment"># [[0.75721315][0.60279781][0.50002859]]</span></span><br></pre></td></tr></table></figure><p>根据上面的结果我们发现跟我们的预期不太相符合, 小明和小张也就算了,妹子对他们有好感,所以得分比较接近1。妹子明明不喜欢小刘,小赵,但是我们的程序给出了一个和小刘,小张差不多的结果。就是说我们预估的权重值不准确,也是嘛,毕竟权重是瞎猜的。</p><p>所以我们要根据我们已知的结果改进一下我们预估的权重</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 所有男孩特征</span></span><br><span class="line">all_user = np.array([[<span class="number">0</span>,<span class="number">1</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">1</span>,<span class="number">1</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>]])</span><br><span class="line">all_res = np.array([[<span class="number">1</span>],[<span class="number">1</span>],[<span class="number">0</span>],[<span class="number">0</span>]])</span><br><span class="line"><span class="comment"># 女孩对他们的印象</span></span><br><span class="line">r1 = sig(np.dot(all_user, w))</span><br><span class="line"><span class="comment"># 因为我们预估的不对,看看差多少</span></span><br><span class="line">error = all_res - r1</span><br><span class="line"><span class="comment"># 这一步是重点。将差的值反向分配回各个权重上</span></span><br><span class="line">w += np.dot(all_user.T,error)</span><br><span class="line"><span class="comment"># 重新计算</span></span><br><span class="line">r1 = sig(np.dot(all_user, w))</span><br><span class="line"><span class="built_in">print</span>(r1)</span><br><span class="line">---新的结果---</span><br><span class="line">[[<span class="number">0.71182252</span>]</span><br><span class="line"> [<span class="number">0.71819966</span>]</span><br><span class="line"> [<span class="number">0.36734682</span>]</span><br><span class="line"> [<span class="number">0.36010402</span>]]</span><br></pre></td></tr></table></figure><p>我们发现结果变好了,为啥呢,小刘和小赵分数变低了,小明和小张分数变高了,他们之间差距变大,说明我们更容易区分妹子的喜好了。好的,我们循环纠正10000次然后试试预测小李</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10000</span>):</span><br><span class="line"> r1 = sig(np.dot(all_user, w))</span><br><span class="line"> error = all_res - r1</span><br><span class="line"> w += np.dot(all_user.T,error)</span><br><span class="line">r1 = sig(np.dot(np.array([[<span class="number">1</span>,<span class="number">1</span>,<span class="number">0</span>]]), w))</span><br><span class="line"><span class="built_in">print</span>(r1)</span><br><span class="line">------</span><br><span class="line">[[<span class="number">1.0</span>]]</span><br></pre></td></tr></table></figure><p>哈哈,看我们的预测,妹子是会愿意跟小李交往的,这其实是符合我们预期的。因为我们发现这个妹子其实只看你是不是富, 你有钱我就跟你交往。 这说明我们的预测还是很准的,至少跟我们的直觉是一致的。</p><h3 id="多层感知机(MLP)"><a href="#多层感知机(MLP)" class="headerlink" title="多层感知机(MLP)"></a>多层感知机(MLP)</h3><p>前面这个妹子比较肤浅,只看富不富,假设有个妹子要求的条件比较多(比如又要富又要高),我们应该怎么处理呢</p><p>这种情况我们可以假设妹子看的条件变多了,妹子同时看多种条件,不只是高富帅三种基本条件还有 高富,富帅,高帅等组合起来的条件。那我们可不可以在造一个神经网络专门根据 高富,富帅和高帅这三个特征来处理呢? 当然是可以的。</p><p>但是我们不想自己制造高帅,富帅,高富这种特征数据了,我们已经可以根据高富帅其中的一个特征判断是否能赢得妹子芳心。我们思考一下,其实高帅,高富这种特征可以通过我们前面的神经网络求出来的。刚才妹子的判断其实就是一个判断是否富有的神经网络</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">高富 = 是否富(高富帅) x 是否高(高富帅) </span><br><span class="line">高富神经网络 等于 一个喜欢高的妹子 和 一个喜欢富的妹子 一起判断。</span><br></pre></td></tr></table></figure><p>那我们预估一下,如果要生成高富,高帅,富帅我们需要几个简单的神经网络呢?</p><p>答案是2个基本的神经网络, 如下图</p><p><img src="https://i.loli.net/2020/08/08/sa7G4PHb5VpCk6X.png" alt="2-image-20200808152700276.png"></p><p>我们假设我们还有一组权重用于判断是否高富</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">all_user = np.array([[<span class="number">0</span>,<span class="number">1</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">1</span>,<span class="number">1</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"> ,[<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>]])</span><br><span class="line"><span class="comment"># 因为又来了一个喜欢高富的妹子,所以四位候选人的结果不同了</span></span><br><span class="line">all_res = np.array([[<span class="number">0</span>],[<span class="number">1</span>],[<span class="number">0</span>],[<span class="number">0</span>]])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 我们假设二层也有三个特征,一层的权重就要调整成根据3个特征判断3个结果</span></span><br><span class="line">w = np.random.random((<span class="number">3</span>,<span class="number">3</span>))</span><br><span class="line"><span class="comment"># 二层根据3个判断过的结果在进行判断</span></span><br><span class="line">w1 = np.random.random((<span class="number">3</span>,<span class="number">1</span>))</span><br><span class="line"><span class="comment"># 根据斜率来更快的进行更新</span></span><br><span class="line">deriv = <span class="keyword">lambda</span> x: x * (<span class="number">1</span> - x)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10000</span>):</span><br><span class="line"> <span class="comment"># 根据预估权重计算</span></span><br><span class="line"> r1 = sig(np.dot(all_user, w))</span><br><span class="line"> r2 = sig(np.dot(r1,w1))</span><br><span class="line"> <span class="comment"># 根据误差重新调整权重应该调整的值</span></span><br><span class="line"> error_1 = all_res - r2</span><br><span class="line"> delta_1 = error_1 * deriv(r2)</span><br><span class="line"> error_0 = error_1.dot(w1.T)</span><br><span class="line"> delta_0 = error_0 * deriv(r1)</span><br><span class="line"> <span class="comment"># 更新权重</span></span><br><span class="line"> w += np.dot(all_user.T,delta_0)</span><br><span class="line"> w1 += np.dot(r1.T, delta_1)</span><br><span class="line"><span class="comment"># 重新预估</span></span><br><span class="line">r1 = sig(np.dot(np.array([[<span class="number">1</span>,<span class="number">1</span>,<span class="number">0</span>]]), w))</span><br><span class="line">r2 = sig(np.dot(r1,w1))</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(r2)</span><br><span class="line">---结果---</span><br><span class="line">[[<span class="number">0.9942417</span>]]</span><br></pre></td></tr></table></figure><p>结果符合我们预期,因为小李又高又富,符合我们假设的妹子的择偶需求,所以我们预估妹子给出了0.99的高分。</p><p>我们如果把权重的变化画出来,会发现权重的均值越来越稳定,这也正是我们要的效果</p><p><img src="https://i.loli.net/2020/08/08/aP7Yq4klzGdpCUs.png" alt="3-image-20200808154754303.png"></p><h3 id="为什么要求导"><a href="#为什么要求导" class="headerlink" title="为什么要求导"></a>为什么要求导</h3><p>上面的代码里面我们加入了一个 <code>deriv</code>函数, 这个函数其实是求sigmod上面的导数也就是斜率</p><p>我们之所以要求斜率,就是因为我们希望最终得分尽可能的往sigmod两端走,因为越往两端走,说明权重越稳定,预测结果越可靠。那我们就可以根据斜率来判断当前权重所处的位置。(通常机器学习会寻找一个函数用来衡量误差,这个函数叫做损失函数)</p><h2 id="神经网络的迁移应用"><a href="#神经网络的迁移应用" class="headerlink" title="神经网络的迁移应用"></a>神经网络的迁移应用</h2><p>很棒,我们已经学会了使用神经网络帮妹子择偶,哪怕妹子要求的特征很多,我们只要多加几个神经网络就可以了</p><p>其实使用神经网络预测很多其他的事情也是一样的道理,我们只要能提供够多的特征数据,神经网络就可以自动学习出来那些特征有用</p><p>但是我们上面的代码有一些地方其实是我们猜测的,比如: 我们猜测的权重,我们猜测的妹子评估的函数,我们猜测的评估误差的方法等,这些东西其实都是可以调整的,大家要记住这一点,只有整个流程是不变的,流程中的每一步都是有不同方法达成的</p><h2 id="多分类的神经网络"><a href="#多分类的神经网络" class="headerlink" title="多分类的神经网络"></a>多分类的神经网络</h2><p>上面我们已经设计了一个神经网络可以根据一个或者多个属性对特征进行分类。但是上面的分类结果只有2种,愿意或者不愿意,我们日常生活中经常会遇到多分类问题,比如判断一个图片是[0,1,2]中的哪一个数字</p><p>其实多分类问题可以转化为多个单分类的问题,转化过程如下</p><p>将上面的神经网络转换为三个神经网络分别用来判断 是否0,是否1,是否2, 结果为0就可以用 非1&非2 这个逻辑关系表示, 也就是说,多分类就是对单分类在进行一次带有逻辑关系的分类即可</p><h2 id="方便的工具"><a href="#方便的工具" class="headerlink" title="方便的工具"></a>方便的工具</h2><h3 id="sklearn"><a href="#sklearn" class="headerlink" title="sklearn"></a>sklearn</h3><p>官网:<a href="https://scikit-learn.org/stable/">https://scikit-learn.org/stable/</a></p><p>sklearn是一个机器学习的大集合,我们上面吭哧吭哧弄的神经网络别人已经帮我们搭建好了,我们直接使用就只需要几行代码,我这里拿sklearn里面的神经网络举例子。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> neural_network </span><br><span class="line"></span><br><span class="line">X = [[<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>],</span><br><span class="line"> [<span class="number">0</span>,<span class="number">1</span>,<span class="number">1</span>],</span><br><span class="line"> [<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>],</span><br><span class="line"> [<span class="number">1</span>,<span class="number">1</span>,<span class="number">1</span>]]</span><br><span class="line">y = [<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">clf = neural_network.MLPClassifier(activation=<span class="string">"relu"</span>,max_iter=<span class="number">10000</span>)</span><br><span class="line"><span class="comment"># 训练</span></span><br><span class="line">clf.fit(X,y)</span><br><span class="line"><span class="comment"># 预测</span></span><br><span class="line">clf.predict([[<span class="number">0</span>,<span class="number">1</span>,<span class="number">0</span>]])</span><br></pre></td></tr></table></figure><h3 id="pytorch-Tensosflow-keras"><a href="#pytorch-Tensosflow-keras" class="headerlink" title="pytorch,Tensosflow,keras"></a>pytorch,Tensosflow,keras</h3><p>这几个工具是偏向深度学习的工具,与前面的sklearn可以配合使用</p>]]></content>
<categories>
<category> 机器学习 </category>
</categories>
<tags>
<tag> 神经网络 </tag>
<tag> 深度学习 </tag>
<tag> 机器学习 </tag>
</tags>
</entry>
<entry>
<title>基于kafka的延迟队列和优先队列的实现</title>
<link href="/2020-06-04-kafka-delay-message/"/>
<url>/2020-06-04-kafka-delay-message/</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>我们在业务中经常会有<strong>延迟队列</strong>或者<strong>优先级队列</strong>之类的需求。比如用户下单15分钟如果没有付款就自动取消订单。</p><p>延迟队列和优先队列本质上是一个需求,延迟队列可以看成一个使用时间作为优先级的队列。Kafka这种基于日志的消息队列并没有自带延迟队列的功能。所以如果想在Kafka的基础上实现此类功能,需要我们自己动手处理。我现在可以想到的实现方法有三种</p><ol><li>消息分级</li><li>外存排序</li><li>针对延迟队列的时间轮</li></ol><h2 id="消息分级"><a href="#消息分级" class="headerlink" title="消息分级"></a>消息分级</h2><p>先说简单的消息分级机制,假如我们遇到需要根据用户会员等级对不同等级的用户消息按照不同的优先级进行处理的需求。</p><p>最直观的办法就是我们制作多个等级的消息队列,每个队列对应一个用户等级的消息。或者在一个队列中使用多个不同分区,每个分区对应不同等级的消息。</p><p>这种方法本质上就是把不同优先级的消息内容分开存储。在消息被消费的时候按照优先级从高到低的方式进行处理。开源的rocketmq的延迟消息就是这种实现机制,其中的延迟消息队列内置了18个不同延迟时间级别的消息队列,客户端消费消息是从这18个队列中按优先级同时获取的。</p><p>按优先级分区处理图:</p><p><img src="https://i.loli.net/2021/03/22/3BYSoLuPwgiDMaX.png" alt="delay_queue1.png"></p><p>假设我们现在有3种用户优先级,我们需要按照一定的vip优先级顺序(假设vip等级 vip1 > vip2 > vip3)处理用户消息。具体流程如下</p><ol><li>用户发送具有优先级的消息到vip_topic</li><li>分流程序实时消费vip_topic,将其中的消息按照优先级分别放入vip_1,vip_2,vip_3三个优先级队列, 这样一来,vip_1, vip_2,vip_3每个topic内部的消息都是保持有序的</li><li>另外有一个程序 每次按照 vip_1,vip_2,vip_3的顺序依次取消息进行处理,只有vip_1里面的消息处理完,才会处理vip_2里面的消息,最后处理vip3里面的消息。这样可以保证每一批次的消息都是按照 vip1> vip2> vip3的优先级进行处理</li></ol><p>这种方法的缺点很明显,就是优先级区分受队列个数限制,只适用于优先级层级比较少的情况。如果我们想实现毫秒级的基于时间的延迟队列,那就要每个1ms区间都要构建一个队列出来,这种场景下明显不合理,所以采用类似方案的rocketmq开源版本仅仅实现了18个时间尺度的优先级队列。</p><h2 id="外存排序"><a href="#外存排序" class="headerlink" title="外存排序"></a>外存排序</h2><p>外存排序是真正的按照某种优先级字段对内容进行重新排序,然后再进行消费的手段。</p><p>我们可以先获取队列中的消息内容, 然后按照一定的优先级字段进行排序。最后按照排序后的结果进行消费即可。这种方法的技术难点在于如何利用小内存对大容量的磁盘内容进行排序。因为通常消息队列中的内容都是T级别的,而机器的可用内存空间往往是G级别的。</p><p>基于排序的优先级队列实现流程:</p><p><img src="https://i.loli.net/2021/03/22/6e7N84KHazWEZuA.png" alt="delay_queue2.png"></p><hr><h3 id="如何在2G内存的机器上对100G文件进行排序"><a href="#如何在2G内存的机器上对100G文件进行排序" class="headerlink" title="如何在2G内存的机器上对100G文件进行排序"></a>如何在2G内存的机器上对100G文件进行排序</h3><p><img src="https://i.loli.net/2021/03/22/EHmgAIRKeZqXOhW.png" alt="图1"></p><p>上图1</p><p><img src="https://i.loli.net/2021/03/22/lRWYwHpnM4D1vya.png" alt="图2"></p><p>上图2</p><p>假设我们要对100G的内容在2G内存的机器上进行排序, 我们可以执行如下操作</p><ol><li>使用split命令把100G文件分割成50个2G的小文件,并对每个分割后的文件使用sort排序(此时的问题已经变成对50个有序的2G文件在2G内存的机器上进行排序),(上图1 的有序文件1,2,3)</li><li>使用split取50个小文件的前40M数据,读取到内存进行排序,排序后的数据的前40M就是全局最小的40M内容(上图中的A,B,C三块文件读入内存变为A’,B’,C’, A’B‘C’在内存中排序后一定能得到全局最小的新A’)</li><li>将内存排序后的前40M内容输出到磁盘上,内存中剩余的1960M内容放回到磁盘,此时磁盘有51个1960M的有序文件(此时问题等价于对51个有序的1960M的文件在2G内存的机器上进行排序,途中的B’C‘被从内存中取出放回磁盘)</li><li>仿照上面的方法, 从51个文件每个读取35M内容进行排序,找出全局最小的35M内容</li><li>循环2-4步骤,直至所有内容都被排序成功</li></ol><blockquote><p>ps: sort,split 均为linux自带的命令可以对文件进行排序和分割,这个算法本质上还是多路归并排序, 但是相当于对归并排序做了一些优化:优化1,每段已排序数据一次取一批数据;优化2,没有采用记录文件指针的形式而是直接分割文件,操作更简单</p></blockquote><hr><p>如果我们可以在Kafka中采用实时程序对Topic中的消息进行外存排序,就可以得到我们需要的优先消息队列。但是在这个过程中我们还要<strong>注意使用kafka和Flink的事务机制,提供kafka端到端的事务保证,防止消息丢失</strong></p><p>实现kafka+Flink的端到端的事务的方案在此不多叙述</p><h2 id="针对延迟队列的时间轮机制"><a href="#针对延迟队列的时间轮机制" class="headerlink" title="针对延迟队列的时间轮机制"></a>针对延迟队列的时间轮机制</h2><p>如果是仅仅为了实现延迟队而非通用的优先级队列,我们可以利用Kafka中的时间轮机制,将任务添加到对应的每一个时间分片中, 随着时间轮的驱动, 顺序处理每个时间分片其中的消息</p><p><img src="https://i.loli.net/2021/03/22/C3SwHUNbcOq9WJF.png" alt="delay_queue3.png"></p><p>图3时间轮</p><p>时间轮是一种循环数据结构,时间轮中的每个槽位代表一个时间粒度,时间轮的时间粒度 = 槽位数量 * 每个槽位的时间粒度。时间轮由一个独立的循环程序驱动上面的当前时间指针前进。时间轮可以用槽位链表或者数组+消息链表的形式来实现。</p><p>用时间轮实现延迟队列具体方法如下</p><ol><li>假设我们提供20d级别的延迟消息队列,我们需要提供3级时间轮,最低级是100ms粒度的时间轮,最大存储范围1min, 第二种是1min粒度的时间轮,最大存储范围6h,第三种是 6h粒度的时间轮,最大存储范围480小时</li><li>当一个消息进来,我们根据消息的延迟时间选择对应的时间轮分片进行消息插入,比如有一个延迟1500ms的消息,将会被插入到当前的1m时间轮的第15个槽位,如果消息的延迟时间为3h将会被插入更高一级的6h的时间轮的第3个槽位。</li><li>采用一个循环程序驱动时间轮转动并消费当前时间槽内的消息,当1m时间轮的所有消息消费完毕,从6h的时间轮中获取当前槽位的所有消息,并驱动6h时间轮指向下一个槽位,对取出来的消息排序补充入更低级的1min时间轮,继续驱动1min的时间轮继续消息处理。</li><li>采用类似手段可以通过控制时间轮的粒度,来控制延迟队列的粒度,达到一种较好的延迟队列实现方法</li></ol><p>时间轮也有自己的缺点,不过在基于时间的延迟消息机制中已经是最好的解决方案了。但是这种方法不像上一种外存排序那么通用,仅适合于延迟队列这一种场景。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>其实以上三种方法我们本质上处理过程都是一样的,无非就是过程中我们使用的工具和排序机制不同。</p><p>第一种方法我们按照优先级分类,然后某个分类里面的消息天然有序, 我们取数据时候人为规定的字段优先级就充当了其中的排序机制。</p><p>第二种方法我们相当于按照消息接收的时间分类, 然后对优先级字段进行排序</p><p>第三种方法我们按照 延迟时间分类,然后对每一类里面的消息,让他变的局部有序。最后得到一个全局有序的结果。</p><p>如果把第二种方法里面的文件换成topic,排序方法换成按topicId排序的排序方法,就跟第一种毫无区别了。</p><p>同样,如果我们把第三种里面的时间轮上的槽位当作topic,也跟第一种方法没有什么区别</p><table><thead><tr><th>方法</th><th>适用范围</th></tr></thead><tbody><tr><td>分区队列</td><td>实现简单,适用于数据量大,排序字段可选值较少的优先级队列和延迟队列场景</td></tr><tr><td>外部排序</td><td>实现复杂,需要端到端的事务保证,适用于排序字段可选值大,数据量较少的优先级队列和延迟队列场景</td></tr><tr><td>时间轮</td><td>实现复杂,仅适用于延迟队列场景</td></tr></tbody></table>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> 延迟队列 </tag>
<tag> kafka </tag>
<tag> 外存排序 </tag>
</tags>
</entry>
<entry>
<title>超高并发场景下的直播红包发放业务的架构设计</title>
<link href="/2020-05-26-live-red-packet/"/>
<url>/2020-05-26-live-red-packet/</url>
<content type="html"><![CDATA[<h2 id="业务背景和需求简介"><a href="#业务背景和需求简介" class="headerlink" title="业务背景和需求简介"></a>业务背景和需求简介</h2><p>业务中经常有直播期间播主给用户发红包的操作,由于直播场景的高实时,高并发场景。这个简单的业务中还是有不少的问题出现,这里给出一种经过验证的可以横向扩展支持超高并发量的实现手段</p><h2 id="业务流程"><a href="#业务流程" class="headerlink" title="业务流程"></a>业务流程</h2><p>主播在直播过程中向用户发送红包,主播会设定金额,红包个数,红包类型(随机红包,等额红包)。用户可以去抢那些个红包。</p><h2 id="关键问题"><a href="#关键问题" class="headerlink" title="关键问题"></a>关键问题</h2><h3 id="生成红包的算法"><a href="#生成红包的算法" class="headerlink" title="生成红包的算法"></a>生成红包的算法</h3><p>其实跟发微信红包类似,播主可以发两种不同的红包。一种是等额红包,一种是随机红包。等额红包没什么技术难度,随机红包会涉及到红包的分割算法。</p><p>在随机金额的红包中为了保证用户体验,通常会允许给每份红包设定一个区间范围,保证用户得到的红包不至于太小也不至于太大。</p><p>也就是说我们需要实现如下函数, 根据红包总金额,红包个数,红包的最小值和最大值,生成一个具有n个红包具体金额的数组</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">gen_red_package</span>(<span class="params">amount, num, min_size, max_size</span>) -> <span class="type">List</span>[<span class="built_in">float</span>]</span><br></pre></td></tr></table></figure><p>实现简单的红包生成算法并不难,难得是设计一个每个红包生成金额概率均等的足够公平的红包生成算法。这里可以采用一种线性分段的方法来实现均匀的红包分割,思路是这样的</p><ol><li>我们可以先按【0,红包总金额】划定一个线段,然后我们在线段上随机位置生成n-1个点(n是红包总数)</li><li>每个生成的点与其前后的点的间隔要满足我们的金额要求</li><li>最后每两个点之间的距离就是我们要的红包金额</li></ol><p>具体代码如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">gen_red_package</span>(<span class="params">amount, num, min_size, max_size</span>) -> <span class="type">List</span>[<span class="built_in">float</span>]:</span><br><span class="line"> amount *= <span class="number">100</span></span><br><span class="line"> lines = [<span class="number">0</span>, amount]</span><br><span class="line"> <span class="comment"># 生成num-1个符合要求的随机的点</span></span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, num - <span class="number">1</span>):</span><br><span class="line"> loop = <span class="literal">True</span></span><br><span class="line"> <span class="keyword">while</span> loop:</span><br><span class="line"> r = random.Random().randint(<span class="number">0</span>, amount)</span><br><span class="line"> loop = <span class="keyword">not</span> is_valid(lines, r, min_size * <span class="number">100</span>, max_size * <span class="number">100</span>)</span><br><span class="line"> <span class="comment"># 生成红包 </span></span><br><span class="line"> paks = []</span><br><span class="line"> <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(lines) - <span class="number">1</span>):</span><br><span class="line"> paks.append((lines[j+<span class="number">1</span>] - lines[j])/<span class="number">100</span>)</span><br><span class="line"> <span class="keyword">return</span> paks</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_valid</span>(<span class="params">arr, value, min_size, max_size</span>) -> <span class="built_in">bool</span>:</span><br><span class="line"> <span class="keyword">if</span> arr.__contains__(value):</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> arr.append(value)</span><br><span class="line"> arr.sort()</span><br><span class="line"> curr = arr.index(value)</span><br><span class="line"> l = arr[<span class="built_in">max</span>(<span class="number">0</span>, curr - <span class="number">1</span>)]</span><br><span class="line"> r = arr[<span class="built_in">min</span>(curr + <span class="number">1</span>, <span class="built_in">len</span>(arr) - <span class="number">1</span>)]</span><br><span class="line"> dl = value - l</span><br><span class="line"> dr = r - value</span><br><span class="line"> <span class="keyword">if</span> dl >= min_size <span class="keyword">and</span> dr >= min_size <span class="keyword">and</span> dl <= max_size <span class="keyword">and</span> dr <= max_size :</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> arr.remove(value)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(gen_red_package(<span class="number">100</span>, <span class="number">5</span>, <span class="number">10</span>, <span class="number">90</span>))</span><br></pre></td></tr></table></figure><blockquote><p> ⚠️注意,这种方法其实是有一定的问题存在的,假设我们想发3个总金额4块的红包,最小1块钱,最大2块钱,此时每个红包最小必须1元,有可能会生成 【1.5, 3】 然后第三个红包永远无法生成的情况,处理这种情况的方法就是调整最小红包和最大红包到一个合适的值</p></blockquote><h3 id="关于红包金额的概率公平性分析"><a href="#关于红包金额的概率公平性分析" class="headerlink" title="关于红包金额的概率公平性分析"></a>关于红包金额的概率公平性分析</h3><p>为了保证验证上面的红包算法对每个金额生成的概率均等,通过对上面的红包生成代码进行10000次测试,我们能得到如下的金额分布。我们可以看到金额在20以上的次数和在20以下的总次数是相当的。这符合我们的预期,因为我们100块钱分成5个红包,均值就是20。<strong>说明金额期望是相等的</strong></p><p><img src="https://i.loli.net/2020/08/28/9KwRprZeNGuSjiz.png" alt="image-20200828203601323.png"></p><p>我们在看下面的各个位置的红包总金额的分布, 也能看到每个位置的总金额是差不多的, 也就是说。每个位置获取金额的大小概率是一样的。这就体现了算法的公平性,<strong>说明红包金额和抢红包的顺序没关系</strong>。</p><p><img src="https://i.loli.net/2020/08/28/k4traPndz5KCsj3.png" alt="image-20200828203613294.png"></p><p>具体的分析金额生成概率的代码如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line">%matplotlib inline</span><br><span class="line">%config InlineBackend.figure_format = <span class="string">'svg'</span></span><br><span class="line"></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">我们将100块钱分成5个红包, 最小5元,最大95元(这个允许的金额区间越大,算法性能越好,范围越小,越消耗性能)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line">amount = <span class="number">100</span></span><br><span class="line">nums = <span class="number">5</span></span><br><span class="line">min_amount = <span class="number">5</span></span><br><span class="line">max_amount = <span class="number">95</span></span><br><span class="line"></span><br><span class="line">step = <span class="number">1</span></span><br><span class="line">stat = {}</span><br><span class="line">pos = {}</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>,<span class="number">10000</span>):</span><br><span class="line"> packages = gen_red_package(amount, nums, min_amount, max_amount)</span><br><span class="line"> idx = <span class="number">1</span></span><br><span class="line"> <span class="keyword">for</span> n <span class="keyword">in</span> packages:</span><br><span class="line"> t = n // step</span><br><span class="line"> base = <span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> stat.__contains__(t):</span><br><span class="line"> base = stat[t] + <span class="number">1</span></span><br><span class="line"> stat[t] = base</span><br><span class="line"> base = n</span><br><span class="line"> <span class="keyword">if</span> pos.__contains__(idx):</span><br><span class="line"> base = pos[idx] + n</span><br><span class="line"> pos[idx] = base</span><br><span class="line"> idx += <span class="number">1</span> </span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">fig, ax = plt.subplots() </span><br><span class="line">ax.bar(np.array([i*step <span class="keyword">for</span> i <span class="keyword">in</span> stat.keys()]),np.array([i <span class="keyword">for</span> i <span class="keyword">in</span> stat.values()]));</span><br><span class="line"></span><br><span class="line">fig2, bx = plt.subplots() </span><br><span class="line">bx.bar(np.array([i <span class="keyword">for</span> i <span class="keyword">in</span> pos.keys()]),np.array([i <span class="keyword">for</span> i <span class="keyword">in</span> pos.values()]));</span><br></pre></td></tr></table></figure><h3 id="如何处理超高并发场景下红包的分发"><a href="#如何处理超高并发场景下红包的分发" class="headerlink" title="如何处理超高并发场景下红包的分发"></a>如何处理超高并发场景下红包的分发</h3><ul><li>方案1:采用消息队列</li></ul><p>我们可以将生成的50个红包放到redis的list中,每个用户到来,就去队列中请求弹出一个红包数据,队列数据消耗完毕, 即待分配的红包消耗完毕</p><p>这种方法实现简单, 但是会遇到并发的问题,用户去获取list的数据的时候, 由于用户的所有红包都在一个redis节点上, 所以用户的所有redis请求都会被动的落到某一个redis节点,哪怕使用集群也无法解决这种热点问题</p><p>所以在用户数据量较大的时候还是建议第二种方案</p><ul><li>方案2: 采用数据分流</li></ul><p>数据分流 + 用户分流:我们生成的50个红包可以分成N份,存储到不同的key上, 使用key_0-key_N存储数据会均匀的分配到redis的各个节点上。</p><p>用户在获取红包时,随机请求其中的一份红包数据,这样用户的请求可以有效打散到各个redis节点上, 同时能处理的请求数据可以随节点数量扩展, 但是这种方法的缺点也很明显, 一个是实现复杂,一个是会对用户造成一定程度上不公平的体验。</p><hr><p><strong>具体的红包发放流程</strong></p><p>播主和服务端</p><p><img src="https://i.loli.net/2020/11/10/AqbUl86k4RDHKaj.png" alt="hongbao1.png"></p><p>用户端</p><p><img src="https://i.loli.net/2020/11/10/FbzM3i4plvj9gSJ.png" alt="hongbao2.png"></p><p>潜在的问题:</p><ol><li><p>用户存在的幻读现象</p><p>可能会出现这种情况,A,B两节点的用户请求分布不均匀,用户1请求被分流到A节点,发现A节点没有红包了,但是重新刷新,这次被分到B节点,又发现B节点还有红包可以领。解决这种问题可以采用按请求顺序轮询分发的方法,最大程度减少不公平。或者采用用户亲和的方法,根据用户的hash进行节点划分</p></li></ol>]]></content>
<categories>
<category> 架构方案 </category>
</categories>
<tags>
<tag> 热点数据 </tag>
</tags>
</entry>
<entry>
<title>推荐系统流量池的构建</title>
<link href="/2020-05-02-rec-flows-pool/"/>
<url>/2020-05-02-rec-flows-pool/</url>
<content type="html"><![CDATA[<p>这篇文章的目的是希望构建一套可行的,灵活的,能支持我们常见业务需求的流量池系统架构设计</p><h2 id="内容流量池的目的"><a href="#内容流量池的目的" class="headerlink" title="内容流量池的目的"></a>内容流量池的目的</h2><p>我们希望通过流量池达到以下目标</p><ul><li><p>解决内容推荐的马太效应</p></li><li><p>限制点击率比较低的内容的投放次数</p></li><li><p>帮助运营提升内容分发的效率,提供一个可以对播主赋能的手段</p></li></ul><h2 id="流量池的整体架构设计"><a href="#流量池的整体架构设计" class="headerlink" title="流量池的整体架构设计"></a>流量池的整体架构设计</h2><p><img src="https://i.loli.net/2020/05/09/sGhXxjwLYngEePB.jpg" alt="推荐系统流量池.jpg"></p><p>流量池系统在整个推荐系统中主要起一个资源吃的作用,可推荐的内容资源以流量池中的内容流量的形式存在。同时为其他各种召回策略提供原始数据集。</p><p>流量池中的流量有多种类型,主要有以下四种</p><ol><li>测试流量:所有合法内容发布以后获得此类流量,用于保证内容的最少推荐次数,通过测试流量来评估内容的质量表现,过滤掉质量比较低的内容</li><li>叠加流量:通过测试流量筛选以后的质量相对较高的内容可以获得此类流量,用于优质内容的自动流量续费</li><li>运营流量:运营人员或者博主自己为内容添加的可用流量 <h2 id="内容流量池的概念"><a href="#内容流量池的概念" class="headerlink" title="内容流量池的概念"></a>内容流量池的概念</h2></li></ol><p>一个承载内容和内容可推荐次数的数据集合,流量池的核心数据是一个类似于<code><c10086, 1000></code>的<code><k,v></code>结构。表示的含义也很简单,就是c10086这个编号的内容拥有1000次的推荐次数。</p><p>流量池内容的推荐符合如下基本规则</p><ol><li>拥有流量的内容才允许被推荐,内容每推荐一次消耗一个推荐流量</li><li>内容流量拥有不同的类型,同一个内容可以同时拥有多种流量</li></ol><h2 id="流量的定义"><a href="#流量的定义" class="headerlink" title="流量的定义"></a>流量的定义</h2><p>一个流量代表一个内容对某用户的一次推荐行为,如果一个用户一次请求中请求了10条的数据, 那么这一次推荐就消耗了10个流量, 本次结果中的每个被推荐的内容推荐流量 -1。</p><p>用户对某一个内容的推荐不会重复, 所以一个用户对某个内容至多拥有一个流量。同时推荐流量也限制了内容的被推荐次数,假设一个内容的总流量是1000,意味着至多被推荐给1000个人。</p><h2 id="流量池的生成和使用规则"><a href="#流量池的生成和使用规则" class="headerlink" title="流量池的生成和使用规则"></a>流量池的生成和使用规则</h2><ol><li>流量分批次,一个批次500个流量。所有内容默认拥有500次的测试流量, 也就是说所有内容至少应被推荐500次</li><li>每个批次的流量消耗完毕进行点击率/互动率的计算,达标的继续分发流量。除第一次外的流量批次统一为叠加流量。叠加流量消耗完毕也进行点击率的计算,达标的继续进行新的流量分发。</li><li>运营可以为内容投放运营流量,流量消耗的优先级:运营流量>叠加流量>测试流量。运营流量也分批次投放,也需要计算点击率,运营流量消耗完毕以后不再进行叠加流量的追加。</li></ol><h3 id="如何限制点击率比较低的内容的投放次数"><a href="#如何限制点击率比较低的内容的投放次数" class="headerlink" title="如何限制点击率比较低的内容的投放次数"></a>如何限制点击率比较低的内容的投放次数</h3><p>每个批次的内容流量消耗完毕立即进行点击率的计算, 达标的才能继续获得流量。点击率低的不达标内容不再进行推荐。<br>每个内容至少拥有500次的测试流量,用于充分测试其质量表现。</p><h3 id="如何解决马太效应"><a href="#如何解决马太效应" class="headerlink" title="如何解决马太效应"></a>如何解决马太效应</h3><p>内容流量的生成门槛随着流量分发的次数逐步提高。 例如,a内容前1000次流量点击率12%, 阈值为 6% ,将继续获得1000次流量的额度,等这1000个流量消耗完毕,对应的门槛提高到 12%, 假如这第二批次的流量点击率为10%, 将被终止流量的继续投放。已消耗流量越多,获得新流量的点击率阈值越高,用以打击热点内容的权重。</p><h3 id="如何帮助运营人员提高内容分发效率"><a href="#如何帮助运营人员提高内容分发效率" class="headerlink" title="如何帮助运营人员提高内容分发效率"></a>如何帮助运营人员提高内容分发效率</h3><p>运营人员可以为内容赋予额外的流量,拥有充足流量的内容将在推荐策略中获得加权, 可用流量越多的内容,相对来说越容易被推荐出去。</p><h2 id="定向推荐-指定用户范围的推荐"><a href="#定向推荐-指定用户范围的推荐" class="headerlink" title="定向推荐: 指定用户范围的推荐"></a>定向推荐: 指定用户范围的推荐</h2><p>上面的流量池只能实现对某内容的一定次数的推荐, 其实还可以在此基础上实现对特征用户的定向流量投放。即我们可以指定某内容向某一类用户进行推荐的次数。</p><p>这个功能可以用来进行运营赋能, 帮助运营更精准的进行流量投放, 也可以帮助创作者进行早期的用户积累和帮创作者提高内容的曝光度。</p><h3 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h3><p>我们可以为用户设置一系列的特征列表例如</p><table><thead><tr><th>用户/特征</th><th>>30岁</th><th>男性</th><th>郭德纲粉丝</th><th>杨幂粉丝</th><th>喜欢综艺</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>0</td><td>1</td><td>0</td><td>1</td></tr><tr><td>2</td><td>0</td><td>1</td><td>0</td><td>1</td><td>0</td></tr><tr><td>3</td><td>0</td><td>0</td><td>1</td><td>0</td><td>0</td></tr></tbody></table><p>我们如果要对用户进行精准投放,只需要选中一部分特征用户,并为此特征生成对应的召回集。 例如,我们要把内容A对杨幂的粉丝进行投放, 内容B对30岁以下的男性用进行投放<br>那我们可以生成如下两个数据序列</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> 杨幂粉丝 = <span class="type">Set</span>(<span class="type">A</span>)</span><br><span class="line"><span class="keyword">val</span> <span class="number">30</span>岁以下男性用户 = <span class="type">Set</span>(<span class="type">B</span>)</span><br></pre></td></tr></table></figure><p>用户1 在获取推荐结果的时候,根据自己的用户特征列表和列表中特征对应的待推荐内容(召回集)进行融合排序推荐</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">getRecommendations</span></span>(user:<span class="type">String</span>): <span class="type">Seq</span> = {</span><br><span class="line"><span class="keyword">val</span> fetureList:<span class="type">Seq</span> = getFeture(user)</span><br><span class="line">fetureList.flatMap(f => getRecallByFeture())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="流量池的效果评估指标"><a href="#流量池的效果评估指标" class="headerlink" title="流量池的效果评估指标"></a>流量池的效果评估指标</h2><p>我们为了评估流量池的运行效果,我们需要制定一部分指标来对流量池进行监控并及时的调整参数</p><p>主要用到的指标有</p><ul><li><p>每日的新订单创建数量和不同类型的订单的创建数量</p></li><li><p>当前流量池的可用流量分布和不同类型的订单的分布</p></li><li><p>已消耗订单的点击率分布和不同类型的订单的点击率分布</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>流量池系统其实是通过一种机制来控制内容的可曝光次数, 来对推荐结果的分布进行干预。<br>为了保证新内容的充分曝光,新内容会被给予一定次数的固定流量。表现好的内容将持续不断的获得推荐流量,表现不好的内容将在一定的推荐次数后不再推荐,空出来的推荐机会留给其他表现更好的内容。</p><p>同时为运营和个人播主提供了额外的手段用于满足部分推广需求。</p>]]></content>
<categories>
<category> 推荐系统 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> 流量池 </tag>
</tags>
</entry>
<entry>
<title>推荐系统AB实验平台</title>
<link href="/2020-04-19-rec-abtest/"/>
<url>/2020-04-19-rec-abtest/</url>
<content type="html"><![CDATA[<h2 id="A-x2F-B实验平台的目的"><a href="#A-x2F-B实验平台的目的" class="headerlink" title="A/B实验平台的目的"></a>A/B实验平台的目的</h2><p><strong>提供一个实验平台,可以方便的使用控制变量的手段同时进行多种功能改进的效果对比实验</strong></p><p>对比一个功能上线后的效果如何需要做用户对比实验, 在此过程中需要严格的控制变量。但是有时候我们可能希望同时实验多种不同维度的功能改进的效果,以此快速验证功能效果,提高产品迭代效率,这个时候就需要一个能提供多功能对比实验的AB实验平台。</p><h2 id="ab实验实际场景举例"><a href="#ab实验实际场景举例" class="headerlink" title="ab实验实际场景举例"></a>ab实验实际场景举例</h2><p>假设我们现在有一个支付服务, 我们设计了红/蓝两种付款按钮,并且支持信用卡和支付宝两种支付方式。我们想通过实验看看哪种颜色的付款按钮付款率更高,还想看看哪种付款方式的付款率更高。</p><p>此时我们就构成了2个实验,实验1: 针对付款按钮颜色的实验。实验2:针对付款方式的实验。</p><p>其中的红按钮,蓝按钮就是实验策略。针对颜色的实验和针对付款方式的实验就是实验层。最终所有用户会分不到四个实验结果中。分别是【红按钮信用卡,红按钮支付宝,蓝按钮信用卡,蓝按钮支付宝】。其中的 “红按钮信用卡”就是某一种实验号。</p><p>将来我们验证不同版本实验的效果, 就需要通过实验号流量进行统计。</p><h2 id="实验平台的接口设计"><a href="#实验平台的接口设计" class="headerlink" title="实验平台的接口设计"></a>实验平台的接口设计</h2><h2 id="分层实验流量模型"><a href="#分层实验流量模型" class="headerlink" title="分层实验流量模型"></a>分层实验流量模型</h2><p><img src="https://i.loli.net/2020/05/09/Eh862JtjNPmkVCS.png" alt="abtest.png"></p><h2 id="ab实验平台接入"><a href="#ab实验平台接入" class="headerlink" title="ab实验平台接入"></a>ab实验平台接入</h2><h3 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h3><p>实验:实验是指对某一个具体功能的一整套对比方案。对于一般的功能只有两个可选项,就是开启功能,关闭功能。部分功能可能具有多种不同的状态。</p><p>实验策略:用于表示实验中的某一个具体的选择。同一层实验策略之间互斥。</p><p>实验层:这个用于实现不同层的实验叠加,目的是充分利用用户流量来进行功能实验。 </p><p>实验号:用户最终拿到的最终的实验策略的集合</p><h3 id="接口设计和代码"><a href="#接口设计和代码" class="headerlink" title="接口设计和代码"></a>接口设计和代码</h3><p>ab实验本质上是需要实现一个接口</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">get_user_abtest</span>(<span class="params">userid: User</span>) -> HitPath</span><br></pre></td></tr></table></figure><h3 id="用户实验"><a href="#用户实验" class="headerlink" title="用户实验"></a>用户实验</h3><p>我们会根据当前需要进行的实验进行实验分层和隔离,层数和每层的正在执行的实验组会为每个用户分配一个实验号。实验需要有版本设计,每个版本的实验,同一个用户只能获得一个实验号,实验版本更新,用户的实验号随之重新分配。</p><p><strong>实验号的本质是获取一个多维的当前实验配置集合</strong></p><p>例如图中的 A1-B3-C2 就代表一个同时参与了A1,B3,C2三个实验的用户</p><h3 id="多实验部署"><a href="#多实验部署" class="headerlink" title="多实验部署"></a>多实验部署</h3><p>当我们想进行一个新的实验时,只需在对应的实验层增加一种配置。同时发布更新实验版本即可。系统会自动对所有用户进行重新的实验号分配,分配完成后,用户的下次请求即可应用新的实验配置。</p><p>ps: 用户的实验号分配原则可以采用根据用户请求实时计算的方法也可以提前分配好实验流量,两者各有好处</p><h3 id="多实验效果回收和评估"><a href="#多实验效果回收和评估" class="headerlink" title="多实验效果回收和评估"></a>多实验效果回收和评估</h3><p>用户使用的实验号和版本信息会随着请求返回客户端,由客户端进行收集埋点。</p><p>如果需要对比某实验的结果,我们只需要根据用户的实验号,做控制变量的反馈效果对比即可。<br>比如上图中我们对比策略层的实验效果,就需要对比所有xx-xx-C1和xx-xx-C2两类实验号中的数据效果。 为了防止其他变量的影响我们可以更详细的对比A2-B1-C1和 A2-B2-C2这两个实验号中的用户的实验结果</p><p>再如例子中,我们想对比不同颜色按钮的效果就可以拿 所有红色按钮的实验号结果集 和所有的蓝色实验号结果集进行对比</p><h2 id="嵌套分层模型"><a href="#嵌套分层模型" class="headerlink" title="嵌套分层模型"></a>嵌套分层模型</h2><p>其实从ab系统本身来说,还有一种更通用更强大但是也更复杂的实验流量分流模型,可以满足几乎所有的分流实验需求。就是具有嵌套结构的分流模型</p><p>不过这种形式的ab系统往往因为配置复杂,流量太过分散,实际生产中使用较少。读者可以仅作了解</p><p>ps:虽然看上去复杂但是实际上嵌套的分流模型在工程和代码实现上反而更为简单</p><h3 id="分流模型设计"><a href="#分流模型设计" class="headerlink" title="分流模型设计"></a>分流模型设计</h3><p><img src="https://i.loli.net/2021/03/10/7VcMkZzsUoO6uan.png" alt="qiantao.png"></p><h3 id="模型设计要求"><a href="#模型设计要求" class="headerlink" title="模型设计要求"></a>模型设计要求</h3><ul><li><p>嵌套模型中只有层和桶两种流量块形式,桶用来做用户流量的隔离(垂直切割),层用来做业务逻辑隔离(水平隔离)。 需要对比的,流量互斥的具体实验应该放到同一层下不同的桶中</p></li><li><p>组织流量模型的时候,层与桶要合理设计并且交替出现,不能出现层里面有层,或者桶里面有桶的情况</p></li><li><p>不允许跨层进行实验,跨层实验需求可以变相的通过增加分桶来满足</p></li></ul><h2 id="ab实验架构设计"><a href="#ab实验架构设计" class="headerlink" title="ab实验架构设计"></a>ab实验架构设计</h2><p><img src="https://i.loli.net/2021/03/10/CAybXrgzDJnqI9E.png" alt="[email protected]"></p><ol><li>整套ab实验的最终目的是使用量化的手段来分析不同策略的效果,所以实验的最终效果分析十分重要</li><li>ab系统本身应该包含完整的实验发放,实验执行,实验效果回收等一些列流程</li><li>整套ab实验系统涉及到服务端,前端,数据仓库,后台等多个系统</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>用户的ab实验本质上是一个分层的动态配置平台, 用户根据不同的配置执行不同的策略。然后对数据进行收集分析。比较值得注意的是,ab实验平台应作为一个纯粹的实验配置分发平台,不应该耦合任何的业务逻辑,所有业务逻辑应该能通过实验配置进行自定义控制</p>]]></content>
<categories>
<category> 推荐系统 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> A/B实验平台 </tag>
</tags>
</entry>
<entry>
<title>用户特征挖掘的方案</title>
<link href="/2020-04-11-analyze-user-feture/"/>
<url>/2020-04-11-analyze-user-feture/</url>
<content type="html"><![CDATA[<p>文中所有内容均以类似今日头条的内容平台做例子</p><h2 id="整体流程"><a href="#整体流程" class="headerlink" title="整体流程"></a>整体流程</h2><p><img src="https://i.loli.net/2020/05/09/nTbNjk8MtJHic19.png" alt="user-feture.png"></p><h2 id="用户兴趣特征的挖掘"><a href="#用户兴趣特征的挖掘" class="headerlink" title="用户兴趣特征的挖掘"></a>用户兴趣特征的挖掘</h2><p>推荐系统所依赖的数据之中一类很重要的信息是用户到底喜欢什么,这个喜欢的标的物可能是某一类内容, 某一个人,某一个话题相关的内容等,我们需要做的事情就是尽可能的发现用户可能喜欢的事物标签。<br>其实用户的兴趣挖掘说难也难,说容易也容易。容易的地方在于很好入门,你只要知道一个序列的用户行为信息,就可以根据行为信息获得用户的部分兴趣特征。难的地方是你如果想要获得比较完整的,或者比较准确的用户兴趣,其实也是非常有挑战性的工作。<br>我想分享一点我自己在挖掘用户兴趣方面的想法和经验。</p><blockquote><p>ps: 我们这里所提及的用户兴趣统统是指用户的隐式特征,就是从用户行为中提取的非主动操作的信息</p></blockquote><h2 id="行为数据-用户特征矩阵-内容特征匹配"><a href="#行为数据-用户特征矩阵-内容特征匹配" class="headerlink" title="行为数据-用户特征矩阵-内容特征匹配"></a>行为数据-用户特征矩阵-内容特征匹配</h2><p>假设我们拥有一段用户的行为信息序列, 我们可以非常简单的统计出来用户经常观看的文章。<br>但是我们不能向用户推荐已经看过的文章, 所以我们需要挖掘用户观看某文章背后的潜在信息, 并给用户推荐内容比较相似的或者关键词比较相关的文章内容。</p><h3 id="用户特征矩阵的生成"><a href="#用户特征矩阵的生成" class="headerlink" title="用户特征矩阵的生成"></a>用户特征矩阵的生成</h3><p>比如:我们可以基于用户观看的文章背后的标签出现次数进行分数统计,标签每出现一次记一分,获得一个用户对标签的喜好表。如下</p><table><thead><tr><th>用户</th><th>新冠肺炎</th><th>金融政策</th><th>李大霄</th><th>新闻资讯</th></tr></thead><tbody><tr><td>1</td><td>5</td><td>6</td><td>0</td><td>1</td></tr><tr><td>2</td><td>2</td><td>0</td><td>9</td><td>3</td></tr><tr><td>3</td><td>3</td><td>4</td><td>3</td><td>1</td></tr><tr><td>4</td><td>0</td><td>5</td><td>4</td><td>9</td></tr></tbody></table><p>从上面我们可以知道用户1喜欢 金融政策和新冠肺炎相关的内容,用户2可能是李大霄的粉丝,喜欢看李大霄相关的文章。</p><p>为了获取这个列表我们也可以根据用户对不同内容标签的点击率进行归一化的分数统计, 取点击率比较高的标签。一样可以得到蕾丝上面的**<用户, 特征,喜好程度>**的三元序列</p><h3 id="根据特征矩阵进行内容推荐"><a href="#根据特征矩阵进行内容推荐" class="headerlink" title="根据特征矩阵进行内容推荐"></a>根据特征矩阵进行内容推荐</h3><p>然后我们就可以根据未推荐文章中出现的用户喜欢的特征和特征权重对不同文章进行计分, 对内容综合积分,按照分数进行排序输出。</p><table><thead><tr><th>文章</th><th>新冠肺炎</th><th>金融政策</th><th>李大霄</th><th>新闻咨询</th></tr></thead><tbody><tr><td>A</td><td>0</td><td>3</td><td>1</td><td>0</td></tr><tr><td>B</td><td>1</td><td>0</td><td>1</td><td>2</td></tr></tbody></table><p>假设我们有A,B两篇文章,内容标签如上。我们可以对用户1, 2与内容的匹配程度进行分值计算。可以得到如下表格</p><table><thead><tr><th>用户/文章</th><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>3 x 6 = 18</td><td>5 x 1+ 2 x 1 = 7</td></tr><tr><td>2</td><td>9 x 1 = 9</td><td>1 x 2 + 1 x 9 + 2 x 3 = 17</td></tr></tbody></table><p>很容的知道应该给用户1 推荐 A 文章, 给用户2推荐B文章。<br>我们也可以采用其他的综合考虑权重的计分方法, 但是核心要点都是两个,一个是获得用户与特征的矩阵, 一个是获得内容和特征的矩阵。鉴于计算量巨大,可以使用 矩阵相乘的算法进行加速。</p><h2 id="行为数据-x2F-用户特征-用户相似度-内容推荐"><a href="#行为数据-x2F-用户特征-用户相似度-内容推荐" class="headerlink" title="行为数据/用户特征-用户相似度-内容推荐"></a>行为数据/用户特征-用户相似度-内容推荐</h2><p>我们也可以根据用户经常观看的信息, 寻找与用户比较相似的用户群体,然后假定用户与相似的用户群具有相似喜好, 从用户群体的共同喜欢内容中挑选出来一些用户未观看的推荐给用户</p><h3 id="计算用户的相似用户"><a href="#计算用户的相似用户" class="headerlink" title="计算用户的相似用户"></a>计算用户的相似用户</h3><p>我们使用Jaccard系数来表征用户的相似度</p><ul><li>使用用户行为计算相似度</li></ul><p>我们可以使用用户行为信息如此计算两个用户的相似度</p><p>$$Y(similar)=\cfrac{U1 ∩ U2}{U1 ∪ U2}\tag{1}$$</p><p>使用用户1 和用户2 的观看内容中相交的部分 / 用户1 和用户2 内容的并集, 能得到两个人都喜欢的内容,在两个人观看总内容的一个比例</p><ul><li>使用用户特征计算相似度</li></ul><p>我们也可以通过用户身上的标签,比如 ,使用的手机型号,年龄,性别,地区等信息。 一样采用如下公式进行用户标签相似度的计算。计算结果与上面用户行为相似度结果一样 </p><p>$$Y(similar)=\cfrac{U1 ∩ U2}{U1 ∪ U2}\tag{1}$$</p><h3 id="根据相似用户进行推荐"><a href="#根据相似用户进行推荐" class="headerlink" title="根据相似用户进行推荐"></a>根据相似用户进行推荐</h3><p>这个比例可以作用两个用户相似度的评价标准, 拿到用户的相似用户集合以后,通过统计相似用户已观看列表中的的内容的出现次数。可以得到如下表格</p><table><thead><tr><th align="center">用户/内容</th><th>A</th><th>B</th></tr></thead><tbody><tr><td align="center">1</td><td>5</td><td>2</td></tr><tr><td align="center">2</td><td>9</td><td>1</td></tr></tbody></table><p>选择出现次数多的进行推荐即可。</p><p>也可以进一步利用相似用户的内容出现次数,通过统计去更新用户的特征矩阵中的(用户->特征)的分值。然后再根据特征的加权获取最终的推荐结果</p><h2 id="用户数据-用户特征-用户分群-用户相似推荐"><a href="#用户数据-用户特征-用户分群-用户相似推荐" class="headerlink" title="用户数据-用户特征-用户分群-用户相似推荐"></a>用户数据-用户特征-用户分群-用户相似推荐</h2><p>我们也可以根据用户本身的特征,对用户进行分类。例如:女性用户,30-35岁, 使用安卓手机,经常在晚上6-9点使用app等特征对相似的用户进行分群</p><p>然后统计分析该分群用户的行为记录。采用类似于上面用户相似度的统计方法,获得用户对内容或者对特征的分数矩阵结果</p><h2 id="综合方案"><a href="#综合方案" class="headerlink" title="综合方案"></a>综合方案</h2><p>我们在实际的生产中可以综合以上各种策略, 获取用户的特征矩阵并对内容进行计算分数。在根据实际场景中的效果进行不同权重和比例的动态调整。<br>一般来说是可以获得不错的效果的。</p>]]></content>
<categories>
<category> 推荐系统 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> 特征工程 </tag>
<tag> jaccard系数 </tag>
</tags>
</entry>
<entry>
<title>基线融合排序算法</title>
<link href="/2020-03-22-baseline-merge-recommendation/"/>
<url>/2020-03-22-baseline-merge-recommendation/</url>
<content type="html"><![CDATA[<h1 id="推荐系统的排序策略"><a href="#推荐系统的排序策略" class="headerlink" title="推荐系统的排序策略"></a>推荐系统的排序策略</h1><p>排序策略起到的作用</p><ol><li>将多种召回集的结果进行融合,挑出少量的推荐结果内容</li><li>返回结果应平衡多种来源的密度分布</li><li>根据排版要求进行精排序</li></ol><h1 id="问题和需求描述"><a href="#问题和需求描述" class="headerlink" title="问题和需求描述"></a>问题和需求描述</h1><p>假设我们现在拥有3个召回策略的来源的数据分别为</p><ol><li>个性化内容约 200 个</li><li>新内容约 2000 个</li><li>热门内容约 200 个</li></ol><p>我们希望达到如下目标</p><ol><li>从这么多用户可能感兴趣的内容中挑出来10个内容返回</li><li>用户已经推荐过的不再进行推荐</li><li>用户的推荐结果中,结果保持多样性各种来源按照一定比例,个性化:新内容:热门内容比例为 3:4:3, 某种题材比例不足采用其他内容进行填充<figure class="highlight scheme"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">;用scheme代码描述如下</span></span><br><span class="line">(<span class="name"><span class="built_in">define</span></span> (<span class="name">recommendation</span> user)</span><br><span class="line">(<span class="name"><span class="built_in">lambda</span></span> (user)</span><br><span class="line"> (<span class="name"><span class="built_in">cons</span></span> (<span class="name"><span class="built_in">cond</span></span> (<span class="name">personal?</span> user) (<span class="name">get-personal</span> user)</span><br><span class="line"> (<span class="name"><span class="built_in">else</span></span> '()))</span><br><span class="line"> (<span class="name"><span class="built_in">if</span></span> hottest? (<span class="name">get-hottest</span>) '())</span><br><span class="line"> (<span class="name"><span class="built_in">if</span></span> lattest? (<span class="name">get-lattest</span>) '())</span><br><span class="line"> (<span class="name"><span class="built_in">if</span></span> hot-spot? (<span class="name">get-hot-spot</span>) '()))))</span><br></pre></td></tr></table></figure><h1 id="朴素处理"><a href="#朴素处理" class="headerlink" title="朴素处理"></a>朴素处理</h1></li><li>直接从个性化内容里面 取10<em>0.3 个没有推荐过的,从新内容里面取 10 * 0.4 个, 从热门里面取 10</em> 0.3 个放到一个队列返回<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">rec</span></span>(a:<span class="type">Set</span>[个性化], b: <span class="type">Set</span>[新], c: <span class="type">Set</span>[热门],d: <span class="type">Set</span>[看过]): <span class="type">Set</span>[<span class="number">10</span>] = {</span><br><span class="line"> ans = <span class="type">Set</span>()</span><br><span class="line"> ans += a.forEach(v -> !d.contains(v)).take(<span class="number">3</span>)</span><br><span class="line"> ans += b.forEach(v -> !d.contains(v)).take(<span class="number">4</span>)</span><br><span class="line"> ans += c.forEach(v -> !d.contains(v)).take(<span class="number">3</span>)</span><br><span class="line"> ans</span><br><span class="line">}</span><br></pre></td></tr></table></figure>这种处理方法在遇到 某种策略内容不够的时候 就需要手动做判断,再从其他两种内容里面获取<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> ans.size() < rec {</span><br><span class="line"> ans += b.forEach(v -> !d.contains(v)).take(<span class="number">4</span>)</span><br><span class="line">} </span><br></pre></td></tr></table></figure>假如其他集合还是不够的话还要继续处理,代码复杂度就直线上升</li></ol><h1 id="基线排序法"><a href="#基线排序法" class="headerlink" title="基线排序法"></a>基线排序法</h1><p>先选择一个数量较多的内容集合作为基线:例如 新内容。将基线集合的数据通过排名映射到[0,1]的空间之内</p><p>映射函数: $x$ 为在内容在该渠道召回分数, $rank$为内容在该渠道当前排名, $size$ 为该渠道内容总数</p><p>$$Score =1-\cfrac{x\cdot rank}{size}$$</p><figure class="highlight scheme"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">; 新内容:假设我们有2000个新内容,那第400个的得分应该为</span></span><br><span class="line"> 第<span class="number">400</span>个</span><br><span class="line"> |</span><br><span class="line"> V</span><br><span class="line"> | | | | | |</span><br><span class="line"> <span class="number">1.0</span> <span class="number">0.8</span> <span class="number">0.6</span> <span class="number">0.4</span> <span class="number">0.2</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><p>然后我们把其他召回集内容也通过某个积分函数均匀散列在以上区间</p><p>由于我们要满足 3:4:3的概率所以我们要求在原来的40个新内容的范围里面,均匀混进去30个热门和30个个性化内容,这样比例就满足了</p><p>也就是说我们要满足如下公式, 由于内容密度要满足一定条件(3:4:3)</p><p>计算热门内容的分数范围的公示如下</p><p>$x = 0.02\cdot\cfrac{200}{30}=0.133$</p><p>也就是说热门内容分布的边界范围应该为 [1,0.867]</p><figure class="highlight scheme"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">; 热门内容</span></span><br><span class="line"> 第<span class="number">30</span>个 第<span class="number">100</span>个 第<span class="number">200</span>个</span><br><span class="line"> | | |</span><br><span class="line"> V V V</span><br><span class="line"> | | | | </span><br><span class="line"> <span class="number">1.0</span> <span class="number">0.98</span> <span class="number">0.935</span> <span class="number">0.867</span></span><br></pre></td></tr></table></figure><p>个性化内容也是同理<br>最后三者进行融合以后重新排序结果如下</p><figure class="highlight scheme"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">; 融合之后的内容集合分布</span></span><br><span class="line"> 第<span class="number">800</span>个</span><br><span class="line"> |</span><br><span class="line"> V</span><br><span class="line"> | | | | | |</span><br><span class="line"> <span class="number">1.0</span> <span class="number">0.8</span> <span class="number">0.6</span> <span class="number">0.4</span> <span class="number">0.2</span> <span class="number">0</span></span><br><span class="line">[<span class="name">1,</span> <span class="number">0.8</span>]的空间内同时含有 <span class="number">400</span>个新内容,<span class="number">200</span>个热门内容和<span class="number">200</span>个个性化内容</span><br></pre></td></tr></table></figure><p>这种情况下顺序取前10个内容, 三种来源的比例就是 3:4:3<br>同样在这种算法情况下, 无论是调整 结果数量, 还是调整比例,都可以用同一个逻辑轻松实现需求, 实现优雅,性能也得到了保证</p>]]></content>
<categories>
<category> 推荐系统 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> 混排 </tag>
</tags>
</entry>
<entry>
<title>文本内容价值评估的方法</title>
<link href="/2020-03-11-content-evaluate/"/>
<url>/2020-03-11-content-evaluate/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在推荐系统的业务场景中我们的最终目的是把优质的内容从内容池中挑出来推荐给用户,以期达到如下目的</p><ol><li>降低用户获取信息的成本</li><li>提供给用户平均质量更高的内容,提升用户活跃度和留存</li><li>助力运营等提高内容分发的效率</li></ol><p>如何评价一个内容的质量好坏就成了我们面对的一个重要问题</p><h2 id="如何找到用技术手段评估内容的质量"><a href="#如何找到用技术手段评估内容的质量" class="headerlink" title="如何找到用技术手段评估内容的质量"></a>如何找到用技术手段评估内容的质量</h2><p>一个内容的质量其实是一个动态变化的评价,与内容本身,所处的环境和面向的对象有很大关系,同一个内容对不同的人价值是不同的,同一个内容对相同的人在不同的时间价值也是不同的</p><p>所以我们可以通过技术手段尽可能的收集相关的信息,采用一些数学方法,找到相对比较合理的内容价值评估的方法</p><h2 id="内容本身"><a href="#内容本身" class="headerlink" title="内容本身"></a>内容本身</h2><h4 id="挖掘内容本身的特征属性进行评估"><a href="#挖掘内容本身的特征属性进行评估" class="headerlink" title="挖掘内容本身的特征属性进行评估"></a>挖掘内容本身的特征属性进行评估</h4><p>假如一个内容A点击率比较高,一定是其中含有吸引人的特征,我们可以思考可能的原因是什么?<br>有可能是作者文字功底比较好,有可能是作者比较出名, 有可能是里面有一些特征一看大家就想看。比如 标题很有吸引力 。<br>我们可以根据内容的 发布时间, 作者,含有的关键词,视频的清晰度,内容的长度,等各种属性给内容计算一个基础的内容得分。</p><table><thead><tr><th>内容类型</th><th>关键因素</th></tr></thead><tbody><tr><td>长文本内容</td><td>作者,标题,插图,关键词,主题</td></tr><tr><td>短文本内容</td><td>关键词,主题,作者</td></tr><tr><td>视频内容</td><td>标题,封面,视频大小,清晰度</td></tr></tbody></table><p>如此一来我们就可以对各种内容做一个基本的价值评估,可以在视频刚发布,还未获得曝光和点击的时候对视频有一个大概的评价。这一部分的评估特点是分数只跟内容本身有关,且一旦内容发布就很少会发生变化,对应的分数我们称之为静态内容质量分。</p><h4 id="通过用户行为来辅助评估"><a href="#通过用户行为来辅助评估" class="headerlink" title="通过用户行为来辅助评估"></a>通过用户行为来辅助评估</h4><p>我们通过直觉可以很明显的知道用户点击率高的内容大概率会是吸引人的内容。由此我们可以对内容的点击率进行统计,根据点击率给予内容一定的分数,点击率越高,内容分数越高, 我们认为内容越优质。</p><p>这种情况是最直观的也最简单的, 但是我们会遇到一些问题</p><ul><li><p>问题1: 如果一个内容刚刚发布,还没有被展示或者被点击,此时没有用户行为数据,该如何评估内容的质量呢, 这部分内容因为展示次数少,误差较大</p></li><li><p>问题2: 如果一个内容A 刚刚发布不久,被展示了10次,点击了5次, 点击率50%, 另一个内容B发布了2天,被展示了20万次,点击了5万次,点击率 25%, 是否能说明A内容比B内容优秀</p></li></ul><p>相信从上面两个问题也能看出来了,单纯的依靠点击率是肯定不合适的,既会受到一定的限制,又会造成得分结果的不稳定。</p><p>处理问题1,2的方法也比较简单,只需要按照数据分布情况给一个预估的分数默认值作为偏置 总分数 = (偏置数 + 点击率得分)(解决问题1), 并加上一定的统计门槛就可以了(解决问题1)。对于问题2我们采用多种维度而不仅仅是通过点击数量和点击率,同时考虑增加速度,受众人群等就能得到一个比较理想的结果</p><h2 id="环境因素"><a href="#环境因素" class="headerlink" title="环境因素"></a>环境因素</h2><p>内容的质量表现还会随着时间和环境因素而变化。<br>比如股市的资讯类消息, 就是在发布的12小时内价值比较高,而且其价值会随着时间逐渐减少,3天以上基本就毫无价值了。<br>内容受到环境影响也很明显,这次的新冠疫情导致口罩,病毒等相关的内容受到了极大的关注。可能在平时来说,口罩等关键词并不会有如此大的权重,但是在疫情期间,全民关注,这个时候就应该根据环境适当的给热门词增加权重表现<br>环境和内容的匹配主要还是要依据统计数据来预估一个特征在环境中的匹配度,然后根据这个匹配度对内容进行评估,在根据用户对内容后续的行为表现来调整这个匹配度的计算规则。</p><p>由于内容热度和环境匹配度这部分的内容分数会随着环境热点,发布时间等发生变化我们称之为动态内容质量分。</p><h2 id="对象信息"><a href="#对象信息" class="headerlink" title="对象信息"></a>对象信息</h2><h4 id="考虑目标用户特征"><a href="#考虑目标用户特征" class="headerlink" title="考虑目标用户特征"></a>考虑目标用户特征</h4><p>同样的内容对不同的用户也会有不同的价值。同一篇手机评测内容,对魅族手机用户和小米手机用户分别展示1000次收到的点击返回结果可能就有很大不同, 对数码爱好者和小白分开推送效果也会不一样。<br>我们如果要对用户和内容匹配一方面要获取到用户的用户画像信息,一方面要对内容做内容特征分析, 然后再进行match。</p><p>这个时候内容的质量又跟目标用户的匹配度相关,而且这部分往往还是影响用户对内容行为的最大影响因素。</p><p>用户喜好特征的收集涉及到用户画像,用户画像的特征最好和内容特征最好能有一定的对应关系。比如某用户身处上海地区,那很有可能就喜欢看上海地区的新闻,如果内容里面出现了上海的某个区也应该通过一定的处理手段变成跟上海相关的内容,特征维度尽可能跟用户画像一致。</p><h2 id="内容质量相关影响因素总结"><a href="#内容质量相关影响因素总结" class="headerlink" title="内容质量相关影响因素总结"></a>内容质量相关影响因素总结</h2><p><img src="https://i.loli.net/2021/03/28/ltcSf3ep1yVENW8.jpg" alt="内容特征分析"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>上面的几种方法都有各自的优点和缺点,多种评估策略一起配合使用可以一定程度上增强价值评估的可信度。综合推荐就是同时考虑 用户, 环境,时间,内容本身等多种因素,根据情况实时的计算,不同属性的得分之间可以根据统计设定不同的权重,并根据实际表现进行权重调整。</p><p>内容的价值评估是一个跟场景,用户,内容都相关的事情, 没有办法使用一个统一的,固定的算法或者模型对内容进行价值评估, 但是我们可以根据现有的用户行为, 环境特征, 内容特征等,尽可能的覆盖所有可能的影响因子, 给出一个相对比较可信的价值度量标准</p>]]></content>
<categories>
<category> 推荐系统 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> 文本分析 </tag>
</tags>
</entry>
<entry>
<title>纯小白向-5分钟搭建个人博客</title>
<link href="/2020-02-01-build-website-in-5-min/"/>
<url>/2020-02-01-build-website-in-5-min/</url>
<content type="html"><![CDATA[<h1 id="五分钟搭建一个博客网站(mac-os)"><a href="#五分钟搭建一个博客网站(mac-os)" class="headerlink" title="五分钟搭建一个博客网站(mac os)"></a>五分钟搭建一个博客网站(mac os)</h1><p>这是一个面向<strong>不懂计算机的纯小白</strong>的搭建个人网站的教程,需要的东西如下</p><ol><li>一台能联网的电脑</li><li>一个知道什么是文件以及会创建文件夹的人</li></ol><h2 id="第一步:必备软件和文件下载"><a href="#第一步:必备软件和文件下载" class="headerlink" title="第一步:必备软件和文件下载"></a>第一步:必备软件和文件下载</h2><p>我们的网站的运行需要一个软件的支持,这个软件就是docker,使用doker能让我们的安装过程非常方便 </p><h3 id="1-下载docker软件"><a href="#1-下载docker软件" class="headerlink" title="1. 下载docker软件"></a>1. 下载docker软件</h3><p>目标:这一步的目的是下载必备的软件docker并安装启动docker</p><p>下载地址: <a href="https://www.docker.com/products/docker-desktop">https://www.docker.com/products/docker-desktop</a></p><p><img src="https://i.loli.net/2020/11/13/eTVfZkXJwyIj4BH.png" alt="docker-1.png"></p><h3 id="2-获取搭建网站需要的文件"><a href="#2-获取搭建网站需要的文件" class="headerlink" title="2. 获取搭建网站需要的文件"></a>2. 获取搭建网站需要的文件</h3><p>目标:这一步的目的用一个文件告诉docker我们要搭建一个网站,这个文件名字必须叫 <code>docker-compose.yml</code></p><p>文件我已经提前准备好了,直接下载就行了</p><p>地址:<a href="https://raw.githubusercontent.com/leriou/docker-env/master/docker-compose/wordpress/docker-compose.yml">https://raw.githubusercontent.com/leriou/docker-env/master/docker-compose/wordpress/docker-compose.yml</a> </p><p>可以直接使用迅雷啥的下载,也可以使用以下命令直接下载</p><p>打开 终端软件(Terminal),</p><p><img src="https://i.loli.net/2020/11/13/REnJX6AZ5r7LKIM.png" alt="docker-2.png"></p><p>粘贴命令:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget --no-check-certificate https://raw.githubusercontent.com/leriou/docker-env/master/docker-compose/wordpress/docker-compose.yml</span><br></pre></td></tr></table></figure><p><img src="https://i.loli.net/2020/11/13/qxfZOnI1aprD4uJ.png" alt="docker-3.png"></p><p>并回车,等待下载文件</p><hr><p>到此,我们的准备工作就结束了</p><p>总结一下,我们需要一个docker软件和一个文件docker-compose.yml在docker上搭建一个网站</p><h2 id="第二步:启动网站"><a href="#第二步:启动网站" class="headerlink" title="第二步:启动网站"></a>第二步:启动网站</h2><p>打开终端, 执行命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls </span><br></pre></td></tr></table></figure><p>检查列出的文件中是否有docker-compose.yml</p><p><img src="https://i.loli.net/2020/11/13/56CnuP2qjmNgeDE.png" alt="docker-4.png"></p><p>执行命令 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p><img src="https://i.loli.net/2020/11/13/EMOW7JcrC2v5VPA.png" alt="docker-5.png"></p><p><img src="https://i.loli.net/2020/11/13/W8OUEBsc1GzTaZi.png" alt="docker-6.png"></p><p>等待下载必备的东西完毕就可以使用网站了</p><h2 id="第三步:管理和使用网站"><a href="#第三步:管理和使用网站" class="headerlink" title="第三步:管理和使用网站"></a>第三步:管理和使用网站</h2><h3 id="测试网站是否成功"><a href="#测试网站是否成功" class="headerlink" title="测试网站是否成功"></a>测试网站是否成功</h3><p>打开浏览器,访问 <a href="http://localhost:8077/">http://localhost:8077</a> </p><p><img src="https://i.loli.net/2020/11/13/Ikei31hNYpXcSLW.png" alt="docker-9.png"></p><h3 id="设置网站语言和账号密码"><a href="#设置网站语言和账号密码" class="headerlink" title="设置网站语言和账号密码"></a>设置网站语言和账号密码</h3><p>就可以设置网站使用的语言和账号密码</p><p><img src="https://i.loli.net/2020/11/13/ExKfGXa24y6dPno.png" alt="docker-7.png"></p><p><img src="https://i.loli.net/2020/11/13/zbft3SWr962ohVP.png" alt="docker-8.png"></p><h3 id="设置网站的主题,变得更好看"><a href="#设置网站的主题,变得更好看" class="headerlink" title="设置网站的主题,变得更好看"></a>设置网站的主题,变得更好看</h3><p>网站是基于Wordpress制作的,所以可以直接从主题库选择喜欢的主题换上就可以了</p><p><img src="https://i.loli.net/2020/11/13/PLS3IAJKamhvOeG.png" alt="docker-10.png"></p>]]></content>
<tags>
<tag> wordpress </tag>
</tags>
</entry>
<entry>
<title>双读和双写的数据库迁移方案</title>
<link href="/2020-01-11-data-migration/"/>
<url>/2020-01-11-data-migration/</url>
<content type="html"><![CDATA[<h1 id="两种不停机的数据库迁移的方案"><a href="#两种不停机的数据库迁移的方案" class="headerlink" title="两种不停机的数据库迁移的方案"></a>两种不停机的数据库迁移的方案</h1><p>我们在工作中可能会遇到业务升级或者是数据库存储更换选型或者其他的需要做数据迁移的需求</p><p>有可能是从oracle更换到mysql这种异构数据的迁移或者是从mysql5.7升级到mysql8这种大版本数据的迁移</p><p>这种时候我们就需要一套数据库迁移的方案</p><p>我们希望</p><ol><li>不能丢数据</li><li>尽量不影响业务</li></ol><h2 id="停机重启方案"><a href="#停机重启方案" class="headerlink" title="停机重启方案"></a>停机重启方案</h2><p>最简单的方案就是对数据库停机, 然后copy旧数据到新的数据库</p><p>这种数据同步方案非常简单,但是有个致命的问题就是业务要中断,所以肯定会被否掉</p><h2 id="不停机方案"><a href="#不停机方案" class="headerlink" title="不停机方案"></a>不停机方案</h2><h3 id="双写机制"><a href="#双写机制" class="headerlink" title="双写机制"></a>双写机制</h3><p>这种方案过程如下</p><ol><li>先改造我们的数据写入端, 使数据同时写入旧数据库和新数据库</li><li>对存量数据进行不停机的迁移</li><li>等到双写服务运行一段时间,再次进行旧数据和新数据的完全同步</li><li>完全切换读取的数据源为新数据库, 关闭旧数据库的写入和读取,下线旧数据库</li></ol><p>该方案比较复杂: 适合业务要求高的事务型数据库的迁移(我们前东家在做oracle到mysql的迁移就采用类似的方案)</p><h3 id="渐进式双读"><a href="#渐进式双读" class="headerlink" title="渐进式双读"></a>渐进式双读</h3><p>这种方案我们采用渐进式的双读方案</p><ol><li>所有新写入的数据都完全写到新数据库</li><li>读取程序先读新数据库,新数据库中不存在的再读取老数据库, 如果老库存在就把老的库的数据迁移到新的数据库中</li><li>等到老的数据库中数据量变为0, 或者到达一个非常低的阈值, 就进行老数据库的完全迁移和下线</li></ol><p>该方案适合读取内容相对简单的k-v数据库之间的迁移(redis中的渐进式rehash就是采用这种机制)</p>]]></content>
<categories>
<category> 架构方案 </category>
</categories>
<tags>
<tag> mysql </tag>
</tags>
</entry>
<entry>
<title>2020年读书记录</title>
<link href="/2020-01-01-record/"/>
<url>/2020-01-01-record/</url>
<content type="html"><![CDATA[<h1 id="2020"><a href="#2020" class="headerlink" title="2020"></a>2020</h1><h1 id="1月"><a href="#1月" class="headerlink" title="1月"></a>1月</h1><p>看书:</p><ul><li><input disabled="" type="checkbox"> 影响力</li><li><input disabled="" type="checkbox"> 文明的冲突</li><li><input disabled="" type="checkbox"> java编程的逻辑</li><li><input checked="" disabled="" type="checkbox"> 投资者的敌人</li></ul><h1 id="2月"><a href="#2月" class="headerlink" title="2月"></a>2月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 海龟交易法则</li><li><input disabled="" type="checkbox"> 债务危机 </li><li><input checked="" disabled="" type="checkbox"> 影响力</li></ul><h1 id="3月"><a href="#3月" class="headerlink" title="3月"></a>3月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 文明的冲突</li><li><input checked="" disabled="" type="checkbox"> java编程的逻辑</li><li><input checked="" disabled="" type="checkbox"> 从一到无穷大</li><li><input checked="" disabled="" type="checkbox"> 未来简史:从智人到智神</li></ul><h1 id="4月"><a href="#4月" class="headerlink" title="4月"></a>4月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 算法导论</li><li><input disabled="" type="checkbox"> 深入理解jvm虚拟机</li><li><input checked="" disabled="" type="checkbox"> rust程序编程语言</li><li><input checked="" disabled="" type="checkbox"> rust primer</li><li><input disabled="" type="checkbox"> Structure and Interpretation of Computer Program(lisp版本)</li></ul><h1 id="5月"><a href="#5月" class="headerlink" title="5月"></a>5月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> Structure and Interpretation of Computer Program(lisp版本)</li></ul><h1 id="6月"><a href="#6月" class="headerlink" title="6月"></a>6月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input checked="" disabled="" type="checkbox"> SICP(python)</li><li><input disabled="" type="checkbox"> Structure and Interpretation of Computer Program(lisp版本)</li><li><input checked="" disabled="" type="checkbox"> rust编程之道</li></ul><h1 id="7月"><a href="#7月" class="headerlink" title="7月"></a>7月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input checked="" disabled="" type="checkbox"> 这就是搜索引擎</li><li><input checked="" disabled="" type="checkbox"> 大数据搜索引擎原理分析</li><li><input disabled="" type="checkbox"> Algorithms 4th edition</li></ul><h1 id="8月"><a href="#8月" class="headerlink" title="8月"></a>8月</h1><p>Books:</p><ul><li><p><input disabled="" type="checkbox"> 领域驱动设计</p></li><li><p><input checked="" disabled="" type="checkbox"> 设计数据密集型应用(round 2)</p></li><li><p><input checked="" disabled="" type="checkbox"> Java并发编程之美</p></li><li><p><input disabled="" type="checkbox"> Algorithms 4th edition</p></li></ul><h1 id="9月"><a href="#9月" class="headerlink" title="9月"></a>9月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input disabled="" type="checkbox"> 检索核心技术20讲(在线课程)</li><li><input disabled="" type="checkbox"> Algorithms 4th edition</li></ul><h1 id="10月"><a href="#10月" class="headerlink" title="10月"></a>10月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input disabled="" type="checkbox"> Natural Language Processing with PyTorch</li></ul><h1 id="11月"><a href="#11月" class="headerlink" title="11月"></a>11月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 领域驱动设计</li><li><input disabled="" type="checkbox"> 认识商业</li><li><input disabled="" type="checkbox"> Streaming System</li></ul><h1 id="12月"><a href="#12月" class="headerlink" title="12月"></a>12月</h1><p>Books:</p><ul><li><input disabled="" type="checkbox"> 深入理解计算机系统</li></ul>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
</entry>
<entry>
<title>Elasticsearch中的常用查询语句示例</title>
<link href="/2019-12-25-es-dsl/"/>
<url>/2019-12-25-es-dsl/</url>
<content type="html"><![CDATA[<h1 id="前记"><a href="#前记" class="headerlink" title="前记"></a>前记</h1><p>由于公司内部的研发团队越来越多的接触到复杂的查询需求,也越来越多的依赖大数据部门提供的es搜索引擎提供查询服务</p><p>特此整理一些es常用的查询语句用于培训,目的在于帮助其他不熟悉es的同学快速熟悉es的dsl语句的编写</p><h1 id="数据初始化和准备工作"><a href="#数据初始化和准备工作" class="headerlink" title="数据初始化和准备工作"></a>数据初始化和准备工作</h1><h2 id="es和kibana安装"><a href="#es和kibana安装" class="headerlink" title="es和kibana安装"></a>es和kibana安装</h2><p>可以从 <a href="https://github.com/leriou/docker-env/tree/master/elasticsearch">https://github.com/leriou/docker-env/tree/master/elasticsearch</a></p><p>直接使用docker编排文件构建基于docker的es本地服务</p><p><code>docker-compose up -d</code> 启动服务,启动成功访问kibana命令控制台</p><p>es: elasticsearch 实例,主要存储和搜索引擎<br>kibana: elasticsearch的一个web层GUI客户端,可以方便的查询es里面的数据,这些年用了一大堆各种各样的第三方GUI,用来用去还是kibana最方便</p><h2 id="准备测试数据"><a href="#准备测试数据" class="headerlink" title="准备测试数据"></a>准备测试数据</h2><p>先准备一部分数据用于演示</p><p>创建测试用的索引<code>put test_idx</code></p><p>创建测试文档</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">put test_idx/_doc/1</span><br><span class="line">{</span><br><span class="line">"name":"111",</span><br><span class="line">"tags":["a","c","d"]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以下是用于示范的数据文档</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> "_index": "test_idx",</span><br><span class="line"> "_type": "_doc",</span><br><span class="line"> "_id": "8",</span><br><span class="line"> "_score": 1,</span><br><span class="line"> "_source": {</span><br><span class="line"> "name": "a888",</span><br><span class="line"> "tags": [</span><br><span class="line"> "ab"</span><br><span class="line"> ],</span><br><span class="line"> "age": 7,</span><br><span class="line"> "content": "明天"</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "_index": "test_idx",</span><br><span class="line"> "_type": "_doc",</span><br><span class="line"> "_id": "2",</span><br><span class="line"> "_score": 1,</span><br><span class="line"> "_source": {</span><br><span class="line"> "name": "222",</span><br><span class="line"> "tags": [</span><br><span class="line"> "a"</span><br><span class="line"> ],</span><br><span class="line"> "age": 18</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "_index": "test_idx",</span><br><span class="line"> "_type": "_doc",</span><br><span class="line"> "_id": "4",</span><br><span class="line"> "_score": 1,</span><br><span class="line"> "_source": {</span><br><span class="line"> "name": "444",</span><br><span class="line"> "tags": [</span><br><span class="line"> "d",</span><br><span class="line"> "i"</span><br><span class="line"> ],</span><br><span class="line"> "age": 0,</span><br><span class="line"> "content": "明天北京的天气很好,是个大晴天 "</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "_index": "test_idx",</span><br><span class="line"> "_type": "_doc",</span><br><span class="line"> "_id": "1",</span><br><span class="line"> "_score": 1,</span><br><span class="line"> "_source": {</span><br><span class="line"> "name": "111",</span><br><span class="line"> "tags": [</span><br><span class="line"> "a",</span><br><span class="line"> "c",</span><br><span class="line"> "d"</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "_index": "test_idx",</span><br><span class="line"> "_type": "_doc",</span><br><span class="line"> "_id": "3",</span><br><span class="line"> "_score": 1,</span><br><span class="line"> "_source": {</span><br><span class="line"> "name": "333",</span><br><span class="line"> "tags": [</span><br><span class="line"> "e",</span><br><span class="line"> "f"</span><br><span class="line"> ],</span><br><span class="line"> "age": 18,</span><br><span class="line"> "content": "明天上海的天气不好,有小雨"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br></pre></td></tr></table></figure><h1 id="简单查询示范"><a href="#简单查询示范" class="headerlink" title="简单查询示范"></a>简单查询示范</h1><h2 id="n对n查询"><a href="#n对n查询" class="headerlink" title="n对n查询"></a>n对n查询</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">1对1匹配查询:适用于 查询条件为1个值,被查询对象字段也为1个值的情况</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> name = 111</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "term": {</span><br><span class="line"> "name": {</span><br><span class="line"> "value": 111</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">1对多查询:适用于 查询条件为一个,查询值为[]的情况</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> find_in_set(tags, <span class="string">"a"</span>)</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags.keyword": ["a"]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">多对1查询:适用于查询条件为[],被查询字段为1个值的情况</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> age <span class="keyword">in</span> (0,18)</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "age": [</span><br><span class="line"> 0,</span><br><span class="line"> 18</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">多对多查询:适用于查询条件为 [], 查询值也为[] 的情况</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags.keyword": ["a","e"]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="万能匹配-match"><a href="#万能匹配-match" class="headerlink" title="万能匹配-match"></a>万能匹配-match</h2><p><code>match</code>查询可以用于多种查询用途,常见的全文检索,关键词匹配等都使用该方法,是es中最常用的查询</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> tags.contains(<span class="string">"a"</span>)</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "match": {</span><br><span class="line"> "tags": "a"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> age = 18</span> </span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "match": {</span><br><span class="line"> "age": 18</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">where</span> content like <span class="string">"%天气%"</span></span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "match": {</span><br><span class="line"> "content": "天气"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="其他常用查询(range-exist-a-and-b等)"><a href="#其他常用查询(range-exist-a-and-b等)" class="headerlink" title="其他常用查询(range, exist, a and b等)"></a>其他常用查询(range, exist, a and b等)</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">范围查询 <span class="built_in">where</span> a between 10 and 20</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "range": {</span><br><span class="line"> "age": {</span><br><span class="line"> "gte": 10,</span><br><span class="line"> "lte": 20</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">字段是否存在 <span class="built_in">where</span> content not null</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "exists":{</span><br><span class="line"> "field":"content"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">多条件查询 <span class="built_in">where</span> (age between 10 and 20) and content like <span class="string">"上海"</span></span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "bool": {</span><br><span class="line"> "must": [</span><br><span class="line"> {</span><br><span class="line"> "range": {</span><br><span class="line"> "age": {</span><br><span class="line"> "gte": 10,</span><br><span class="line"> "lte": 20</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "match": {</span><br><span class="line"> "content": "上海"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="查询原理解析"><a href="#查询原理解析" class="headerlink" title="查询原理解析"></a>查询原理解析</h2><h3 id="es文档字段的存储逻辑"><a href="#es文档字段的存储逻辑" class="headerlink" title="es文档字段的存储逻辑"></a>es文档字段的存储逻辑</h3><p>es中的字段看起来有多种数据结构,实际抽象出来只有一种数据结构就是 <code>k-v</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">类似</span><br><span class="line">`tags:["a","b","c"]`</span><br><span class="line">的数组数据结构在es中实际上是</span><br><span class="line">{</span><br><span class="line">tags.a : a </span><br><span class="line">tags.b : b</span><br><span class="line">tags.c : c </span><br><span class="line">}</span><br><span class="line">这样的分解成多个字段进行存储的</span><br></pre></td></tr></table></figure><h3 id="match为什么可以做到万能查询"><a href="#match为什么可以做到万能查询" class="headerlink" title="match为什么可以做到万能查询"></a><code>match</code>为什么可以做到万能查询</h3><p>是因为match在查询时候会对查询条件进行分词</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">不分词的查询查不到 tags:[<span class="string">"a"</span>,<span class="string">"b"</span>]的值, 只能查询到 tags:[<span class="string">"ab"</span>]的值</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "match": {</span><br><span class="line"> "tags.keyword": "ab"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>ps: es自带了一部分内容格式转换规则,类似 type = “VIDEO” 这种字段如果要使用term查询的话需要用 term:{type.keyword:”VIDEO”}, 因为大写的字段值会被默认分词, 如果是type =”video”这种小写 就可以用 term:{type:”video”}来进行匹配</p></blockquote><h1 id="分值相关查询"><a href="#分值相关查询" class="headerlink" title="分值相关查询"></a>分值相关查询</h1><p>有时候我们希望按照某种特殊的顺序对es的文档进行排序,这个时候往往需要自定义文档查询得分</p><h2 id="filter过滤"><a href="#filter过滤" class="headerlink" title="filter过滤"></a>filter过滤</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">filter使用布隆过滤器进行过滤所以没有分值,性能较好</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "bool": {</span><br><span class="line"> "filter": {</span><br><span class="line"> "range": {</span><br><span class="line"> "age": {</span><br><span class="line"> "gte": 10</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="constant-score和boost提权"><a href="#constant-score和boost提权" class="headerlink" title="constant_score和boost提权"></a>constant_score和boost提权</h2><p><code>constant_score</code>用于指定查询命中的单位得分值,每个查询价值一个分值单位</p><p><code>boost</code> 用于提升权重</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">该查询命中则价值 1.2分 且忽略tf/idf得分</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "constant_score" : {</span><br><span class="line"> "filter": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags": [</span><br><span class="line"> "a",</span><br><span class="line"> "d"</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> "boost": 1.2</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">boost进行提权</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags": ["a"],</span><br><span class="line"> "boost":3</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="function-score"><a href="#function-score" class="headerlink" title="function_score"></a>function_score</h2><p><code>function_score</code>自定义分值,可以根据文档内容进行得分定制</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">该查询根据文档的age字段 * 5 作为最终分值</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "function_score": {</span><br><span class="line"> "query": {</span><br><span class="line"> "match_all": {</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> "script_score" : {</span><br><span class="line"> "script" : {</span><br><span class="line"> "source": "5*doc['age'].value"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="查询示例:根据用户关注的标签匹配数量计算得分"><a href="#查询示例:根据用户关注的标签匹配数量计算得分" class="headerlink" title="查询示例:根据用户关注的标签匹配数量计算得分"></a>查询示例:根据用户关注的标签匹配数量计算得分</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">假设用户关注了[<span class="string">"a"</span>,<span class="string">"b"</span>] 标签,根据用户的关注标签匹配数量进行分数计算,a标签价值1.3分,b标签1.1</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "query": {</span><br><span class="line"> "bool": {</span><br><span class="line"> "should": [</span><br><span class="line"> {</span><br><span class="line"> "constant_score": {</span><br><span class="line"> "filter": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags": [</span><br><span class="line"> "a"</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> "boost": 1.3</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "constant_score": {</span><br><span class="line"> "filter": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "tags": [</span><br><span class="line"> "d"</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> "boost": 1.1</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "minimum_should_match": 1</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="聚合查询"><a href="#聚合查询" class="headerlink" title="聚合查询"></a>聚合查询</h1><h2 id="terms求count值"><a href="#terms求count值" class="headerlink" title="terms求count值"></a>terms求count值</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价于 group by tags</span></span><br><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "size": 0,</span><br><span class="line"> "aggs": {</span><br><span class="line"> "t": {</span><br><span class="line"> "terms": {</span><br><span class="line"> "field": "tags.keyword",</span><br><span class="line"> "size": 10</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="avg求平均值"><a href="#avg求平均值" class="headerlink" title="avg求平均值"></a>avg求平均值</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "size": 0,</span><br><span class="line"> "aggs": {</span><br><span class="line"> "t": {</span><br><span class="line"> "avg": {</span><br><span class="line"> "field": "age"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="其他查询和dsl"><a href="#其他查询和dsl" class="headerlink" title="其他查询和dsl"></a>其他查询和dsl</h1><h2 id="原子更新文档"><a href="#原子更新文档" class="headerlink" title="原子更新文档"></a>原子更新文档</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">由于es本身不支持源字形的更新文档,我们需要借助内置脚本的帮助来操作</span></span><br><span class="line">POST test_idx/_update/1</span><br><span class="line">{</span><br><span class="line"> "script" : {</span><br><span class="line"> "source": "ctx._source.age += params.count",</span><br><span class="line"> "lang": "painless",</span><br><span class="line"> "params" : {</span><br><span class="line"> "count" : 4</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="explain"><a href="#explain" class="headerlink" title="explain"></a>explain</h2><p>explain 用于查看查询的执行过程和各部分的具体得分,一般用于排查问题</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br></pre></td><td class="code"><pre><span class="line">GET test_idx/_search</span><br><span class="line">{</span><br><span class="line"> "explain": true, </span><br><span class="line"> "query": {</span><br><span class="line"> "bool": {</span><br><span class="line"> "must": [</span><br><span class="line"> {</span><br><span class="line"> "range": {</span><br><span class="line"> "age": {</span><br><span class="line"> "gte": 10,</span><br><span class="line"> "lte": 20</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "should": [</span><br><span class="line"> {</span><br><span class="line"> "match": {</span><br><span class="line"> "content": "上海"</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "minimum_should_match": 1</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">explain 结果示例</span></span><br><span class="line">{</span><br><span class="line"> "_explanation": {</span><br><span class="line"> "value": 1.5753641,</span><br><span class="line"> "description": "sum of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "age:[10 TO 20]",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 0.5753642,</span><br><span class="line"> "description": "sum of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "weight(content:上 in 0) [PerFieldSimilarity], result of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "docFreq",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "docCount",</span><br><span class="line"> "details": []</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "termFreq=1.0",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1.2,</span><br><span class="line"> "description": "parameter k1",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 0.75,</span><br><span class="line"> "description": "parameter b",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 12,</span><br><span class="line"> "description": "avgFieldLength",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 12,</span><br><span class="line"> "description": "fieldLength",</span><br><span class="line"> "details": []</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "weight(content:海 in 0) [PerFieldSimilarity], result of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 0.2876821,</span><br><span class="line"> "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "docFreq",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "docCount",</span><br><span class="line"> "details": []</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",</span><br><span class="line"> "details": [</span><br><span class="line"> {</span><br><span class="line"> "value": 1,</span><br><span class="line"> "description": "termFreq=1.0",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 1.2,</span><br><span class="line"> "description": "parameter k1",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 0.75,</span><br><span class="line"> "description": "parameter b",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 12,</span><br><span class="line"> "description": "avgFieldLength",</span><br><span class="line"> "details": []</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "value": 12,</span><br><span class="line"> "description": "fieldLength",</span><br><span class="line"> "details": []</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><hr><p>参考资料</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html</a></p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> Elasticsearch </tag>
</tags>
</entry>
<entry>
<title>闭包代码如何控制高并发的正确</title>
<link href="/2019-07-07-closure-concurrent/"/>
<url>/2019-07-07-closure-concurrent/</url>
<content type="html"><![CDATA[<h2 id="关于处理闭包的并发问题"><a href="#关于处理闭包的并发问题" class="headerlink" title="关于处理闭包的并发问题"></a>关于处理闭包的并发问题</h2><p>闭包(closure)的概念在很多语言中都有。闭包通常在函数式编程语言或者具有函数式特性的编程语言中会单独列出来,作为一个语言特性。以展示这个语言的强大。</p><p><strong>什么是闭包</strong> 我们通常对闭包的解释是<code>带有运行环境上下文变量的函数</code></p><p>给一个闭包的代码示例(Go)</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">a := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i++</span><br><span class="line"> fmt.Println(i)</span><br><span class="line"> }</span><br><span class="line">}()</span><br></pre></td></tr></table></figure><p>上面的这个函数就实现了闭包, 每调用一次,内部的变量i就增加1。而且这个变量从外部访问不到</p><p>简单的闭包我们都能写,问题是我们如果处理高并发场景下的闭包问题呢,例如下面这段代码</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原子计数器</span></span><br><span class="line">ato := <span class="type">int32</span>(<span class="number">0</span>)</span><br><span class="line"><span class="comment">// 闭包</span></span><br><span class="line">a := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> atomic.AddInt32(&ato, <span class="number">1</span>)</span><br><span class="line"> i++</span><br><span class="line"> <span class="keyword">if</span> ato == <span class="number">1000</span> {</span><br><span class="line"> fmt.Println(<span class="string">"i -> "</span>, i, <span class="string">"ato -> "</span>, ato)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}()</span><br><span class="line"><span class="comment">// 记录调用了多少次协程执行</span></span><br><span class="line"><span class="keyword">for</span> n := <span class="number">0</span>; n < <span class="number">1000</span>; n++ {</span><br><span class="line"> <span class="keyword">go</span> a()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 等待所有协程执行完毕</span></span><br><span class="line"><span class="keyword">for</span> ato < <span class="type">int32</span>(<span class="number">1000</span>) {</span><br><span class="line"> time.Sleep(<span class="number">1000000</span>)</span><br><span class="line">}</span><br><span class="line">fmt.Println(<span class="string">"done"</span>)</span><br></pre></td></tr></table></figure><p> 我们期望的输出值是1000, 但是i的实际的输出值有可能不到1000。</p><p>原因也很简单,因为i++并不是原子性的。由于协程运行的乱序执行,导致有可能会出现两次以3 为基数的自增,这个时候,两次自增的结果都是4就有一次自增相当于无效化</p><p>所以我们处理的方法也可以从i++的原子性来考虑</p><h2 id="方法一"><a href="#方法一" class="headerlink" title="方法一"></a>方法一</h2><h3 id="使用原子操作"><a href="#使用原子操作" class="headerlink" title="使用原子操作"></a>使用原子操作</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int32</span>, j)</span><br><span class="line">a := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="type">int32</span>(<span class="number">0</span>)</span><br><span class="line"> ch <- i</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> atomic.AddInt32(&i, <span class="number">1</span>) <span class="comment">// 原子操作</span></span><br><span class="line"> fmt.Println(i)</span><br><span class="line"> }</span><br><span class="line">}()</span><br></pre></td></tr></table></figure><p>这种情况下, i的值的更新都变成了原子操作,即便乱序执行,结果也是一致的</p><h3 id="原子类的局限"><a href="#原子类的局限" class="headerlink" title="原子类的局限"></a>原子类的局限</h3><p>如果我们要锁定的不是一个变量的变化, 而是一系列的代码操作,这个时候原子类就无用武之地了</p><p>这种时候要么用锁,要么用下面的处理办法</p><h2 id="方法二"><a href="#方法二" class="headerlink" title="方法二"></a>方法二</h2><p>使用csp机制来控制并发。csp是基于消息的常用并发模型的一种(另一种是ActorModel)。</p><h3 id="csp传递值变量"><a href="#csp传递值变量" class="headerlink" title="csp传递值变量"></a>csp传递值变量</h3><p>go语言中有一个csp并发模型,我们可以把要更新的值放到channel中, 使用的时候取出来更新完在放回去, 由于channel的阻塞性,自然就实现了一致性保证。我们用go来写个代码测试一下</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">a := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line"> ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>, <span class="number">1</span>)</span><br><span class="line"> ch <- i</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> t := <-ch</span><br><span class="line"> t++</span><br><span class="line"> fmt.Printf(<span class="string">" i %d, j %d\n"</span>, t, j)</span><br><span class="line"> ch <- t</span><br><span class="line"> }</span><br><span class="line">}()</span><br></pre></td></tr></table></figure><h3 id="channel传递函数指针"><a href="#channel传递函数指针" class="headerlink" title="channel传递函数指针"></a>channel传递函数指针</h3><p>或者更激进一点, 把整个函数指针放到channel中, 这样就能对整个闭包函数中的代码加锁了。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">a := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line"> j := <span class="number">0</span></span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> i++</span><br><span class="line"> j++</span><br><span class="line"> fmt.Println(<span class="string">"i ->"</span>, i, <span class="string">" j ->"</span>, j)</span><br><span class="line"> }</span><br><span class="line">}()</span><br><span class="line"></span><br><span class="line">fc := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>, <span class="number">1</span>)</span><br><span class="line">fc <- a <span class="comment">// 先预置一个用于启动</span></span><br><span class="line"><span class="keyword">for</span> p := <span class="number">0</span>; p < j; p++ {</span><br><span class="line"> t := <-fc</span><br><span class="line"> t()</span><br><span class="line"> fc <- a</span><br><span class="line">}</span><br><span class="line">m := <-fc <span class="comment">// 检查最终的结果</span></span><br><span class="line">m()</span><br></pre></td></tr></table></figure><p>这种方法对语言有要求,需要语言对函数支持比较好,像java这种oop语言就没法使用</p><h3 id="rust的channel实现"><a href="#rust的channel实现" class="headerlink" title="rust的channel实现"></a>rust的channel实现</h3><p>rust里面也可以跟go一样使用channel 传递函数指针来实现闭包函数的并发控制</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::sync::mpsc::channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">c</span>() <span class="punctuation">-></span> <span class="type">Box</span><<span class="keyword">dyn</span> <span class="title function_ invoke__">FnMut</span>() <span class="punctuation">-></span> <span class="type">i32</span>> {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="number">1</span>;</span><br><span class="line"> Box::<span class="title function_ invoke__">new</span>(<span class="keyword">move</span> || {</span><br><span class="line"> a += <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"a -> {}"</span>, a);</span><br><span class="line"> a</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">t</span> = <span class="title function_ invoke__">c</span>();</span><br><span class="line"> <span class="title function_ invoke__">t</span>();</span><br><span class="line"> <span class="title function_ invoke__">t</span>();</span><br><span class="line"> <span class="keyword">let</span> (s, r) = channel::<<span class="type">Box</span><<span class="keyword">dyn</span> <span class="title function_ invoke__">FnMut</span>() <span class="punctuation">-></span> <span class="type">i32</span>>>();</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> (<span class="title function_ invoke__">Ok</span>(_), <span class="title function_ invoke__">Ok</span>(<span class="keyword">mut</span> n)) = (s.<span class="title function_ invoke__">send</span>(t), r.<span class="title function_ invoke__">recv</span>()) {</span><br><span class="line"> <span class="title function_ invoke__">n</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 计算机编程 </category>
</categories>
<tags>
<tag> 编程语言 </tag>
<tag> 闭包 </tag>
</tags>
</entry>
<entry>
<title>排行榜系统设计</title>
<link href="/2019-06-01-recommended-system-ranking-system-md/"/>
<url>/2019-06-01-recommended-system-ranking-system-md/</url>
<content type="html"><![CDATA[<h1 id="为啥需要排行榜"><a href="#为啥需要排行榜" class="headerlink" title="为啥需要排行榜"></a>为啥需要排行榜</h1><p>推荐系统有一个避不开的问题就是冷启动问题。也就是新用户第一次进入应用的推荐,此时我们没有任何的用户行为信息,无法根据用户行为进行推荐, 根据用户基础信息的推荐也极有可能没有构建完成或者因为基础数据不完善导致效果不够好。</p><p>此时就需要一种针对所有用户通用的推荐策略,防止推荐系统出现“开天窗” 。我们一般使用以下策略来处理冷启动问题</p><ol><li>最新内容:平台的最近发布的比较新鲜的资源</li><li>热门内容:平台上最近一段时间发布的比较热门的资源</li><li>实时基于用户属性进行分群,基于用户群进行推荐(比如,上海地区用户,女性用户,30-40岁用户)</li></ol><p>其中的热门内容就需要排行榜系统的支持</p><h1 id="排行榜系统"><a href="#排行榜系统" class="headerlink" title="排行榜系统"></a>排行榜系统</h1><p>排行榜系统一般有两种, 一种是类似于“本月最火歌曲排行”或者“第N期XXX排行”之类的以自然月为统计维度的,另一种是“最近30天最火歌曲排行”之类的动态时间范围的排行榜</p><h2 id="固定时间范围的排行榜"><a href="#固定时间范围的排行榜" class="headerlink" title="固定时间范围的排行榜"></a>固定时间范围的排行榜</h2><p><strong>需求:统计本月播放次数最多的歌曲</strong></p><p>针对“本月最火歌曲”这种需求的排行榜,处理起来相对简单</p><p>只要统计从本月从1号到现在的所有歌曲的播放数据就可以了,如果数据量不大且要求不高,可以定时每一段时间计算一次, 计算方法如下</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(id) <span class="keyword">from</span> play_record <span class="keyword">where</span> play_time <span class="operator">></span> "2019-05-01" <span class="keyword">group</span> <span class="keyword">by</span> itemid;</span><br></pre></td></tr></table></figure><p>如果实时性要求较高, 也可以使用基于事件的实时增量统计的方式,同时每天处理一次批量统计,做数据矫正。</p><figure class="highlight scheme"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(<span class="name">add-score</span> video_10086_play_times <span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>具体就是每天计算一个排行榜之后, 使用流处理系统,当有新的播放事件,在榜单现有基础上做incr操作即可</p><p>这种排行榜相对来说实现比较简单, 缺点也很明显</p><p>比如今天如果是本月的1号, 那你的排行榜数据就由于样本数据有限,误差较大,无法起到排行榜真正的作用</p><p>处理这种问题就需要“最近30天最火的歌曲”这种滚动排行榜</p><h2 id="滚动排行榜"><a href="#滚动排行榜" class="headerlink" title="滚动排行榜"></a>滚动排行榜</h2><p>滚动排行榜是指基于最近一段时间范围的数据获得的排行榜统计结果 </p><p>拿最近3天排行榜为例, 假设现在是是10号的10点钟, 那滚动排行榜覆盖的区间就是前推3天的数据 </p><p><code>7号从10点以后的数据 + 8,9号全天数据 + 10号截至目前的数据 的统计结果</code></p><p>滚动排行榜相对来说难度增加了很多</p><p>如果数据量不大,对实时性要求不高的话, 也可以采用每一段时间计算一次最近3天的播放量的批量的方式</p><p>但是如果数据量较大或者对实时性有要求较高,那就需要设计一个更好的实时方案</p><h3 id="实时的滚动排行榜"><a href="#实时的滚动排行榜" class="headerlink" title="实时的滚动排行榜"></a>实时的滚动排行榜</h3><p>假如我要做3天的滚动歌曲榜单, 我就需要获取最近72小时的播放记录进行统计,拿数据举例来说</p><p>假设有如下的播放记录, 当前日期是 2019-04-04 13:00, 排行榜统计区间应该是 <code>04-01 13:00 ~ 04-04 13:00</code> </p><table><thead><tr><th>歌曲id</th><th>播放时间</th></tr></thead><tbody><tr><td>1009</td><td>2019-04-01 9:00</td></tr><tr><td>1010</td><td>2019-04-01 14:00</td></tr><tr><td>1020</td><td>2019-04-02 8:00</td></tr><tr><td>1089</td><td>2019-04-03 10:00</td></tr><tr><td>1010</td><td>2019-04-04 9:00</td></tr><tr><td>1023</td><td>2019-04-04 12:00</td></tr></tbody></table><p>那我们获取到的3天榜单应该是这样(1009的播放记录已经过期)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 歌曲id:播放次数</span><br><span class="line">{</span><br><span class="line"> 1023:1,</span><br><span class="line"> 1010:2,</span><br><span class="line"> 1089:1,</span><br><span class="line"> 1020:1</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们设计的这套方案需要存储2类信息</p><ol><li>第一类按固定周期维度的用户点击数据,我们称之为周期榜,其中记录每个歌曲在当前周期的播放次数,比如20190401周期榜就记录当天的所有歌曲播放次数,这个数据的生成十分简单,只要用redis的zset结构实时``i ncr`即可</li><li>另一类是我们业务要用的滚动榜单,也就是我们的目标数据</li></ol><p>排行榜处理过程最关键的有两点</p><ol><li><p>对一个元素加分时,加当日周期榜、滚动榜;<br>还需根据其在今日滚动榜中的分数s、及n-1天日榜中的分数r,计算出其在明日滚动榜中的初始分数s-r写入明日滚动榜中,即3个写操作。</p></li><li><p>如果一个元素在当日没有任何加分操作,那么不会触发写入初始分数操作,所以还需要一个离线工具补齐。<br>该离线工具可提前一天运行,即当日运行离线工具补齐次日的滚动榜数据即可。</p></li></ol><p>R:每日的周期榜统计数据 S: 每日的滚动排行榜数据</p><p>如果S(i)-R(i) > 0 ,说明该歌曲在指定周期内有播放行为,有播放行为才进行写入操作</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">以3天滚动榜为例,次日滚动榜初始态为当日滚动榜减去n-2天的日榜数据。</span><br><span class="line"> +-------------------------------------------+</span><br><span class="line"> | |</span><br><span class="line">+----+---+ +--------+ +--------+ |</span><br><span class="line">| R(i-2) | | R(i-1) | | R(i) | |</span><br><span class="line">+----+---+ +----+---+ +---+----+ |</span><br><span class="line"> | | | |</span><br><span class="line"> | | v+ v-</span><br><span class="line"> | |</span><br><span class="line"> | | + +--------+ +--------+</span><br><span class="line"> | +-----> | | + | |</span><br><span class="line"> | + | S(i) | +---+> | S(i+1) |</span><br><span class="line"> +-----------------+> | | | |</span><br><span class="line"> +--------+ +--------+</span><br><span class="line"></span><br><span class="line">分数变化</span><br><span class="line"> +--------------+</span><br><span class="line"> | AddScore |</span><br><span class="line"> +-+----+-----+-+</span><br><span class="line"> |+ | |</span><br><span class="line"> v | |</span><br><span class="line">+--------+ +--------+ +--------+ | |+</span><br><span class="line">| R(i-2) | | R(i-1) | | R(i) | | |</span><br><span class="line">+--------+ +--------+ +--------+ | |</span><br><span class="line"> | v</span><br><span class="line"> +--------+ | +--------+</span><br><span class="line"> | S(i) |<--+ | S(i+1) |</span><br><span class="line"> +--------+ +--------+</span><br><span class="line"> ^</span><br><span class="line"> |+</span><br><span class="line"> +------------+</span><br><span class="line"> | Tool |</span><br><span class="line"> +------------+</span><br></pre></td></tr></table></figure><h3 id="使用flink的滑动窗口来统计"><a href="#使用flink的滑动窗口来统计" class="headerlink" title="使用flink的滑动窗口来统计"></a>使用flink的滑动窗口来统计</h3><p>我们可以使用kafka这种消息队列来存储最近一段时间的数据,然后使用flink的窗口进行计算<br>为了能处理较大的数据量,我们可以先开一个tumbling窗口对1分钟维度的数据进行计算,然后在基于1分钟的维度开sliding窗口进行数据统计,如果要统计1年之类的较长周期,那可以在1分钟的基础上再做1天的聚合数据,在1天的数据基础上进行1年的聚合分析。</p><p>这种分层聚合的方式能有效降低数据量并能支持较大的数据长度</p><h2 id="概率模型HyperLogLog"><a href="#概率模型HyperLogLog" class="headerlink" title="概率模型HyperLogLog"></a>概率模型HyperLogLog</h2><p>上面我们提到基于自然时间的统计也可以通过给时间分区间对数据进行统计, 但是在某些情况下,比如统计本月的月活,小公司还好,像阿里这种公司都是数亿的日活, 这种级别的数据统计,如果采用redis的hash或者 bitmap也是一种超级大的开销。</p><p>如果在极大的数据量下可以允许一定的误差, 就可以采用HyperLogLog这种概率模型来进行日活用户这种统计</p><p>HyperLogLog能达到在极大的用户登陆记录中快速做到类似<code>distinct</code>的效果,比如我们有100多亿的用户登陆记录,我们想统计其中一共有多少用户,传统的方案就需要<code>select distinct(userid) from A </code></p><h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><p>由于redis已经实现了HyperLogLog,所以我们可以直接使用redis来进行操作</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">pfadd 2020:06:active:<span class="built_in">users</span> user1 user2 user3 user1</span></span><br><span class="line">1</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">pfcount 2020:06:active:<span class="built_in">users</span></span></span><br><span class="line">3</span><br></pre></td></tr></table></figure><p>也可以合并两个HyperLogLog的结果</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">; 将6月和5月的日活合并统计</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">pfmerge 2020:06:active:<span class="built_in">users</span> 2020:05:active:<span class="built_in">users</span></span></span><br></pre></td></tr></table></figure><h3 id="基本原理"><a href="#基本原理" class="headerlink" title="基本原理"></a>基本原理</h3><p>HyperLogLog是一种基于概率模型</p><p>基本原理大概是如下流程</p><ol><li>先对要计数的值进行hash,得到一个64bit的hash结果</li><li>hash结果的后14位转为10进制数字m,前50位从低到高第一个1出现的位置记为n,可知$0\leq m < 16384,1\leq n <50$</li><li>然后创造一个拥有16384个桶,每个桶有6位,一共长度为 16384 * 6的12kb的数组</li><li>将第m个桶的值置为n</li><li>查询总量时,对所有桶求调和平均值</li></ol>]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
<tag> HyperLogLog </tag>
</tags>
</entry>
<entry>
<title>如何设计一个海量数据过滤模块</title>
<link href="/2019-04-01-recommended-system-filter-module-md/"/>
<url>/2019-04-01-recommended-system-filter-module-md/</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>所有的推荐系统都有一个基本的要求:<code>不给用户推荐重复的信息</code></p><p>这里的重复的信息有两层含义 </p><ol><li><p>这个信息是id完全相同的一条信息<br> 这个很好理解,就是一模一样的两条信息,从数据库的角度将就是信息记录的主键id都相同<br> 如果用户已经接受过一次id为10086的信息的推荐,那这个10086就不能出现在后续的同类推荐中</p></li><li><p>这两个个信息相似度很高,但是id不同<br> 这种情况出现于两条信息内容相似,比如有两篇文章10001,10002都在说特朗普要在美国边境造墙的事情<br> 那如果用户观看过10001,此时再给用户推荐10002,就可能会让用户觉得推荐的东西已经看到过了,毫无意义</p></li></ol><h1 id="处理相同id的过滤"><a href="#处理相同id的过滤" class="headerlink" title="处理相同id的过滤"></a>处理相同id的过滤</h1><p>我们的目的是给定一个id, 和一个已推荐集合,判断id是否在给定的集合以内</p><h2 id="HashMap的方案"><a href="#HashMap的方案" class="headerlink" title="HashMap的方案"></a>HashMap的方案</h2><p>如果集合数据量比较少的情况下,我们可以使用Java中的<code>HashSet</code>存储集合, 使用contains来直接判断id是否在给定集合中,其他编程语言中也都有类似<code>HashSet</code>的数据结构</p><p>由于<code>HashSet</code>的底层原理是使用的<code>HashMap</code>, 所以我直接使用 <code>HashMap</code>来进行原理的说明</p><p>这种做法的原理其实是先将我们要查找的数据进行hash散列,映射到一个固定长度的地址空间<br>然后使用数组存储,如果有多个id经过hash映射到相同的地址空间那就做一个链表,存储相同hashcode对应的值 </p><p>具体结构如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">|0|1009|0|1008|1998| // hash数组槽</span><br><span class="line"> ↓ ↓ </span><br><span class="line"> 107 87 // 槽内的指针对应的数据</span><br><span class="line"> ↓ </span><br><span class="line"> 16</span><br></pre></td></tr></table></figure><p>当我们查找id时, 也是先将要查找的id进行hash, 然后去对应的hashcode位置沿着链表/红黑树寻找是否有要查找的数字</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ol><li>使用简单,性能好,比较通用</li><li>容易理解,无误判</li></ol><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ol><li>空间利用率太低</li></ol><h2 id="Bitmap"><a href="#Bitmap" class="headerlink" title="Bitmap"></a>Bitmap</h2><p>考虑到<code>HashMap</code>的空间利用率太低,不适合海量数据的存储,我们可以利用计算机存储的一些特性,用另外一种方式来。</p><p>我们都知道计算机底层是用bit来存储信息的,每个bit能存储一个0或者1的信息,如果我们使用二进制bit的位置信息来表示数字,对应位的bit值是1来代表这个数字存在,我们就可以在极小的空间存储大量的信息,使用时只需使用位运算,查看对应位置的bit值即可。</p><p>Java中的bitset,redis中的bit操作都提供了这种bitmap的实现,bitmap的详情可以查看 <a href="https://leriou.github.io/2017-12-29-user-tag-sys-on-bitmap/">使用bitmap构建用户标签</a></p><p>如下表示 【6,4,3】的集合,第6,4,3位为1,其他位为0</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">bit值 0 1 0 1 1 0 0 0 // 0x01011000</span><br><span class="line">bit位 7 6 5 4 3 2 1 0 </span><br></pre></td></tr></table></figure><h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><ol><li>空间利用率高,性能好,无误判</li></ol><h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><ol><li>不够通用,要求数据的类型必须是数字且元素范围跨度不能太大</li></ol><h2 id="BloomFilter"><a href="#BloomFilter" class="headerlink" title="BloomFilter"></a>BloomFilter</h2><p>bloomfilter跟bitmap具有相似的原理, 都是使用位来存储信息,区别在于</p><p>bitmap使用元素自身数字对应的位置信息来存储数据,如果要存储的元素是个字符串或者其他类型的数据就无法使用这种方式了,或者你要存储的数字范围特别的大,比如你要存储一个100亿的数字, 那样即使只有一个数字你也需要一个前面的位置都是0的100亿bit来表示,对空间的利用率还是不高 (现在有一些空间压缩的bitmap实现能一定程度解决数据范围分布过大和分布稀疏的问题)</p><p>BloomFilter就是针对这些做了优化,如果我们把要处理的数字进行hash, 映射到一个固定长度的地址空间,这样就同时解决了以上两个问题, 即缩减了映射的空间范围,又可以存储更通用的对象。但是由于hash函数本身会有冲突,就会出现两个不同元素因为同样的hashcode而产生误判的情况。</p><p><strong>既如果判定结果是不存在(False),则一定不存在;但是如果判定结果是存在(True),则实际情况其实是可能存在,而不是一定存在,即假阳性。</strong></p><h3 id="误判率的计算"><a href="#误判率的计算" class="headerlink" title="误判率的计算"></a>误判率的计算</h3><p>我们假设</p><ul><li>欲插入Bloom Filter中的元素数目: n</li><li>Bloom Filter误判率: P(true)</li><li>BitArray数组的大小: m</li><li>Hash Function的数目: k</li></ul><p>则有误判率:<br>$$P(true) = (P^n_1)^k=[1-(1-\frac{1}{m})^kn]^k \tag{1}$$<br>即:<br>$$P(true)\approx(1-e^{-\frac{nk}{m}})^k \tag{2}$$<br>也就是说当BitArray数组的大小m增大 或 欲插入Bloom Filter中的元素数目n 减小时,均可以使得误判率P(true)下降</p><h3 id="至于hash-function的数目k"><a href="#至于hash-function的数目k" class="headerlink" title="至于hash function的数目k"></a>至于hash function的数目k</h3><p>$$f(k)=(1-e^{-\frac{nk}{m}})^k \tag{3}$$<br>令 $a=e^{\frac{nk}{m}}$ ,则有:<br>$$f(k)=(1-e^{-1})^k$$<br>分别对上式两边,先取对数,再对k求一次导,可有:<br>$$\frac{1}{f(k)}f(k’)=\ln(1-a^{-k})+\frac{ka^{-k} \ln a}{1-a^{-k}}$$<br>易知,当k取极值点时,有 $f(k)’=0$ ,故将其带入上式即可求出k<br>$$\ln(1-a^{-k})+\frac{ka^{-k} \ln a}{1-a^{-k}}=0$$<br>$$=> (1-a^{-k}) \ln(1-a^{-k})=-ka^{-k} \ln a$$<br>$$=> e^{-\frac{kn}{m}}=\frac{1}{2}$$<br>$$=> k=\frac{m}{n}\ln2\approx0.7\frac{m}{n}$$<br>所以我们通过调整k的值也能一定程度上降低误判率, 但是基于概率的问题,误判率依然存在。所以这种方法适合于允许一定误判率,并拥有海量数据要过滤的场景</p><h3 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h3><ol><li>通用,空间效率高,灵活,可以在性能和误判率之间做取舍</li></ol><h3 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h3><ol><li>有误判,性能不如bitmap</li></ol><h1 id="处理相似文本的重复"><a href="#处理相似文本的重复" class="headerlink" title="处理相似文本的重复"></a>处理相似文本的重复</h1><p>能处理相同的id造成的重复之后,如果我们可以找出一个文章的相似内容, 只要把相似的内容id也添加到需要过滤的集合, 就可以完成对相似内容的过滤</p><h2 id="simhash"><a href="#simhash" class="headerlink" title="simhash"></a>simhash</h2><p>如果只是检测一模一样的两个字符串,那我们完全可以采用md5之类的摘要算法, 但是这种方法对文章内容又哪怕一个字的差别,就不再适用</p><p>所以我们使用<code>simhash</code>来处理内容相似度的问题</p><p>simhash的核心思想是先提取文章的最重要核心特征, 如果两篇文章的核心特征重复度较高,那就有可能是相似文章,具体步骤如下</p><ol><li>我们先对文本进行分词,并计算每个词的权重</li><li>对每个词进行hash,并把hash结果的对应二进制位的 0 变为 -1</li><li>把每个文章的每个词的处理过的 hash值 x 权重,得到加权向量,并把每个加权向量相加得到最终向量</li><li>把这个最终向量中的负数变为0,正数变为1</li></ol><p>然后通过汉明距离比较二进制位不同的个数,其实就是计算两个指纹的异或结果,结果中如果包含较少的1, 比如小于3个, 那就说明内容相同</p>]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> 推荐系统 </tag>
</tags>
</entry>
<entry>
<title>MongoDB和Elasticsearch的对比</title>
<link href="/2019-01-09-mongodb-compareto-elasticsearch/"/>
<url>/2019-01-09-mongodb-compareto-elasticsearch/</url>
<content type="html"><![CDATA[<h1 id="MongoDB-vs-Elasticsearch"><a href="#MongoDB-vs-Elasticsearch" class="headerlink" title="MongoDB vs Elasticsearch"></a>MongoDB vs Elasticsearch</h1><table><thead><tr><th align="center"></th><th align="center">MongoDB</th><th align="center">ElasticSearch</th><th>备注</th></tr></thead><tbody><tr><td align="center">定位</td><td align="center">(文档型)数据库</td><td align="center">(文档型)搜索引擎</td><td>一个管理数据,一个检索数据</td></tr><tr><td align="center">资源占用</td><td align="center">一般</td><td align="center">高</td><td>mongo使用c++, es使用Java开发</td></tr><tr><td align="center">写入延迟</td><td align="center">低</td><td align="center">高</td><td>es的写入延迟默认1s, 可配置, 但是要牺牲一些东西</td></tr><tr><td align="center">全文索引支持度</td><td align="center">一般</td><td align="center">非常好</td><td>es本来就是搜索引擎, 这个没啥可比性</td></tr><tr><td align="center">有无Schema</td><td align="center">无</td><td align="center">无</td><td>两者都是无Schema</td></tr><tr><td align="center">支持的数据量</td><td align="center">PB+</td><td align="center">TB+ ~ PB</td><td>两者支持的量并不好说的太死, 都支持分片和横向扩展, 但是相对来说MongoDB的数据量支持要更大一点</td></tr><tr><td align="center">性能</td><td align="center">非常好</td><td align="center">好</td><td>MongoDB在大部分场景性能比es强的多</td></tr><tr><td align="center">索引结构</td><td align="center">B树</td><td align="center">LSM树</td><td>es追求写入吞吐量, MongoDB读写比较均衡</td></tr><tr><td align="center">操作接口</td><td align="center">TCP</td><td align="center">Restful(Http)</td><td></td></tr><tr><td align="center">是否支持分片</td><td align="center">是</td><td align="center">是</td><td></td></tr><tr><td align="center">是否支持副本</td><td align="center">是</td><td align="center">是</td><td></td></tr><tr><td align="center">选主算法</td><td align="center">Bully(霸凌)</td><td align="center">Bully(霸凌)</td><td>相比于Paxos和Raft算法实现更简单并有一定可靠性上的妥协,但是选举速度比较快</td></tr><tr><td align="center">扩展难度</td><td align="center">容易</td><td align="center">非常容易</td><td>es真的是我用过的扩展最方便的存储系统之一</td></tr><tr><td align="center">配置难度</td><td align="center">难</td><td align="center">非常容易</td><td></td></tr><tr><td align="center">地理位置</td><td align="center">支持</td><td align="center">支持</td><td></td></tr><tr><td align="center">运维工具</td><td align="center">丰富</td><td align="center">一般</td><td></td></tr><tr><td align="center">插件和引擎</td><td align="center">有多个存储引擎供选择</td><td align="center">有大量插件可以使用</td><td>-</td></tr></tbody></table><h1 id="两者的定位"><a href="#两者的定位" class="headerlink" title="两者的定位"></a>两者的定位</h1><p><code>MongoDB</code>和<code>Elasticsearch</code>都属于NoSQL大家族, 且都属于文档型数据存储</p><p>所以这两者的很多功能和特性高度重合, 但其实两者定位完全不同 </p><p>MongoDB 是 <strong>文档型数据库</strong>, 提供 <strong>数据存储和管理服务</strong><br>Elasticsearch 是<strong>搜索服务</strong>, 提供 <strong>数据检索服务</strong></p><p>两者的很大区别在于源数据的存储和管理</p><ul><li>MongoDB作为一个数据库产品, 是拥有源数据管理能力的 </li><li>Elasticsearch作为一个搜索引擎, 定位是<strong>提供数据检索服务</strong>, 也就是说我只管查, 不管写 ^_^, Elasticsearch的Mapping不可变也是为此服务的, 带来的代价就是<code> es不适合作为数据管理者</code>, es可以从其他数据源同步数据过来提供查询, 但是不适合自己对数据进行存储和管理</li></ul><p>es更侧重数据的查询, 各种复杂的花式查询支持的很好, 相比来说 MongoDB的查询能力就显得比较平庸了</p><p>由此可见, 对于个人, 如果你有一批数据要看, 但是不经常进行修改, 这个时候毫无疑问可以用es, 但是如果你还打算继续修改数据, 最好就是使用MongoDB,但其实对大多数人公司来讲,这两者的数据管理能力并没有多大的影响</p><blockquote><p>ps: es修改Mapping的代价非常高, 所以我们一般都是把新数据重新写入一份新索引,然后直接切换读取的别名到新的索引</p></blockquote><h1 id="两者读写数据的异同"><a href="#两者读写数据的异同" class="headerlink" title="两者读写数据的异同"></a>两者读写数据的异同</h1><p><code>MongoDB</code>和<code>ElasticSearch</code>都支持全文索引, 虽然MongoDB的全文索引效果完全无法跟es相比(es毕竟是专业的搜索引擎产品, 着重提供数据的检所支持, 这方面吊打MongoDB也是可以理解的)</p><p>MongoDB虽然在支持的部分查询功能上稍微弱于es, 但是在大部分场景下性能方面完爆es, 不管是读性能, 还是写性能</p><p>es的写入延迟默认为1s, 这个虽然是写入延迟的范畴, 但是毫无疑问是一大缺点, 虽然可以配置为更短的时间, 但是这样就要牺牲一定的数据吞吐量, 会造成更频繁的磁盘刷新操作</p><p>es底层使用<code>Lucene</code>作为核心引擎, 很多es的设计就是为了匹配Lucene中的概念, 其实es可以看成一个lucene的proxy层包装,将lucene的原生接口封装的更好用, 同时还实现了很多管理和监控等辅助功能, 但是整体来说es上层的模块和lucene的隔阂还是挺明显的, 耦合度上有一定的欠缺</p><p>MongoDB则是完整的一个单体数据库产品, 虽然内部的存储引擎也是可插拔式的, 整体而言还是更加的浑然一体</p><blockquote><p>MongoDB支持多种存储引擎, 本文所有涉及mongo存储引擎的只谈默认的WiredTiger引擎, 其实还有某些方面更优秀的其他引擎,例如: MongoRocks等</p></blockquote><h1 id="部署和资源占用"><a href="#部署和资源占用" class="headerlink" title="部署和资源占用"></a>部署和资源占用</h1><p>单机部署的话其实MongoDB和Elasticsearch都十分的方便, 不过es相对来说资源占用更多一点, 性能也比MongoDB要弱一点</p><p>集群化的部署, 我们一般都会选择分片+副本的部署方式, 这种方式下, es部署起来比MongoDB方便太多, MongoDB要部署一套完整的分片 + 副本模式还是比较麻烦的, 没有经验的人部署起来需要一定的学习成本 </p><p>资源占用方面, MongoDB可以支持存储文件类型的数据, 作为数据库也有数据压缩能力, es则因为大量的索引存在需要占用大量的磁盘和内存空间</p><h1 id="可用性和容错"><a href="#可用性和容错" class="headerlink" title="可用性和容错"></a>可用性和容错</h1><p>MongoDB和ElasticSearch作为天生分布式的代表产品都支持数据分片和副本 </p><p>两者都通过分片支持水平扩展, 同时都通过副本来支持高可用(HA) </p><p>分片就是一个数据集的数据分为多份, 同时分布在多个节点上存储和管理, 主流分片方式有两种: hash分片和range分片, 两种分片方式各有优势, 适合不同的场景</p><p>副本就是一份数据集同时有一个或者多个复制品(有些地方叫主从), 每份复制品都一模一样, 但是为了保证数据的一致性, 往往多个副本中只有一个作为Primary副本(通过选主算法从多个副本中选出Primary), 提供写服务, 其他副本只提供读, 或者只提供备份服务</p><blockquote><p>ps:es和MongoDB都可以通过副本增强读能力, 这与kafka很不一样(kafka的副本只有备份功能)</p></blockquote><h2 id="两者分布式方案的一些不同"><a href="#两者分布式方案的一些不同" class="headerlink" title="两者分布式方案的一些不同"></a>两者分布式方案的一些不同</h2><p>MongoDB和Elasticsearch虽然都是分布式服务, 但是还是有一些不同方案的选择的</p><ul><li>分片和副本单位的划分</li></ul><p>MongoDB是以节点为单位划分角色, 一旦一个节点被指定为副本, 其上面的数据都是副本</p><p>Elasticsearch是以分片为单位划分角色, 一个节点上即可以拥有某分片的主分片和可以同时拥有另一个分片的副本分片, 同时es还支持自动的副本负载均衡, 如果一个新节点上面什么数据都没有, 系统会自动分配分片数据过来 </p><ul><li>架构模式</li></ul><p>MongoDB的副本和分片是两种不同的模式, 虽然可以同时使用但是依然有各自的架构设计, 用户可以任意选择选型进行搭配, 每个节点的职责更加专一, 方便据此调整机器配置和进行优化</p><p>Elasticsearch中的分片 + 副本是一套统一的架构设计, 每个节点具有接近同等的地位, 配置使用起来更加简单, 但是如果要针对节点所负责的功能对机器进一步做定制就不如MongoDB灵活</p><h1 id="文档型数据库的特点和问题"><a href="#文档型数据库的特点和问题" class="headerlink" title="文档型数据库的特点和问题"></a>文档型数据库的特点和问题</h1><h2 id="无schema"><a href="#无schema" class="headerlink" title="无schema"></a>无schema</h2><p>文档型数据存储既能享受无schema限制带来的灵活, 又能享受索引查询的快速和类SQL查询的便捷</p><p>使他们用起来不像传统的RDBMS那么麻烦, 又不像 Redis,Hbase这种数据库查询功能不够强大, 处在一个传统RDBMS和经典K-V存储之间的比较均衡的位置</p><p>我个人很喜欢这个特性, 没有schema的限制, 存储数据更方便也更灵活了, 但是有得有失, 很多固定schema的好处就无法享受到了, 比如: 对数据的高效压缩</p><h2 id="鸡肋的Collection-和-Type"><a href="#鸡肋的Collection-和-Type" class="headerlink" title="鸡肋的Collection 和 Type"></a>鸡肋的Collection 和 Type</h2><p>早期为了跟传统rdbms数据库保持概念一致 ,mongodb和elasticsearch都设计了跟传统数据库里面的<code>库->表->记录行</code>对应的概念,具体如下</p><table><thead><tr><th>RDBMS</th><th>MongoDB</th><th>Elasticsearch</th></tr></thead><tbody><tr><td>库</td><td>库</td><td>索引</td></tr><tr><td>表</td><td>集合</td><td>类型</td></tr><tr><td>记录</td><td>文档</td><td>文档</td></tr></tbody></table><p>其实对于nosql数据库来讲, 集合/类型的意义其实不大, Nosql数据库几乎都是k-v类型的存储结构,完全可以通过key进行业务隔离和区分,真的没有必要为了跟传统数据库对应强行搞出来一个中间概念 ^_^</p><p>Elasticsearch从<code>6.x</code>版本开始强制只允许一个索引使用一个type, 其实就是意识到这个这个设计的失误, 不想让你用这个type类型, 因为type和传统数据库里面的表概念其实是不一样的,这种概念类比给人造成了误解,到了es的7.x版本会默认取消type类型, 就说明这个type字段真的是鸡肋的不行</p><h2 id="弱事务"><a href="#弱事务" class="headerlink" title="弱事务"></a>弱事务</h2><p>MongoDB以前只是支持同一文档内的原子更新, 以此来实现伪事务功能, 不过Mongo4.0支持Replica Set事务, 大大加强了事务方面的能力 </p><p>es在这方面倒没有什么进展,因为从应用场景上es对事务的需求不高,不过用户其实也可以使用同文档更新或者通过程序自己来实现事务机制</p><h2 id="无join支持"><a href="#无join支持" class="headerlink" title="无join支持"></a>无join支持</h2><p>文档型数据库大多数都不支持join(也有少量支持的), 但是我一般也用不上多表join的功能, 即便真的需要使用join也可以通过应用层或者通过耦合数据来实现(不过据说未来Mongo4.2版本会带来对join的支持)</p><p>不支持join带来的问题就是我们需要自己对数据进行连接, 但是这在擅长使用分布式计算的大数据领域不算什么问题, 相应的缺少join功能可能对善于使用SQL的数据分析师就不大友好</p><h2 id="Bully的选主算法的缺陷"><a href="#Bully的选主算法的缺陷" class="headerlink" title="Bully的选主算法的缺陷"></a>Bully的选主算法的缺陷</h2><p>elasticsearch和MongoDB选择的选主算法实现很简单, 但是代价就是有几率出现脑裂的情况, 当然, 具体情况跟配置也有关系(比如:你有三个es节点但是设置的最小主节点数为1, 将最小主节点数设置为2可以避免脑裂情况)</p><p>不过脑裂问题一方面发生概率较低,另一方面即使出现了脑裂的情况, 使用<code>重启大法</code>一般就能解决 ^_^</p><p>总体来说, 这方面不如使用Paxos和Raft算法或者使用zk做协调器的其他分布式系统靠谱</p><h1 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h1><ul><li>运维工具</li></ul><p>两者背后都有商业公司的支持</p><p>MongoDB的很多客户端和运维工具更丰富, 但是MongoDB作为一个数据库产品, 相对应的对运维人员的要求也要更高一点</p><p>Elasticsearch则有整套的数据分析和收集工具提供, 配套的kibana就是一个很不错的管控es的工具</p><ul><li>操作接口</li></ul><p>es使用Restful来提供统一的操作接口, 屏蔽了各种语言之间的障碍, 但是同样带来了表达能力和性能的损失</p><p>MongoDB则使用TCP, 降低了序列化和网络这一层的性能损耗, 并最大程度保留了接口的内容表达能力, 但是相对的使用起来就不如http那么的方便</p><h1 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h1><p>两者其实在很多使用场景上有重合之处, 是可以互相替代, 比如日志收集</p><p>但是某些方面两者又各有特色,比如: 如果打算使用一个文档型的业务数据库, 那最好还是选mongodb, 如果你有要求复杂查询又并发性能要求高的场景,类似搜索服务,那最好的选择是elasticsearch</p><p>除此之外:</p><p>MongoDB有多个存储引擎可以选择, 而且MongoDB不仅看重数据的分析, 对数据的管理同样看重, 总的来说MongoDB更倾向于数据的存储和管理, 可以作为数据源对外提供, 未来说不定还会有支持join和支持倒排索引的mongo引擎出现</p><p>Elasticsearch则有很多插件可以使用, 相对来讲Elasticsearch更倾向于数据的查询, 一般情况下elasticsearch仅作为数据检索服务和数据分析平台, 不直接作为源数据管理者</p><ul><li>MongoDB适合</li></ul><ol><li>对服务可用性和一致性有高要求</li><li>无schema的数据存储 + 需要索引数据</li><li>高读写性能要求, 数据使用场景简单的海量数据场景</li><li>有热点数据, 有数据分片需求的数据存储</li><li>日志, html, 爬虫数据等半结构化或图片,视频等非结构化数据的存储</li><li>有js使用经验的人员(MongoDB内置操作语言为js)</li></ol><ul><li>Elasticsearch适合</li></ul><ol><li>已经有其他系统负责数据管理</li><li>对复杂场景下的查询需求,对查询性能有要求, 对写入及时性要求不高的场景</li><li>监控信息/日志信息检索</li><li>小团队但是有多语言服务,es拥有restful接口,用起来最方便</li></ol><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>MongoDB和Elasticsearch都是我比较喜欢的存储产品</p><p>两者的功能特性也存在很多重合的地方, 其实现在很多数据库产品都在互相借(chao)鉴(xi), 功能和特性都在逐渐变得相似, 这也是未来很多存储产品的发展趋势, 大家都希望自己能覆盖尽量多的场景和用户群体</p><p>很多产品总是在不断的从<code>没有</code>-><code>有</code>-><code>功能丰富</code>,但是功能丰富一定是做了很多的妥协, 于是又有了 <code>功能众多的单体服务</code>-><code>多个功能单一的子服务</code> 方向的转变,就像三国里面说的 “天下大势, 分久必合合久必分”. </p><p>现在NoSQL数据库产品就在这个路上, NoSQL归根到底都是 RDBMS的某个方面的妥协, 现在各种NoSQL 也都在加入对经典SQL和传统RDBMS的 join, 事务的支持, 但是我相信等到两者区别足够小的时候, 一定会有放弃了大而全, 而专注于某一场景的新的存储产品出现,到时候搞不好又是一波新的Nosql潮流 </p>]]></content>
<categories>
<category> 编程工具 </category>
</categories>
<tags>
<tag> NoSQL </tag>
<tag> MongoDB </tag>
<tag> Elasticsearch </tag>
</tags>
</entry>
<entry>
<title>2019年记录</title>
<link href="/2019-01-01-summary-md/"/>
<url>/2019-01-01-summary-md/</url>
<content type="html"><![CDATA[<h1 id="2019"><a href="#2019" class="headerlink" title="2019"></a>2019</h1><h1 id="1月"><a href="#1月" class="headerlink" title="1月"></a>1月</h1><p>看书:</p><ul><li><input disabled="" type="checkbox"> 神经网络与深度学习</li><li><input disabled="" type="checkbox"> 深入理解Java虚拟机</li></ul><h1 id="2月"><a href="#2月" class="headerlink" title="2月"></a>2月</h1><p>看书:</p><ul><li><input disabled="" type="checkbox"> 设计数据密集型应用</li></ul><h1 id="3月"><a href="#3月" class="headerlink" title="3月"></a>3月</h1><p>看书:</p><ul><li><input checked="" disabled="" type="checkbox"> 富爸爸穷爸爸</li></ul><h1 id="4月"><a href="#4月" class="headerlink" title="4月"></a>4月</h1><p>看书:</p><ul><li><input disabled="" type="checkbox"> 一本书看懂经济学</li><li><input checked="" disabled="" type="checkbox"> 从零开始学炒股</li><li><input disabled="" type="checkbox"> 设计数据密集型应用</li><li><input disabled="" type="checkbox"> 剑指offer</li></ul><h1 id="5月"><a href="#5月" class="headerlink" title="5月"></a>5月</h1><p>看书:</p><ul><li><input checked="" disabled="" type="checkbox"> 设计数据密集型应用</li><li><input disabled="" type="checkbox"> 股市真规则</li><li><input checked="" disabled="" type="checkbox"> hbase权威指南</li></ul><h1 id="6月"><a href="#6月" class="headerlink" title="6月"></a>6月</h1><ul><li><input disabled="" type="checkbox"> 剑指offer</li><li><input disabled="" type="checkbox"> 深入理解Java虚拟机</li><li><input disabled="" type="checkbox"> 推荐系统三十六式(在线课程)</li><li><input checked="" disabled="" type="checkbox"> 数据结构与算法之美(在线课程)</li><li><input checked="" disabled="" type="checkbox"> 大规模数据处理实战(在线课程)</li><li><input disabled="" type="checkbox"> Mysql实战45讲(在线课程)</li><li><input disabled="" type="checkbox"> Java核心技术36讲(在线课程)</li></ul><h1 id="7月"><a href="#7月" class="headerlink" title="7月"></a>7月</h1><ul><li><input checked="" disabled="" type="checkbox"> 推荐系统三十六式(在线课程)</li><li><input disabled="" type="checkbox"> 从一到无穷大</li><li><input disabled="" type="checkbox"> 海龟交易法则</li></ul><h1 id="8月"><a href="#8月" class="headerlink" title="8月"></a>8月</h1><ul><li><input checked="" disabled="" type="checkbox"> Java核心技术36讲(在线课程)</li><li><input checked="" disabled="" type="checkbox"> rust程序设计语言</li></ul><h1 id="9月"><a href="#9月" class="headerlink" title="9月"></a>9月</h1><ul><li><input checked="" disabled="" type="checkbox"> 数学之美</li><li><input disabled="" type="checkbox"> 剑指offer</li><li><input checked="" disabled="" type="checkbox"> 深入理解Java虚拟机</li><li><input checked="" disabled="" type="checkbox"> 从0开始学大数据(在线课程)</li></ul><h1 id="10月"><a href="#10月" class="headerlink" title="10月"></a>10月</h1><ul><li><input disabled="" type="checkbox"> Mysql实战45讲(在线课程)</li><li><input disabled="" type="checkbox"> 从一到无穷大</li><li><input checked="" disabled="" type="checkbox"> 阶层越迁</li><li><input checked="" disabled="" type="checkbox"> kafka核心技术与实战(在线课程)</li><li><input checked="" disabled="" type="checkbox"> 小岛经济学</li><li><input disabled="" type="checkbox"> Java编程的逻辑</li><li><input checked="" disabled="" type="checkbox"> 走进搜索引擎(第二遍)</li></ul><h1 id="11月"><a href="#11月" class="headerlink" title="11月"></a>11月</h1><ul><li><input disabled="" type="checkbox"> 思考,快与慢</li><li><input disabled="" type="checkbox"> Java编程的逻辑</li><li><input checked="" disabled="" type="checkbox"> 用户行为网络画像</li><li><input disabled="" type="checkbox"> 这就是搜索引擎</li><li><input checked="" disabled="" type="checkbox"> 分布式技术原理与算法解析(在线课程)</li><li><input disabled="" type="checkbox"> 推荐系统与深度学习</li></ul><h1 id="12月"><a href="#12月" class="headerlink" title="12月"></a>12月</h1><ul><li><input disabled="" type="checkbox"> Java编程的逻辑</li><li><input disabled="" type="checkbox"> 这就是搜索引擎</li><li><input disabled="" type="checkbox"> 推荐系统与深度学习</li></ul>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
</entry>
<entry>
<title>初步探索实时数据处理系统</title>
<link href="/2018-09-30-real-time-proccess/"/>
<url>/2018-09-30-real-time-proccess/</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>因为业务需要, 公司现在需要一个实时的计算平台来支撑上层的各种业务</p><p>借这个机会, 对我们用到的相关技术部分进行了整理</p><h1 id="业务场景分析"><a href="#业务场景分析" class="headerlink" title="业务场景分析"></a>业务场景分析</h1><p>下面拿我自己经历的两个项目来探讨一下实时计算平台的构建,以及其中遇到的一些坑</p><h1 id="业务1-统一的产品池服务"><a href="#业务1-统一的产品池服务" class="headerlink" title="业务1. 统一的产品池服务"></a>业务1. 统一的产品池服务</h1><h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><h3 id="统一产品数据池"><a href="#统一产品数据池" class="headerlink" title="统一产品数据池"></a>统一产品数据池</h3><p>由于公司部门比较分散,公司的不同品类的产品(在线旅游公司)分属不同的BU(Business Unit),不同部门之间不仅数据不互通, 而且使用的数据库,产品数据结构和使用的存储技术也都不相同, 数据库存储主要使用Oracle和MySQL</p><p>我们组的业务由于含有统一的列表页和内容服务, 所有分类产品的相关信息都需要进行聚合展示, 所以原来我们使用产品都需要根据产品品类调用不同部门提供的接口进行数据查询</p><p>考虑到接口性能和未来业务的增长,我们需要一个统一的产品池功能来帮助汇总所有的产品信息,向上层业务提供一个统一的最基本的产品信息查询, 之后所有组内的产品信息统统通过产品池进行获取, 这样把数据和业务进行充分解耦 </p><p>上层业务不需要了解各种分类的产品信息的存储位置和处理逻辑,只需要从统一的产品池获取产品信息即可,同时作为基础的数据服务还需要保证服务的性能和高可用性,于是有了产品池这个项目 </p><h2 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h2><p>主要涉及的中间件和服务<code>redis</code>,<code>kafka</code>,<code>storm</code>,<code>elasticsearch</code>,<code>mysql</code></p><h2 id="项目详情"><a href="#项目详情" class="headerlink" title="项目详情"></a>项目详情</h2><ol><li>对接各BU, 整合各BU的产品信息到统一的产品容器内(选择redis/es作为主要的对外存储容器)</li><li>提供统一的产品信息获取接口</li></ol><h2 id="整体结构"><a href="#整体结构" class="headerlink" title="整体结构"></a>整体结构</h2><p><img src="https://i.loli.net/2020/05/09/DOkCVIlZ7BKz16r.png" alt="productpool.jpg"></p><p>其中各组件的主要功能:</p><p><code>Redis</code>: 存储k-v结构的产品信息, 提供前台api接口的产品基础信息查询数据</p><p><code>Elasticsearch</code>: 提供后台和部分前台对产品的搜索功能</p><p><code>kafka</code>: 数据总线, 后台数据流转的核心</p><p><code>mysql/oracle</code>: 提供最初始的数据源</p><p><code>storm</code>: 产品信息计算平台</p><h2 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h2><h3 id="前台api获取产品的流程"><a href="#前台api获取产品的流程" class="headerlink" title="前台api获取产品的流程"></a>前台api获取产品的流程</h3><p><img src="https://i.loli.net/2020/05/09/YlEnJMbQuqDXBNh.png" alt="flow1.png"></p><h3 id="后台构建产品的流程"><a href="#后台构建产品的流程" class="headerlink" title="后台构建产品的流程"></a>后台构建产品的流程</h3><p><img src="https://i.loli.net/2020/05/09/iVF5gAlwafOmSXv.png" alt="flow2.png"></p><h2 id="详细步骤描述"><a href="#详细步骤描述" class="headerlink" title="详细步骤描述"></a>详细步骤描述</h2><ul><li><p>定时的产品id添加: 定期进行全量的产品数据重建, 为了方便控制重建过程, 将要处理的产品id分批存入kafka中的<code>全量重建topic</code>, 也就是把批处理转化为流处理 </p></li><li><p>失效的产品id: 当某个产品不存在于redis中时, 也会重新放入kafka的另外的<code>miss产品topic</code>中进行重建 </p></li><li><p>当产品信息变更时候也会有对应的变更产品id入kafka的<code>变更产品topic</code>中进行重建 </p></li><li><p>处理产品时会从以上三个产品源topic中读取需要重建的产品, 根据分类发放到<code>不同的分类topic</code>, 然后交给storm进行产品信息计算, 这部分信息只有简单的产品ID和更新类型标识 </p></li><li><p>storm中构建失败的产品(数据库中不存在等原因), 会在redis中进行标记暂时不可用(有效期1天), 不可用的产品不会继续进行重建 </p></li><li><p>kafka多个topic中的消息含有需要构建的 产品id和产品需要构建的内容, 也就是说可以通过消息内容格式控制构建产品的某个部分的信息(例如: 只更新产品的基本信息, 只更新价格信息, 只更新评论数,好评数等信息)</p></li><li><p>storm从kafka中获取消息, 进行产品的信息计算, 计算完成的信息会重新返回kafka, 同样根据产品分类发放到不同的<code>分类topic</code>, 这部分信息含有全量的产品信息数据</p></li><li><p>整合各个分类topic的产品计算结果, 写入redis 和 es, 并回写部分mysql表</p></li></ul><h2 id="产品数据更新"><a href="#产品数据更新" class="headerlink" title="产品数据更新"></a>产品数据更新</h2><p>通过<code>canal</code>监听mysql数据库的产品表数据变更, 将变更数据发给kafka中的<code>产品表日志topic</code>, 后续从kafka的<code>产品日志topic</code> ,根据数据内容解析出来产品更新事件, 封装对应的事件消息, 存入<code>产品事件topic</code> </p><p>通过读取<code>产品事件topic</code>中的数据, 根据品类和变更内容, 向产品池<code>变更产品topic中发送</code>发送产品池信息重构需求</p><h2 id="经验和总结"><a href="#经验和总结" class="headerlink" title="经验和总结"></a>经验和总结</h2><ul><li>为什么要分多个产品数据源topic</li></ul><ol><li><p>为了优先级考虑, 不同来源的产品对时效性要求是不同的, 但是kafka本身又做不了带有优先级的消息处理</p></li><li><p>不同的分类的产品的处理逻辑不同, 更新频率和数据量也不同, 提前进行分流</p></li></ol><ul><li>为什么不同分类的产品要用不同的写入topic</li></ul><ol><li>如果有其他业务需要使用其中某个分类的产品数据只需订阅对应的产品topic流就可以了, 免去了从全量产品流中过滤的步骤</li></ol><ul><li>为什么最后还要把产品信息吐会回kafka</li></ul><ol><li><p>为了统一控制写入源并做优化, 使用统一的topic存储数据可以让整个程序只有一个数据写入的源, 所有写入操作统统使用写总线来处理, 解耦了功能, 提高了可靠性, 扩展性和可维护性</p></li><li><p>可以对数据写入做优化, 比如:幂等处理, 批压缩写入处理, ABA问题的重写</p></li><li><p>为了数据重用, 因为其他部分业务组也可能需要使用产品信息, 到时候直接订阅最终的产品信息表就可以了</p></li><li><p>为了方便扩展, 如果将来数据量大, 出现了写入瓶颈, 只要对这一部分承担写总线功能的写入程序进行扩展就可以了</p></li></ol><h1 id="业务2-用户画像之用户信息完善系统"><a href="#业务2-用户画像之用户信息完善系统" class="headerlink" title="业务2. 用户画像之用户信息完善系统"></a>业务2. 用户画像之用户信息完善系统</h1><h2 id="需求-1"><a href="#需求-1" class="headerlink" title="需求"></a>需求</h2><p>这个项目是用户画像的子项目, 目的是将用户分布在不同BU的信息进行整合, 提供一份统一最完整的用户信息出来</p><p>同时进行一些数据清洗和数据统计</p><ul><li>对分散在各个表中的会员信息进行梳理, 整合一份相对比较完善的用户信息</li></ul><p>例如: 用户1在基本信息中填写了一份信息 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{ </span><br><span class="line"> "username":"zhang",</span><br><span class="line"> "birthday":"1990-01-01",</span><br><span class="line"> "gender":"M"</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>同时用户上传了一张个人身份证, 通过解析, 身份证含有的信息是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "birthday":"1990-12-07",</span><br><span class="line"> "gender":"F"</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>也就是说用户自己填写的信息和身份证中的信息不一致, 相同的情况可能出现在多个业务部门, 因为业务拆分各部门相互独立, 同一个用户在多个业务部门可能拥有多份不太一致的用户信息 </p><ul><li>对进行过清洗的用户数据进行完善度的计算</li></ul><p>根据不同用户信息字段占有的不同分值权重, 使用完善后的用户信息, 对用户的完善度进行实时统计</p><ul><li>定期统计用户的完善度报表</li></ul><p>根据用户会员等级/地区/性别 等基本属性和 对应的销售vip客服人员进行用户信息的报表统计</p><h2 id="组件-1"><a href="#组件-1" class="headerlink" title="组件"></a>组件</h2><p>主要相关的组件有 <code>mysql</code>,<code>kafka</code>,<code>storm</code>,<code>hbase</code>, <code>es</code></p><h2 id="项目详情-1"><a href="#项目详情-1" class="headerlink" title="项目详情"></a>项目详情</h2><ol><li>对接各数据源, 根据用户身份表示整合统一的用户信息</li><li>统一存储用户信息</li></ol><blockquote><p>ps:由于部分原因, 项目的实际开发时间很短, 只有200左右的工时, 也就是一个人工作一个月, 而且大部分时间都花在内部数据问题的处理上面, 所以项目未能做到最终非常完善的程度</p></blockquote><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><ol><li>定期的全量用户信息补全</li><li>用户信息变更以后触发的补全</li><li>用户资料补全以后进行完善度的计算</li><li>定期根据用户属性对用户完善度进行报表统计</li></ol><p>具体的细节跟上面产品池相似, 都是利用<code>kafka</code>的数据流转, 将需要计算的消息流到<code>storm</code>, 经过计算以后再通过<code>kafka</code> 回馈给数据库和存储 </p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>其实回顾这两个项目</p><p>在其中主要起作用的中间件主要是<code>kafka</code>和<code>storm</code></p><p><code>kafka</code> 承担了系统几乎所有的数据流转需求, 做了一个数据总线的角色, 提供了<code>事件驱动</code>,<code>ETL</code>,<code>解耦</code>等功能 </p><p><code>storm</code> 则承担了主要的计算任务和部分数据转发功能 </p><p>其他 <code>mysql</code>, <code>redis</code>,<code>elasticsearch</code>则一直充当数据提供方和数据使用方(业务)之间的数据桥接作用 </p><p>这一套消息处理流程目前来看还没遇到太大的问题, 但是因为我们部门业务相对比较单一, 尚不能完全发挥这套架构的潜力 </p><p>希望以后可以多尝试, 并进行改进</p>]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> 数据处理 </tag>
</tags>
</entry>
<entry>
<title>平衡和度</title>
<link href="/2018-06-06-balance/"/>
<url>/2018-06-06-balance/</url>
<content type="html"><![CDATA[<h1 id="什么是智慧"><a href="#什么是智慧" class="headerlink" title="什么是智慧"></a>什么是智慧</h1><p>一直以来我都很认同<code>大道至简</code>的看法</p><p>所以我天真的认为处理世界上的所有事情肯定有一个通用的框架, 该框架应该适用于所有麻烦的事情<br>而我们庸庸众人只需要学习这一种处事方式就能轻松应付生活,即所谓的The One Truth <br>后来的我渐渐意识到,<code>世界本来就是混乱无序的</code>, 那个万能法则肯定是不存在的 </p><blockquote><p>从物理学上来讲, 一个封闭的系统中, 熵(代表混乱程度)处在不断增加的状态,一味地想维持低熵状态往往需要额外的付出更多能量.同样的,如果我强行要求这个系统是有序的并且规则的,那对这个系统来说,必然需要极大的能量来维持这种状态</p></blockquote><p><strong>智慧的本质, 就是对空间和时间的理解</strong></p><p><img src="https://i.loli.net/2020/05/09/b2at6W9HpIVl4rJ.png" alt="wisdom_mindnode1.png"></p><p>所谓智慧, 就是能很好地平衡时间和空间, 而怎么平衡, 就涉及到度的把握</p><h2 id="度"><a href="#度" class="headerlink" title="度"></a>度</h2><p><strong>度</strong>或者叫<strong>分寸</strong>, 一种抉择中的取舍<br>我们无时无刻不在面临许多的抉择<br>大部分的抉择都存在我们可以感知的正面和反面效果,即便看起来非常正面的行为背后也隐藏着隐忧<br>例如: </p><ul><li>培养好的习惯是应该的, 但是好的习惯不可能一直增加. 我们的精力是有限的,我们所处的的环境也在不断改变, 根据环境调整自己习惯才是我们要做的,否则随着时间增加我们的习惯也越来越多,如果不及时调整, 你的所有时间会被积累下来的习惯完全占据</li></ul><p>一个人在待人接物方面能很好地把握分寸, 我们会说他情商高<br>一个人在自己和外界的联系方面能很好地把握分寸, 我们会说他有品位<br>一个人在处理冲突方面能很好地把握分寸, 我们会说他有智慧 </p>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
<tags>
<tag> 思考 </tag>
</tags>
</entry>
<entry>
<title>终于找到了魅族flow耳机的模特啦</title>
<link href="/2018-05-20-meizi-qingguya/"/>
<url>/2018-05-20-meizi-qingguya/</url>
<content type="html"><![CDATA[<p>前几天找到了我一直寻找的给魅族耳机代言的小姐姐, 微博: @青谷娅</p><p>我从第一眼看到这个小姐姐就觉得气质好好啊</p><p>从网上搜了好久都找不到个人信息</p><p>后来偶然间从某个摄影师处了解到这个小姐姐的微博</p><p>开心了好多天</p><h1 id="以下是美图欣赏"><a href="#以下是美图欣赏" class="headerlink" title="以下是美图欣赏"></a>以下是美图欣赏</h1><p>魅族耳机照</p><p><img src="https://i.loli.net/2020/05/09/MHXrfROKTq7JNgS.png" alt="WX20180528-200414.png"></p><p><img src="https://i.loli.net/2020/05/09/6jiXNxMYbq8UuOe.png" alt="WX20180529-095529.png"></p><p>以下图片来自微博</p><p><img src="https://i.loli.net/2020/05/09/9w3e671KApZhjOk.jpg" alt="IMG_1104.JPG"></p><p><img src="https://i.loli.net/2020/05/09/MRLX5p3rikS2W74.jpg" alt="IMG_1107.JPG"></p><p><img src="https://i.loli.net/2020/05/09/sdLI9fo5q8P4miS.jpg" alt="IMG_1106.JPG"></p><p><img src="https://i.loli.net/2020/05/09/oiAJGuB9EVlxpcN.jpg" alt="IMG_1103.JPG"></p>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
<tags>
<tag> 青谷娅 </tag>
</tags>
</entry>
<entry>
<title>胡思乱想-有关当前社会的一些隐忧</title>
<link href="/2018-03-09-woolgather-social-problem/"/>
<url>/2018-03-09-woolgather-social-problem/</url>
<content type="html"><![CDATA[<p>我想说说我自己对当前社会的一些担忧</p><p>我总觉得未来的3年(2018-2020)会是我国社会的重大转折点</p><h1 id="人口结构的隐忧"><a href="#人口结构的隐忧" class="headerlink" title="人口结构的隐忧"></a>人口结构的隐忧</h1><p>中国的人口老龄化加剧已经是一个无法避免的事情了</p><p>即便国家最近几年放开了二胎政策, 但是从数据上面看效果并不是很好</p><p>下面是一份部分年份的出生和死亡人数的图(单位:万人)</p><p><img src="https://i.loli.net/2020/05/09/KFjRLxNfbq9cUd5.png" alt="people-1.png"></p><h2 id="出生与死亡人口"><a href="#出生与死亡人口" class="headerlink" title="出生与死亡人口"></a>出生与死亡人口</h2><p>目前我国的人均寿命男性在74岁, 女性在77岁, 综合平均值大概在75.5岁上下</p><p>也就是说去年2017年, 按照平均寿命来算, 去年的大量正常死亡人口都是出生于1941年的, 假设我们预计平均寿命每5年增长一岁的话, 1946年出生的人口正常死亡时间在2023年左右,再加上一些非正常因素, 也就是说2023年左右死亡人数至少在1200万+</p><p>从上面的图推论, 我们有理由相信在未来10年, 中国每年的死亡人口将迅速由每年970万左右上升到每年1600万+( 深绿色曲线将沿着浅绿色曲线旧轨迹上升)</p><p>到2020年,1955年出生的人将步入65岁, 人到了这个阶段, 已经进入疾病高发期, 也就是说未来几年我国将新增至少5200万65岁以上的老人,这批人对养老和医疗造成的压力会很大, 我非常担忧之后医疗资源和社会资源的消耗情况</p><h1 id="社会影响和政策"><a href="#社会影响和政策" class="headerlink" title="社会影响和政策"></a>社会影响和政策</h1><ul><li>对出生人口的预测</li></ul><p>回过头来看我们的出生人口, 最近几年国内出生人口持续下降, 2016-17年由于二胎政策有小幅回升, 17年1700万出生人口中,二胎占800万(这其实是一个很不好的信号)</p><p>当前国内的生育主力,还是1980-90年这一批人, 等到他们这一代人的生育意愿消耗完毕, 国内出生人口必然大幅下降, 因为91-96年出生人口本来就少了许多, 适龄生育人数更是大减</p><p>我国人口出生的第一个高峰在60年代,第二个高峰在1985-1990年,第二个人口高峰出生的人的父母就是第一次人口高峰中出生的人,理论上来说85-90这一代人的孩子将会形成第三次人口高峰,预计在2015-2022年, 但是, 我们看一下出生数据表:</p><p><img src="https://i.loli.net/2020/05/09/uHxwt5FIsrqBfGA.png" alt="people-2.png"></p><p>我们来看1985-1990年的出生人口分别为<code>2042,2319,2528,2457,2513,2621</code>,平均 2413万人/年</p><p>这一代人到了最佳生育期(24-30岁)之后对应的实际出生人口为 <code>1615,1574,1604,1635,1640,1687</code>(2009-2014),平均每年1625万人</p><p><strong>也就是说正常情况下, 每年2400万人的适龄生育人口对应的生育婴儿人数大概是1600万,比例大概是 3:2</strong></p><p>过去5年提供生育力量的主力人口就是这一批人,我们根据他们这一代人的出生人口数和生育情况可以预估未来5年的出生人口情况</p><p>未来5年提供生育力量的的主力人口也就是 1991-1996这几年的出生人口分别为 <code>2008,1875,1791,1647,1693,1522</code> 平均1756万人/年</p><p>根据3:2的比例,我们可以预测未来5年的生育人数是平均 1200万人/年</p><p>我们根据以上信息, 来做一个有关中国人口的预测:</p><p><img src="https://i.loli.net/2020/05/09/kP43NK5jmI8UZYJ.png" alt="people-3.png"></p><p><strong>预计最迟中国将在2022年迎来人口负增长!!!</strong></p><p><strong>最迟中国将在2022年迎来人口负增长!!!</strong></p><p><strong>2022年人口负增长!!!</strong></p><p>中国人口一旦减少, 将是一个革命性的时刻, 到时候很有可能中国将会走上一条没有人能预想到的道路</p><h2 id="人口老龄化的影响"><a href="#人口老龄化的影响" class="headerlink" title="人口老龄化的影响"></a>人口老龄化的影响</h2><p><strong>人是社会的基础</strong> </p><p>人口结构和数量的变化将会带来社会的巨大改变</p><p>而目前我国逐步严重的人口老龄化将会为我们的社会带来难以想象的巨大的压力</p><ul><li>公众人物的离世</li></ul><p>在2018年有很多人感叹今年是怎么了</p><p>一个接一个知名公众人物离世, 大家纷纷都说不喜欢2018年, 因为失去了太多喜爱的老前辈</p><p>其实这个现象是正常的</p><ol><li>80, 90后是新媒体的第一代受众, 这一代人开始认识比父辈更多的公众人物, 我们认识的公众人物更多 </li><li>电视, 广播时代的前几代先驱者到现在都逐步步入老年时代, 他们普遍比我们年纪大很多, 他们在变老</li></ol><p>以后这个情况恐怕会越来越严重</p><p>我算一笔账, 现代网络时代每个人听说过的的公众人物进入老年人行列的少说也有 1000人 </p><p>假设他们都在30年内相继离世, 每年是30人, 平均下来, 差不多每10天就有一个你熟知的公众人物离世</p><p>但是真实情况是我们每个人熟知的人远超 1000人, 他们也不一定都能长寿到100岁, 所以现实情况未来只会更糟糕</p><ul><li>未来还会有的影响</li></ul><ol><li>殡葬行业的需求量未来会爆增, 加上我国传统思想的影响, 行业将会迎来前所未有的机遇</li><li>医疗资源紧张, 成人纸尿裤一定会大卖 ^_^</li><li>健康保健行业的机遇, 以后大家会越来越重视健康生活, 保健品行业可能会迎来爆发期</li><li>旅游行业和保险行业未来也会进入一段时间的黄金时期</li><li>同时由于国内文化的特殊性, 老龄化还会造成教育行业的快速发展</li></ol><p>诸位注意投资</p><h2 id="平均寿命和养老金"><a href="#平均寿命和养老金" class="headerlink" title="平均寿命和养老金"></a>平均寿命和养老金</h2><p>我国早期制定的养老金计划是依照平均寿命60岁制定的, 可当时的人们没有想到人类的平均寿命增长的如此之快</p><p>以至于旧有的养老金制度无法满足当前社会的需要, 我国养老金亏空已经是人尽皆知的事情了</p><p>现在年轻人生育欲望低, 未来的养老更是没有保障, 这样我估计将来怕是连这个养老金制度都会崩溃</p><p>养老金问题已经成为了我国一个十分重大的社会问题, 不知道未来国家会采用怎么样的方式来解决</p><h2 id="房价"><a href="#房价" class="headerlink" title="房价"></a>房价</h2><p>在我们的邻国日本有研究表明, 日本的房价和年轻人的生育意愿呈负相关.</p><p>国内现在房价如此之高,导致养孩子的成本极高, 没有一定的物质积累,年轻人怕是不敢妄谈生育</p><p>高房价会进一步降低年轻人的生育意愿</p><p>我上面对我国出生人口的预测还是太过乐观了</p><p>不过我同时也觉得现在房价泡沫有点偏大了, 预计未来3年(2018-2020)就会破灭, 房价就会崩盘</p><p>即便很多人说国家不会允许房价崩盘, 但是我想说, 房价不崩盘, 其他所有实体行业统统要崩盘, 两害相权取其轻, 相信国家会这么选择的</p><p>所以打算买房的朋友可以稍微等上几年</p><h2 id="国家未来几年可能会采取鼓励生育政策"><a href="#国家未来几年可能会采取鼓励生育政策" class="headerlink" title="国家未来几年可能会采取鼓励生育政策"></a>国家未来几年可能会采取鼓励生育政策</h2><p>由于人口压力,国家未来几年一定会出台各种鼓励生育的政策</p><p>我在这里做一下预测, 未来几年可能会采取的政策</p><ol><li>取消多胎生育限制</li></ol><p>现在仅仅是取消了二胎的限制, 多胎依然是违法的, 未来很有可能会彻底取消生育限制, 以刺激农村乡镇人民的生育意愿</p><blockquote><p>ps: 据彭博社消息, 中国将来2018年年底取消多胎限制,消息未经官方证实</p></blockquote><ol start="2"><li>延长退休年龄</li></ol><p>国家已经在进行这方面的政策调整了, 未来说不定会跟新加坡一样, 彻底取消退休年龄, 永不退休</p><ol start="3"><li>增加女性生产福利</li></ol><p>增加女性生产福利(比如: 强制半年产假,男方陪产3个月). 不过如此一来很可能会起到其他意想不到的效果</p><p>比如:增加产假时长等于变相增加企业雇佣女性的成本, 没有企业愿意在同等条件下雇用女性, 导致女性工作难找, 不得不在家生孩子, 现在的欧洲已经在这么做了</p><ol start="4"><li>利用媒体鼓动年轻人谈恋爱</li></ol><p>鼓励年轻人谈恋爱同事灌输多子多福的思想才是理论上的可持续发展战略</p><p>其他的政策都只能治标不能治本</p><h1 id="教育和阶级固化"><a href="#教育和阶级固化" class="headerlink" title="教育和阶级固化"></a>教育和阶级固化</h1><h2 id="教育周期的延长"><a href="#教育周期的延长" class="headerlink" title="教育周期的延长"></a>教育周期的延长</h2><p>随着现在社会的发展, 人类掌握的知识总量呈指数发展</p><p>同样, 一个人从出生到达科研领域最前沿的时间也在不断增长</p><p>牛顿时代,一个25岁的数学系高材生就能接触到最前沿的数学理论研究, 此时的物理更是连基本的框架都没有, 这个时期人学习某种技术的周期相对较短, 一般能在25岁完成对所需基本知识的积累</p><p>到了现代, 哪怕你专研一个领域并且博士毕业, 都未必能接触到最前沿的研究</p><p>科研要突破必须先走到研究的最前沿, 但是要走到最前沿又必须有足够的知识积累, 这就导致现代人接触前沿科学的时间被大大拉长</p><p>以前 25-30岁的科研人员就能接触到最前沿的技术, 现在得等到40-50岁才有可能完成早起必要的知识积累, 但是4,50岁的人了, 哪还有那么多精力来搞研究, 前沿研究的迟滞会导致整个科学学科的发展变慢</p><p>人类的科技进步已经不可避免要出现瓶颈了</p><p>这种矛盾产生的原因是因为人类社会的发展太过迅速, 人类的进化速度赶不上社会的发展速度, 彼此之间不能很好的匹配</p><p>可能有效的解决方案</p><ol><li>领域继续细分,降低研究人员积累必要知识的负担</li><li>借助电脑帮助加快研究速度</li></ol><h2 id="阶级固化"><a href="#阶级固化" class="headerlink" title="阶级固化"></a>阶级固化</h2><p><strong>社会阶层的固化是历史上每一段和平时期的主旋律</strong></p><p>我只说一个现象,我当年上学时候, 同学里面还有县长的孩子,教育局长的孩子.公安局副局长的孩子<br>但是我弟弟上学的时候, 都已经很少遇到权贵子弟了, 是当任的权贵年龄偏大吗, 不是,是因为这些人的孩子早出国去了 </p><p>大家自己富裕了, 自然就想给孩子更好地条件<br>富者愈富, 穷者愈穷<br><strong>而且这是个死循环,你还不能不给孩子投入</strong><br>我有朋友说,”有多少钱,出多少资源, 不可能什么都给孩子准备好,只能靠他自己,我当年没钱也不过来了吗”<br>其实这种看法我是很反对的 </p><p>现在很多小孩子你不给他报什么钢琴班,美术班,英语班, 他会的技能就比其他孩子少,小孩子们之间攀比心很重,你孩子不懂这些,没有这些,人家别的小孩子不跟你玩…<br>别人有Switch,你没有, 别人报了绘画班,钢琴班,你没有,别人学了英语/美术,你没有, 那别人就不跟你玩…因为孩子也有圈子,圈子是由共同语言组形成的 </p><p>你的孩子不应该拿来跟20多年前的你来比, 他的竞争对手是他的同龄人, 应该看看他们同龄人是怎么样的条件<br>而且现在很多老师会在入学时候收集家长信息,对孩子因家庭条件施教,甚至你不给老师送红包就不在意你孩子<br><strong>这都是中国特色的现象,逼着你的孩子不得不拼爹</strong><br>这几年这种现象尤为明显, 现在大家都希望把最大的投入放到孩子的教育上面, 由于教育的投入,孩子的差距也会越来越大<br>就像<<名侦探柯南>>里面有一集说的: 政治家的儿子依然是政治家, 企业家的孩子依然是企业家, 明星的孩子依旧是明星<br>日本已经经历过我过正在经历的阶段和遇到的诸多问题, 日本的现状我们能从中学到很多 </p><blockquote><p>秦人不暇自哀,而后人哀之;后人哀之而不鉴之,亦使后人而复哀后人也。</p></blockquote>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
<tags>
<tag> 思考 </tag>
</tags>
</entry>
<entry>
<title>学习分布式计算框架-MapReduce</title>
<link href="/2018-02-08-distribute-map-reduce/"/>
<url>/2018-02-08-distribute-map-reduce/</url>
<content type="html"><![CDATA[<p>Google搜索背后的索引计算工作是搜索引擎的核心之一, Google现有的搜索引擎是基于Caffeine的增量索引系统构建的(Caffeine相关论文极少)。由于谷歌的网页索引和计算数据量巨大,Google发布了一种适用于超大规模数据的分布式计算模型,就是map-reduce,Google后续的一系列大数据计算引擎都是基于MapReduce的思想构建出来的</p><h1 id="MapReduce"><a href="#MapReduce" class="headerlink" title="MapReduce"></a>MapReduce</h1><p>在2004年Google发布了一篇论文, 描述了Google内部针对大数据处理的一种通用模式:MapReduce(简称MR)</p><p>MapReduce是一种编程模型,主要用来对大量的数据进行分布式处理和计算,MR的本质是对大量通用计算过程的抽象,经过谷歌工程师长期的计算经验总结,发现很多常见的数据处理任务都可以被拆分为Map和Reduce两个计算过程。MR描述的就是如何使用这两个计算过程实现常用的数据计算工作</p><p>论文地址:<a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf">MapReduce论文</a></p><h2 id="从简单例子说起"><a href="#从简单例子说起" class="headerlink" title="从简单例子说起"></a>从简单例子说起</h2><p>我们用一个实际的问题来描述MapReduce的思想</p><p>假设我们有一个文档集合<code>C</code>,里面包含<code>M</code>个文档,我们要对文档集合中的文档进行单词次数统计,统计在所有文档中的每个单词出现的次数</p><p>我们自然的想法就是 先统计每个文档里面的单词和出现次数, 在统计一个集合里面的所有文档的单词和出现次数,最后统计所有文档的单词和出现次数。</p><p>没错,这种直觉的解决方案就可以用以下MR过程描述:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">1. 拿到文档 -> map -> (文档 , [("word":1)])</span></span><br><span class="line"><span class="string">这一步需要编写一个map函数, 该函数接受一个文档名和文档内容, 返回 文档内的关键词频次序列</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">2. 拿到 [(文档,关键词统计)] -> 通过集合内的文档名 group by -> (集合 -》 [(word, 1)])</span></span><br><span class="line"><span class="string">这一步需要编写一个reduce函数,可以对[("name",value)]类型的数据按照name进行累加</span></span><br><span class="line"><span class="string">"""</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">map</span>(<span class="params">key: string, values: string</span>) -> <span class="type">List</span>[(string, <span class="built_in">int</span>)]: </span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> key: document name</span></span><br><span class="line"><span class="string"> return 该函数返回一个[("to",1),("yours",12)]这样的列表数据</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> ans = []</span><br><span class="line"> content = get_doc(key)</span><br><span class="line"> <span class="keyword">for</span> word <span class="keyword">in</span> content:</span><br><span class="line"> ans.append((word,<span class="number">1</span>))</span><br><span class="line"> <span class="keyword">return</span> ans</span><br><span class="line"> </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">reduce</span>(<span class="params">key: string, values: <span class="type">List</span></span>) -> <span class="type">List</span>[(string, <span class="built_in">int</span>)]: </span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> key: a word eg: "t1"</span></span><br><span class="line"><span class="string"> values: a list of counts 示例: [1,2,3]</span></span><br><span class="line"><span class="string"> return 该函数同样返回一个[("to",1),("yours",12)]的数据</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="built_in">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> v <span class="keyword">in</span> values:</span><br><span class="line"> result += ParseInt(v);</span><br><span class="line"> Emit(result)</span><br></pre></td></tr></table></figure><h2 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h2><p>Map是一个将问题分解成多个小问题为后续的分发提供基础的技术</p><p>Map的过程用函数来表示就是:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">map</span>(<span class="params">k,v</span>) -> (k1,<span class="type">List</span><v1>):</span><br><span class="line"> <span class="comment"># map函数接收一对k,v键值对,返回一个(k1,v1<list>)</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><p>map函数的目的是为了将任务分割方便后续的任务合成,之所以要传入文档名字是为了在磁盘上对返回结果进行标记,标记出调用方是谁。现代化的基于内存的MR基本不需要传文档标记参数了</p><h2 id="Reduce"><a href="#Reduce" class="headerlink" title="Reduce"></a>Reduce</h2><p>Reduce是将多个k-v pair按照相同的k进行合并的过程</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">reduce</span>(<span class="params">k,<span class="built_in">list</span>[v2]</span>) -> (<span class="built_in">list</span>[v3]>):</span><br><span class="line"> <span class="comment"># reduce函数接收一个k,v1<list>, 返回一个v2<list></span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><blockquote><p>ps:大多数情况下map的返回结果不能直接用于reduce函数,需要特殊处理一下</p></blockquote><h2 id="过程图"><a href="#过程图" class="headerlink" title="过程图"></a>过程图</h2><p>以下是一个很详细的对MongoDB中的MapReduce过程的解读图</p><p><img src="https://i.loli.net/2020/05/09/4lu5Ek2yZDi7doF.png" alt="map-reduce.png"></p><p>我们可以看到map函数的输入是多个被查询过滤过的文档集合,返回值是一个map对应的值列表,我们可以认为map函数式对所有符合条件的数据进行一次简单的处理</p><p>reduce则是将这个值列表按照key进行处理,即对map的结果进行最终结果合并操作</p><h2 id="代码-单机版"><a href="#代码-单机版" class="headerlink" title="代码(单机版)"></a>代码(单机版)</h2><p>以下是使用mapreduce进行文档词频统计的示例代码</p><p>其中的主要代码简单解释一下</p><h3 id="MapReduce类"><a href="#MapReduce类" class="headerlink" title="MapReduce类"></a>MapReduce类</h3><p><code>MapReduce</code>类是一个通用的MapReduce框架,理论上任意MapReduce任务都可以套用这个框架</p><p>要处理不同的问题我们只需要修改对应的map和reduce函数即可</p><p>MapReduce类接收3个参数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">i: 要处理的数据源,格式是一个普通的字典; </span><br><span class="line">mapper: 映射函数,该函接收一个kv键值对,根据需要对每个v值进行处理,返回一个(k,v<list>),此处的k值并不一定是传入的k值; </span><br><span class="line">reducer: 压缩函数,接收一个(k,v<list>),根据需求对数据进行压缩合并;</span><br></pre></td></tr></table></figure><h3 id="map函数"><a href="#map函数" class="headerlink" title="map函数"></a>map函数</h3><p>其中<code>get_most_common_from_text</code>使用了结巴分词插件</p><p>参数:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">k:"a"</span><br><span class="line">v:"The quick brown fox jumped over the lazy grey dogs."</span><br></pre></td></tr></table></figure><p>返回:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> (<span class="string">"the"</span>,<span class="number">1</span>),</span><br><span class="line"> (<span class="string">"quick"</span>,<span class="number">1</span>),</span><br><span class="line"> (<span class="string">"fox"</span>,<span class="number">1</span>)</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="reduce函数"><a href="#reduce函数" class="headerlink" title="reduce函数"></a>reduce函数</h3><p>参数:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">k:<span class="string">"the"</span></span><br><span class="line">v<<span class="built_in">list</span>>:[<span class="number">1</span>,<span class="number">1</span>,<span class="number">1</span>]</span><br></pre></td></tr></table></figure><p>返回: </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[(<span class="string">"the"</span>,<span class="number">3</span>),(<span class="string">"quick"</span>:<span class="number">1</span>)...]</span><br></pre></td></tr></table></figure><p>完整代码如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> itertools</span><br><span class="line"><span class="keyword">import</span> jieba</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MapReduce</span>:</span><br><span class="line"> __doc__ = <span class="string">'''提供map_reduce功能'''</span></span><br><span class="line"></span><br><span class="line"><span class="meta"> @staticmethod</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">map_reduce</span>(<span class="params">i, mapper, reducer</span>):</span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> map_reduce方法</span></span><br><span class="line"><span class="string"> :param i: 需要MapReduce的集合</span></span><br><span class="line"><span class="string"> :param mapper: 自定义mapper方法</span></span><br><span class="line"><span class="string"> :param reducer: 自定义reducer方法</span></span><br><span class="line"><span class="string"> :return: 以自定义reducer方法的返回值为元素的一个列表</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> intermediate = [] <span class="comment"># 存放所有的(intermediate_key, intermediate_value)</span></span><br><span class="line"> <span class="keyword">for</span> (key, value) <span class="keyword">in</span> i.items():</span><br><span class="line"> intermediate.extend(mapper(key,value))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># sorted返回一个排序好的list,因为list中的元素是一个个的tuple,key设定按照tuple中第几个元素排序</span></span><br><span class="line"> <span class="comment"># groupby把迭代器中相邻的重复元素挑出来放在一起,key设定按照tuple中第几个元素为关键字来挑选重复元素</span></span><br><span class="line"> <span class="comment"># 下面的循环中groupby返回的key是intermediate_key,而group是个list,是1个或多个</span></span><br><span class="line"> <span class="comment"># 有着相同intermediate_key的(intermediate_key, intermediate_value)</span></span><br><span class="line"> groups = {}</span><br><span class="line"> <span class="keyword">for</span> key, group <span class="keyword">in</span> itertools.groupby(<span class="built_in">sorted</span>(intermediate, key=<span class="keyword">lambda</span> im: im[<span class="number">0</span>]), key=<span class="keyword">lambda</span> x: x[<span class="number">0</span>]):</span><br><span class="line"> groups[key] = [y <span class="keyword">for</span> x, y <span class="keyword">in</span> group]</span><br><span class="line"> <span class="comment"># groups是一个字典,其key为上面说到的intermediate_key,value为所有对应intermediate_key的intermediate_value</span></span><br><span class="line"> <span class="comment"># 组成的一个列表</span></span><br><span class="line"> <span class="comment"># print(groups)</span></span><br><span class="line"> <span class="keyword">return</span> [reducer(intermediate_key, groups[intermediate_key]) <span class="keyword">for</span> intermediate_key <span class="keyword">in</span> groups]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">test</span>:</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">get_most_common_from_text</span>(<span class="params">self,text,n = <span class="number">100</span></span>):</span><br><span class="line"> word_list = [x <span class="keyword">for</span> x <span class="keyword">in</span> jieba.cut(text) <span class="keyword">if</span> <span class="built_in">len</span>(x) >= <span class="number">2</span>]</span><br><span class="line"> <span class="keyword">return</span> Counter(word_list).most_common(n)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">map</span>(<span class="params">self,k,v</span>): <span class="comment"># k:文档名, v:文档内容</span></span><br><span class="line"> <span class="keyword">return</span> self.get_most_common_from_text(v,<span class="number">10000</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">reducer</span>(<span class="params">self,k,v</span>): <span class="comment"># k:词 v:词出现的次数</span></span><br><span class="line"> <span class="keyword">return</span> k, <span class="built_in">sum</span>(v)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line"> </span><br><span class="line"> i = {</span><br><span class="line"> <span class="string">"a"</span>:<span class="string">"The quick brown fox jumped over the lazy grey dogs."</span>,</span><br><span class="line"> <span class="string">"b"</span>:<span class="string">"That's one small step for a man, one giant leap for mankind."</span>,</span><br><span class="line"> <span class="string">"c"</span>:<span class="string">" Mary had a little lamb,Its fleece was white as snow;And everywhere that Mary went,The lamb was sure to go"</span>,</span><br><span class="line"> <span class="string">"d"</span>:<span class="string">"I pledge to honor and defend you and yours above all others"</span>,</span><br><span class="line"> <span class="string">"e"</span>:<span class="string">"To share in blessings and burdens, to be your advocate, your champion"</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> t = MapReduce.map_reduce(i,self.<span class="built_in">map</span>,self.reducer)</span><br><span class="line"> <span class="built_in">print</span>(t)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">m = test()</span><br><span class="line">m.run()</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>MapReduce其实就是我们常说的分而治之的思想,统一了数据模型规范, 使之能适用于更广泛的数据计算</p><p>不过这个过程中借助了中间存储,早些年因为内存价格昂贵,所以谷歌选择采用磁盘作为中间存储,后来随着技术发展,出现了spark等利用内存做数据中转的新型MR工具,但是本质还是MR的思想</p><p>本文提到的MR只是一个为了学习制作的简单的单机模型,真正的用MR处理大规模数据的难点往往不在map和reduce函数的编写,而在分布式集群调度和任务执行上面 </p>]]></content>
<categories>
<category> 分布式计算 </category>
</categories>
<tags>
<tag> MapReduce </tag>
</tags>
</entry>
<entry>
<title>分布式文件系统-GFS学习总结</title>
<link href="/2018-02-02-distribute-storage-gfs/"/>
<url>/2018-02-02-distribute-storage-gfs/</url>
<content type="html"><![CDATA[<p>本文试图解释如下问题</p><ol><li>GFS是什么,做什么用的,要解决什么问题</li><li>GFS是怎么解决这些问题的</li><li>GFS的设计有什么优点和缺点</li></ol><h1 id="GFS简介"><a href="#GFS简介" class="headerlink" title="GFS简介"></a>GFS简介</h1><p>GFS(Google File System)是谷歌开发的一个分布式文件系统, 目的是提供一个基于众多廉价服务器工作的基础层分布式的文件存储服务。</p><p>GFS服务的是上层的<code>Bigtable</code>, <code>Megastore</code>等上层数据库应用,所以GFS的读写基本都是其他应用的大文件批量数据读写,Google于2003年放出了GFS的设计论文。</p><p>论文地址 <a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/gfs-sosp2003.pdf">The Google File System</a></p><h1 id="GFS的特点"><a href="#GFS的特点" class="headerlink" title="GFS的特点"></a>GFS的特点</h1><ol><li>仅支持文件追加操作 (适用于谷歌的爬虫数据存储需求),文件块大小为64M (优点很多, 减少元数据量, 减低master服务器压力)</li><li>采用中心化的master节点管理元数据 (可能造成单点故障和性能存储瓶颈)</li><li>基于廉价服务器实现高容错高可用</li><li>控制流和数据流分离,针对当年的网络环境进行优化</li></ol><h1 id="核心组件"><a href="#核心组件" class="headerlink" title="核心组件"></a>核心组件</h1><p>GFS的核心服务分为三个部分,client, master 和chunkserver。</p><ol><li><p><strong>Client</strong> Client就是各种应用中使用GFS的客户端,以库文件的形式提供</p></li><li><p>**MasterServer ** MasterServer相当于对ChunkServer数据进行管理的管理者,存储整个文件传统的目录结构和文件元信息(包括Chunk分片信息和分片位置),Client从Master获取到具体的文件所在的ChunkServer的地址,然后直接与ChunkServer通信进行数据操作</p></li><li><p><strong>ChunkServer</strong> 存储具体文件数据的服务器</p></li></ol><p><img src="https://i.loli.net/2020/05/09/jB41UqSaMQwFWZc.jpg" alt="1334712344_6225.jpg"></p><p>GFS采用中心化的管理方式,Client作为应用使用方,Master作为ChunkServer的管理者,ChunkServer来负责数据的存储,client与master进行交互获取控制信息,然后与对应的ChunkServer交互获取具体的数据</p><h1 id="内部数据管理机制"><a href="#内部数据管理机制" class="headerlink" title="内部数据管理机制"></a>内部数据管理机制</h1><h2 id="Master数据存储"><a href="#Master数据存储" class="headerlink" title="Master数据存储"></a>Master数据存储</h2><p>MasterServer中存储3类信息:</p><ol><li>文件系统的命名空间,整个文件系统的目录结构和Chunk基本信息</li><li>文件与Chunk的映射关系</li><li>Chunk副本的位置信息,默认每个Chunk使用3个副本</li></ol><p>由于MasterServer采用中心化的单节点管理,所以MasterServer的内存使用和性能都是我们要关注的点:</p><p><strong>假设要存储1Pb的数据,则MasterServer的内存使用为</strong></p><p>$$\cfrac{1P * 64b * 3}{64Mb}=3Gb$$</p><pre><code>1P : 是总数据大小64Mb: 是每个Chunk的容量3 : 是Chunk备份数量,默认为364b : 是每个Chunk的元属性所占的空间</code></pre><h2 id="ChunkServer存储的数据"><a href="#ChunkServer存储的数据" class="headerlink" title="ChunkServer存储的数据"></a>ChunkServer存储的数据</h2><p>ChunkServer中存储Chunk的具体文件内容。GFS将每个Chunk限制为64M, Chunk内部又分为众多的Block,同时ChunkServer还负责进行具体每个Chunk文件的读写操作, 接受并执行每个主Chunk(租约Chunk)的指令。ChunkServer还需要定时与master进行心跳同步,上报自己的运行状态和维护的chunk信息</p><p>ChunkServer启动时会向MasterServer上报存储的文件信息,也会周期性的向MasterServer上报自己的服务器状态, 以此来保证master上的ChunServer信息保持更新, 并及时发现ChunkServer的故障</p><h2 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h2><p>由于GFS是由众多的廉价服务器组成的系统,所以系统的负载问题就是十分重要。GFS会根据每个服务器的负载和最近操作数来决定新数据的分布,以保证数据分布的均匀。一般有三个基本原则</p><ol><li>同一个Chunk的多个副本不会放在同一个机架</li><li>ChunkServer最近操作数有一定的限制</li><li>优先选择磁盘负载较低的服务器</li></ol><p>第二点十分重要但时常被忽略,如果没有第二条规则限制, 很容易出现新加的机器由于负载过低导致短时间内大量数据都往这个机器上操作, 导致新添加的机器被压垮</p><h2 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h2><p>GFS采用标记回收的方式处理,删除一个文件之后,GFS并不会立即要求归还可用的物理空间,而是在元数据中将文件表示为一个不可用的隐藏名字,标记一个删除的时间戳</p><p>Master定时检查,文件被删除超过一定时间,Master会删除文件的元数据信息,之后在与ChunkServer交互时通知ChunkServer删除对应的Chunk信息,ChunkServer来处理后续的存储释放</p><p>过期的Chunk也是通过垃圾回收机制来进行删除</p><h2 id="文件快照"><a href="#文件快照" class="headerlink" title="文件快照"></a>文件快照</h2><p>一但对一个文件采取快照, GFS会通过租约机制先停止所有Chunk的写操作, 更新所有Chunk副本的引用计数</p><p>然后之后的写请求在执行时会copy一个Chunk副本,后续的修改都会落到新的Chunk上面</p><p>例如:</p><p>对文件 F 执行快照生成 F’ ,F在GFS中有三个Chunk: C1,C2,C3 。Master首先会回收C1,C2,C3的写租约,从而保证此时的F状态一致,然后Master复制 F的元数据生成一个新的文件 F’。</p><p>此时F’的 Chunk仍然指向 C1,C2,C3. 快照之前, C1,C2,C3只被一个文件引用,引用计数为1, 快照之后引用技术更新为2</p><p>当客户端向C3增加数据时,Master发现c3引用计数超过1,会通知ChunkServer生成新的C3’, 新的操作也会在C3’上面进行,F的Chunk映射也会更新为 C1,C2,C3’</p><blockquote><p>ps: 这个机制叫写时复制(Copy On Write)</p></blockquote><h1 id="GFS读写数据的流程"><a href="#GFS读写数据的流程" class="headerlink" title="GFS读写数据的流程"></a>GFS读写数据的流程</h1><h2 id="读取流程"><a href="#读取流程" class="headerlink" title="读取流程"></a>读取流程</h2><p>GFS中的文件读取流程大致如下:</p><pre><code>1. client发送给master需要获取的文件名和偏移量(告诉服务器我要读某文件的某段数据)2. master根据文件名查找命名空间中的文件对应的文件块id,返回对应的ChunkServer和副本的位置3. client根据返回的ChunkServer的位置信息去对应的Chunk上面取对应的数据</code></pre><blockquote><p>ps: client会缓存一部分的ChunkServer元信息(某个ChunkServer在某个机器上面,副本分布情况等),但并不会缓存具体的文件内容, 以此降低Master服务器的负载, ChunkServer会对服务器的请求进行校验, 当ChunkServer信息有变动时, 客户端如果使用过期的Chunk信息, 能从ChunkServer得到反馈, 重新去Master获取最新的Chunk信息</p></blockquote><p>这种读取方式的好处是client直接与chunk服务器进行数据交互,由于chunk服务器数量较多,可以同时支持极高的并行数据传输。</p><h2 id="数据的写"><a href="#数据的写" class="headerlink" title="数据的写"></a>数据的写</h2><p>GFS中写入数据的流程如下:</p><p><img src="https://i.loli.net/2020/05/09/J5EbuqjnQHvRUIf.jpg" alt="1334931385_9113.jpg"></p><p>master使用租约授权一个chunk副本为primary副本,执行client的写操作</p><ol><li>client需要更新一个数据块,询问master谁拥有该数据块的租约(谁是primary);</li><li>master将持有租约的primary和其它副本的位置告知client,client缓存之;</li><li>client向所有副本传输数据,这里副本没有先后顺序,根据网络拓扑情况找出最短路径,数据从client出发沿着路径流向各个chunkserver,这个过程采用流水线(网络和存储并行)。chunkserver将数据放到LRU缓存;</li><li>一旦所有的副本都确定接受数据,client向primary发送写请求,primary为这个前面接受到的数据分配序列号(primary为所有的写操作分配连续的序列号表示先后顺序),并且按照顺序执行数据更新;</li><li>primary将写请求发送给其它副本,每个副本都按照primary确定的顺序执行更新;</li><li>其它副本向primary汇报操作情况;</li><li>primary回复client操作情况,任何副本错误都导致此次请求失败,并且此时副本处于不一致状态(写操作完成情况不一样)。client会尝试几次3到7的步骤,实在不行就只能重头来过了</li></ol><p>也就是说GFS也是使用持有租约的primary副本来进行一致性保证, 其他所有副本均按照primary确定的写入顺序执行</p><h1 id="故障恢复和容错机制"><a href="#故障恢复和容错机制" class="headerlink" title="故障恢复和容错机制"></a>故障恢复和容错机制</h1><h2 id="快速恢复"><a href="#快速恢复" class="headerlink" title="快速恢复"></a>快速恢复</h2><p>GFS能使用checkpoint 文件和日志文件快速进行故障的恢复</p><h2 id="副本复制"><a href="#副本复制" class="headerlink" title="副本复制"></a>副本复制</h2><p>GFS通过副本进行数据备份, 只要一个chunk有一个副本所在的机器存活,数据就可以恢复</p><h2 id="Matser的容错"><a href="#Matser的容错" class="headerlink" title="Matser的容错"></a>Matser的容错</h2><p>Master会进行远程备份,Master存储文件的信息有</p><ol><li>文件的命名空间信息(整个文件系统的目录)</li><li>Chunk服务器和文件名的映射关系</li><li>Chunk服务器的地址和副本信息</li></ol><p>对于前两种操作GFS通过操作日志提供容错,日志会被被分到远程服务器<br>最后一种保存在ChunkServer上, 当ChunkServer跟master注册时,或者Master启动时,使用轮询的方式去ChunkServer获取元数据</p><h2 id="ChunkServer的容错"><a href="#ChunkServer的容错" class="headerlink" title="ChunkServer的容错"></a>ChunkServer的容错</h2><ol><li>每个Chunk默认拥有3个副本, 分布在不同的ChunkServer上面</li><li>ChunkServer在发送数据之前会检查block的32的校验和,如果不一致就会上报Master,Master会从其他副本进行复制,并删除出错的副本数据</li></ol><blockquote><p>ps:为什么是默认3个副本呢,是因为副本的分布要同时满足性能和安全性需求,也就是同一机架放两个副本,另一副本放到另一机架,平衡安全性和速度。<br>同时三个副本正好能覆盖chunk所有的可能存在的状态,1. 正常获得租约状态 2. 租约到期,进行转换 3. 作为其他chunk的副本 </p></blockquote><h1 id="其他优化的点"><a href="#其他优化的点" class="headerlink" title="其他优化的点"></a>其他优化的点</h1><ul><li>文件树存储</li></ul><p>由于master需要存储所有的文件树和块的对应关系,采用了前缀树进行数据压缩,大大提高了可存储数据的容量</p><ul><li>高并发热点文件的读写</li></ul><p>GFS采用副本机制和错峰控制来处理热点文件的高并发读写,同时提出了一种长效解决方案:允许客户端读取客户端数据,形成客户端链</p><ul><li>为什么要采用中心化的服务</li></ul><p>为了简化系统设计,保持灵活性</p><ul><li>读数据时候的数据流</li></ul><p>GFS写入数据的时候客户端并不是采用星型或者树形结构,同时持有多个副本的链接并向副本发送数据,而是经过了一定的拓扑优化<br>客户端会将数据发送给离自己最近的节点s1,同时该节点会继续将数据发送给离自己最近的节点s2,没一个节点都发送给离自己最近同时又没有接受数据的节点,以其充分利用机器的带宽</p><blockquote><p>ps: 这个问题有点像旅行商问题</p></blockquote><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ol><li>GFS是一个中心化的分布式文件系统, 文件的具体信息分块存储, 同一文件可能被分为多个Chunk块, 每个Chunk块有多个副本</li><li>Master负责文件的元数据的管理, ChunkServer负责文件具体数据的管理</li><li>Client读数据需要先从Matser处获取到文件的Chunk分布信息, 然后去对应的ChunkServer上取得真正的文件数据</li><li>Client写数据会先跟Master交互获取Chunk文件的信息, 然后向所有Chunk副本发送文件数据流, 最后向PrimaryChunk发送写入控制流, 由PrimaryChunk通知其他Chunk副本执行真正的写操作</li><li>GFS可以以Chunk为单位在不同机器之间调度数据分布, 还有 CheckPoint和Redo日志来处理容错性</li></ol>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> GFS </tag>
</tags>
</entry>
<entry>
<title>2018-追番-看书记录</title>
<link href="/2018-01-01-comic-book-2018/"/>
<url>/2018-01-01-comic-book-2018/</url>
<content type="html"><![CDATA[<h2 id="2018"><a href="#2018" class="headerlink" title="2018"></a>2018</h2><p>年度目标: <em>20本书以上</em></p><h3 id="January"><a href="#January" class="headerlink" title="January"></a>January</h3><p>Done:</p><ul><li>Book</li></ul><ul><li><input checked="" disabled="" type="checkbox"> HTML5游戏开发实战</li><li><input checked="" disabled="" type="checkbox"> 刻意练习</li><li><input disabled="" type="checkbox"> PRINCIPLES(原则)</li></ul><ul><li>Bangumi</li></ul><ul><li><input disabled="" type="checkbox"> 鬼途奇行录</li><li><input disabled="" type="checkbox"> 紫罗兰的永恒花园</li><li><input checked="" disabled="" type="checkbox"> 少年锦衣卫第二季</li><li><input checked="" disabled="" type="checkbox"> 画江湖之换世门生</li><li><input disabled="" type="checkbox"> 狐妖小狐娘</li></ul><p>Plan:</p><ul><li><input disabled="" type="checkbox"> 机器学习相关基础知识的学习</li></ul><h3 id="February"><a href="#February" class="headerlink" title="February"></a>February</h3><ul><li>Book</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 计算机组成与操作系统</li><li><input disabled="" type="checkbox"> 分布式服务架构</li><li><input disabled="" type="checkbox"> 神经网络与深度学习</li><li><input checked="" disabled="" type="checkbox"> 东方快车谋杀案</li></ul><ul><li>Bangumi</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 冰菓</li><li><input disabled="" type="checkbox"> 狐妖小红娘</li><li><input disabled="" type="checkbox"> 紫罗兰的永恒花园</li><li><input checked="" disabled="" type="checkbox"> Angel Beats</li><li><input disabled="" type="checkbox"> 龙王的工作</li></ul><h3 id="March"><a href="#March" class="headerlink" title="March"></a>March</h3><ul><li>Book</li></ul><ul><li><input disabled="" type="checkbox"> 原则</li><li><input disabled="" type="checkbox"> 月亮与六便士</li></ul><ul><li>Bangumi</li></ul><ul><li><input disabled="" type="checkbox"> 紫罗兰的永恒花园</li><li><input disabled="" type="checkbox"> 鬼途奇行录</li><li><input checked="" disabled="" type="checkbox"> 龙王的工作</li></ul><h3 id="April"><a href="#April" class="headerlink" title="April"></a>April</h3><ul><li>Book</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 原则</li><li><input disabled="" type="checkbox"> 神经网络与深度学习</li><li><input disabled="" type="checkbox"> 大规模分布式存储系统</li></ul><ul><li>Bangumi</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 紫罗兰永恒花园</li><li><input disabled="" type="checkbox"> 鬼途奇行录</li><li><input disabled="" type="checkbox"> 一人之下</li><li><input disabled="" type="checkbox"> 狐妖小红娘</li></ul><h3 id="May"><a href="#May" class="headerlink" title="May"></a>May</h3><ul><li>Book</li></ul><ul><li><input disabled="" type="checkbox"> 大规模分布式存储系统</li><li><input disabled="" type="checkbox"> 分布式实时处理系统</li><li><input checked="" disabled="" type="checkbox"> 神经网络与深度学习</li><li><input checked="" disabled="" type="checkbox"> 月亮与六便士</li></ul><ul><li>Bangumi</li></ul><ul><li><input disabled="" type="checkbox"> 鬼途奇行录</li><li><input disabled="" type="checkbox"> 一人之下</li><li><input disabled="" type="checkbox"> 没关系, 是爱情啊</li></ul><h3 id="June"><a href="#June" class="headerlink" title="June"></a>June</h3><ul><li>Book</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 大规模分布式存储系统</li><li><input disabled="" type="checkbox"> 大规模分布式系统架构与设计实战</li></ul><ul><li>Bangumi</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 鬼途奇行录</li></ul><h3 id="July"><a href="#July" class="headerlink" title="July"></a>July</h3><ul><li>Books</li></ul><ul><li><input disabled="" type="checkbox"> 国富论</li></ul><ul><li>Bangumi</li></ul><ul><li><input disabled="" type="checkbox"> 海贼王</li></ul><h3 id="August"><a href="#August" class="headerlink" title="August"></a>August</h3><ul><li> Books</li></ul><ul><li><input disabled="" type="checkbox"> 国富论</li></ul><ul><li>Bangumi</li></ul><ul><li><input disabled="" type="checkbox"> 工作细胞</li><li><input disabled="" type="checkbox"> 魔道祖师</li></ul><h3 id="Sep"><a href="#Sep" class="headerlink" title="Sep"></a>Sep</h3><ul><li>Books</li></ul><ul><li><input checked="" disabled="" type="checkbox"> 编码</li></ul><h3 id="Oct"><a href="#Oct" class="headerlink" title="Oct"></a>Oct</h3><ul><li>Books</li></ul><ul><li><input disabled="" type="checkbox"> 深入理解Java虚拟机</li></ul><h3 id="Nov"><a href="#Nov" class="headerlink" title="Nov"></a>Nov</h3><ul><li>Books</li></ul><ul><li><input disabled="" type="checkbox"> 深入理解Java虚拟机</li><li><input checked="" disabled="" type="checkbox"> 大数据技术体系详解</li><li><input disabled="" type="checkbox"> Designing Data-Intensive Application</li><li><input checked="" disabled="" type="checkbox"> 走向分布式(Scalability)</li><li><input disabled="" type="checkbox"> Spark大数据处理</li><li><input checked="" disabled="" type="checkbox"> Spark编程指南</li></ul><h3 id="Dec"><a href="#Dec" class="headerlink" title="Dec"></a>Dec</h3><ul><li>Book</li></ul><ul><li><input disabled="" type="checkbox"> 深入理解Java虚拟机</li><li><input disabled="" type="checkbox"> Designing Data-Intensive Application</li><li><input checked="" disabled="" type="checkbox"> kafka权威指南</li><li><input disabled="" type="checkbox"> Scala编程实战</li></ul><h2 id="统计"><a href="#统计" class="headerlink" title="统计"></a>统计</h2><p>图书: </p><p>读完: 11本(技术类7本, 其他4本)<br>未完: 6本</p>]]></content>
<categories>
<category> 生活记录 </category>
</categories>
</entry>
<entry>
<title>使用Bitmap来存储海量用户标签系统</title>
<link href="/2017-12-29-user-tag-sys-on-bitmap/"/>
<url>/2017-12-29-user-tag-sys-on-bitmap/</url>
<content type="html"><![CDATA[<h1 id="海量用户标签的存储方案"><a href="#海量用户标签的存储方案" class="headerlink" title="海量用户标签的存储方案"></a>海量用户标签的存储方案</h1><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>我们在日常的工作中经常遇到这种场景</p><p>对一个用户添加许多的标签信息方便对用户身份进行搜索和精细化运营</p><blockquote><p>ps:本文我们不考虑用户身上的标签是怎么来的,只讨论用户已经拥有标签的情况下怎么进行存储</p></blockquote><h1 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h1><p>我们给用户做标签的目的是为了支持更加精细化的运营,算是用户画像的一部分,用户的标签来源可能跟消费,登录,浏览等记录都有关系</p><p>我们要做的是可以根据用户身上已经存在的标签,筛选出来符合我们需求的用户</p><p>我们可以在大量的标签中查找具有某一些标签的用户,或者获取某用户身上的所有标签</p><p>我们如果要满足以上的需求, 需要提供以下几个基本接口来方便进行数据查找</p><ol><li>查找某标签的所有用户以及非该标签的用户</li><li>查找某个用户身上的所有标签</li><li>判断某个用户是否有某个标签</li></ol><p>一般来说对以上需求,对于用户和用户身上的标签数据,如果我们采用数据库来进行存储</p><p>可能会采用以下方式(为了方便我们模拟了7个用户,7个标签,以下测试都基于该假数据),例如:</p><ol><li>使用字段标识标签信息</li></ol><table><thead><tr><th align="left">id</th><th align="left">name</th><th align="left">vip</th><th align="left">mobile</th><th align="left">email</th><th align="left">male</th><th align="left">mac</th><th align="left">supervip</th><th align="left">lost</th></tr></thead><tbody><tr><td align="left">1</td><td align="left">小明</td><td align="left">1</td><td align="left">1</td><td align="left">0</td><td align="left">1</td><td align="left">0</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">2</td><td align="left">小花</td><td align="left">0</td><td align="left">1</td><td align="left">0</td><td align="left">0</td><td align="left">0</td><td align="left">0</td><td align="left">1</td></tr><tr><td align="left">3</td><td align="left">小江</td><td align="left">0</td><td align="left">0</td><td align="left">0</td><td align="left">1</td><td align="left">1</td><td align="left">0</td><td align="left">1</td></tr><tr><td align="left">4</td><td align="left">小红</td><td align="left">1</td><td align="left">1</td><td align="left">0</td><td align="left">0</td><td align="left">0</td><td align="left">0</td><td align="left">1</td></tr><tr><td align="left">5</td><td align="left">小九</td><td align="left">0</td><td align="left">0</td><td align="left">1</td><td align="left">0</td><td align="left">1</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">6</td><td align="left">小七</td><td align="left">0</td><td align="left">1</td><td align="left">0</td><td align="left">1</td><td align="left">1</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">7</td><td align="left">小四</td><td align="left">1</td><td align="left">0</td><td align="left">1</td><td align="left">1</td><td align="left">0</td><td align="left">0</td><td align="left">1</td></tr></tbody></table><p>或者是这样</p><ol start="2"><li>使用记录标识标签信息</li></ol><table><thead><tr><th align="left">tag</th><th align="left">uid</th><th align="left">result</th></tr></thead><tbody><tr><td align="left">vip</td><td align="left">1</td><td align="left">1</td></tr><tr><td align="left">mobile</td><td align="left">1</td><td align="left">1</td></tr><tr><td align="left">email</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">male</td><td align="left">1</td><td align="left">1</td></tr><tr><td align="left">mac</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">supervip</td><td align="left">1</td><td align="left">1</td></tr><tr><td align="left">lost</td><td align="left">1</td><td align="left">0</td></tr><tr><td align="left">vip</td><td align="left">2</td><td align="left">0</td></tr><tr><td align="left">mobile</td><td align="left">2</td><td align="left">1</td></tr><tr><td align="left">email</td><td align="left">2</td><td align="left">0</td></tr><tr><td align="left">male</td><td align="left">2</td><td align="left">0</td></tr></tbody></table><p>以上两种方式功能上都可以达到我们想要的效果,但第一种方式在标签数量非常多的时候明显是不合适的,我们不可能给每个标签都添加一个字段,那样性能和扩展性都损失非常大</p><p>在上面的两个表中第二个表相当于对第一个表进行了拆分,增强了标签的扩展性.如果我们采用第二种方式存储,对于上面的需求 1,2,3 都能很好的满足</p><p>但是方式2依然有两个可能遇到的问题</p><ol><li>我们要查找在某一些标签的用户需要使用如下sql</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> </span><br><span class="line"> uid </span><br><span class="line"><span class="keyword">from</span> </span><br><span class="line"> tag_table </span><br><span class="line"><span class="keyword">where</span> </span><br><span class="line"> <span class="keyword">result</span> <span class="operator">=</span> <span class="number">1</span> </span><br><span class="line"><span class="keyword">and</span> tag <span class="keyword">in</span> (<span class="string">'vip'</span>,<span class="string">'mobile'</span>,<span class="string">'email'</span>,<span class="string">'male'</span>,<span class="string">'supervip'</span>,<span class="string">'lost'</span>)</span><br></pre></td></tr></table></figure><p>这样的语句在标签数万甚至数十万的时候对性能影响会非常大</p><ol start="2"><li>存储: 因为每个行记录同时标明了用户,标签,和结果, 所以其中的重复数据非常的多,对数据库存储是个极大地浪费</li></ol><h1 id="Bitmap"><a href="#Bitmap" class="headerlink" title="Bitmap"></a>Bitmap</h1><h2 id="Bitmap的概念"><a href="#Bitmap的概念" class="headerlink" title="Bitmap的概念"></a>Bitmap的概念</h2><p>Bitmap 翻译做中文称为”位图”, 其核心里面是充分利用一部分数据本身就存在的元属性(空间/位置/容量)信息,我们这里主要是使用其中的每一位的位置信息,达到使用一个信息表达两种含义的作用</p><p>其实就也是一种特殊的编码(coding)过程(或者叫多工(multiplex))</p><h2 id="解决的问题"><a href="#解决的问题" class="headerlink" title="解决的问题"></a>解决的问题</h2><p>bitmap可以用来有效解决两类问题</p><ol><li>存储大量值可以用布尔值标识的数据</li><li>部分有用到交,并,差等集合运算的数据</li></ol><p>第一个特性主要是利用位存储的节省空间的特性,第二个是利用计算机位运算比较快速的特性</p><p>eg: </p><ol><li><p>以前的搜索引擎爬虫在处理网页爬取的时候需要给已经爬取过的网页做标记,避免陷入死循环的重复爬取,当时的搜索网站的爬虫就有一些采用过bitmap来给爬取过的网页做标记,大致就是取页面的url取hash,然后处理成数字,把对应的数字位置为1</p></li><li><p>微博里面你关注的A也关注了B, 使用B的粉丝列表和你的关注列表进行交集运算就可以了,同样 购买这件商品的人也购买了M,也可以用 购买这件商品的用户列表里面取某个用户购买过的某个商品即可</p></li></ol><p>以上应用确实能有效的减少数据的存储容量和提高集合计算速度, 如果我们用这种方法来存储用户标签信息也能大量减少存储容量</p><p>但是怎么把用户标签的表信息数据转换成bitmap形式的数据呢?</p><h2 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h2><p>我们如果要记录一个用户对应的一个标签的信息,假如我们知道5号用户是小九,而她是一位超级会员用户(我们可以在上面的表中查到该信息)</p><p>我们要如何使用bitmap来表示这条信息呢</p><ul><li>存储用户和标签的关系</li></ul><p>我们可以这样:</p><ol><li>使用一个键<code>user:supervip</code>来记录所有用户是否是超级会员的信息,这个值最初是空的字符串值,表明没有超级会员用户</li><li>我们为了标明 5 号用户是超级会员 可以使用这个键中对应位置的二进制位来表明会员的身份,将这个键的第 5 位置为1, 这样这个<code>user:supervip</code>值现在是’000001’(从第0位开始计算)</li><li>同样,如果<code>user:supervip</code>的值现在是’01001010’ 我们就可以知道 1,4,6号用户都是超级会员用户</li></ol><p>我们根据这个数据可以做到2点:</p><ul><li><p>我们可以根据该标签数据键的对应位置的二进制位的值来判断以该位置为id的用户的标签结果</p></li><li><p>也可以查询某个标签下的所有用户</p></li></ul><p>这样我们存储上万个标签也只需要上万个键</p><ul><li>存储所有用户</li></ul><p>但是我们如果需要查找不属于某个标签的用户怎么办啊,如果直接对上一个例子取反肯定是不行的</p><p>为了解决这个问题我们需要一个存储所有用户的键</p><p>我们知道了所有用户,知道了拥有某标签的用户</p><pre><code>不含某标签的用户 = 总用户 - 含有某标签的用户</code></pre><p>用二进制的操作方法就是使用<code>异或</code>, 举例:</p><p>我们有7个用户(编号1-7),5号用户是超级vip,我们要查找所有不是vip的用户可以使用下面的运算</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">01111111</span> ^ <span class="number">00001000</span> = <span class="number">01110111</span> <span class="comment">// 127 ^ 8 = 119</span></span><br><span class="line"><span class="comment">// 01111111:所有用户的二进制键 00001000:5号用户是超级会员的键 01110111:所有不是超级会员的用户</span></span><br></pre></td></tr></table></figure><p>以上操作我们就能得到所有不是超级会员的用户</p><ul><li>存储某用户的所有标签</li></ul><p>我们如果要获得用户的所有标签,也可以将用户拥有的标签id在用户标签键中所对应的位置置为1,这样每一个用户的表示所有标签的键的最大位长度就是固定的,比如:</p><p>我们可以用如下方式存储用户的所有标签</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">usertag:all:<span class="number">1</span> => <span class="number">01101010</span> <span class="comment">// 1号用户的所有标签</span></span><br><span class="line">usertag:all:<span class="number">5</span> => <span class="number">00010110</span> <span class="comment">// 5号用户的所有标签</span></span><br></pre></td></tr></table></figure><p>这样我们就能使用bitmap来满足以上基本查询需求</p><p>同样我们也可以将所有标签存储成一个<code>usertag:alltag</code>键, 再使用异或运算计算某用户不含有的标签</p><h2 id="实现方案"><a href="#实现方案" class="headerlink" title="实现方案"></a>实现方案</h2><p>我们如果自己来对位运算做管理就有点麻烦了,我们可以借助<code>redis</code></p><p><code>redis</code>原生提供了可以对字符串进行位操作的命令,具体如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">SETBIT key pos value // 将 key 的第 pos 位设为 value(只能取1/0)</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">GETBIT key pos // 获取 key 的第 pos 位的值</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITOP cmd key1 key2 key3 ... // 对 key2,key3 等执行</span> </span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITOP NOT destkey srckey</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITPOS key bit start end // 将 key 的 strat 到 end 位全部设为 bit(0/1)</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITCOUNT mykey 1 1</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">BITFIELD mystring SET i8 <span class="comment">#0 100 i8 #1 200</span></span></span><br></pre></td></tr></table></figure><p>我们就直接使用<code>redis</code>来存储数据了,这样方便点</p><h2 id="预处理"><a href="#预处理" class="headerlink" title="预处理"></a>预处理</h2><p>我们这边为了方便直接使用redis提供的<code>setbit</code>,<code>getbit</code>和<code>bitop</code>来进行字符串的位操作</p><p>因为我们要存储用户标签,所以我们首先需要对用户和标签进行编号,这样我们需要两个表</p><p>用户表:</p><table><thead><tr><th align="left">uid</th><th align="left">name</th></tr></thead><tbody><tr><td align="left">1</td><td align="left">小明</td></tr><tr><td align="left">2</td><td align="left">小花</td></tr><tr><td align="left">3</td><td align="left">小江</td></tr><tr><td align="left">4</td><td align="left">小红</td></tr><tr><td align="left">5</td><td align="left">小九</td></tr><tr><td align="left">6</td><td align="left">小七</td></tr><tr><td align="left">7</td><td align="left">小四</td></tr></tbody></table><p>标签表:</p><table><thead><tr><th align="left">tid</th><th align="left">name</th><th align="left">备注</th></tr></thead><tbody><tr><td align="left">1</td><td align="left">vip</td><td align="left">是否vip</td></tr><tr><td align="left">2</td><td align="left">mobile</td><td align="left">是否绑定手机</td></tr><tr><td align="left">3</td><td align="left">email</td><td align="left">是否绑定邮箱</td></tr><tr><td align="left">4</td><td align="left">male</td><td align="left">是否男性</td></tr><tr><td align="left">5</td><td align="left">mac</td><td align="left">是否使用Mac</td></tr><tr><td align="left">6</td><td align="left">supervip</td><td align="left">是否年费会员</td></tr><tr><td align="left">7</td><td align="left">lost</td><td align="left">是否易流失用户</td></tr></tbody></table><h2 id="存储"><a href="#存储" class="headerlink" title="存储"></a>存储</h2><p>我们这里为了性能考虑使用redis来进行存储</p><p>我们将最上面的表格数据转换成以下键值对</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="comment">// 所有用户</span></span><br><span class="line"> <span class="attr">"user:all"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"01111111"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">// 所有vip用户</span></span><br><span class="line"> <span class="attr">"user:vip"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"01001001"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">// 所有绑定了手机的用户</span></span><br><span class="line"> <span class="attr">"user:mobile"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"01101010"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">// 所有绑定了邮箱的用户</span></span><br><span class="line"> <span class="attr">"user:email"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"00001010"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">// 所有男性用户</span></span><br><span class="line"> <span class="attr">"user:male"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"01010011"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">// 用户1的所有标签</span></span><br><span class="line"> <span class="attr">"usertag:all:1"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"01101010"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="comment">//用户2的所有标签</span></span><br><span class="line"> <span class="attr">"usertag:all:2"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line"> <span class="string">"00100001"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><h2 id="查询操作"><a href="#查询操作" class="headerlink" title="查询操作"></a>查询操作</h2><p>我们可以使用redis的命令<code>getbit</code>来查询某个键的某个位置的值<br>比如,我们要查询5号用户是否具有vip标签,可以使用以下命令 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ getbit user:vip <span class="number">5</span> <span class="comment">// 返回 0</span></span><br></pre></td></tr></table></figure><p>要查询某用户身上的所有标签可以使用如下 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ get usertag:all:<span class="number">1</span> <span class="comment">// 获取用户1的所有标签 返回'01101010'</span></span><br></pre></td></tr></table></figure><p>我们如果要获取某标签下的所有用户可以使用如下命令 </p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">get user:vip // 返回一个二进制字符串 类似 <span class="string">'01001001'</span></span></span><br></pre></td></tr></table></figure><p>查询不具有某个标签的用户 </p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">bitop xor user:not_vip user:all user:vip // 根据所有用户和具有标签的用户进行异或运算,得到不含有某标签的用户</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">get user:not_vip // 返回二进制字符串</span></span><br></pre></td></tr></table></figure><p>我们现在知道如何快速的获取我们想要的数据了,但是我们发现有时候我们获取到的都是二进制的数据例如 <code>00001000</code> 这种,而群殴们想从这样的数据中获取的是 <code>[5]</code> 这样的比较易读的信息</p><p>我们需要有一个将二进制字符串 转化为对应位置为1的位置数组的形式</p><p>如: function(<code>01001010</code>) => <code>[1,4,6]</code> </p><h2 id="结果解析"><a href="#结果解析" class="headerlink" title="结果解析"></a>结果解析</h2><p>这里我们提供两个函数来进行这样的操作</p><ol><li>遍历法</li></ol><p>我们遍历二进制字符串中的每一位, 每遇到一个为1的位置就将该位置放入数组</p><p>这种方法比较慢,不建议使用,这里贴一个示例代码</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">key2array</span>(<span class="params">self, key</span>): <span class="comment"># 将二进制('\x05'->'0b00000101')变为数组[5,7], 表示第五位和第七位为1</span></span><br><span class="line"> tmpstr = <span class="string">''</span>.join([<span class="built_in">bin</span>(i).replace(<span class="string">'0b'</span>, <span class="string">''</span>).zfill(<span class="number">8</span>) <span class="keyword">for</span> i <span class="keyword">in</span> key])</span><br><span class="line"> arr = []</span><br><span class="line"> str_len = <span class="built_in">len</span>(tmpstr)</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, str_len):</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">int</span>(tmpstr[i]) == <span class="number">1</span>:</span><br><span class="line"> arr.append(i)</span><br><span class="line"> <span class="keyword">return</span> (arr)</span><br></pre></td></tr></table></figure><ol start="2"><li>查表</li></ol><p>我们可以观察一下redis返回的二进制数据的特点, 每8个二进制位属于一个字节,每个字节都可以表示成具体的数字(如:0,23,127)这个数字最大也只能到255,而且同一个数字有可能出现非常多次,而每个数字所对应的转换过后的位置数组都是固定的,比如: 100(二进制:1100100) => [1,2,5]</p><p>我们可以利用这一点,提前制作一个 <code>0-255</code>的所对应的位置表,然后每次处理8位,处理完把当前处理的位数加上新表中对应的值就可以快速的得到这个值了</p><blockquote><p>ps: 我们也可以扩大这个表的容量以提高速度</p></blockquote><p>贴下示例代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">build_bit_table</span>(<span class="params">self</span>): <span class="comment"># 生成0-255的表</span></span><br><span class="line"> arr = []</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line"> tmp_arr = []</span><br><span class="line"> tstr = <span class="built_in">bin</span>(i).replace(<span class="string">'0b'</span>, <span class="string">''</span>).zfill(<span class="number">8</span>)</span><br><span class="line"> n = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> k <span class="keyword">in</span> tstr:</span><br><span class="line"> n = n + <span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">int</span>(k) == <span class="number">1</span>:</span><br><span class="line"> tmp_arr.append(n)</span><br><span class="line"> arr.append(tmp_arr)</span><br><span class="line"> self.bit_table = arr</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">key2array</span>(<span class="params">self, key</span>): <span class="comment"># 查表法</span></span><br><span class="line"> arr = []</span><br><span class="line"> n = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> key:</span><br><span class="line"> pos = self.bit_table[i]</span><br><span class="line"> <span class="keyword">for</span> k <span class="keyword">in</span> pos:</span><br><span class="line"> arr.append(n + k - <span class="number">1</span>)</span><br><span class="line"> n = n + <span class="number">8</span></span><br><span class="line"> <span class="keyword">return</span> (arr)</span><br></pre></td></tr></table></figure><blockquote><p>ps:jdk中的BitSet就是对bitmap的一种简单实现</p></blockquote><h1 id="如果标签过于稀疏会不会浪费空间"><a href="#如果标签过于稀疏会不会浪费空间" class="headerlink" title="如果标签过于稀疏会不会浪费空间?"></a>如果标签过于稀疏会不会浪费空间?</h1><p>如果我们在一个很长的bitmap中只存除了极少量的数据是不是会对空间造成浪费呢?</p><p>例如: 在bitmap的第40000位置为1,那存储的数据大概就类似: 00000000000…0000000001</p><p>这样的数据前面的39999位都是0,不会浪费空间吗</p><h2 id="Google的EWAHCompressedBitmap"><a href="#Google的EWAHCompressedBitmap" class="headerlink" title="Google的EWAHCompressedBitmap"></a>Google的EWAHCompressedBitmap</h2><p>Google的EWAHCompressedBitmap就对这种情况做了优化</p><p>EWAHCompressedBitmap 将整个的二进制数据分成每64位一个的word</p><p>一个空的Bitmap默认拥有 4 个word 也就是 <code>4*64</code> 位</p><p>其中 word0 存储bitmap的头信息</p><p>当我们改变对应位置的比特位的值时 word 会跟着变化</p><p>当我们插入的值非常大的时候(例如:40000), 算法会根据当前的值 创建两个新的word </p><p>一个用于存储第40000个数据所在的word的信息(LW), 还有一个存储跨度信息(称为:跨度word /RLW )</p><p>假如说我们给一个空的bitmap,我们插入40000的话正常情况下会有6个word,前4个是头信息word+3个空word,第6个中保存40000这个数字所在的位置信息,第5个word中保存从第 4-625 word的跨度信息,第626word中存储有 40000 这个数据 </p><blockquote><p>ps: 第一个word存储头信息, 625 = floor( (40000 + 1) / 64 ) </p></blockquote><p>存储跨度信息的word和普通的存储数据的word虽然空间一样但是存储的内容不一样,存储跨度信息的word大概内容这样</p><pre><code>前32位存储 `当前跨度word(RLW)横跨了多少空word` 后32位存储 `当前跨度word(RLW)后方有多少个连续的LW`</code></pre><p>当我们存储 位置在跨度word(RLW)之中的数据(例如:20000), RLW会进行分裂</p><p>变成3个word,中间一个存储20000所在的LW信息,前后各有一个RLW保存新的跨度信息</p><p>EWAHCompressedBitmap对应的maven依赖如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>com.googlecode.javaewah<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>JavaEWAH<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>1.1.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> bitmap </tag>
<tag> 标签系统 </tag>
</tags>
</entry>
<entry>
<title>程序语言不是工具</title>
<link href="/2017-12-29-programming-languages-are-not-tools/"/>
<url>/2017-12-29-programming-languages-are-not-tools/</url>
<content type="html"><![CDATA[<p>原作者: 王垠<br>原文: <a href="http://www.yinwang.org/blog-cn/2013/04/21/programming-languages-are-not-tools">http://www.yinwang.org/blog-cn/2013/04/21/programming-languages-are-not-tools</a></p><h1 id="程序语言不是工具"><a href="#程序语言不是工具" class="headerlink" title="程序语言不是工具"></a>程序语言不是工具</h1><p>在谈论到程序语言的好坏的时候,总是有人说:程序语言只是一种工具。只要你的算法好,不管用什么语言都能写出一样好的程序。在本科第一堂编程课上,我的教授就这么对我们说。可是现在我却发现,这是一个根本错误的说法。</p><p>我不知道这种说法确切的来源,然而昨天在浏览网页的时候,偶然发现了 C++ 的设计者 Bjarne Stroustrup 的一些类似的说法。这些说法来自于 2007 年 MIT Technology Review 对 Stroustrup 的采访。</p><blockquote><p>问:一个好的语言是什么样的?<br>Stroustrup:所有能帮助人们表达他们的想法的东西都会让语言更好。一个语言在一个好的工匠手里应该能胜任每天的任务。语言是否优美是次要的问题。被认为是丑陋的语言开发出来的有用的系统,比优美的语言开发出来的系统要多得多。</p></blockquote><blockquote><p>问:优雅难道不重要吗?<br>Stroustrup:优雅很重要,可是你如何衡量”优雅”?可以表达问题答案的最少字数?我觉得我们应该看构造出来的应用程序的优雅程度,而不是语言自身的优雅程度。就像你不能把木工的一套复杂的工具(很多是危险的工具)叫做”优雅”一样。但是我的餐桌和椅子却真的很优雅,很美。当然,如果一个语言本身也很美,那当然最好。</p></blockquote><h2 id="一些基本的错误"><a href="#一些基本的错误" class="headerlink" title="一些基本的错误"></a>一些基本的错误</h2><p>对这两个回答,我都不满意,我觉得这只是他对于 C++ 的恶劣设计的借口而已。下面我对其中几个说法进行质疑:</p><p>所有能帮助人们表达他们的想法的东西都会让语言更好。</p><p>作为一个程序语言,并不是好心想”帮助人”就可以说是好的。如果是这样的话,那么我就可以把所有国家的脏话都加到你的语言里面,因为它们可以帮助我们骂人。</p><p>被认为是丑陋的语言开发出来的有用的系统,比优美的语言开发出来的系统要多得多。</p><p>系统的数量再多也不能说明这个语言好。正好相反,众多的系统由于语言的一些设计失误,把人们的生命置于危险之中,这说明了这个语言的危害性之大。一种像炸药一样的语言,用的人越多越是危险。</p><h2 id="语言不是工具,而是材料"><a href="#语言不是工具,而是材料" class="headerlink" title="语言不是工具,而是材料"></a>语言不是工具,而是材料</h2><p>我这篇文章想说的最关键的部分,其实是他所支持的”语言工具论”的错误。</p><p>Stroustrup 说:</p><p>我觉得我们应该看构造出来的应用程序的优雅程度,而不是语言自身的优雅程度。就像你不能把木工的一套复杂的工具(很多是危险的工具)叫做”优雅”一样。但是我的餐桌和椅子却很优雅,很美。</p><p>他的言下之意就是把程序语言比作木工的工具,而餐桌也椅子就是这些工具做出来的产品。比方的威力是很大的,很多人一见到大牛给出这么形象的比方,想都不用想就接受了。如果你不仔细分析的话,这貌似一个恰当的比方,然而经过仔细推敲,这却是错误的比方。这是因为程序语言其实不是一种”工具”,而是一种”材料”。</p><p>木工不会把自己的锯子,墨线等东西放进餐桌和椅子里面,而程序员却需要把语言的代码放到应用程序里面。虽然这些程序经过了编译器的转化,但是程序本身却仍然带有语言的特征。这就像一种木材经过墨线和锯子的加工,仍然是同样的木材。一个 C++ 的程序在编译之后有可能产生内存泄漏和下标越界等低级错误,而更加安全的语言却不会出现这个问题。</p><p>所以在这个比方里面,程序语言所对应的应该是木工所用的木料,钉子和粘胶等”材料”,而不是锯子和墨线等”工具”。这些材料其实随着应用程序一起,到了用户的手里。那么对应木工工具的是什么呢?是 Emacs, vi, Eclipse, Visual Studio 等编程环境,以及各种编译器,调试器,make,界面设计工具,等等。这些真正的”工具”丑一点,真的暂时无所谓。</p><p>现在你还觉得程序语言的优雅程度是次要的问题吗?一个复杂而不安全的语言就像劣质的木料和粘胶。它不但会让餐桌和椅子的美观程度大打折扣,而且会造成它们结构的不牢靠,以至于威胁到用户的生命安全。同时它还可能会造成木工的工作效率低下以及工伤的产生。</p><p>这也许就是为什么我的一个同事说,他看 C++ 代码的时候都会带上 OSHA(美国职业安全与健康管理局)批准的护目镜。</p>]]></content>
<categories>
<category> 计算机编程 </category>
</categories>
<tags>
<tag> 编程语言 </tag>
</tags>
</entry>
<entry>
<title>分布式存储服务组内分享</title>
<link href="/2017-11-02-distribute-database-share-in-group/"/>
<url>/2017-11-02-distribute-database-share-in-group/</url>
<content type="html"><![CDATA[<p>这个是写给组内成员的分布式知识简单分享</p><h1 id="什么是分布式"><a href="#什么是分布式" class="headerlink" title="什么是分布式"></a>什么是分布式</h1><p>什么是集群? 什么是分布式?<br>集群: 一个任务多个人可以做(实际是一个人做), 集群的主要目的是高可用, 通过冗余解决单点故障问题<br>分布式: 一个任务拆分成多个部分由多个机器来做, 解决业务解耦, 水平扩展和性能问题 </p><p>几个典型的分布式系统:</p><table><thead><tr><th>名称</th><th>类型</th><th>数据分布方式</th><th>故障转移</th><th>节点类型</th></tr></thead><tbody><tr><td>kafka</td><td>消息系统</td><td>broker->partition</td><td>partition选举</td><td>对等节点</td></tr><tr><td>redis</td><td>缓存</td><td>instance->shard</td><td>sentinal选举/主从</td><td>对等节点</td></tr><tr><td>es</td><td>搜索服务</td><td>node->shard</td><td>master选举和partition选举</td><td>对等节点</td></tr><tr><td>Tidb</td><td>数据库</td><td>TiDB+TiKV+PD</td><td>node选举</td><td>非对等节点</td></tr><tr><td>hdfs</td><td>文件系统</td><td>namenode+datanode</td><td>主从</td><td>非对等节点</td></tr></tbody></table><blockquote><p> 节点类型:对等节点是说节点之间功能相似,非对等节点是说节点之间功能不同,无法互相取代</p></blockquote><h1 id="分布式要解决的常见问题"><a href="#分布式要解决的常见问题" class="headerlink" title="分布式要解决的常见问题"></a>分布式要解决的常见问题</h1><p>分布式要满足三个特性之二 CAP(C:一致性, P:分区容忍性, A:可用性)</p><ul><li>一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本),换句话就是说,任何时刻,所用的应用程序都能访问得到相同的数据。</li><li>可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性),换句话就是说,任何时候,任何应用程序都可以读写数据。</li><li>分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择,换句话说,系统可以跨网络分区线性的伸缩和扩展。</li></ul><p>CAP三个特性不可能同时满足, 只能同时满足两个特性<br>对大多数的分布式应用来说 CAP中的 P 是必不可少的<br>所以我们一般都会从CA中选取一个, 一般会选择CP 或者 AP </p><p>具体处理cap协议的方法如下图</p><p><img src="https://i.loli.net/2020/05/09/bO1gZiWHtFBvKj2.jpg" alt="cap.jpg"></p><h1 id="如何解决分区容忍性"><a href="#如何解决分区容忍性" class="headerlink" title="如何解决分区容忍性"></a>如何解决分区容忍性</h1><p>解决分区容忍性问题首先就是对数据进行分区或者叫分片</p><p>分区手段有很多, 但大多分两种, hash分区和线性分区 ,系统将数据分为逻辑上的几个区块, 每个区块可以在多个机器上进行自由的部署和移动。<br>这样就做到了将数据和机器进行隔离, 可以以分片为单位在不同机器上进行数据的移动和备份,但是这样在使用数据的时候就需要从多个分片同时获取数据进行合并。<br>如果因为某些原因, 导致某些分片所在的节点出现故障, 此时我们就认为出现了网络分区, 分区容忍性要求在此种状况下,系统依然能对外提供有效服务 。</p><h2 id="Gossip"><a href="#Gossip" class="headerlink" title="Gossip"></a>Gossip</h2><p>使用者: Redis<br>Gossip协议是节点将自己的数据通知给集群内所有节点的协议, 但是不能做到数据一致性<br>Gossip只能保证可用性和分区容忍性</p><p>如果对数据的分片进行备份, 同时将备份分布到多个不同的网络节点上, 这样即便部分数据分区不可用, 在可容忍的范围内只要不是该分区的所有数据分片都出问题, 还是能提供正常的数据服务 </p><p>数据分片是分布式存储的基础, 数据分片好处很多 </p><ol><li>对数据分片可以使数据不受单机存储制约</li><li>对数据分片可以通过多分区共同协作并行处理提高性能</li><li>对分区数据进行冗余可以提高系统可用性</li></ol><p>但是数据分片和副本会带来一致性问题</p><h1 id="如何保证数据一致性"><a href="#如何保证数据一致性" class="headerlink" title="如何保证数据一致性"></a>如何保证数据一致性</h1><h2 id="单节点的数据一致性"><a href="#单节点的数据一致性" class="headerlink" title="单节点的数据一致性"></a>单节点的数据一致性</h2><p>单节点如何保证数据一致性和完整性</p><p>比如: 数据库索引的更新, B树在分裂过程中出现问题怎么办, </p><p>一般处理单节点的一致性可以用 WAL(预写日志,或者叫redo日志) 或者 写时复制(copy on write)</p><p>WAL就是先把操作追加到一个日志文件, 然后再对内存进行操作, lsm树中的memtable就是典型的使用这种方案</p><p>写时复制就是先在其他地方把数据处理完毕, 最后直接修改数据引用, gfs的快照技术有使用类似的方案</p><p>典型的例子是 修改B+树时预先生成一个小的B+树, 然后直接替换B+树上的节点指针</p><h2 id="分布式的一致性"><a href="#分布式的一致性" class="headerlink" title="分布式的一致性"></a>分布式的一致性</h2><h3 id="主从一致性"><a href="#主从一致性" class="headerlink" title="主从一致性"></a>主从一致性</h3><p>目前业界的普遍做法是将分区的多个副本形成一个小组, 组内选举一个primary副本来执行写操作或确定写入顺序, 其他副本仅提供读操作或根本只提供备份功能, 这样对外部系统只有一个主副本做写入操作, 就可以保证数据写入的一致性</p><p>如何从多个副本集合中选举主副本就涉及到共识算法(选主算法),常见的共识算法有 <code>Paxos</code>,<code>Raft</code>,<code>Zab协议</code>,<code>Bully</code> 等</p><h3 id="NRW算法"><a href="#NRW算法" class="headerlink" title="NRW算法"></a>NRW算法</h3><p>NRW是一种特殊的保障一致性的算法, 通过饱和读取策略充分保证数据读取的一致性,具体详情如下: </p><pre><code>R(读取分片数) + W(写入分片数) > N(节点总分片数)</code></pre><p>只要满足以上公式, 我们必然可以拿到一个正确分片的数据<br>举例: 我们对某个数据有5个分片, 我们只要保证 写入分片为3,读取分片为3,这样,我们必然可以保证读取的分片其中有一个含有最新的数据 </p><h1 id="如何处理可用性"><a href="#如何处理可用性" class="headerlink" title="如何处理可用性"></a>如何处理可用性</h1><p>实现高可用一般都是通过节点或数据备份来实现, 采用主从或主备节点, 主节点或主分片不可用, 就采用副本分片或备份节点替代原有的服务</p><h1 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h1><p>解决分布式事物要处理ACID 四个问题,常用如下方式</p><ol><li>2PC, 使用者: mysql/flink, 两阶段提交协议</li><li>3PC, 三阶段提交协议, 二阶段提交协议的优化</li><li>TCC 事务补偿</li></ol><h1 id="使用经验"><a href="#使用经验" class="headerlink" title="使用经验"></a>使用经验</h1><h3 id="如何设置合理的分区和副本数量"><a href="#如何设置合理的分区和副本数量" class="headerlink" title="如何设置合理的分区和副本数量"></a>如何设置合理的分区和副本数量</h3><ol><li>其实对于副本数量的设置一般取决于副本的用途</li></ol><p>如果你副本只做备份,不对外提供读取服务,那设置3个是比较理想的情况,因为3个副本足以覆盖副本所有的可能状态(可用,不可用,升级中),也就是说只要有3个副本, 就一定有一个副本处于可用状态</p><p>如果副本同时提供读功能, 那可以根据情况酌情增加副本数量</p><ol start="2"><li>分片的设置</li></ol><p>其实针对不同的系统,不通的分片方式,分片设置有各自的考虑,不过一般受以下因素影响</p><ul><li>数据量</li><li>分布节点数量</li><li>使用者数量</li><li>数据同步和网络开销</li></ul><p>我们以一个服务于全国的500万用户的15个节点的存储容量为5TB的es集群来举例子。<br>我们可以设置 3个副本,15-30个分片。副本数太多会增加集群同步的成本,副本数太少会导致数据不安全,读性能较差,分片太多会增加后期数据合并的成本,分片太少会导致单个分片数据量过大。</p>]]></content>
<categories>
<category> 计算机编程 </category>
</categories>
<tags>
<tag> 分布式 </tag>
</tags>
</entry>
<entry>
<title>浅谈Kafka消息系统</title>
<link href="/2017-11-01-kafka-share-in-group/"/>
<url>/2017-11-01-kafka-share-in-group/</url>
<content type="html"><![CDATA[<h1 id="kafka分享"><a href="#kafka分享" class="headerlink" title="kafka分享"></a>kafka分享</h1><p>听闻kafka已经是很早之前的事情了,在2016年的时候, 就在一次我们公司的数据团队的内部分享中听过他们基于kafka做的一套数据处理系统。不过当时我对他的认知还仅仅知道是一个能处理海量数据的消息队列服务,后来随着深入使用才发现kafka其实不仅仅是纯粹的消息队列, 而是一种分布式消息系统甚至于流处理平台(基于scala开发)。</p><h2 id="消息传递在现代业务中的地位"><a href="#消息传递在现代业务中的地位" class="headerlink" title="消息传递在现代业务中的地位"></a>消息传递在现代业务中的地位</h2><p><strong>面向对象的程序 = 对象 + 消息传递</strong></p><p>虽然上面的这歌公式针对的是面向对象和语言设计方面的描述,但是消息系统在我们业务中的重要性由此可见, 消息系统可以作为系统与系统进行交互,或者对业务进行解耦的一个利器</p><p>我们通常会把许多实时性要求不那么高的任务处理通过消息系统进行解耦,以平衡数据处理的性能和功能</p><p>例如: 用户下了一个订单,我们需要马上告诉用户下单成功, 但是之后的物流发货, 订单入账和商品信息更新等消息都可以通过一个消息系统进行拆分,这样不仅不会影响用户体验,也可以对之后触发的多个属于不同系统的业务进行并行处理,提升系统处理的性能</p><h1 id="Kafka基础介绍"><a href="#Kafka基础介绍" class="headerlink" title="Kafka基础介绍"></a>Kafka基础介绍</h1><p>kafka作为一个消息平台, 其中有一些基础概念跟传统的消息队列服务对应</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">broker(instance) 承载服务的实例</span><br><span class="line">topic (queue) 逻辑上的消息通道</span><br><span class="line">paitition(sharding) 一个消息通道进行存储的区域(物理上的,本质是对应着一个文件序列)</span><br><span class="line">consumer 消费者 (通常指客户端的使用者)</span><br><span class="line">producer 生产者 (也是客户端的使用者)</span><br><span class="line">consumer group 消费者组 (虚拟概念, 消费者的逻辑分组)</span><br></pre></td></tr></table></figure><p><strong>broker</strong></p><p>broker就相当于我们的kafka实例, 每个集群由多个broker组成, 每个broker具有完整的存储消息的功能, 多个broker可以组成broker集群, 但是这些实例不一定要分布在不同的机器上(虽然我们建议不要在同一个机器上部署多个broker)</p><p>broker是链接<code>producer</code>和<code>consumer</code>的中间桥梁, 是消息真正的存储者, producer将消息发送给broker, broker对消息进行存储, 等待consumer消费和使用消息,kafka的性能, 很大程度上取决于broker参数配置</p><p><strong>topic</strong></p><p>topic就相当于其他消息队列中的queue, 我们发送消息需要指定topic, 读取消息也需要指定topic, 同一个topic可以有多个生产者和多个消费者</p><p>topic是一个逻辑上的概念, 每个topic由至少一个或多个partition组成, 每个partition保存topic内的<code>一部分</code>消息。topic内的消息无法保证有序, 除非只有一个partition</p><p><strong>partition</strong></p><p>partition相当于是topic内部的一个分片 </p><p>假如说我们有个topic其中有5个patition, 一个消息在每个topic中只会存储在一个patition中, 整个topic的消息等于该topic下所有partition的总和 </p><p>每个partition至多只能有一个消费者, topic下的总消费者数量也受限于partition, 比如你topic有10个分片, 如果使用12个消费者就会有两个消费者永远获取不到数据 </p><p>每个partition是一个文件集合, 集合内同一时刻只有一个文件可写入数据,单个partition里面的数据因为这个关系可以保持有序 </p><p>partition还可以有自己的replication,replication只有一个功能, 就是提供数据冗余, 防止partition出问题时造成数据丢失 。不过同一个partition的多个replication中同一时间只能有一个primary replication(通过选举得出),由这个primary replication来执行整个partition的数据操作</p><p><strong>consumer</strong></p><p>每个消费者可以消费一个topic的一个partition, 可以同时消费多个topic</p><p>consumer数量如果超过一个topic的分片数量, 会造成某些consumer永远消费不到数据</p><p>消费者消费数据需要提交offset告诉broker自己已经消费过某条数据</p><p>当topic新增一个consumer的时候会触发其他消费此topic的consumer group内consumer的<code>Rebalance</code>, 重新在consumer之间重新进行分区分配</p><p><strong>consumer group</strong></p><p>消费者组也是一个逻辑上的概念, 每个消费者组内的消费者只能消费同一个topic内的某一条消息一次, 除非进行手动offset调整重新消费</p><p>如果某个topic中的数据希望同时给多个业务方使用, 每个业务方应该使用一个单独的consumer group</p><p><strong>producer</strong></p><p>每个生产者可以为一个或多个topic生产数据, producer只负责将数据发送给broker, 后续操作通通由broker来负责</p><h1 id="kafka的主要应用何特点"><a href="#kafka的主要应用何特点" class="headerlink" title="kafka的主要应用何特点"></a>kafka的主要应用何特点</h1><p><strong>常见应用场景</strong></p><ol><li><p>消息队列:kafka通常被用作消息队列, 这也是kafka的主要用途之一, 因为他可以一定程度上保证消息的可靠和有序</p></li><li><p>日志/消息存储:kafka由于基于文件存储, 所以很适合用来存储日志信息, 通知消息等有序且数据巨大的信息</p></li><li><p>数据总线:kafka还适合在不同的存储系统和业务之间做数据总线, 这样可以方便的把一份数据传递给多方公用<br><strong>优点</strong></p><ol><li>增加了partition层, 高度解耦, 支持分布式, 支持副本, 扩展方便</li><li>基于文件存储消息, 采用文件指针的读方式, 速度快, 且可重复读</li><li>保证多消费者情况下消息的有序性</li><li>在producer, broker, consumer三者做了大量性能优化,例如:<code>cache buff</code>和<code>sendfile()</code>等</li></ol></li></ol><p>kafka的大部分分布式特性都得益于partition的设计,由于采用了文件集合来存储每一个partition,使得kafka在性能和有序性方面获得了巨大的优势</p><p><strong>kafka作为消息队列的缺点</strong></p><pre><code>1. 只有topic一个逻辑隔离级别2. 高并发依赖于partition数量限制, 扩展不是特别的方便3. 没有消息优先级机制4. 数据中心级别的数据同步不成熟5. 功能和数据存储系统没有隔离开</code></pre><p>同时也由于kafka的部分设计不可避免的有一些缺点</p><p>由于partition的限制, 应对高并发场景, 如果需要加快一个topic的处理速度只能通过增加消费者的方式, 这个增加过程又不像其他内存式的消息队列来的方便</p><p>相比于很多传统消息队列服务, kafka也没有消息优先级的机制</p><p>kafka的竞争对手Apache Pulsar在后两点比kafka要更加优秀, 且在很多基础功能上提供了更多的选择性</p><h1 id="kafka工作流程示意图"><a href="#kafka工作流程示意图" class="headerlink" title="kafka工作流程示意图"></a>kafka工作流程示意图</h1><p><img src="https://i.loli.net/2020/05/09/JaQFedmhsk175PS.png" alt="kafka1.png"></p><p>kafka中的一个完整的消息流程如上图所示</p><p>Producer将消息发给broker中的topic,存储到topic下的某一个partition</p><p>Consumer从partition中消费数据,将该消费者在该topic中的数据偏移标记为最新</p><h1 id="kafka为什么这么快"><a href="#kafka为什么这么快" class="headerlink" title="kafka为什么这么快"></a>kafka为什么这么快</h1><ol><li>吞吐量和延迟</li></ol><p>吞吐量和延迟是一个kafka的平衡选择</p><p>吞吐量大, 延迟就高, 延迟高, 吞吐量就小</p><p>这个需要自己做抉择, kafka一定程度上选择了用牺牲延迟换吞吐量</p><p>kafka在producer和broker中都使用了<code>cache buff</code>的方式来增加吞吐量</p><ol start="2"><li>零拷贝</li></ol><p>kafka的broker发送数据时采用零拷贝技术, 减少了一次内部的从用户态到内核态的状态切换过程, 使用<code>sendfile</code>将文件直接通过内存地址发送给网卡</p><ol start="3"><li>基于文件的追加方式</li></ol><p>kafka采用追加文件记录的形式来处理数据, 这种方式要比随机读写快上很多 </p><ol start="4"><li>buff发送</li></ol><p>对发送的文件进行了了缓冲区处理, 缓冲区满了以后或者到了一定时间才会发送数据</p><p>相当于对发送信息做了批处理</p><h2 id="kafka和ZooKeeper"><a href="#kafka和ZooKeeper" class="headerlink" title="kafka和ZooKeeper"></a>kafka和ZooKeeper</h2><p>kafka 使用zk存储一些关键配置信息</p><p>如: 某个topic的消息总量, 每个partition的消费数据等信息,消费者消费的记录offset等都存储于zk</p><p>许多的kafka监控应用也都是通过读取zk中的kafka数据来进行监控的</p><blockquote><p>ps: kafka新版本中已经允许客户端提交offset到kafka的topic中</p></blockquote><p>具体保存消息的节点路径如下图: </p><p><img src="https://i.loli.net/2020/05/09/8hymVr34eTK6ndq.jpg" alt="kafka_zk.jpeg"></p><p><img src="https://i.loli.net/2020/05/09/md1eiYFCEnIVBst.jpg" alt="kafka_zk2.jpeg"></p><h1 id="kafka数据存储"><a href="#kafka数据存储" class="headerlink" title="kafka数据存储"></a>kafka数据存储</h1><p>kafka的数据存储大致分为三层, broker, partition, segment</p><p>kafka配置文件中的 server.properties中的以下属性指明了kafka的数据存储位置</p><p><code>log.dirs=/usr/local/var/lib/kafka-logs</code></p><p>具体如下图:</p><p><img src="https://i.loli.net/2020/05/09/2gVbKren4BGyxQ1.jpg" alt="kafka_store.jpeg"></p><p>一个broker中虽然可以随处多个topic数据, 但是真正的存储还是要落实到 segment上<br>topic和partition只能决定存放segment的上层文件夹的名字</p><h2 id="segment内的存储细节"><a href="#segment内的存储细节" class="headerlink" title="segment内的存储细节"></a>segment内的存储细节</h2><p><img src="https://i.loli.net/2020/05/09/AO3746RkVfgZiL2.jpg" alt="kafka_segment.jpeg"></p><p>一个topic的一个partition拥有多个segment file, 每个segment file拥有多个部分 </p><p>一般由以下四个类型文件组成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">xxx.index //segment的索引文件</span><br><span class="line">xxx.log //segment的数据文件</span><br><span class="line">xxx.timeindex //segment的时间索引</span><br><span class="line">leader-epoch-checkpoint //检查点文件</span><br></pre></td></tr></table></figure><h1 id="使用kafka遇到的问题"><a href="#使用kafka遇到的问题" class="headerlink" title="使用kafka遇到的问题"></a>使用kafka遇到的问题</h1><p><strong>1.kafka的topic不消费</strong><br>原因:<br>topic中的消息容量是有限制的,假如短时间内某topic中进入了大量的消息<br>消费者来不及消费可能导致消费者的消费offset小于当前topic的最小消息偏移</p><p>举例:<br>假如我们topic最大可以存储200万消息,消费者每分钟消费30万的消息<br>现在有个入消息的接口每分钟入100万的消息,那topic, consumer就会产生如下问题</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"><span class="string">"topic"</span>:{</span><br><span class="line"><span class="string">"start"</span>: <span class="number">211450</span>, <span class="comment">// 当前topic消息最小值</span></span><br><span class="line"><span class="string">"end"</span>: <span class="number">2211450</span>, <span class="comment">// 当前topic的消息最大值</span></span><br><span class="line">},</span><br><span class="line"><span class="string">"consumer"</span>:{</span><br><span class="line"><span class="string">"topic1"</span>: <span class="number">311450</span>, <span class="comment">// 消费者在topic中的消费位置</span></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 然后等了一分钟</span></span><br><span class="line">{</span><br><span class="line"><span class="string">"topic"</span>:{</span><br><span class="line"><span class="string">"start"</span>:<span class="number">2211450</span>,<span class="comment">// 当前最小的消息值</span></span><br><span class="line"><span class="string">"end"</span>:<span class="number">4211450</span>,<span class="comment">// 当前最大的消息值</span></span><br><span class="line">},</span><br><span class="line"><span class="string">"consumer"</span>:{</span><br><span class="line"><span class="string">"topic1"</span>: <span class="number">461450</span>, <span class="comment">// 消费者当前的消费消息位置</span></span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也就是说消费者的消费值在内存中的topic中找不到了.因为消息入得太快,消费者跟不上,当前消费消息,被”顶”出去了</p><p>解决方案:</p><ol><li>可以手动设置消费者的消费位置,将其置为 当前topic中可以找到的消息偏移位置</li><li>可以重新设置消费方式,这种方式也是变相将该消费者在该topic的消费位置重置</li></ol><p><strong>2.kafka的topic不能直接删除</strong></p><p>原因: 因为要删除一个topic必须符合两个条件</p><ol><li>无消费者消费 </li><li>文件中无消息记录,普通的删除只能删除文件中的消息记录,无法删除消费者得消费信息</li></ol><p>方案: 将该topic的所有消费者消费偏移置为0 ,然后执行删除,或者使用kafka tool工具可以直接删除</p><p><strong>3.kafka的分片数据分布不均匀</strong></p><p>原因: kafka早期的算法会根据key的hash值来对消息进行分配<br>如果没有key可能会被分配至随机的一个固定的partition中<br>这样会导致topic中的消息分布不均匀,</p><p>方案: 1. 可以更改分配算法 2. 使用时间戳作为key的结尾 </p><p><strong>4.kafka的消费者分组的使用</strong></p><p>描述: 由于每个消费者分组中的topic只能被消费一次<br>kafka可以通过消费者分组来对某一topic中的数据进行重复消费<br>我们可以通过给不同部门设置消费者分组来实现类似订阅的机制</p><p>举例: 我们有一个订单消息队列, topic为 <code>order-topic</code><br>我们可以通过给 订单部门和 物流部门 分配不同的消费者分组<br>来对同一个topic中的消息进行重复消费</p><h1 id="Kafka的高级特性和流处理"><a href="#Kafka的高级特性和流处理" class="headerlink" title="Kafka的高级特性和流处理"></a>Kafka的高级特性和流处理</h1><ul><li>kafka事务</li></ul><p>kafka现在已经支持事务, 这个是kafka一致性保证的重大进步</p><p>kafka的事务目前还有一定的限制, 实现方式是使用 事务ID和客户端id做幂等处理</p><p>kafka事务流程:</p><ol><li><p>生产者发起事务请求</p></li><li><p>发送消息</p></li><li><p>服务器接受数据,进行追加写入</p></li><li><p>生产者结束事务</p></li></ol><p>如果客户端没有结束事务, kafka虽然将数据写入到了broker,但是不会让其他消费者客户端读到这部分数据</p><p>这部分数据在kafka上会被标记为abort掉的数据</p><ul><li>kafka多版本混布</li></ul><p>kafka现在支持多个kafka版本混合部署, 可以同时使用1.0 和2.0 版本组建一个kafka集群,这个功能可以支持我们无缝升级Kafka集群的版本</p><ul><li>流处理</li></ul><p>流处理的目的是尽可能的保证业务处理的实时性,就是事件一旦发生我们就希望立刻处理</p><p>目前常见的流处理框架有 spark, storm, flink等,kafka streaming与他们相比显得更为轻量和易用</p><p>流处理更像业务逻辑的一部分, 而不是业务的分拆, 是一个独立的微服务, 而不是MapReduce任务</p><ul><li>流处理的常用方法</li></ul><p>流处理常用的方法有 <code>filter /map /join /aggregate</code></p><p>其中 <code>map/filter</code> 属于对单个数据进行的无状态操作</p><p><code>join/aggregate</code> 属于数据统计需求, 有一定的状态要求</p><p>此外还有<code>window函数</code>等</p><p>kafka stream作为一个库的形式像我们提供了以上各种方法, 使得我们使用起来非常容易</p><ul><li>使用kafka streaming的一个场景</li></ul><p>例如在产品池项目中, 我们用 kafka stream 从canal的事件消息topic中接收数据 </p><p>从中根据不同的消息内容将事件按照<code>库和表</code>发送给不同的后续topic, 达到了消息路由的功能 </p><p>kafka stream将三个不同来源的topic中的待更新产品信息融合到同一个 待更新的产品信息Topic中 </p><ul><li>Kstream和Ktable</li></ul><p>kafka向我们提供了以下两个概念方便我们进行流处理</p><ol><li>KStream</li></ol><p>一个纯粹的流就是所有的更新都被解释成INSERT语句(因为没有记录会替换已有的记录)的表。</p><p>在一个流中(KStream),每个key-value是一个独立的信息片断,比如,用户购买流是:alice->黄油,bob->面包,alice->奶酪面包,我们知道alice既买了黄油,又买了奶酪面包。</p><blockquote><p>ps: 表中每条记录的变动就是一个流</p></blockquote><ol start="2"><li>KTable(changelog流)</li></ol><p>KTable 一张表就是一个所有的改变都被解释成UPDATE的流(因为所有使用同样的key的已存在的行都会被覆盖)。</p><p>对于一个表table( KTable),是代表一个变化日志,如果表包含两对同样key的key-value值,后者会覆盖前面的记录,因为key值一样的,比如用户地址表:alice -> 纽约, bob -> 旧金山, alice -> 芝加哥,意味着Alice从纽约迁移到芝加哥,而不是同时居住在两个地方。</p><blockquote><p>ps: 对某一时刻的流数据进行切面,按时间对数据进行覆盖,那个切面数据就是表</p></blockquote><p>这两个概念之间有一个二元性,一个流能被看成表,而一个表也可以看成流。</p><p>我们一般用KStream来支持流式处理功能,同时使用KTable支持批处理功能作为补充,两者互相结合可以满足大部分的业务处理场景</p><p>同时KTable 还提供了通过key查找数据值的功能,该查找功能可以用在Join等功能上。</p><p>总的来说kafka streaming做为流式处理系统跟老牌的spark streaming/flink还有一定的差距,但是很适合轻量级的数据处理场景</p><p>还是拥有一定的市场空间</p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> Kafka </tag>
</tags>
</entry>
<entry>
<title>给团队成员的Redis分享总结</title>
<link href="/2017-10-29-redis-share-in-group/"/>
<url>/2017-10-29-redis-share-in-group/</url>
<content type="html"><![CDATA[<h1 id="redis分享"><a href="#redis分享" class="headerlink" title="redis分享"></a>redis分享</h1><p>Author: leriou lee 2017.10.12<br>本篇内容主要是针对团队成员的redis中间件分享</p><p>目的是帮助不熟悉redis的团队成员快速的了解redis,对redis大概有一个了解<br>已经使用过redis的能对其有进一步的了解,以及总结了一些遇到的业务问题和解决方案</p><h1 id="redis基础"><a href="#redis基础" class="headerlink" title="redis基础"></a>redis基础</h1><ol><li>redis是什么</li></ol><p><code>redis</code>是一个使用c语言开发的k-v存储系统,其实不仅仅作为缓存,由于<code>redis</code>内部的底层数据结构设计的非常完善</p><p>我们也可以把<code>redis</code>作为一个现成的数据结构实现集合,只要了解<code>redis</code>内部的基础数据结构和实现,我们就能利用它做很多事情</p><p>比如我们想利用某种数据结构的特性(例如:跳跃表),又不想自己实现,就可以在<code>redis</code>的基础之上构建业务</p><p>redis目前支持5种数据对象, 基本可以满足大多数的业务场景</p><blockquote><p>ps: 现在5.0发布已经有6种了</p></blockquote><ol start="2"><li><p>redis的大致工作流程</p><ol><li>启用redis-server,使用socket服务监听端口(redis服务端)</li><li>客户端启用socket连接到服务器,通过认证,服务器维护client的信息(使用链表持有)</li><li>客户端发送命令, 服务器接收到命令根据命令表进行执行和查找等,返回结果(将结果保存到client的输出缓冲区)</li><li>客户端解析服务器返回的结果</li></ol></li><li><p>redis能做什么</p></li></ol><p>redis作为一个缓存中间件, 可以帮助我们解决很多业务中遇到的问题,由于redis内部功能机制比较丰富,其实不仅仅可以作为简单的缓存中间件使用</p><p>redis可以作为高速的k-v存储,非常适合存储一些直接针对用户的应用数据,或者进行用户行为统计。 比如:对用户进行限流,统计用户访问量</p><ol start="4"><li>基于redis可以解决的一些常见问题</li></ol><p>redis可以帮助我们解决很多业务中的问题,下面简单列举一下</p><ul><li><p>作为服务间的共享空间或临时存储,解决数据共享问题 例如: 计数器,注册器/协调器,在分布式应用中做桥接</p><p> 由于redis是一个独立的服务,不依赖任何其他的服务,同时又具有高效的存储功能<br> 我们可以用redis在不同的应用和系统服务之间进行简单的数据传递,或者存放部分中间数据<br> 相当于多个服务之间的共享内存,我们可以”使用共享内存来通信”</p></li><li><p>为高读写要求的场景提供数据存储,解决性能问题 例如: 热点数据缓存/流量控制(漏桶和令牌桶)</p><p> redis由于是一个内存数据库,读写速度执行非常的快<br> 在一个最低配的的阿里云机器上可以达到8w/s的ops,非常适合用来处理一些对读写性能要求极高的场景<br> 比如:部分热点商品数据,秒杀活动,流量控制等</p></li><li><p>其他可以利用redis特性的场景,解决业务问题 例如: 搜索/bitmap/数据匹配/消息队列/发布订阅</p><p> redis内部还有很多的特殊机制实现了比较丰富的功能, 如:发布订阅.<br> 我们也可以利用redis的一写数据结构特性来构建倒排索引,以实现简单的搜索功能<br> 也可以利用list的数据结构的特性实现简单的消息队列</p></li></ul><h1 id="Redis的读写流程"><a href="#Redis的读写流程" class="headerlink" title="Redis的读写流程"></a>Redis的读写流程</h1><ul><li>通信协议RESP</li></ul><p>RESP协议在Redis 1.2中引入,但它成为与Redis 2.0中的Redis服务器通信的标准方式。 这是每一个Redis客户端中应该实现的协议。</p><p>RESP实际上是一个支持以下数据类型的序列化协议:单行字符串,错误信息,整型,多行字符串和数组。<br>RESP在Redis中用作请求 - 响应协议的方式如下:</p><p>客户端将命令作为字符串数组发送到Redis服务器。<br>服务器根据命令实现回复一种RESP类型数据。<br>在 RESP 中, 一些数据的类型通过它的第一个字节进行判断:</p><p>单行回复:回复的第一个字节是 “+”</p><p>错误信息:回复的第一个字节是 “-“</p><p>整形数字:回复的第一个字节是 “:”</p><p>多行字符串:回复的第一个字节是 “$”</p><p>数组:回复的第一个字节是 “*”</p><p>此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。<br>在RESP中,协议的不同部分始终以“\ r \ n”(CRLF)结束。</p><ul><li>读写过程</li></ul><p>Redis的读过程可以简化为:</p><ol><li>client客户端通过socket发请求</li><li>server端监听服务端口, 收到请求, 解析协议, 查找命令表</li><li>server端进行数据操作, 更新数据状态</li><li>server端返回数据或信息, client端接受并解析</li></ol><p>其中细节颇多, 值得注意的就是 server端内部会进行很多检查工作</p><p>比如: 检查键的订阅者, 是否有监听者(monitor), hash负载因子如何, 是否需要rehash, 主从同步等信息 </p><p>Redis在集群模式下, 会把<code>key</code>根据hash映射到 <code>16384</code>个槽其中的一个, 再根据槽所在的节点对客户端操作进行应答</p><p>如果该<code>key</code>所在槽不归本节点维护, 服务器会返回<code>moved</code>错误</p><p>而且cluster模式下不能使用Redis的pipeline功能, 除非你能保证pipeline操作的所有key都在同一节点上</p><h1 id="Redis的特色功能"><a href="#Redis的特色功能" class="headerlink" title="Redis的特色功能"></a>Redis的特色功能</h1><ol><li>Redis的持久化</li></ol><p>redis有别于memcache的一个区别就是redis支持数据持久化,redis可以通过采用一定的策略将内存中的数据持久化到本地磁盘上面</p><p>这么做不仅有利于数据的完整性和可用性, 同时也可以在服务器重启或者迁移过程中实现方便的数据恢复,一般来说如果设置了合理的持久化策略,就算是服务进程出了问题只要重启服务,并不会丢失太多的数据</p><p>redis的持久化相关的主要有以下几点:</p><ol><li>RDB: 基于内存状态的持久化操作</li><li>AOF: 基于命令操作的持久化操作</li><li>AOF重写: 基于内存的持久化(命令格式)</li></ol><p><strong>RDB</strong></p><p>RDB:持久化相当于对当前的数据库状态进行一次快照备份, 是将当前的内存数据库中的数据进行序列化保存到本地的操作, 如果数据库使用量比较大,在持久化的时候可能会对性能造成比较大的影响,可以使用命令 <code>save</code>(在主进程执行持久化,会造成主进程阻塞) 或 <code>bgsave</code>(创建一个子进程来执行持久化,不会阻塞主进程,但是执行时会消耗大量的内存)来执行RDB持久化</p><p><strong>AOF</strong></p><p>AOF(append only file): AOF持久化是对redis的命令进行记录,恢复时按照命令重新执行一遍,以恢复数据库状态的持久化形式,有点类似于GFS里面的基于日志的恢复机制. AOF持久化对性能影响没有RDB那么大,每当redis执行一个命令,redis会根据AOF持久化的设置规则判断是否进行持久化,AOF可以根据命令执行时间和频率来执行持久化策略,比如: 3s执行100个命令则进行持久化,每个命令都持久化等</p><p><strong>Rewrite AOF</strong></p><p>AOF重写: 但是AOF也有一些缺点,有可能造成AOF文件非常的大,举个例子: 我先设置键a为10(set a 10),然后设置键a为5(set a 5),重新设置键a为10(set a 10), 这三条命令执行以后最终的数据状态中的表现结果是a=10,但是在AOF文件中会有3条命令.如果某个键的变动频率非常的高,就会消耗还多的数据命令来记录数据的变化,比如计数器. </p><p>redis提供了AOF重写功能来解决这种情况, AOF重写是对当前的内存数据库状态进行命令反向解析,比如,上面的例子,在进行AOF重写的时候,redis看到数据库中的键a的值是10 ,会反向生成一条命令 set a 10,将该命令写到重写文件中,这样就能有效的减小AOF文件的体积</p><ol><li><p>事务</p><pre><code> redis中的事务基于链表实现,跟普通的数据库事务不同的是,redis的事务不支持回滚数据,执行失败了也不会进行通知 redis在的事务其实相当于使用一个pipeline 将一系列的操作一起打包发给redisServer来执行</code></pre></li><li><p>发布订阅</p><pre><code> redis的发布订阅也是基于链表实现,redis的订阅相当于将某个客户端加入到订阅该模式的链表中,redis在执行发布消息的时候会沿着链表去检查所有订阅了符合该模式的所有客户端,将消息发送给他们.</code></pre></li><li><p>监控(monitor)</p><pre><code> 监控功能也是基于链表实现,redis的监视器是一种特殊的redis客户端,服务器会在执行命令时候,将命令同时发送给所有监视器列表上面的客户端</code></pre></li><li><p>哨兵(sentinal)</p><pre><code> redis哨兵是官方的集群方案出来之前的一种分布式解决方案,哨兵可以监控服务器,并在服务器出问题时候采用选举策略将从服务器省纪委主服务器保证服务稳定</code></pre></li><li><p>排序(内部实现)</p><pre><code> redis的内部排序主要对set,sort set,list这三个数据结构起作用,旨在让用户可以用过自定义的方式对内部数据进行排序</code></pre></li></ol><h1 id="redis-Representation"><a href="#redis-Representation" class="headerlink" title="redis Representation"></a>redis Representation</h1><p>这里做了一个简单的redis的脑图, 着重描述了一下内部的基本数据结构的关系</p><p><img src="https://i.loli.net/2020/05/09/BKtdElZifANIg8c.png" alt="redis.png"></p><p>百度脑图地址:<br><a href="http://naotu.baidu.com/file/ee2d1316a2eb6b2d4fc5b0c876c50685?token=cb284164f0989037">http://naotu.baidu.com/file/ee2d1316a2eb6b2d4fc5b0c876c50685?token=cb284164f0989037</a></p><h1 id="Redis集群"><a href="#Redis集群" class="headerlink" title="Redis集群"></a>Redis集群</h1><p>redis的常见高可用部署方案</p><ul><li>keepalived(基于VRRP协议)</li><li>哨兵(sentinal)</li><li>集群(cluster): redis3.0之后提供,同时也是个人比较建议的方案</li><li>其他第三方方案</li></ul><p>其中各种方案各有优点, 我们使用的是redis的官方cluster方案</p><p><strong>集群使用中的一些坑</strong></p><ol><li>主从切换,故障转移</li></ol><p>redis的主服务器出问题以后,从服务器会顶替主服务器对外提供副服务,但是如果客户端没有对集群中的主节点配置进行更新, 会导致客户端和服务器主节点配置对应不上,从而导致redis操作失败,部分redis客户端支持集群模式,可以自动判断当前集群的主节点,从而自动进行配置调整</p><ol start="2"><li>分片导致的数据分布问题</li></ol><p>redis内部采用16384个槽来对存储的数据进行分配,数据分配到槽上面,槽分配到节点机器上面.但是这样也而导致在进行部分数据操作的时候会出现问题</p><p><strong>故障转移</strong></p><p>主节点挂掉之后,怎么自动修改配置,使服务正常?</p><p>3个思路:</p><ol><li>后台定时检查,修改配置或将配置放入zk等,实例化客户端的时候从zk中实时获取配置信息</li><li>客户端程序执行redis命令失败时,进行消息通知,检查当前的节点配置,修改配置 </li><li>每次redis对象实例化之后检查集群状态,程序中动态修改配置</li></ol><p>以上思路都需要使用<code>cluster node</code>命令从集群中获取当前节点信息,解析出来当前的集群主节点,区别就是修改配置的方式和时间点不同</p><ol><li><p>使用<code>cluster info</code> 命令检查集群状态</p><p>cluster info返回信息如下(redis3.2):</p></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cluster_state</span>:ok</span><br><span class="line"><span class="attr">cluster_slots_assigned</span>:<span class="number">16384</span></span><br><span class="line"><span class="attr">cluster_slots_ok</span>:<span class="number">16384</span></span><br><span class="line"><span class="attr">cluster_slots_pfail</span>:<span class="number">0</span></span><br><span class="line"><span class="attr">cluster_slots_fail</span>:<span class="number">0</span></span><br><span class="line"><span class="attr">cluster_known_nodes</span>:<span class="number">6</span></span><br><span class="line"><span class="attr">cluster_size</span>:<span class="number">3</span></span><br><span class="line"><span class="attr">cluster_current_epoch</span>:<span class="number">9</span></span><br><span class="line"><span class="attr">cluster_my_epoch</span>:<span class="number">8</span></span><br><span class="line"><span class="attr">cluster_stats_messages_sent</span>:<span class="number">8625814</span></span><br><span class="line"><span class="attr">cluster_stats_messages_received</span>:<span class="number">8601220</span></span><br></pre></td></tr></table></figure><ol start="2"><li><p>使用<code>cluster nodes</code> 命令检查集群节点的状态</p><p>cluster nodes返回信息如下:</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">634f54bcec482a1a048f2604793c06acd47c93c6 10.113.1.231:7001 slave c1eecd493eee0644a76bb9e691b6d13a31b25628 0 1510321448626 6 connected</span><br><span class="line">1ed54647073d60118a35df253e9b04459cc30e49 10.113.2.82:7001 slave c464639a33800da984875866391a549647af4a5a 0 1510321447623 3 connected</span><br><span class="line">01334b7a5802032461966cc382b2f97f004ab027 10.113.2.82:7000 myself,master - 0 0 1 connected 0-5460</span><br><span class="line">c1eecd493eee0644a76bb9e691b6d13a31b25628 10.113.1.231:7000 master - 0 1510321446623 5 connected 10923-16383</span><br><span class="line">c464639a33800da984875866391a549647af4a5a 10.113.1.42:7000 master - 0 1510321445622 3 connected 5461-10922</span><br><span class="line">98b58ef327f872dec466761cabae23bad74622db 10.113.1.42:7001 slave 01334b7a5802032461966cc382b2f97f004ab027 0 1510321450631 4 connected</span><br></pre></td></tr></table></figure><p>其他问题:</p><pre><code>1. 多主节点的槽分配导致的无法对复杂数据结构(例如hash)进行重命名操作2. pipeline的使用受节点限制</code></pre><h1 id="数据对象和底层数据结构"><a href="#数据对象和底层数据结构" class="headerlink" title="数据对象和底层数据结构"></a>数据对象和底层数据结构</h1><p><strong>redis基本数据对象在我们自己项目中的使用</strong></p><p>redis提供了多种基本的数据对象,已经能满足我们的大部分业务需求</p><p>以下是各种数据结构在我们业务中的使用示例</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">str</span>: 适用于简单存储(临时标记,计数器等) </span><br><span class="line"><span class="attr">list</span>: 适用于线性存储和类似场景(简单线性容器,vector,简单的消息队列)</span><br><span class="line"><span class="attr">hash</span>: 适用于对象存储(产品池,产品信息)</span><br><span class="line"><span class="attr">set</span>: 适用于需要进行集合运算的场景(行政区服务,A和B都喜欢的产品,倒排索引等)</span><br><span class="line"><span class="attr">zset</span>: 适用于有排序需求的场景(<span class="number">360</span>凤舞系统,消息系统中的优先级实现)</span><br><span class="line"><span class="attr">stream</span>: 流式对象, 类似于kafka中的topic (<span class="number">5.0</span>新增)</span><br></pre></td></tr></table></figure><p>其他一些不建议的数据对象使用方法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1.</span> 使用str存储json对象</span><br><span class="line">如: <span class="attr">a</span>:{<span class="string">"name"</span>:<span class="string">"xiaohua"</span>,<span class="string">"age"</span>:<span class="number">8</span>},像这种可以使用hash来存储</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 过度使用有序集合</span><br><span class="line">集合和有序集合和list在部分特性上面比较类似,很多人时候问题都可以使用list替代而不必要使用集合,因为有序集合底层使用的跳跃表,性能方面比简单的列表稍微差点,但是集合和有序集合都可以方便的使用集合操作,交/并/差集等</span><br></pre></td></tr></table></figure><p><strong>redis中的数据库键空间</strong></p><p><img src="https://i.loli.net/2020/05/09/deSBNkvgwpaE2tK.jpg" alt="IMG_0255.jpeg"></p><p><strong>数据对象和底层实现的数据结构对应关系</strong></p><p>Redis数据对象的内部复杂实现,其实是针对2个不同场景, 省内存和常规使用</p><table><thead><tr><th align="left">对象</th><th align="left">省内存</th><th align="left">常规</th></tr></thead><tbody><tr><td align="left">str</td><td align="left">int/embstr</td><td align="left">row</td></tr><tr><td align="left">list</td><td align="left">ziplist(quicklist)</td><td align="left">linkedlist(quicklist)</td></tr><tr><td align="left">hash</td><td align="left">ziplist</td><td align="left">hashtable</td></tr><tr><td align="left">set</td><td align="left">intset</td><td align="left">hashtable</td></tr><tr><td align="left">zset</td><td align="left">ziplist</td><td align="left">skiplist</td></tr></tbody></table><p>redis在3.2之后使用quicklist替代了linkedlist作为列表对象的底层实现</p><blockquote><p>ps: 我还另外写过两篇稍微详细点的关于redis数据结构(<a href="https://leriou.github.io/post-redis-data-structure/">https://leriou.github.io/post-redis-data-structure/</a>)和数据对象的文章(<a href="https://leriou.github.io/post-redis-object/">https://leriou.github.io/post-redis-object/</a>)</p></blockquote><h1 id="生产问题实例"><a href="#生产问题实例" class="headerlink" title="生产问题实例"></a>生产问题实例</h1><ol><li>使用redis注册器起到类似分布式锁的作用</li></ol><p>一个曾经的小问题,一段伪代码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">flag = get(redis_key)?redis_key:<span class="number">0</span></span><br><span class="line">res = DB.op(select * from table where id > flag)</span><br><span class="line"><span class="keyword">while</span>(res) {</span><br><span class="line">foreach(a in res) {</span><br><span class="line"><span class="keyword">do</span>(a)</span><br><span class="line">}</span><br><span class="line">write(redis_key,flag)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>潜在的问题: 不支持多线程并发, 任务不能多线程同时进行</p><p>解决方案: 借用redis作为注册器,实现类似乐观锁的机制</p><p>具体方案: </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">redis中存储正在进行的处理进程:</span><br><span class="line">[<span class="number">2000</span>:<span class="number">1505290677</span>,<span class="number">3000</span>:<span class="number">1505287997</span>,<span class="number">5000</span>:<span class="number">1505287993</span>,<span class="number">6000</span>:<span class="number">1505287892</span>,<span class="number">8000</span>:<span class="number">1505287844</span>]</span><br><span class="line">流程:</span><br><span class="line"><span class="number">1.</span> 从注册器根据过期时间和处理范围获取当前应该处理的flag的值</span><br><span class="line"><span class="number">2.</span> 将自己的当前处理进度存入注册器列表,处理数据</span><br><span class="line"><span class="number">3.</span> 处理结束移除注册器中自己注册的进度和范围</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>以上这个程序的核心思想可以用以下流程来描述:</p><ol><li><p>第一步: 正常的数据处理流程 </p><p>第一个进程来到向注册器索要自己处理的数据起始值,注册器发现当前没有进程在进行,<br>并且没有flag标记(程序处理到哪里了),给出起始值0,<br>并在注册器记录里面记录0:1505287993,意思是有程序正在执行从0开始的数据,<br>进程取1000条数据,将待处理的最大的数据的id设为flag = 1006,<br>处理完毕将删除注册器中的0:1505287993记录;</p></li><li><p>第二步: 有其他进程在执行时候的情况 </p><p>然后第二个程序来执行的时候向注册器索要可以执行的起始值,<br>注册器查看记录发现0:1505287997并且时间没有过期,<br>说明0其实的数据已经有人在处理,并且没有过期的注册信息,<br>于是发放当前的flag给第二个程序,并想注册期记录1006:1505287997,<br>第二个程序取数据1000条,假设ID范围为1006-2390,将处理标记标记为flag = 2390,<br>处理完毕删除注册其中的自己的处理进度记录1006:1505287997;</p></li><li><p>第三步: 针对有部分进程断掉的情况 </p><p> 如果第一个程序中间断掉,<br> 则不能删除自己的处理进度记录0:1505287993,<br> 此时新的程序向注册器索要起始值时,<br> 注册器会发送当前过期的(过期表示处理该记录的程序处理中断了)的程序起始值0,<br> 并标记当前处理的程序是二次处理,<br> 二次处理的程序不会更新flag,<br> 处理完毕删除自己的处理记录</p></li></ol><blockquote><p>ps: 整个流程有点类似于常见的”锁”的概念</p></blockquote><p><strong>缺点</strong></p><p>这个处理方案有个缺点, 要求数据的处理操作是<code>幂等</code>的,也就是无论操作多少次,操作结果都是一样的,或者不具有累加效应</p><p>查询操作就是最常见的<code>幂等操作</code></p><p><strong>流程图</strong></p><p>具体流程图:</p><p><img src="https://i.loli.net/2020/05/09/xpNhU9XPYFn4OcA.png" alt="flow1.png"></p><ol start="2"><li>使用redis解决超高并发</li></ol><p><strong>使用redis的bitmap,hyperloglog等数据结构</strong></p><p>redis的Bitmap非常适合用来做一些数据量大,id分布紧凑,且值类型为bool型的数据的存储, 比如用户标签</p><p>hyperloglog也适合用来进行用户访问量统计,视频播放统计等大数据量的统计工作</p><p><strong>其他应用</strong></p><p>我们也可以使用redis的集合来构建倒排索引以实现搜索功能</p><p>也能使用redis的发布订阅来实现简单的消息通知系统(不过redis的发布订阅缺点很明显,不建议使用)</p><p>或者使用redis的有序集合统计同时在线用户数</p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> NoSQL </tag>
<tag> redis </tag>
</tags>
</entry>
<entry>
<title>Redis集群遇到的一些问题</title>
<link href="/2017-10-03-redis-cluster-problem/"/>
<url>/2017-10-03-redis-cluster-problem/</url>
<content type="html"><![CDATA[<p>我们在使用Redis集群的使用过程中发现了很多问题</p><p>这些问题产生的原因归根到底都是因为redis将数据映射到槽(slot)上面, 而不同的键分布在不同的Node节点上面导致的</p><h1 id="键分布导致的问题"><a href="#键分布导致的问题" class="headerlink" title="键分布导致的问题"></a>键分布导致的问题</h1><p>当在集群模式下进行多键操作, 同时操作的键中有部分不在该节点时,会报如下错误</p><p><code>CROSSSLOT Keys in request don't hash to the same slot</code></p><p>例如:</p><p><code>hmget key1 key2</code></p><p><code>rename key1 key2</code></p><h2 id="错误类型"><a href="#错误类型" class="headerlink" title="错误类型"></a>错误类型</h2><ul><li>moved错误</li></ul><p>该错误表明当前键的落点槽不属于当前Node, 一般之后会跟着一个槽落点和节点地址</p><p>例如:</p><p><code>MOVED 1584 10.200.6.185:7001</code></p><ol><li><p>基于该错误可以调整客户端, 让不支持集群模式的客户端也能支持集群功能</p></li><li><p>也可以提前计算好key, 直接去负责该key所在槽的服务器上取数据</p></li></ol><ul><li>ASK错误</li></ul><p>该错误表明该key对应的数据正在做数据迁移, 槽迁移会引起该槽上的数据返回该错误</p><h1 id="集群模式下不支持select-db"><a href="#集群模式下不支持select-db" class="headerlink" title="集群模式下不支持select db"></a>集群模式下不支持select db</h1><p>单机模式我们可以使用 <code>select 1</code> 来选择使用的数据库(redis默认提供16个数据库)</p><p>但是集群模式下redis不支持该功能</p><p>如果不同的业务组之间需要做业务隔离最好采用不同redis集群的形式进行</p><p>可以把多个redis集群组成redis组,多个redis组组成redis池进行资源的统一管理</p><h1 id="cluster模式不支持使用Pipeline"><a href="#cluster模式不支持使用Pipeline" class="headerlink" title="cluster模式不支持使用Pipeline"></a>cluster模式不支持使用Pipeline</h1><p>在缓存的使用过程中,有时候我们会有快速获取一批k-v存储结果的需求,比如首页列表产品页,此时如果我们对每一个记录使用get操作在数据量较大的时候将会导致整个请求时间过长难以接受</p><p>Redis的<code>pipeline</code>是一种效果很明显的加快获取数据速度的手段,我们可以用pipeline一次性读取多个key的值 </p><p>像这样</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">batch</span> </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">get a</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">get b</span> </span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">get c</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">....</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">exec</span></span></span><br></pre></td></tr></table></figure><p>但是有一个缺点, 我们一旦使用了Redis的Cluster集群, 就没有办法对整个集群使用Pipeline功能</p><p><strong>原因</strong></p><p>因为Redis的Pipeline的原理是在一个连接中持续的进行命令发送, 需要持有一个稳定的连接</p><p>但是一旦使用了集群, 我们每个连接只能连接一个Node节点, 可是Pipeline中发送的数据键的数据落点未必会落到我们当前连接的集群节点上去</p><p>那经过Pipeline发送过去的key就很明显不能正确的拿到内容</p><h2 id="解决方案1"><a href="#解决方案1" class="headerlink" title="解决方案1"></a>解决方案1</h2><ul><li>我们先计算出来每个key的数据落点, 然后将key进行分组, 分组取数据</li></ul><p>步骤:</p><ol><li><p>实现集群节点选择方法, 给每个节点起一个名字, 例如 节点1, 节点2, 节点3</p></li><li><p>查找每个节点上面的数据槽(slot)的范围</p></li><li><p>对Pipeline中的每个key进行<code>crc16(key)&16383</code>, 计算出来每个key的槽, 并找到槽对应的节点</p></li><li><p>将落到相同节点的key进行分组</p></li><li><p>对每一组key再进行Pipeline操作</p></li></ol><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><p>充分利用集群特性, 普适性好, 不会造成数据倾斜</p><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><p>按照节点计算key, 最大请求次数等于节点数, 节点太多的情况下不太合适, 适合小规模集群</p><h2 id="解决方案2"><a href="#解决方案2" class="headerlink" title="解决方案2"></a>解决方案2</h2><ul><li>存储时候就把key存到一起</li></ul><p>使用<code>{}</code>来对key进行标记</p><p>例如: <code>{user}:name:3</code>, <code>{user}:name</code>,<code>{user}:age</code> </p><p>这三个key因为{}部分都相同所以会落到同一个slot上面, 数据自然就落到同一台redis机器上面了</p><h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><p>不受集群规模和节点数量的影响</p><h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><p>但是这种方法限制很大, 没办法充分利用Redis的集群特性, 仅仅适合使用比较频繁的小数据量</p><p>数据量太大会导致大量数据存储在同一个槽内, 造成数据倾斜</p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> redis </tag>
<tag> 分布式存储 </tag>
</tags>
</entry>
<entry>
<title>读红楼</title>
<link href="/2017-09-10-reading-hongloumeng/"/>
<url>/2017-09-10-reading-hongloumeng/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>说起来惭愧,最近才有幸拜读中国古代小说的巅峰之作<红楼梦></p><p>从很早之前打算好好看一下了, 遗憾的是一直以来都没有时间</p><p>最近才抽出一部分时间将曹雪芹所写的前八十回看完</p><p>之所以只看前八十回</p><p>是因为我个人并不认同后四十回的高鹗/程伟元续写的版本</p><p>所以只说前八十回的内容</p><h1 id="关于全书"><a href="#关于全书" class="headerlink" title="关于全书"></a>关于全书</h1><p><红楼梦>原著全本因为各种原因,现在已经寻不见了 </p><p>现在流传的大多是经过后人补全或者修改的,不过版本太多了</p><p>但是大多数版本都已被后人篡改太多内容,目前看来脂砚斋重批版本的应该是最符合原书原意的</p><p>脂砚斋在批注中剧透了大量后面的故事情节对于解读红楼原意非常有帮助,也是解读红楼最重要的依据之一</p><p>虽然我也很希望看到全书, 但是又有点犹豫</p><p>毕竟从脂砚斋的批注中我们可以知道大部分人物的结局都是悲剧的</p><p>我不想亲眼看到这种悲剧. 现在的高鹗和程伟元的续书虽然也有悲剧</p><p>但是我还能骗自己说这不是原书,不可作信</p><p>如果真的原本红楼被挖出来了,且不说我能不能接受得了,便是那些依靠红楼吃饭的红学家,可能也会有部分人因此丢掉饭碗</p><blockquote><p>ps: 清朝的富察明义看过全本的红楼梦, 这在他的诗集<绿烟琐窗集>里面有记载</p></blockquote><h1 id="关于87版电视剧"><a href="#关于87版电视剧" class="headerlink" title="关于87版电视剧"></a>关于87版电视剧</h1><p>如果是没看过红楼梦的书的人, 非常不建议直接去看电视剧</p><p>因为电视剧里面其实是演的一个一个的书中的片段,而且后面几集被删减的内容太多,连续性不强</p><p>还是建议先把书读一遍,然后再去看电视剧</p><h2 id="电视剧中的一些非常好的点"><a href="#电视剧中的一些非常好的点" class="headerlink" title="电视剧中的一些非常好的点"></a>电视剧中的一些非常好的点</h2><ol><li>服饰</li></ol><p>剧中的服饰非常讲究,林黛玉的各种服饰造型既符合传统,又完全没有传统汉朝服饰那种笨,糙的感觉</p><p>据说为了拍87版的电视剧剧组设计了2700多套衣服, 按照三年的时间来计算平均每天设计三套…</p><ol start="2"><li>建筑</li></ol><p>剧中的荣国府是专门为此建造的影视基地,建筑用料,建制,建筑上面的一砖一瓦都极为讲究,之前看过一部与此相关的纪录片</p><ol start="3"><li>音乐</li></ol><p>剧中的词曲甚多, 也有人专门对此进行研究,剧中的<枉凝眉>简直好听到爆</p><h2 id="其他相关"><a href="#其他相关" class="headerlink" title="其他相关"></a>其他相关</h2><p>87版的红楼梦毫无疑问是最经典的版本, 而其中的林黛玉毫无疑问是表演非常非常好的一个角色</p><p>我其实看书的时候并没有那么喜欢林黛玉, 但是后来看了陈晓旭的林黛玉, 越看越喜欢</p><p>其实我个人觉得87版的陈晓旭老师的林黛玉演得好绝非是因为长得好看</p><p>就纯粹的长相而言,剧中的林黛玉绝对算不上顶尖的美女,但是架不住里面各种俏皮的小动作, 实在是太可爱了</p><p>我现在依然记得几个经典的片段</p><blockquote><p>part1: 贾宝玉用老鼠偷香芋的故事编排黛玉的时候<br>part2: 史湘云第一次出场,黛玉笑湘云说话咬字,被湘云反过来调戏的时候,生气追湘云被宝玉拦下来的时候<br>part3: 凤姐调笑黛玉吃了我家的茶怎么还不给我们家做媳妇的时候,黛玉生气的样子<br>part4: 宝玉被魇好了以后,黛玉向菩萨祈愿,被宝钗调笑<br>part5: 探宝钗黛玉半含酸<br>part6: 蘅芜君兰言解疑癖<br>part7: 以及湘云刚出场, 黛玉问宝玉去处<br>part8: 宝玉跟黛玉一块哭, 黛玉把手帕递给他<br>part9: 惊呆雁片段</p></blockquote><p>以上几个片段我也读过书, 分明就有好多小动作是陈晓旭自己加上去的</p><p>而且看之后的而采访时候也听扮演贾宝玉的欧阳奋强说,片场的陈晓旭十分鬼灵</p><p>陈晓旭老师真的是把林黛玉身上那种俏皮可爱,表现的淋漓尽致</p><p>陈晓旭老师自己都说她就是林黛玉,我觉得此言非虚</p><h2 id="跟10版的对比"><a href="#跟10版的对比" class="headerlink" title="跟10版的对比"></a>跟10版的对比</h2><p>我之前老是听人说红楼梦是一部伟大的现实主义作品, 但是我个人其实并不是很同意这个说法</p><p>10版就是认为应该写实, 结果拍出来跟那啥一样</p><p><红楼梦>中浪漫主义的体现非常之多</p><p>贾宝玉的玉的来历,贾宝玉性格,薛宝钗吃的药,很多地方都说明红楼梦不是完完全全的写实主义, 反而是浪漫主义偏多</p><p>书中人物性格之所以那么明显, 不就是因为各种戏剧化事件的推动吗</p><p>所以我很喜欢87版偏浪漫主义的拍摄</p><h1 id="关于人物"><a href="#关于人物" class="headerlink" title="关于人物"></a>关于人物</h1><h2 id="薛宝钗"><a href="#薛宝钗" class="headerlink" title="薛宝钗"></a>薛宝钗</h2><p>在我还没有看过红楼梦的时候我就听过好多人说喜欢薛宝钗而不喜欢林黛玉, 因为宝钗明显更符合传统的封建礼教的那一套大家闺秀的形象,通古博今,待人友善,同时又很少干涉他人私事,王熙凤说她”不关己事不开口,一问摇头三不知”</p><p>书中的宝钗是个脾气极好的人,好像除了清虚观那一段发过脾气外就再也没有发过脾气了,刘心武说宝钗那一段时间心情不好是因为选秀失败了,结合宝玉把她比作杨妃,宝钗生气说出的话来看,确实有一定的道理</p><p>而且宝钗在元春省亲时候的态度和所说的话,明显看得出宝钗是很崇拜元春的,可能因为她也想当娘娘</p><p>宝钗一出场,气场就与众不同,日常吃的冷香丸的配方看似普通,实则需要极强的机缘与运气.</p><p>宝钗自来到贾府与贾府众人都关系融洽(我个人觉得王熙凤不喜欢薛宝钗,按理说,王熙凤和薛宝钗因为王夫人的关系还算亲人呢,结果前八十回,王熙凤和薛宝钗正面交流的描写极少)</p><p>宝钗在书中几件比较重要的出场无一不是以控制场面的形象出现的,少数几个宝钗受挫的场面大概也只有清虚观生气,宝玉睡梦中说木石前盟,宝琴出现等几个</p><p>宝钗到底才情到什么地步,书中并没有直接描写,不过能跟黛玉并列金陵十二钗之首,才情可想而知</p><p>黛玉跟宝玉一起看过牡丹亭和西厢记, 然而宝钗早就看过, 惜春会画画, 然而宝钗给她列画画需要的物品列表时候如数家珍, 你猜宝钗会不会画画?后来还给湘云出主意办螃蟹宴,细想以上几件事情,件件执行者都不是宝钗,但宝钗却都在以上几件事情上扮演了极为重要的角色</p><p>总体而言,宝钗给人的感觉是比黛玉和宝玉高一个level的,宝钗完全明白宝玉在想什么但是宝玉却猜不透宝钗所想,两人在感情地位上实际上是有点不平等的</p><h2 id="林黛玉"><a href="#林黛玉" class="headerlink" title="林黛玉"></a>林黛玉</h2><p>相比宝钗,黛玉和宝玉才是真正的互为知己,宝玉懂黛玉,理解黛玉</p><blockquote><p>“林妹妹有说过那混账话吗,林妹妹压根就不说那混账话”</p></blockquote><p>一句话就表明了宝玉和黛玉互为知己到什么地步</p><p>两人互相理解互相关心</p><p>宝玉偷偷吊祭金钏儿,黛玉不问而知</p><p>宝玉有什么好东西第一个想到的一定是林妹妹,”西厢记妙词通戏语”基本上已经是明写两人的感情了</p><blockquote><p>“你我既为知己,又何必来一宝钗”</p></blockquote><p>可惜宝玉身在花丛中,为此,林黛玉还吃醋, 开始吃宝钗的醋, 湘云来了吃湘云的醋, 后来宝琴来了倒没见有什么大的反应</p><p>从书中看,黛玉是极为关心宝玉的, 宝玉被魇和挨打那两次,黛玉都十分的担心</p><p>“慧紫鹃情辞试莽玉”一回, 听说宝玉的状况, 说出了”你倒是找个绳子勒死我才是正经”</p><blockquote><p>“仙杖香桃芍药花”</p></blockquote><p>可惜因为寄人篱下,只能把自己的希望寄予贾母身上,仙杖是不是就是指贾母啊</p><h2 id="贾探春"><a href="#贾探春" class="headerlink" title="贾探春"></a>贾探春</h2><p>探春其实是个十分有才气的人, “才自精明志自高”, 从探春兴利除弊就能看出来,她对家族的一些问题早就看在眼里,并早就在思考解决方案. 据说也是曹雪芹寄托自己精神的一个人物, 可惜为家族和身份所累</p><p>书中的探春毫无疑问是三春之冠,在十二钗排名中排第四,据说书中的四春分别对应 琴棋书画, 探春也确实工于书法,屋里陈设也有很明显的体现</p><p>书中<code>抄检大观园</code>一段,探春的表现使的这个人物形象无比的丰满,一个片段就有如此的威力</p><h2 id="史湘云"><a href="#史湘云" class="headerlink" title="史湘云"></a>史湘云</h2><p>湘云是十分可爱的一个角色,说话咬字,天天爱哥哥的, 黛玉打趣她,她也不生气, 书中的史湘云十分可爱, 然而也免不了成为封建制度的牺牲品.</p><p>湘云才情不下于宝黛, 擅长对联, 在书中有一段把黛钗都给比下去了, 也就宝琴能跟她比比,还和黛玉对出了”寒塘渡鹤影,冷月葬花魂”这样的句子.</p><p>黛玉其实是懂世故而不世故, 但是湘云是真的没心机,所以叫”憨湘云”</p>]]></content>
<categories>
<category> 阅读记录 </category>
</categories>
<tags>
<tag> 红楼梦 </tag>
</tags>
</entry>
<entry>
<title>多git账号配置</title>
<link href="/2017-06-01-multi-git-user-config/"/>
<url>/2017-06-01-multi-git-user-config/</url>
<content type="html"><![CDATA[<h2 id="多Git账号场景"><a href="#多Git账号场景" class="headerlink" title="多Git账号场景"></a>多Git账号场景</h2><p>我们有时候可能会遇到这样的情况,公司部署了一个gitlab服务器,我们自己也有在github上面使用仓库。</p><p>但是这两个服务器上面的账号是不一样的,我们需要在公司的项目中使用公司的git账号,私人项目使用私人的git账号,这时候就需要在同一台电脑上面同时使用多个git账号</p><h2 id="生成两个ssh-key"><a href="#生成两个ssh-key" class="headerlink" title="生成两个ssh-key"></a>生成两个ssh-key</h2><p>现在大家普遍使用ssh-key来作为授权验证的工具.</p><p>大多数的git服务器也使用这样的方式</p><p>那我们就需要生成两个对应的ssh-key, 一个用于私人项目,一个用于公司项目</p><h2 id="获取服务器项目权限"><a href="#获取服务器项目权限" class="headerlink" title="获取服务器项目权限"></a>获取服务器项目权限</h2><p>首先我们要拥有对应服务器(github/gitlab/coding等)的权限</p><p>一般取得权限的方法是</p><ol><li><p>注册github/gitlab/coding账号</p></li><li><p>生成ssh-key,<code>ssh-keygen -t rsa -C "[email protected]"</code> 生成ssh-key</p></li><li><p>将生成的ssh-key中的xxx.pub公钥添加到github或者gitlab的ssh-key授权中</p></li></ol><p>此时我们的电脑实际上已经获得了往对应的平台中的账户下面的仓库中推送代码的权利, git推送代码是只认机器不认人,但是服务器还无法针对不同的服务使用不同的ssh-key设置</p><h2 id="在ssh中增加config文件"><a href="#在ssh中增加config文件" class="headerlink" title="在ssh中增加config文件"></a>在ssh中增加config文件</h2><p>可以通过配置.ssh文件夹下的config文件,通知ssh对不同的服务器使用不同的ssh-key</p><p>例如,config内容:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># github</span></span><br><span class="line">Host github.com <span class="comment"># 指定主机地址</span></span><br><span class="line"> HostName github.com <span class="comment"># 主机名, 选填</span></span><br><span class="line"> User [email protected] <span class="comment"># 用户名</span></span><br><span class="line"> PreferredAuthentications publickey <span class="comment"># 授权方式</span></span><br><span class="line"> IdentityFile ~/.ssh/id1_rsa <span class="comment"># 该服务器上使用的ssh-key</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># gitlab</span></span><br><span class="line">Host gitlab.com</span><br><span class="line"> HostName gitlab.com</span><br><span class="line"> User [email protected]</span><br><span class="line"> PreferredAuthentications publickey</span><br><span class="line"> IdentityFile ~/.ssh/id2_rsa</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>以上内容就是 对github.com 使用 id1_rsa这份公钥,对gitlab.com使用 id2_rsa公钥,配置好了以后ssh就可以的对不同的服务器使用不同的公钥了</p><blockquote><p>ps: 没有域名host可以直接设置为服务器ip</p></blockquote><p>但是此时我们可能推送代码的用户标识可能不正确</p><p>例如: 你的私人用户名叫A ,公司账号叫B, 此时你推送代码到公司账户但是却显示推送者是A</p><h2 id="在目标项目中使用git-config设置用户"><a href="#在目标项目中使用git-config设置用户" class="headerlink" title="在目标项目中使用git config设置用户"></a>在目标项目中使用git config设置用户</h2><p>git中的配置分为全局配置和项目配置,默认使用全局配置,如果要在特定项目中使用特定的用户名,需要在项目的git配置中进行指定</p><p>可以在项目目录中执行以下命令,指定需要使用的用户名和邮箱</p><p><code>git config user.email "[email protected]"</code>: 设置项目用户邮箱</p><p><code>git config user.name "aaa"</code>: 设置项目用户名</p><p>也可以手动修改<code>项目名/.git/config</code>文件中的user标签下的内容</p><h2 id="ssh-T-测试"><a href="#ssh-T-测试" class="headerlink" title="ssh-T 测试"></a>ssh-T 测试</h2><p>使用如下命令可以测试配置结果,需要测试@ 后面的服务器地址可以自己修改</p><p><code>ssh -T [email protected]</code></p>]]></content>
<categories>
<category> 编程工具 </category>
</categories>
<tags>
<tag> git </tag>
</tags>
</entry>
<entry>
<title>Google-Bigtable学习记录</title>
<link href="/2017-03-27-google-bigtable/"/>
<url>/2017-03-27-google-bigtable/</url>
<content type="html"><![CDATA[<h2 id="Google-Bigtable"><a href="#Google-Bigtable" class="headerlink" title="Google Bigtable"></a>Google Bigtable</h2><p>本文希望能解答如下问题</p><ol><li>Bigtable产生的原因是什么,解决了什么问题</li><li>Bigtable是怎么解决这些问题的</li><li>Bigtable当前采用的方案有什么优点和不足</li></ol><h2 id="GFS的限制"><a href="#GFS的限制" class="headerlink" title="GFS的限制"></a>GFS的限制</h2><p>GFS是谷歌的分布式文件系统,提供了基础的分布式存储和读写服务,解决了大规模的数据的存储和使用的问题。但是由于GFS接口过于底层,内部存储的都是纯粹的二进制文件数据。Google希望提供一个具有结构模型的数据库产品以方便上层业务易于使用分布式数据存储服务</p><h2 id="数据模型"><a href="#数据模型" class="headerlink" title="数据模型"></a>数据模型</h2><p>Bigtable 是一个稀疏的、分布式的、持久化存储的多维度排序 Map。Map 的索引是行关键字、列关键字以及时间戳;Map 中的每个 value 都是一个未经解析的 byte 数组。</p><p>map的key是以 <row, column, time> 为综合key的字符串</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(row:string, column:string,time:int64) -> string</span><br></pre></td></tr></table></figure><p>之所以采用这个一个简单的模型主要原因有以下几个</p><ul><li>足够简单灵活,可以满足大多数的数据存储需求</li><li>足够可靠</li></ul><p><img src="https://i.loli.net/2020/05/09/rvzm8XYQhSPCpfD.png" alt="bigtable_figure1.png"></p><p>这套k-v模型看起来简单, 却足以表达我们的其他表模型, 例如如下表格-table1</p><table><thead><tr><th>rowkey</th><th>info:name</th><th>info:age</th><th>meta:status</th></tr></thead><tbody><tr><td>1</td><td>小明</td><td>19</td><td>1</td></tr><tr><td>2</td><td>小红</td><td>17</td><td>0</td></tr><tr><td>3</td><td>小刚</td><td>13</td><td>1</td></tr></tbody></table><p>像上面的一个常见的二维表格在Bigtable中我们可以使用如下方式表示</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">table1:<span class="number">1</span>:info:name -> 小明</span><br><span class="line">table1:<span class="number">1</span>:info:age -> <span class="number">19</span></span><br><span class="line">table1:<span class="number">1</span>:meta:status -> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>上面3个条记录共同构成原表中的rowkey为1的一行记录</p><h3 id="行"><a href="#行" class="headerlink" title="行"></a>行</h3><p>表中的行关键字可以是任意小于64k的字符串,同一个行关键字的读或者写都是原子性的。上述的例子里面我们就可以把1/2/3作为行关键字。某种意义上,行关键字类似于传统数据库中的主键ID。</p><p>Bigtable中的数据拆分是按照行关键字来进行的,也就是说,如果我们有3个数据节点, 1 , 2, 3这三条记录可以被被分到不同的机器上面</p><h3 id="列族"><a href="#列族" class="headerlink" title="列族"></a>列族</h3><p>我们上述的表中的INFO和META就是列族, 图表1 中的anchor也是列族的体现。 列族必须提前创建。列族下还可以含有多个列,像NAME和AGE就同属于一个列族下面的不同列。</p><p>列族的存在是为了方便对数据进行压缩</p><h3 id="时间戳"><a href="#时间戳" class="headerlink" title="时间戳"></a>时间戳</h3><p>在 Bigtable 中,表的每一个数据项都可以包含同一份数据的不同版本;不同版本的数据通过时间戳来索 引。Bigtable 时间戳的类型是 64 位整型。Bigtable 可以给时间戳赋值,用来表示精确到毫秒的“实时”时间; 用户程序也可以给时间戳赋值。</p><p>如果应用程序需要避免数据版本冲突,那么它必须自己生成具有唯一性的时间戳。数据项中,不同版本的数据按照时间戳倒序排序,即最新的数据排在最前面。</p><h2 id="接口约定"><a href="#接口约定" class="headerlink" title="接口约定"></a>接口约定</h2><p>客户程序可以对 Bigtable 进行如下的操作:写入或者删除 Bigtable 中的值、从每个行中查找值、或者遍历表中的一个数据子集。</p><p>Bigtable 可以和 MapReduce一起使用,MapReduce 是 Google 开发的大规模并行计算框架。Google已 经开发了一些 Wrapper 类,通过使用这些 Wrapper 类,Bigtable 可以作为 MapReduce 框架的输入和输出。</p><h2 id="Bigtable架构"><a href="#Bigtable架构" class="headerlink" title="Bigtable架构"></a>Bigtable架构</h2><p>Bigtable是建立在其它的几个Google基础构件上的。BigTable使用Google的分布式文件系统(GFS)存储日志文件和数据文件。</p><p>刚才提到的列族就是Bigtable存储的文件单位, 同一个列族的信息会被整合成一个SSTable文件,会随着rowkey分布到不同的机器上。多个SSTable会由索引文件来定位数据,也可以被加载到内存,通过二分查找查找其中的有序数据。</p><p>BigTable 还依赖一个高可用的、序列化的分布式锁服务组件,叫做 Chubby。一个 Chubby 服务包括了 5 个活动的实例,其中的一个实例被选为 Master,并且处理请求。</p><p>Bigtable 包括了三个主要的组件:链接到客户程序中的库、一个 Master 服务器和多个 Tablet 服务器。针 对系统工作负载的变化情况,BigTable 可以动态的向集群中添加(或者删除)Tablet 服务器。</p><p>Google使用一个三层的、类似B+树的结构存储 Tablet 的位置信息</p><p><img src="https://i.loli.net/2020/05/09/JETjbsYLZaFNyDA.png" alt="bigtable_figure4.png"></p><p>第一层数据在chubby中,提供root tablet的位置信息。</p><p>Bigtable读写流程</p><p><img src="https://i.loli.net/2020/05/10/kaMqH1gF7BNvE4b.png" alt="FElNjQbZkntR3YJ.png"></p><p>Bigtable采用LSM树的形式进行读写操作,新数据先写入日志文件和内存,等内存达到一定数量或者一定时间在将内存中的数据固化到磁盘。 读取的时候先读区内存中的数据, 如果内存中没有要找的数据,返回磁盘进行逐级向上的查找。</p><p>写入日志的存在是为了防止机器挂掉以后内存数据的丢失</p><h3 id="Bigtable怎么解决一致性,可用性和分区容忍性"><a href="#Bigtable怎么解决一致性,可用性和分区容忍性" class="headerlink" title="Bigtable怎么解决一致性,可用性和分区容忍性"></a>Bigtable怎么解决一致性,可用性和分区容忍性</h3><p>Bigtable构架于GFS之上, Bigtable本身并没有提供备份或者主从副本的方案。所以Bigtable依赖于GFS提供一致性和可用性保证。</p><h2 id="开源的Bigtable实现"><a href="#开源的Bigtable实现" class="headerlink" title="开源的Bigtable实现"></a>开源的Bigtable实现</h2><p>Hadoop系列中的<a href="https://hbase.apache.org/">Hbase</a>一般被认为是bigtable的开源实现,两者采用了相似的设计思路,适用场景也大部分重合。还有一个<a href="https://cassandra.apache.org/">Cassandra</a>也拥有类似的功能不过在部分设计思路上有很大不同,有兴趣的读者可以自行研究</p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> Bigtable </tag>
</tags>
</entry>
<entry>
<title>两种网站限流方案</title>
<link href="/2017-03-03-rate-limit-method/"/>
<url>/2017-03-03-rate-limit-method/</url>
<content type="html"><![CDATA[<h1 id="网站限流"><a href="#网站限流" class="headerlink" title="网站限流"></a>网站限流</h1><p>随着网站用户规模的增加,业务的扩张, 我们网站所承受的流量规模和并发数也会不断增加</p><p>到了一定时候我们就会希望可以对网站的流量进行一定程度的控制,因为我们的业务处理能力是有限的,我们需要优先保证关键业务的正常运转</p><p>技术人员一直以来都在致力于可以彻底的解决高并发问题,但是到目前为止也没有一种可以彻底解决的方案</p><p>我们只能尽量的提升业务处理的性能,做业务拆分,分布式,进行错峰处理等手段</p><p>其实我们可以从一整个用户请求的过程中的每个阶段进行分析, 在不同的阶段采用不同的方案</p><p>在用户请求刚刚进入的时候进行限流处理就是一种十分有效的手段</p><h2 id="限流"><a href="#限流" class="headerlink" title="限流"></a>限流</h2><p>限流就是从用户访问出限制用户的请求,常用于秒杀等并发量极高的场景之下</p><p>限流的核心思想就是人为的丢弃一部分用户请求, 不作处理, 这样相当于从最根源处就避免的用户后续的操作,虽然对用户体验来说影响非常大, 但是只要采用合适的丢弃策略,就能在有效保护系统的同时,尽量减少对用户体验的影响</p><h2 id="漏桶算法"><a href="#漏桶算法" class="headerlink" title="漏桶算法"></a>漏桶算法</h2><p>漏桶算法就是一种有效的限流算法,顾名思义,就是像漏桶一样以固定的速率将用户请求控制在一个确定的范围之内</p><p>漏桶有两个关键属性,一个是漏桶的大小(最大存储的请求容量),另一个是漏桶的开口(处理请求的速率)</p><p>用户的请求过来之后可以认为会被放到一个漏桶内,然后桶本身以一定的速率处理请求,当用户的请求速率过快,桶内的请求数量过多就会造成请求溢出,这部分请求就会被视为无效的请求</p><p>特点: 根据时间以固定速率允许请求通过</p><p>缺点: 针对部分由突发场景的效率有点低</p><p>流程图: </p><p><img src="https://i.loli.net/2020/05/09/EKMs4BrGwvPmN5c.png" alt="flow1.png"></p><h2 id="令牌桶算法"><a href="#令牌桶算法" class="headerlink" title="令牌桶算法"></a>令牌桶算法</h2><p>另一种常用的限流算法叫令牌桶算法,令牌桶的思想跟漏桶有点不大一样,令牌桶是先假设了一个桶, 桶内装有令牌(token),系统以一定的时间往桶里添加令牌.请求过来以后需要使用桶里面的令牌才能执行,也就是说我们可以通过控制桶内令牌的数量来控制最大请求数,也能通过改变添加令牌的速率来调整请求的处理速率</p><p>令牌桶也有关键参数,一个是桶的大小,一个是令牌的发放速率</p><p>特点: 使用请求+令牌来进行请求处理,没有令牌的请求不予处理</p><p>缺点: 实现起来比漏桶算法复杂一点</p><p>流程图:</p><p><img src="https://i.loli.net/2020/05/09/EerPIDbnmcM92XB.png" alt="flow2.png"></p><blockquote><p>ps: 往令牌桶内添加令牌并不需要一个单独的程序来执行,只要在请求过来时候根据时间自动计算可用的令牌就行了</p></blockquote><p>虽然两种算法都能控制请求的处理速率, 但是这两者其实都受到之后请求处理速率的影响, 也就是说就算我们限流部分允许每秒2万的请求,但是后台业务的处理速度只有每秒1千,依然会造成严重的业务阻塞</p><h2 id="RateLimiter"><a href="#RateLimiter" class="headerlink" title="RateLimiter"></a>RateLimiter</h2><p>Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流</p>]]></content>
<tags>
<tag> 漏桶算法 </tag>
<tag> 令牌桶算法 </tag>
</tags>
</entry>
<entry>
<title>消息通知系统的设计</title>
<link href="/2017-03-01-message-system/"/>
<url>/2017-03-01-message-system/</url>
<content type="html"><![CDATA[<h1 id="站内信通知系统的设计"><a href="#站内信通知系统的设计" class="headerlink" title="站内信通知系统的设计"></a>站内信通知系统的设计</h1><p>站内信系统是一个成熟的后端系统所应该具有的基本系统组件</p><h2 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h2><p>站内信通知系统的核心目标是为系统提供一个 用户与用户,系统与用户交互的手段,属于网站信息传播的一个重要途径,如果详细考虑,该系统实在是一个非常庞大的系统设计,可以做的事情非常的多</p><p>这里只是简单梳理一下普通消息系统需要做到的部分和功能设计,并提供一个可用的实际消息系统框架</p><p>一个完整的消息通知系统大概可以分为两部分, 消息系统和通知系统</p><p>消息系统主要负责消息的产生,接收等,通知系统则要实现事件机制,通知机制,对接各种消息通知平台(短信,微信,邮件等)</p><p>消息通知系统的核心处理大概可以分为以下3个部分:</p><ol><li><p>消息产生</p><pre><code> 消息的产生:消息如何产生,来源和消息对象的结构</code></pre></li><li><p>推送消息</p><pre><code> 消息的分发:消息如何到达用户,用户如何获取消息</code></pre></li><li><p>处理消息</p><pre><code> 消息的处理:用户可以对消息所做的操作</code></pre></li></ol><p>同时还要在整个过程中随时持有消息的状态,这样才能最大化消息通知系统的功能</p><p>一个成熟消息处理的流程大概如下图:</p><p><img src="https://i.loli.net/2020/05/09/HhJdn5CA14lwLjc.png" alt="flow1.png"></p><h2 id="消息产生"><a href="#消息产生" class="headerlink" title="消息产生"></a>消息产生</h2><p>消息系统的消息按类型大致可以分为私信类和通知类,其中私信类就是上文提到的消息部分,又可以分为管理员发送的和用户个人发送的</p><p>私信类的消息大概情况如下:</p><ol><li>A给B发送了S内容,B给A回复了私信S2</li><li>管理员(admin)给A/B发送了S内容(这一种也可以看做是公告)</li></ol><p>通知类,消息是由用户某些动作产生的提醒类的信息,具体情况大概如下(拿知乎举例子):</p><ol><li>A回答了问题W</li><li>A在专栏Z中发布了文章P</li><li>B评论了A在问题W下的回答H</li><li>B赞了A在问题W下的回答H</li><li>B赞了A在问题W中的回答H下的评论C</li></ol><blockquote><p>ps:用户: A,B,C 专栏: Z 回答: H 问题: W 文章: P 消息: S 评论: C</p></blockquote><p>私信形式的实现起来比较容易,这里不多做表述,现在主要针对消息通知类型的进行方案分析</p><h3 id="基于订阅模式的消息产生"><a href="#基于订阅模式的消息产生" class="headerlink" title="基于订阅模式的消息产生"></a>基于订阅模式的消息产生</h3><p>消息类的通知总结一下就是A对B的某操作进行了某操作,具体模式是: </p><pre><code> 用户X 收到了 用户B 对 对象O 的 事件E 操作的通知</code></pre><p>这种模式很符合订阅模型,以下几种消息都可以用订阅关系表示</p><ol><li>B订阅了问题W的回答事件ER</li><li>B订阅了专栏Z的发表事件EP</li><li>B订阅了回答H的评论</li><li>B订阅了回答H的点赞事件</li><li>B订阅了评论C的点赞事件</li></ol><p>只要根据触发改消息的那条记录生成对应的消息即可</p><h3 id="表结构设计"><a href="#表结构设计" class="headerlink" title="表结构设计"></a>表结构设计</h3><p>具体设计如下:</p><blockquote><p>ps:(这里模拟了几条记录,方便用来做演示)</p></blockquote><h4 id="订阅关系表"><a href="#订阅关系表" class="headerlink" title="订阅关系表"></a>订阅关系表</h4><p>用于记录用户的订阅信息</p><table><thead><tr><th>id</th><th>用户</th><th>订阅对象id</th><th>订阅对象类型</th><th>订阅事件</th><th>时间</th></tr></thead><tbody><tr><td>1</td><td>B</td><td>30</td><td>post</td><td>answer</td><td>2017-01-01</td></tr><tr><td>2</td><td>B</td><td>1</td><td>zhuanlan</td><td>publish</td><td>2016-01-01</td></tr><tr><td>3</td><td>B</td><td>112</td><td>answer</td><td>common</td><td>2016-01-01</td></tr><tr><td>4</td><td>B</td><td>113</td><td>answer</td><td>up</td><td>2018-01-01</td></tr><tr><td>5</td><td>B</td><td>12</td><td>comment</td><td>up</td><td>2018-01-01</td></tr></tbody></table><p>当某对象产生某动作的时候,根据订阅关系表的订阅关系生成消息</p><h4 id="订阅配置"><a href="#订阅配置" class="headerlink" title="订阅配置"></a>订阅配置</h4><p>用于为用户生成默认的订阅配置</p><table><thead><tr><th>id</th><th>动作</th><th>订阅事件</th></tr></thead><tbody><tr><td>1</td><td>关注问题</td><td>问题更新/问题回答</td></tr><tr><td>2</td><td>回答</td><td>回答被评论/被点赞</td></tr></tbody></table><p>这个表格记录了用户的某些操作会订阅怎样的对象动作,用于生成用户默认的订阅事件,后期如果开放权限,用户就可以对自己收到的提醒类型进行定制</p><h4 id="消息内容表"><a href="#消息内容表" class="headerlink" title="消息内容表"></a>消息内容表</h4><p>消息内容表用来存储消息的具体内容,用户将来收到的信息就是该表中的信息</p><table><thead><tr><th align="center">id</th><th align="center">type</th><th align="center">content</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">notice</td><td align="center">用户C回答了问题W(id=30)</td></tr><tr><td align="center">2</td><td align="center">announce</td><td align="center">知乎形象刘看山发布了</td></tr><tr><td align="center">3</td><td align="center">notice</td><td align="center">B赞了你在问题W下面的R</td></tr></tbody></table><h4 id="消息记录表"><a href="#消息记录表" class="headerlink" title="消息记录表"></a>消息记录表</h4><p>消息记录表用来存储消息和用户的分发关系</p><table><thead><tr><th>id</th><th>remindid(消息ID)</th><th>senderid(发送方)</th><th>reciverid(接收方)</th><th>isread</th><th>type</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td><td>message</td></tr></tbody></table><h3 id="私信类消息"><a href="#私信类消息" class="headerlink" title="私信类消息"></a>私信类消息</h3><p>对于第一类私信类的消息,原则上即便用户不上线也需要对用户进行推送,也即是直接写入消息表</p><p>现在从消息的产生开始分析消息的数据流程</p><p>用户A. -> 发消息(“你吃饭了吗”) -> 消息内容表 -> 消息记录表<br>用户B上线 -> 查询消息记录表中的未读 -> 阅读消息 -> 回复内容</p><p>私信类型的比较简单,如果是管理员的话,把id设置为特殊值或者将消息类型标记为announce,在前台就可以进行相应的展示限制</p><h3 id="提醒类消息"><a href="#提醒类消息" class="headerlink" title="提醒类消息"></a>提醒类消息</h3><p>重点是第二类提醒类的消息,提醒类消息也不允许用户漏接</p><p>提醒类消息的产生流程就比较麻烦</p><ol><li><p>某用户在某专栏发布某文章->生成消息存入消息表->检查订阅该专栏文章发布的用户和关注了该用户动态的其他用户->把消息表中的记录分发给这些用户->这些用户上线收到消息</p></li><li><p>你回答了一个问题->增加订阅该回答的点赞和评论动作->有人评论你的回答->生成消息内容表的内容->检查订阅该回答评论的用户->分发消息</p></li></ol><p>例如以上记录会产生消息:</p><ol><li>用户C在专栏Z(id=1)中发布了文章P</li><li>用户C评论了某用户在问题W下的回答H</li><li>B赞了你在问题W下的回答(该消息推送给回答者)</li></ol><p>消息表用于存储消息信息,当某事件被触发时,会生成对应的消息提醒内容,然后查询订阅该事件的所有用户,将消息和用户关系写入消息记录表</p><h3 id="通知合并"><a href="#通知合并" class="headerlink" title="通知合并"></a>通知合并</h3><p>有时候,当某用户收到大量用户对某对象进行相似的操作的时候为了性能和用户体验,我们需要对用户的同志进行合并</p><p>比如: 用户A 发布了一篇文章, 有5万人在1小时内都点赞了该文章, 我们就可以生成一条”张三,李四等5万个用户点赞了你的文章XXX”.</p><p>消息合并的规则:</p><ol><li>按时间合并消息</li><li>按发送方合并消息</li><li>按种类合并消息</li></ol><p>合并的周期:</p><ol><li>固定时间的周期性的消息进行汇总</li><li>无固定时间,产生未读消息即汇总</li></ol><p>合并的具体方法:</p><ol><li>C点赞了你的回答之后,这条消息会被标记为可聚合,聚合keyword为操作ID/对象类型/对象ID</li></ol><p>例如: 在某段时间之类有两个用户赞了你的评论,这个时候可以使用 C,V等两个用户赞了你的评论C,当产生第一条通知的时候,消息表中有一条消息: C赞了你的回答,这个时候V赞了你的回答之后,两条记录可以合并成一条”c,v等两个人赞了你的回答”, 这个例子中的两条记录的操作类型(都是点赞)和操作对象(都是你的回答)相同</p><h2 id="消息分发"><a href="#消息分发" class="headerlink" title="消息分发"></a>消息分发</h2><p>通知消息产生以后我们只是有个要推送给用户的消息体,怎么把消息推送给用户也是一个很重要的部分</p><h4 id="通知的分发"><a href="#通知的分发" class="headerlink" title="通知的分发"></a>通知的分发</h4><p>消息分发一半常用的有两种方式,一种是消息推送(push)一种是消息拉取(pull),不过现在大多采用两者结合的方式,针对不同的场景使用不同的方式</p><ol><li>push方式: 推送你有XX条消息未读(针对在线的用户)</li><li>pull方式: 用户点击未读消息时对内容进行拉取</li></ol><p>消息分发的话可以采用redis来作为中间桥梁,将未读消息的数量存入redis, uid:unread:10 用户有10条未读消息</p><p>当用户点击未读消息的标志的时候,从消息记录+消息内容表获取该用户的具体未读信息内容</p><p>这里可以做一些优化:</p><ol><li>未读消息太多的话会每次取前20条</li><li>某些公用的消息比如公告和某文章发布的消息可以存入redis,使用类似messageid:content:xxxx.这样的内容存储消息内容,当所有订阅该类消息的用户获取消息的时候会先从redis中获取数据,取不到的才从数据库中查询</li></ol><p>分发频率</p><ol><li>实时分发</li><li>按小时分发</li><li>按周和天分发</li></ol><p>分发管道 Web,微信,邮件,短信</p><h4 id="用户对消息的处理"><a href="#用户对消息的处理" class="headerlink" title="用户对消息的处理"></a>用户对消息的处理</h4><h5 id="通知已读"><a href="#通知已读" class="headerlink" title="通知已读"></a>通知已读</h5><p>每条消息都应该带有一个是否已读的状态,以防止对用户造成重复的打扰</p><p>一旦用户点击获取消息,打开消息详情或点击消息体的任意连接,就算作已读该消息,已读消息不做重复提醒</p><p>已读消息的排序: 用户有30条未读消息,点击列表已读20条,剩余10条未读消息怎么处理<br>解决方案: 获取消息未读消息列表时按时间顺序取前20条</p><h5 id="通知内容的处理"><a href="#通知内容的处理" class="headerlink" title="通知内容的处理"></a>通知内容的处理</h5><pre><code> 点击链接: 点击链接之后进入到与该消息有关的详情页面 回复: 用户可以对私信进行回复 删除: 用户可以删除消息</code></pre><blockquote><p>ps: 不同终端消息状态应保持统一</p></blockquote><h4 id="redis中消息的存储"><a href="#redis中消息的存储" class="headerlink" title="redis中消息的存储"></a>redis中消息的存储</h4><p>redis中不存储普通的用户消息,但是会存储系统公告和文章更新之类容易被复用的消息</p><p>消息:</p><pre><code>msgid:xx:content:你订阅的XXX专栏更新了uid:xx:unread:10</code></pre><h3 id="消息回收"><a href="#消息回收" class="headerlink" title="消息回收"></a>消息回收</h3><p>消息处理还有其他一些需要系统处理的地方</p><ol><li>用户对话消息的显示范围(可以根据时间),是否允许用户删除</li><li>用户拉黑名单是否自动删除会话消息</li><li>用户长时间未读取的系统消息自动回收的时间</li><li>长时间的未读消息的处理, 永久保留,二次推送(通过其他渠道)</li></ol><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>一个消息系统还涉及到其他的一些关键的地方</p><h3 id="消息的离线计算和处理"><a href="#消息的离线计算和处理" class="headerlink" title="消息的离线计算和处理"></a>消息的离线计算和处理</h3><p>消息系统数据量有可能非常大,如果一个用户有50万粉丝,该用户发一篇文章,理论上就要为50万用户产生消息.想要实时计算消息基本是不可行的, 所以应该有一套成熟的数据处理系统来支持消息系统</p><h3 id="新消息到达时候的交互"><a href="#新消息到达时候的交互" class="headerlink" title="新消息到达时候的交互"></a>新消息到达时候的交互</h3><p>用户获取消息以后的UI交互也是消息系统的一部分功能,也是需要考虑的一部分</p><ol><li>声音提示</li><li>标题闪烁</li><li>未读信息浮动层</li><li>弹窗</li></ol><h3 id="防骚扰"><a href="#防骚扰" class="headerlink" title="防骚扰"></a>防骚扰</h3><ol><li>增加屏蔽功能</li><li>设定接受消息的权限(例如:仅我关注的人可以给我发消息)</li><li>黑名单</li></ol><h3 id="用户拉回"><a href="#用户拉回" class="headerlink" title="用户拉回"></a>用户拉回</h3><ol><li>长时间未处理消息的用户进行二次推送(通过短信和邮件等)</li></ol>]]></content>
<categories>
<category> 系统设计 </category>
</categories>
<tags>
<tag> 系统设计 </tag>
<tag> 消息通知系统 </tag>
</tags>
</entry>
<entry>
<title>redis的持久化</title>
<link href="/2017-01-29-redis-persistence/"/>
<url>/2017-01-29-redis-persistence/</url>
<content type="html"><![CDATA[<h1 id="redis的AOF和RDB持久化"><a href="#redis的AOF和RDB持久化" class="headerlink" title="redis的AOF和RDB持久化"></a>redis的AOF和RDB持久化</h1><h2 id="缓存数据的持久化"><a href="#缓存数据的持久化" class="headerlink" title="缓存数据的持久化"></a>缓存数据的持久化</h2><p>由于redis是一个纯内存型的k-v数据库, 所以存在机器宕机以后数据丢失的风险,为了应对这种情况,redis设计了数据的持久化机制,主要目的就是为了尽量减少数据丢失的情况。</p><p>Redis支持两种持久化方式, 一种是快照形式, 一种是重放命令日志的形式。在实际的生产中我们往往会两种机制配合使用,所以我认为有必要了解一下redis的持久化机制的细节</p><h2 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a>RDB持久化</h2><p>描述: RDB持久化是对当前数据库的状态进行备份,备份的对象是当前数据库的数据状态</p><p>例如:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"><span class="attr">"age"</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">"name"</span><span class="punctuation">:</span><span class="string">"zhangsan"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>这样的持久化机制会将当前的数据库数据转换为二进制信息,并保存到磁盘文件,进行数据恢复的时候是直接读入RDB文件解析数据。拥有持久化的能力使得redis在服务重启时能保留大部分的内存数据,同时RDB文件在redis主从同步时候会被发送给从节点,从节点使用RDB进行全量的数据恢复</p><h3 id="主动触发保存"><a href="#主动触发保存" class="headerlink" title="主动触发保存"></a>主动触发保存</h3><ol><li><p>使用 <code>save</code> 命令可以手动对redis进行保存, 该命令会阻塞redis主进程</p></li><li><p>使用<code>bgsave</code>会在后台创建子线程来进行存储, 此时要求redis节点有1倍以上的空闲内存</p></li></ol><p>比如: 机器内存有32G, 此时Redis里面数据20G, 使用 <code>bgsave</code> 就不可以,必须保证空闲内存大于20G才能使用</p><h2 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a>AOF持久化</h2><p>上面的rdb持久化的方式虽然可以保存数据信息但是rdb过程需要一定的时间,如果机器在某两次rdb之间宕机,由于部分数据未来得及写入磁盘,依旧会丢失上次rdb之后的内存数据信息。所以redis设计了aof机制来应对这种情况</p><p>Aof描述: 对写命令进行备份,一般是在有写命令的时候把命令追加到AOF文件中,客户端每进行一次操作,服务器吧命令写入AOF文件,数据恢复就是从AOF文件中读取命令并执行,整体流程有点类似于mysql的redo日志</p><p>但是会出现这么一种情况(ABA):</p><p>对某个命令的写命令太多的话,有可能会出现这种情况</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">set</span> a 10</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">set</span> a 18</span> </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">set</span> a 10</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">set</span> a 20</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">set</span> a 10</span></span><br></pre></td></tr></table></figure><p>这种情况下,经过6次操作a的值实际最终还是10,不过因为aof对所有操作的命令都会记录,导致大量的命令对数据最终的状态来说其实都是”无效”的,还会引起存储和执行效率问题,为了减小AOF文件的大小,这个时候需要对AOF进行重写</p><h3 id="AOF重写"><a href="#AOF重写" class="headerlink" title="AOF重写"></a>AOF重写</h3><p>为了应对命令有冗余,提高数据备份效率,会对数据库进行AOF重写,重写是通过对当前的数据库数据进行读取并进行反向的命令解析来进行的</p><p>某种意义上来讲AOF重写和RDB有部分类似,都是针对当前数据库状态进行的备份</p><p>只不过aof会把所有数据反向解析成操作命令保存起来</p><p>AOF重写可以使用命令 <code>BGREWRITEAOF</code></p><h3 id="AOF保存的格式"><a href="#AOF保存的格式" class="headerlink" title="AOF保存的格式"></a>AOF保存的格式</h3><p>redis的命令会被以RESP(redis的客户端通信协议)的格式保存到 *.aof的文件中</p>]]></content>
<categories>
<category> 分布式存储 </category>
</categories>
<tags>
<tag> RDB </tag>
<tag> AOF </tag>
</tags>
</entry>
<entry>
<title>软件分享(Mac OS)</title>
<link href="/2017-01-24-my-software/"/>
<url>/2017-01-24-my-software/</url>
<content type="html"><![CDATA[<h1 id="常用软件分享"><a href="#常用软件分享" class="headerlink" title="常用软件分享"></a>常用软件分享</h1><p>仅仅列举了常用的完整APP程序,终端服务应用不在此列</p><span id="more"></span><h2 id="日常"><a href="#日常" class="headerlink" title="日常"></a>日常</h2><table><thead><tr><th align="center">软件名</th><th align="center">功能描述</th><th align="left">备注</th></tr></thead><tbody><tr><td align="center">QQ</td><td align="center">聊天,交流</td><td align="left"></td></tr><tr><td align="center">微信</td><td align="center">聊天</td><td align="left"></td></tr><tr><td align="center">Chrome</td><td align="center">浏览器</td><td align="left"></td></tr><tr><td align="center">Firefox</td><td align="center">浏览器</td><td align="left">启用新的引擎之后也还不错</td></tr><tr><td align="center">有道云笔记</td><td align="center">云笔记</td><td align="left">配合本地md使用</td></tr><tr><td align="center">网易云音乐</td><td align="center">音乐客户端</td><td align="left"></td></tr><tr><td align="center">虾米音乐</td><td align="center">音乐</td><td align="left"></td></tr><tr><td align="center">QQ音乐</td><td align="center">音乐</td><td align="left">版权比较多</td></tr><tr><td align="center">爱奇艺</td><td align="center">视频</td><td align="left"></td></tr><tr><td align="center">优酷</td><td align="center">视频</td><td align="left"></td></tr><tr><td align="center">腾讯视频</td><td align="center">视频客户端</td><td align="left"></td></tr><tr><td align="center">Maipo</td><td align="center">微博客户端</td><td align="left"></td></tr><tr><td align="center">欧陆词典</td><td align="center">词典</td><td align="left"></td></tr><tr><td align="center">有道词典</td><td align="center">另一个英文词典工具</td><td align="left"></td></tr><tr><td align="center">Grammarly</td><td align="center">英文语法检测工具</td><td align="left"></td></tr><tr><td align="center">Spark</td><td align="center">邮件客户端</td><td align="left">在我的电脑上退出时候老有bug所以暂时不用了</td></tr><tr><td align="center">IINA</td><td align="center">基于MPV的视频播放器,但是更加好用</td><td align="left"></td></tr><tr><td align="center">vox</td><td align="center">本地音乐播放</td><td align="left"></td></tr><tr><td align="center">lastPass</td><td align="center">密码管理</td><td align="left"></td></tr><tr><td align="center">企业微信</td><td align="center">通信工具</td><td align="left">工作沟通利器</td></tr></tbody></table><blockquote><p>ps: firefox现在感觉有些不如chrome了</p></blockquote><h2 id="苹果家"><a href="#苹果家" class="headerlink" title="苹果家"></a>苹果家</h2><table><thead><tr><th align="center">mail</th><th align="center">邮件客户端</th><th align="left"></th></tr></thead><tbody><tr><td align="center">ibooks</td><td align="center">pdf阅读器</td><td align="left"></td></tr><tr><td align="center">QuickTime</td><td align="center">视频播放,视频音频录制</td><td align="left"></td></tr><tr><td align="center">GarageBand</td><td align="center">谱曲,作曲</td><td align="left"></td></tr><tr><td align="center">iMovie</td><td align="center">视频制作</td><td align="left"></td></tr><tr><td align="center">Grapher</td><td align="center">函数绘图</td><td align="left"></td></tr><tr><td align="center">page/number/keynote</td><td align="center">apple办公三件套</td><td align="left"></td></tr><tr><td align="center">automator</td><td align="center">自动化工具,可以做工作流</td><td align="left"></td></tr><tr><td align="center">iturns</td><td align="center">不得不用的管理工具,但是并不好用</td><td align="left">现在不怎么用了</td></tr></tbody></table><blockquote><p>ps:mail有时会出现高CPU占用的情况,所以不经常用了</p></blockquote><h2 id="工具类"><a href="#工具类" class="headerlink" title="工具类"></a>工具类</h2><table><thead><tr><th align="center">SmartCoverter</th><th align="center">视频,音频格式转换</th><th align="left"></th></tr></thead><tbody><tr><td align="center">迅雷</td><td align="center">下载工具</td><td align="left"></td></tr><tr><td align="center">motrix</td><td align="center">下载工具</td><td align="left">替代迅雷比较好</td></tr><tr><td align="center">百度网盘</td><td align="center">文件中转</td><td align="left"></td></tr><tr><td align="center">MPV</td><td align="center">全能视频播放器</td><td align="left"></td></tr><tr><td align="center">lantern</td><td align="center">科学上网</td><td align="left"></td></tr><tr><td align="center">shadowsocksx-ng</td><td align="center">科学上网</td><td align="left"></td></tr><tr><td align="center">Github Desktop</td><td align="center">github官方GUI</td><td align="left">感觉官方的很好用,之前用过sourcetree</td></tr><tr><td align="center">Cornerstone</td><td align="center">SVN管理工具</td><td align="left"></td></tr><tr><td align="center">SwitchHosts!</td><td align="center">Host管理</td><td align="left"></td></tr><tr><td align="center">DaisyDisk</td><td align="center">磁盘清理和管理</td><td align="left"></td></tr><tr><td align="center">CleanMyMac</td><td align="center">软件管理,垃圾清理</td><td align="left"></td></tr><tr><td align="center">Sip</td><td align="center">屏幕取色</td><td align="left"></td></tr><tr><td align="center">Cerebro</td><td align="center">效率工具</td><td align="left"></td></tr><tr><td align="center">Alfred</td><td align="center">效率工具</td><td align="left"></td></tr><tr><td align="center">MindNode</td><td align="center">思维导图工具</td><td align="left"></td></tr><tr><td align="center">xmind-zen</td><td align="center">轻量级的思维导图</td><td align="left"></td></tr><tr><td align="center">iStat Menus 6</td><td align="center">系统监控工具</td><td align="left"></td></tr><tr><td align="center">马克飞象</td><td align="center">MarkDown工具</td><td align="left"></td></tr><tr><td align="center">Typora</td><td align="center">Markdown工具</td><td align="left"></td></tr><tr><td align="center">iMazing</td><td align="center">比iTunes好用的iOS设备管理工具</td><td align="left"></td></tr><tr><td align="center">iTerm2</td><td align="center">强大的终端工具</td><td align="left"></td></tr><tr><td align="center">Spectacle</td><td align="center">窗口管理工具</td><td align="left"></td></tr><tr><td align="center">Transmit</td><td align="center">FTP服务器管理</td><td align="left"></td></tr><tr><td align="center">zeplin</td><td align="center">看产品设计图</td><td align="left"></td></tr><tr><td align="center">sketch</td><td align="center">设计产品</td><td align="left"></td></tr><tr><td align="center">numi</td><td align="center">超级好用的编程计算器,实际上就是个编程工具</td><td align="left"></td></tr><tr><td align="center">rightfont</td><td align="center">字体管理</td><td align="left"></td></tr><tr><td align="center">Polarr Photo</td><td align="center">图片处理</td><td align="left"></td></tr><tr><td align="center">Photolemur</td><td align="center">智能的图片处理</td><td align="left">自动处理的效果很不错</td></tr><tr><td align="center">Paste</td><td align="center">多剪贴板管理</td><td align="left">非常好用</td></tr><tr><td align="center">keka</td><td align="center">压缩/解压缩工具</td><td align="left"></td></tr></tbody></table><h2 id="开发专用"><a href="#开发专用" class="headerlink" title="开发专用"></a>开发专用</h2><table><thead><tr><th align="center">sublime text3</th><th align="center">轻便的编辑器</th><th align="left"></th></tr></thead><tbody><tr><td align="center">Atom</td><td align="center">备胎编辑器,性能有点问题,其他不错</td><td align="left"></td></tr><tr><td align="center">Visual Studio Code</td><td align="center">微软出品的编辑器,感觉比atom要好点</td><td align="left">性能很好,但是权限好像有问题</td></tr><tr><td align="center">Xcode</td><td align="center">ios,mac os开发IDE</td><td align="left"></td></tr><tr><td align="center">unity</td><td align="center">游戏开发引擎</td><td align="left"></td></tr><tr><td align="center">robo-3t</td><td align="center">mongodb数据库管理</td><td align="left"></td></tr><tr><td align="center">MongoDB compass</td><td align="center">mongodb官方的GUI管理工具</td><td align="left"></td></tr><tr><td align="center">Sequel Pro</td><td align="center">MySQL数据库管理</td><td align="left"></td></tr><tr><td align="center">Charles</td><td align="center">抓包工具</td><td align="left"></td></tr><tr><td align="center">Surge</td><td align="center">调试工具</td><td align="left"></td></tr><tr><td align="center">Dash</td><td align="center">开发文档集合</td><td align="left"></td></tr><tr><td align="center">Medis</td><td align="center">GUI的redis查看工具</td><td align="left">简直第一好用的redis的GUI客户端</td></tr><tr><td align="center">Postman</td><td align="center">API测试</td><td align="left"></td></tr><tr><td align="center">Kafka Tool</td><td align="center">kafka管理客户端</td><td align="left">能查看的信息比较详细,应该是通过读zk里面的节点信息</td></tr></tbody></table><h2 id="游戏"><a href="#游戏" class="headerlink" title="游戏"></a>游戏</h2><table><thead><tr><th align="center">Civilization VI</th><th align="center">(文明6)一款策略游戏</th><th align="left"></th></tr></thead><tbody><tr><td align="center">Beholder</td><td align="center">经营策略游戏,剧情黑暗</td><td align="left"></td></tr><tr><td align="center">MiniMetro</td><td align="center">益智小游戏</td><td align="left"></td></tr><tr><td align="center">Battle.net</td><td align="center">暴雪全家桶</td><td align="left"></td></tr><tr><td align="center">机械迷城</td><td align="center">益智解谜游戏</td><td align="left">额,老游戏了</td></tr></tbody></table>]]></content>
<categories>
<category> 编程工具 </category>
</categories>
<tags>