-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.xml
1398 lines (1389 loc) · 132 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom"><id>https://github.com/zzy131250/gitblog</id><title>RSS feed of zzy131250's gitblog</title><updated>2023-09-29T00:00:45.348234+00:00</updated><author><name>zzy131250</name><email>[email protected]</email></author><link href="https://github.com/zzy131250/gitblog"/><link href="https://raw.githubusercontent.com/zzy131250/gitblog/master/feed.xml" rel="self"/><generator uri="https://lkiesow.github.io/python-feedgen" version="0.9.0">python-feedgen</generator><entry><id>https://github.com/zzy131250/gitblog/issues/63</id><title>知识工作行业了解</title><updated>2023-09-29T00:00:45.665845+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>未来的一个备选方向</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/63" rel="alternate"/><category term="WIP"/><category term="2023"/><published>2023-09-26T02:33:57+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/62</id><title>资产与负债</title><updated>2023-09-29T00:00:45.757939+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>增加资产,减少负债。</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/62" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="随想"/><published>2023-09-25T15:30:19+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/61</id><title>[摘录]可逆决定与不可逆决定</title><updated>2023-09-29T00:00:45.844193+00:00</updated><content type="html"><![CDATA[<p>做决定的时候,你可以做两种决定。</p>
<p>一种是不可逆决定,它就像一扇单向门,你穿过了就不能回来了。</p>
<p><img src="https://github.com/zzy131250/gitblog/assets/7437470/cb8d59d8-7c0d-400c-a4a5-d6ed7f61cdee" alt="mmexport1695564144143.jpg" /></p>
<p>另一种是可逆决定,就像双向门,穿过了还可以再回来。</p>
<p>怎么区分它们呢?你可以看撤销成本。</p>
<p>撤消成本越高,决定就越不可逆;撤销成本越低,决定就越可逆。</p>
<p>这里的窍门是,可逆决定要快,不可逆决定要晚。</p>
<p>当决定是可逆的,就要快速做决定。最大的风险是拖拖拉拉、犹豫不决。当决定是不可逆的,就要放慢速度,设法获取更多的决策信息。最大的风险是做出错误的决定。</p>
<p>亚马逊的老板贝佐斯认为,不可逆决定如果有70%的把握能确定结果,就是采取行动的时候。如果不那么有把握,那就不妨再观察一下。</p>
<p>总之,可逆决定的最大风险是拖到了最后一分钟,不可逆决定的最大风险是在真正需要决策的时刻来临之前就做出决定。</p>
<h1>参考资料</h1>
<p><a href="https://fs.blog/reversible-irreversible-decisions/">Reversible and Irreversible Decisions</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/61" rel="alternate"/><category term="2023"/><category term="原则"/><published>2023-09-24T14:03:54+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/60</id><title>RPC 系列(一)——使用 Go rpc 库和 gob 实现最简版调用</title><updated>2023-09-29T00:00:45.944214+00:00</updated><content type="html"><![CDATA[<h1>前言</h1>
<p>net/rpc 库是 Go 语言自带的 RPC 库,可以作为 RPC 学习的首选。
gob 库是 net/rpc 库的编解码库,可以实现对 TPC 包的编解码。</p>
<h1>实现一个Service</h1>
<p>使用 net/rpc 库实现 RPC 服务,其注册的 Service 方法需要符合以下要求:</p>
<ol>
<li>导出类型的导出方法(首字母大写表示导出)</li>
<li>方法有两个导出类型的入参(请求和响应)</li>
<li>第二个入参是指针类型</li>
<li>一个 error 类型返回值</li>
</ol>
<p>我们根据要求实现一个 HelloService:</p>
<pre><code class="language-Go">import "fmt"
type HelloService struct{}
type HelloReq struct{
Name string
}
type HelloResp struct{
Resp string
}
func (*HelloService) Hello(req HelloReq, resp *HelloResp) error {
resp.Resp = fmt.Sprintf("Hello, %v", req.Name)
return nil
}
</code></pre>
<h1>Server 端</h1>
<p>Server 端的主要作用是实现一个 Service 并提供服务。提供服务的方式是接收 TCP 请求,处理并返回 TCP 响应,代码如下:</p>
<pre><code class="language-Go">import (
"log"
"net"
"net/rpc"
)
func main() {
// 注册RPC服务
rpc.Register(&HelloService{})
// 监听TCP端口
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatalf("ListenTCP err: %v", err)
}
log.Println("Server start")
for {
// 接收TCP连接
conn, err := listener.Accept()
if err != nil {
log.Fatalf("Accept err: %v", err)
continue
}
log.Println("Handle req")
// 处理TCP连接
go rpc.ServeConn(conn)
}
}
</code></pre>
<h1>Client 端</h1>
<p>Client 端主要是发起 TCP 连接,并进行 RPC 调用。Client 端代码如下:</p>
<pre><code class="language-Go">import (
"fmt"
"log"
"net/rpc"
)
func main() {
// 建立TCP连接
conn, err := rpc.Dial("tcp", ":1234")
if err != nil {
log.Fatalf("Dial err: %v", err)
}
resp := &HelloResp{}
// 进行RPC调用
err = conn.Call("HelloService.Hello", HelloReq{Name: "Zia"}, &resp)
if err != nil {
log.Fatalf("Call err: %v", err)
}
fmt.Println(resp)
}
</code></pre>
<h1>最终效果</h1>
<pre><code class="language-Shell"># Server 端
go run hello.go server.go
2023/09/24 18:39:20 Server start
2023/09/24 18:39:28 Handle req
# Client 端
go run hello.go client.go
&{Hello, Zia}
</code></pre>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/60" rel="alternate"/><category term="2023"/><category term="RPC系列"/><published>2023-09-24T09:46:09+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/59</id><title>我的仪式</title><updated>2023-09-29T00:00:46.026957+00:00</updated><content type="html"><![CDATA[<h1>晨起仪式</h1>
<ul>
<li>喝水</li>
<li>冥想</li>
<li>拉伸</li>
</ul>
<h1>睡前仪式</h1>
<ul>
<li>烧水</li>
</ul>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/59" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="习惯"/><published>2023-09-16T06:56:49+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/58</id><title>软件行业适合什么样的雇佣方式?</title><updated>2023-09-29T00:00:46.104767+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>传统的劳动合同方式是否适合软件行业?</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/58" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="随想"/><published>2023-09-15T02:07:56+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/57</id><title>研发流程备忘</title><updated>2023-09-29T00:00:46.204492+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>整体流程与重点关注</p>
</blockquote>
<p>研发流程是指在软件开发过程中的标准流程,建立流程的目标是整个团队对齐标准,提高需求交付率,降低故障发生概率。</p>
<h1>整体流程</h1>
<p>研发流程整体上分为需求、设计、开发、测试、上线、效果评估等流程,每个过程会有不一样的侧重点。</p>
<h1>需求</h1>
<p>需求阶段重点是考虑需求的价值和可行性。
需求价值指的是需求是否值得做,需要解决什么问题。
可行性指的是需求能不能做出来,避免做到一半发现做不了,白白浪费人力。
可行性评估的一个方法是Case Rundown,就是把实际场景都推演一遍,看看有没有Badcase。</p>
<h1>设计</h1>
<h1>开发</h1>
<h1>测试</h1>
<h1>上线</h1>
<h1>效果评估</h1>
<p>模块拆解,每个模块时序图+流程图
兼容性,影响范围
性能
可灰度,可监控,可回滚,可恢复数据</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/57" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="总结"/><published>2023-09-13T16:00:16+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/56</id><title>影响范围评估的重要性</title><updated>2023-09-29T00:00:46.289993+00:00</updated><content type="html"><![CDATA[<h1>协议</h1>
<p>协议是一种约定,约定调用方传入什么参数,则被调用方会如何运行逻辑并返回结果。</p>
<h1>影响范围</h1>
<p>平时的开发,会有接口改动,接口改动就涉及协议、约定的改动。
协议、约定的改动需要特别注意兼容性,如果不兼容,则需要评估调用方的影响范围,并周知调用方做相应改造。
实践看来,我大部分 Bug 都是因为改了约定,影响范围不明确导致。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/56" rel="alternate"/><category term="技术"/><category term="2023"/><published>2023-09-13T13:32:56+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/55</id><title>[翻译]哈里·布朗的金融安全17条黄金法则</title><updated>2023-09-29T00:00:46.392497+00:00</updated><content type="html"><![CDATA[<h1>法则1:你的事业提供你的财富</h1>
<p>在事业上积累财富。你很可能会从你的业务或职业中赚到比从你的投资中赚更多的钱。很少有人从投资中赚大钱。
您的投资可以使您的未来更加安全,退休生活更加繁荣。但他们不能把你从破烂带到富有。因此,不要冒险使用复杂的计划,以期快速增加您的资本。您的投资计划首先应该以保护您所拥有的为目标——保护它免受投资损失、政府干预或管理不善的影响。
大多数试图击败市场的兼职投资者失去了他们努力积累的部分或全部储蓄。
你能依靠一个具有适当资格的专家来赚取巨额利润吗?你如何找到真正的专家?这项任务并不比选择正确的投资容易。如果你不像专业人士那样了解投资,你就不会知道如何检查那些寻求为你建议的人。而且你不能依赖顾问的业绩记录,即使它是诚实地呈现的。往绩记录只告诉你顾问过去的表现,而不是他们明年的表现。</p>
<h1>规则2:不要假设你可以取代你的财富</h1>
<p>你赚到了你所拥有的并不意味着如果你失去了它,你可以再次获得它。市场和机会在变,技术在变,法律在变。今天的条件可能与你建造现在的庄园时大不相同。随着时间的流逝,越来越多的监管使得积累财富变得越来越困难。</p>
<h1>规则3:认识到投资和投机之间的区别</h1>
<h1>规则4:没有人能预测未来</h1>
<h1>规则5:没有人能始终如一地以精确和有利可图的时间进出投资</h1>
<h1>规则6:未来没有一个交易系统会像过去那样运作</h1>
<h1>规则7:不要使用杠杆</h1>
<p>当一个人完全破产时,几乎总是因为他用了借来的钱。在许多情况下,这个人已经很富有了,但他想用借来的钱来传播他的财富。
使用保证金账户或抵押贷款(房屋以外的其他)会使您面临损失超过原始投资的风险。如果您以现金为基础处理所有投资,那么几乎不可能失去一切——无论世界上会发生什么——特别是如果您遵循此处给出的其他规则。</p>
<h1>规则8:不要让任何人做你的决定</h1>
<h1>规则9:永远不要做任何你不理解的事情</h1>
<h1>规则10:不要依赖任何一项投资、机构或个人来保证您的安</h1>
<h1>规则 11:创建防弹投资组合以进行保护</h1>
<p>为了你需要照顾你余生的钱,建立一个简单、平衡、多元化的投资组合。我称之为“永久投资组合”,因为一旦你设置好它,你就永远不需要重新安排投资组合——即使你对未来的展望发生了变化。
投资组合应确保您的财富在任何事件中幸存下来——包括对投资组合中的任何单个元素造成毁灭性打击的事件。换句话说,无论未来如何,这个投资组合都应该保护你。
拥有这样一个如此安全的投资组合并不困难或复杂。您可以通过一个非常简单的投资组合实现大量的多样化。
投资组合应确保您的财富在任何事件中都能幸存下来,包括对任何一项投资造成毁灭性打击的事件。此类投资组合的三个绝对要求是:</p>
<ol>
<li>安全:它应该保护您免受所有可能的经济未来的影响。你应该在正常繁荣时期获利,但在糟糕的时期——通货膨胀、衰退甚至萧条——你也应该安全(甚至利润)。</li>
<li>稳定性:无论经济环境如何,投资组合的表现都应该如此稳定,以至于您不会怀疑投资组合是否需要改变。即使在最坏的情况下,投资组合的价值也不应略有下降——这样你就不会惊慌失措并放弃它。这种稳定性还允许您将注意力从投资上移开,相信您的投资组合将在任何情况下保护您。</li>
<li>简单性:投资组合应该非常易于维护,并且需要的时间很少,以至于您永远不会试图寻找看似更简单但不太安全的东西。</li>
</ol>
<p>你别管它——以相同的比例永久持有相同的投资。你不会随着你、你的朋友或投资大师改变他们对未来的看法而改变比例。
您的投资组合只需要对这些广泛的变动做出良好的反应。它们分为四大类:</p>
<ol>
<li>繁荣:生活水平提高、经济增长、商业繁荣、利率通常下降、失业率下降的时期。</li>
<li>通货膨胀:消费者价格普遍上涨的时期。它们可能温和上升(通货膨胀率在6%左右),迅速(10%到20%左右,如1970年代后期),或以失控的速度(25%或更高)。</li>
<li>货币紧缩或衰退:流通货币供应增长放缓的时期。这使得人们的现金比他们预期的要少,通常会导致经济衰退——一段经济状况不佳的时期。</li>
<li>通货紧缩:与通货膨胀相反。消费者价格下降,货币购买力价值增长。过去,通货紧缩有时会引发萧条——长期非常糟糕的经济状况,如1930年代。</li>
</ol>
<p>投资价格可能会受到金融体系外发生的事情的影响——战争、政府政策变化、新税收规则、内乱和其他事项。但是,只有当这些事件将经济从我刚才描述的四种环境推向另一种环境时,它们才会对投资产生持久影响。这四个经济类别包罗万象。在任何时候,其中一个都会占主导地位。因此,如果您在这四种情况下受到保护,那么您在所有情况下都会受到保护。
因此,四项投资覆盖了所有四种经济环境:</p>
<ol>
<li>股票利用繁荣。在通货膨胀、通货紧缩和货币紧缩时期,它们往往表现不佳,但随着时间的推移,这些时期不会抵消股票在繁荣时期取得的收益。</li>
<li>债券也利用繁荣。此外,当通货紧缩期间利率崩溃时,他们也会获利。您应该预期债券在通货膨胀和货币紧张时期表现不佳。</li>
<li>黄金不仅在通货膨胀剧烈时期表现良好,而且表现也非常好。在1970年代,随着通货膨胀率飙升至1980年15%的峰值,黄金上涨了20倍。黄金在繁荣、货币紧缩和通货紧缩时期通常表现不佳。</li>
<li>现金在资金紧张的时期最有利可图。它不仅是一种流动资产,可以在您的收入和投资可能不佳时为您提供购买力,而且利率的上升会增加您的美元回报。在通货紧缩期间,随着价格下跌,现金也变得更加有价值。现金在繁荣时期基本上是中性的,在通货膨胀时期是输家。</li>
</ol>
<p>任何试图聪明地将部分分配给投资的尝试都可能弊大于利。我更喜欢将25%分配给四项投资中的每一项的简单性。
唯一需要的维护是每年检查一次投资组合的构成。
如果四项投资中的任何一项的价值低于投资组合总价值的 15% 或超过 35%,则需要恢复原始百分比。
当您每年检查一次投资组合的价值时,如果所有四项投资都在 15-35% 的范围内,则无需重新平衡。在这一年中,如果您碰巧注意到投资价格发生了很大变化,您可能需要检查投资的价值。同样,如果任何投资偏离了15-35%的范围,请继续重新平衡整个投资组合。
对永久投资组合的考验是它是否能让您安心。永久投资组合应该让您完全平静地观看晚间新闻或阅读投资出版物。任何实际或威胁的事件都不会困扰您,因为您会知道您的投资组合受到保护。
如果有人警告当前十年与1920年代之间的“惊人相似之处”,你不应该怀疑你是否需要出售所有股票。你会知道你的永久投资组合会照顾你——即使明年是1929年的重温。可能摧毁股市的通货紧缩将推动利率下行,并为您的债券带来巨额利润。
当有人声称通货膨胀率将回到15%时,你不应该怀疑是否要抛售所有的债券。您将知道,您的永久投资组合黄金的收益将远远超过债券的任何损失。
当有人宣布新的债务危机即将来临,或者股票、债券或黄金的牛市即将开始时,你不会感到有压力去决定他是否正确。您将知道永久投资组合将对任何可能发生的情况做出积极的反应。
我无法列出每个潜在事件。因此,如果您对任何可能性感到担忧,请重读本章,您应该放心,如果最坏的情况发生,您的永久投资组合中有一项投资将涵盖您。无论潜在的危机或机遇是什么,您的永久投资组合都应该已经照顾好您了。
投资组合不能保证每年都有利润;没有投资组合可以。它不会在他最好的一年中超越热门顾问。而且它的表现不会超过今年最好的投资。但它可以让你相信没有危机会摧毁你,保证你的储蓄在任何情况下都是安全的和不断增长的,并且知道你不再容易受到你或最好的顾问很容易犯的判断错误的影响。</p>
<h1>规则12:只用你能承受损失的钱进行投机</h1>
<h1>规则13:将一些资产存放在您居住的国家/地区之外</h1>
<p>不要让你拥有的一切成为你的政府可以触及它的地方。通过拥有政府无法触及的东西,你会不那么脆弱——你会觉得不那么脆弱。你不再需要太担心政府下一步会做什么。
例如,维护外国银行账户非常简单;这与在美国银行或经纪人处拥有邮件或互联网帐户几乎没有什么不同。
将一些投资保留在国外可以提供安全便捷的保护,以防止可能发生在任何地方的意外 - 政府没收黄金持有量,外汇管制,内乱,甚至战争。
没有人知道未来几年当选的人会如何选择解决该国将面临的经济问题。他们可能会觉得快速简便的解决方案是拿走您的财产——就像经常发生的那样。即使战争、内乱、执法削弱或物理灾难会破坏您自己国家的记录保存,您的资产也将是安全的。
您的整个遗产将不再容易受到您所在国家的经济、政治或法律挫折的影响。
地域多元化是确保永久投资组合能够处理任何危险的必要组成部分。</p>
<h1>参考资料</h1>
<p><a href="https://thetaoofwealth.wordpress.com/2013/02/17/harry-brownes-17-golden-rules-of-financial-safety/">Harry Browne’s 17 Golden Rules of Financial Safety
</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/55" rel="alternate"/><category term="2023"/><category term="翻译"/><published>2023-09-10T12:03:47+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/54</id><title>关于幸福的观点收集</title><updated>2023-09-29T00:00:46.521921+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>收集关于幸福的观点</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/54" rel="alternate"/><category term="2023"/><category term="人生意义"/><published>2023-09-10T10:44:00+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/53</id><title>TCP的拆包与粘包</title><updated>2023-09-29T00:00:46.615632+00:00</updated><content type="html"><![CDATA[<p>TODO</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/53" rel="alternate"/><category term="技术"/><category term="WIP"/><category term="2023"/><published>2023-09-10T08:19:24+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/52</id><title>字节范摘录</title><updated>2023-09-29T00:00:46.698002+00:00</updated><content type="html"><![CDATA[<p><img src="https://github.com/zzy131250/gitblog/assets/7437470/da692fcd-39c1-4814-8a1d-a1c8848c3a20" alt="v2-9e007c24bd4176acf901e2d09288a859_r" /></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/52" rel="alternate"/><category term="2023"/><category term="原则"/><published>2023-09-10T03:47:46+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/51</id><title>35岁之后的工作</title><updated>2023-09-29T00:00:46.855941+00:00</updated><content type="html"><![CDATA[<p>“重要的不在于你的努力程度,而在于仔细选择工作、人员和项目。”</p>
<p>“真正有效的工作方式,不是铁人三项或马拉松,比拼谁坚持的时间长,而是短跑,当机会来临的时候冲刺,平时注意健康和休息。”</p>
<p>“你要像狮子一样,看到猎物一跃而起,而不要牛一样,从早到晚劳作。”</p>
<h1>体制内</h1>
<h1>民企</h1>
<p>不靠时间,靠经验,钻研</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/51" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="随想"/><published>2023-09-03T14:31:15+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/50</id><title>想不明白的时候,就去运动</title><updated>2023-09-29T00:00:46.958528+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>运动解千愁</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/50" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="随想"/><published>2023-09-03T14:14:29+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/49</id><title>接受自己的平凡</title><updated>2023-09-29T00:00:47.037129+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>平平淡淡才是真</p>
</blockquote>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/49" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="随想"/><published>2023-09-03T02:10:10+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/48</id><title>虚拟内存</title><updated>2023-09-29T00:00:47.127785+00:00</updated><content type="html"><![CDATA[<p>todo</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/48" rel="alternate"/><category term="技术"/><category term="WIP"/><category term="2023"/><published>2023-08-31T01:39:08+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/46</id><title>结硬寨,打呆仗</title><updated>2023-09-29T00:00:47.217763+00:00</updated><content type="html"><![CDATA[<p>“结硬寨,打呆仗”是曾国藩的打仗策略。说的是一步一个脚印,缓慢而踏实地攻城略地。</p>
<p>我想,在工作上能否也有这样的心态与毅力。把每件事情都思考周全,然后行动。而不是以时间不够、快速起量为由,糙快猛地完成工作。</p>
<p>在现在的互联网行业环境下,这样做很难。但是,一个成熟的行业,迟早会回归理性。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/46" rel="alternate"/><category term="WIP"/><category term="2023"/><published>2023-08-27T13:49:25+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/45</id><title>我的标准</title><updated>2023-09-29T00:00:47.310047+00:00</updated><content type="html"><![CDATA[<p><a href="https://github.com/zzy131250/gitblog/issues/38">一些工作中值得追求的品质</a>中提到高标准,我想这不是一蹴而就的,而是要不断磨合、提升的。而提升的前提就是明确现状,所以打算把自己现在的各种标准都写出来,并不断优化提升。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/45" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="标准"/><published>2023-08-18T13:19:27+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/44</id><title>冥想的作用</title><updated>2023-09-29T00:00:47.402747+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>5 whys</p>
</blockquote>
<h1>冥想有什么作用?</h1>
<p>冥想可以放空,让大脑静下来,回到当下。
长期的练习可以帮助我们</p>
<ol>
<li>让大脑平静下来,减少大脑中的杂念</li>
<li>打破思维惯性,做出更加理性的选择</li>
<li>更好地感知身体、感受生活,且在情绪波动时更加清晰地感受到情绪</li>
</ol>
<h1>为什么冥想能让大脑平静下来?</h1>
<p>冥想短暂关闭我们的感官,让我们进去放空状态。这个时候,潜意识里的想法会冒出来,我们可以记录下潜意识的想法并解决他们。长期这样做,我们潜意识的想法会越来越少,我们变得更加专注,大脑负担变轻,能够更加有效地解决问题和感受生活。</p>
<h1>为什么潜意识里会一直有想法?</h1>
<p>在平时忙碌的生活中,我们的大脑不断切换上下文,做不同的工作。但是当我们切换到下一份工作时,可能上一份工作的余温还在,这个时候潜意识里就会有上一份工作的残留。当我们平静下来不处理工作,潜意识里的东西会冒出来给我们反刍。
但是现在我们空闲下来的选择是刷视频,也是输入,相当于没有给大脑空闲的机会。那么潜意识里的残留会越来越多,只能一直积压,或者在睡觉前大脑终于平静下来时冒出来,导致失眠。</p>
<h1>总结</h1>
<p>冥想通过让大脑平静下来,给我们机会清空潜意识,最终减轻大脑负担,让我们更加清醒,专注。所谓由定生慧。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/44" rel="alternate"/><category term="2023"/><category term="冥想"/><published>2023-08-15T15:48:13+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/43</id><title>谈谈高标准与可落地</title><updated>2023-09-29T00:00:47.501784+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>知道很多道理,却仍旧过不好这一生。</p>
</blockquote>
<h1>高标准</h1>
<p>在工作中,我们一直强调高标准,这里的高标准不仅仅是喊口号,而且得在实际行动中坚持。
比如,我们要坚持事事有回应,那么重点不在于你喊出口号的那一刻,而在于你并行做很多事情,时间明显不够的情况下,能否仍然做到事事有回应。</p>
<h1>可落地</h1>
<p>可落地重点考虑的是要做的事情是否清晰,是否能做到。
比如,一个事情是“提升服务可靠性”,这个事情就是不可落地的。当前服务可靠性如何?要提高到多少?具体需要做什么事情提高可靠性?这些问题都还没有解答。
再比如,“要写好单测”这件事情,也不够具体,不可衡量,无法落地。更好的事情是“单侧覆盖度达到70%”、“分支覆盖率70%”等。</p>
<p>由此可见,从道理到具体行动之间,还横埂着巨大鸿沟,如何从道理中落地出具体行动,需要小心谨慎。否则,光明白道理是没有用的。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/43" rel="alternate"/><category term="2023"/><category term="随想"/><published>2023-08-15T14:32:34+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/42</id><title>一些有用的心理学规律</title><updated>2023-09-29T00:00:47.590175+00:00</updated><content type="html"><![CDATA[<h1>峰终定律</h1>
<p>如果在一段体验的高峰和结尾,体验是愉悦的,那么对整个体验的感受就是愉悦的。</p>
<h1>帕金森定律</h1>
<p>任何任务都会拖延,直到所有可用时间都用完。
如果一个人给自己安排了充裕的时间去完成一项工作,他就会放慢节奏或者增加其他项目以便用掉所有的时间。</p>
<h1>二八法则</h1>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/42" rel="alternate"/><category term="2023"/><category term="心理学"/><published>2023-07-23T08:36:46+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/41</id><title>记录下我的爱好</title><updated>2023-09-29T00:00:47.664756+00:00</updated><content type="html"><![CDATA[<h1>读书</h1>
<p>悬疑、科幻、历史</p>
<h1>电视</h1>
<p>日剧</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/41" rel="alternate"/><category term="2023"/><category term="爱好"/><published>2023-07-23T08:07:27+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/40</id><title>《分析模式》笔记</title><updated>2023-09-29T00:00:47.753563+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>2020 年版,马丁·福勒著,钟敬译</p>
</blockquote>
<p>随手记录,对于不熟悉相关领域知识的人来说比较难懂,希望 10 年内能把他看完:)</p>
<h1>第九章 交易</h1>
<p>抽象类型和具体类型</p>
<ul>
<li>如果两个相似类型之间的差别通常可以忽略,那么就可以使用一个抽象超类型。如果两者之间的差别通畅很重要,那么就不要使用抽象超类型。</li>
<li>如果一个抽象类型不会给客户端带来更多工作量,那么就应当提供这个抽象类型。</li>
</ul>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/40" rel="alternate"/><category term="WIP"/><category term="2023"/><category term="读书笔记"/><published>2023-07-22T14:13:12+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/38</id><title>一些工作中值得追求的品质</title><updated>2023-09-29T00:00:47.846337+00:00</updated><content type="html"><![CDATA[<h1>积极主动</h1>
<h2>勇于露拙</h2>
<p>承认自己的无知,勇于暴露错误并善于总结提升,这样才会更快成长。</p>
<h2>善意假设</h2>
<p>Yes and。默认开放信任。从利他的角度思考问题,这样或许能找到一些工作的动力和意义。</p>
<h2>及时反馈</h2>
<p>及时同步进度,暴露风险,反馈问题,不憋大招。</p>
<h1>高标准</h1>
<p>坚持高标准很难,特别是在凡事都有两面的情况下,“追求极致”也可能被认为“吹毛求疵”。但是谁能分辨,谁能评说?对于自己来说,只要坚定信念,明确标准,坚持原则,能自洽且勿忘心安即可。
真正的高标准不是在资源充足下的十全十美,而是资源不足下仍旧在重要的环节坚持标准。</p>
<h2>不畏难</h2>
<p>做难而正确的事。</p>
<h1>自信且谦卑</h1>
<p>培养自己的原则与方法论,包容开放。</p>
<h1>聚焦</h1>
<h2>提高效率</h2>
<p>通过优化流程、自动化等方式来不断提高效率,完成不重要事项,从而让自己专注在最重要的事项上面。</p>
<h2>以终为始</h2>
<p>想清楚终态,制定实现路径,避免方向性问题。有毅力坚持,把事情做到 done 的状态,避免三分钟热度。</p>
<h1>平常心</h1>
<p>放下期待,专心做事,不过度焦虑,也不过度无聊。
坚定信念,学会在没有资源、没有认可,只有压力的情况下达成目标。</p>
<h2>长期主义</h2>
<p>做长期有价值的事情,工作不一定马上有成果,但长期会有回报。</p>
<h1>情绪稳定</h1>
<p>个人认为这是上述品质的前提,只有情绪稳定的情况下,才能冷静理性分析,从而做到上面的其他品质。否则就会回到原始的应激状态。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/38" rel="alternate"/><category term="2023"/><category term="随想"/><published>2023-07-21T15:47:36+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/37</id><title>《八次危机》读书笔记</title><updated>2023-09-29T00:00:47.935754+00:00</updated><content type="html"><![CDATA[<h1>工业化的三个条件</h1>
<p>原材料
人力
外部债务,债务周期
阿根廷
亚洲金融风暴</p>
<h1>工业化的三个阶段</h1>
<p>原始积累
产业扩张
产业升级</p>
<h1>农民对工业化的贡献</h1>
<p>剪刀差
人力
社会保障</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/37" rel="alternate"/><category term="WIP"/><category term="2023"/><published>2023-07-15T15:06:58+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/36</id><title>友情链接</title><updated>2023-09-29T00:00:48.021115+00:00</updated><content type="html"><![CDATA[<p>这个 Issue 是友情链接的 Issue, 如果你想加到友情链接里,请在这条 Issue 上评论,我打心后会自动加到友情链接里,感谢大家。</p>
<p>格式如下(请参考我下面的 comment):
冒号为中文冒号 :</p>
<p>名字:xxxxxx
链接:xxxxxx
描述:xxxxxx</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/36" rel="alternate"/><category term="2023"/><category term="Friends"/><published>2023-07-14T15:09:38+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/32</id><title>微习惯打卡</title><updated>2023-09-29T00:00:48.112735+00:00</updated><content type="html"><![CDATA[<p>这个 Issue 作为微习惯每日打卡贴。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/32" rel="alternate"/><category term="2023"/><category term="记录"/><category term="打卡"/><published>2023-07-05T01:43:07+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/29</id><title>KMP 算法</title><updated>2023-09-29T00:00:48.192865+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2020-02-01</p>
</blockquote>
<h1>前言</h1>
<p>在字符串匹配问题中,有很多经典算法。其中,KMP算法是单模字符串匹配的“终极”算法。
KMP算法是根据三位作者(D.E. Knuth、J.H. Morris、V.R. Pratt)的名字来命名的,算法全称为 Knuth Morris Pratt 算法,简称KMP算法。</p>
<h1>问题简述</h1>
<p>单模字符串匹配,指的是一个字符串 a 和一个字符串 b 进行匹配,a 的长度远大于 b,需要查找在 a 中是否包含 b。其中 a 称为主串,b 称为模式串。</p>
<h1>问题分析</h1>
<p>我们能想到的最简单的办法,就是模式串在主串中滑动,从前往后依次查看有没有匹配的串。如下图:
<img src="https://github.com/zzy131250/gitblog/assets/7437470/03ce8bcb-eb66-4e01-bca5-96a90ec4a9c2" alt="31e97a47f46e9dcf.png" /></p>
<p>这种算法称为BF算法,或朴素匹配算法。算法的最坏时间复杂度为 O(n∗m),其中 n 为主串长度,m 为模式串长度。
下面,我们思考一个问题:在遇到不匹配的字符时,我们只是把模式串向后移动一位,是不是效率低了?有没有办法让模式串多向后移动几位?KMP算法,正是基于这个初衷,提出来的。</p>
<h2>坏字符与好前缀</h2>
<p>在介绍KMP算法之前,我们先介绍一些前置概念。
如下图,在主串和模式串匹配过程中,遇到的不能匹配的字符,我们称之为坏字符。相应的,我们把坏字符前,已经匹配的前缀子串称为好前缀。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/4414130c-b9ae-41c1-a7ab-c3ff6d6551ee" alt="b07eac8e9576dd3a.png" /></p>
<h2>模式串滑动</h2>
<p>我们知道,当我们遇到坏字符时,意味着模式串需要向后滑动。那么,我们最多可以滑动多少位呢?
如下图,我们可以考察好前缀的后缀子串与模式串的前缀子串,找到好前缀的后缀子串中,最长的能够与模式串前缀子串匹配的串,然后进行滑动(可以思考下,为什么要求是最长子串?如果不是最长子串,有可能滑动位数过多,导致不能覆盖全部情况,导致漏匹配)。注意,由于此时模式串的前缀子串=好前缀的前缀子串,故我们可以换一种说法,即找到好前缀的后缀子串中,最长的能够与好前缀的前缀子串匹配的串,然后进行滑动。像图中的情况,我们就可以把模式串向后滑动2位。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/5d520107-a8d5-4f41-809f-a486634d60db" alt="97720928e0d134d5.png" /></p>
<p>一般的,假设坏字符在主串中的位置为 i,在模式串中的位置为 j,好前缀的最长可匹配前后缀子串(字符串中,前缀子串与后缀子串的最长匹配串)长度为 k,那么,我们需要把模式串向后滑动 j−k 位,并从模式串的 k+1 位开始,继续进行匹配。如下图:
<img src="https://github.com/zzy131250/gitblog/assets/7437470/15c9847b-9f26-44cd-b725-44b20fec21a7" alt="daafd1aca998cf59.png" /></p>
<h2>next数组</h2>
<p>我们发现,可以预先根据模式串,求得各前缀的最长可匹配前后缀子串,记录最长前缀子串的位置,存储在数组中,再和主串进行匹配,快速得到匹配结果。我们把这个数组定义为next数组,很多地方也把这个数组定义为失效函数。
数组的下标是每个前缀结尾字符的下标,数组的值为该前缀的最长可匹配前后缀子串中,前缀子串结尾字符的下标,相当于前缀子串长度 k 减去1。下图为一个例子:
<img src="https://github.com/zzy131250/gitblog/assets/7437470/07871681-1969-4931-9baa-42270918ac9d" alt="5c69f31f548b1f14.png" /></p>
<h3>next数组的求解</h3>
<p>next数组的求解,我们可以利用动态规划的思想。我们知道,对于动态规划,最重要的是找到状态和状态转移方程。在本例中,状态是数组中的值,即模式串每个前缀的最长可匹配前后缀子串中前缀子串的位置。对于状态转移方程,要求得模式串 b 中,第 i 个字符的next值,即 next[i],我们可以先假设已经求得了 next[0]…next[i−1],且 next[i−1]=k−1,即子串 b[0,k−1] 是 b[0,i−1] 的最长可匹配前后缀子串。下面,我们分类讨论:</p>
<ol>
<li>
<p>如果 b[k]=b[i],即 b[k−1] 的下一个字符等于 b[i−1] 的下一个字符,那么,我们可以马上得出结论:b[0,k] 即为 b[0,i] 的最长可匹配前后缀子串,next[i]=k。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/44489a94-0089-46e9-b03f-a5588b8bbba4" alt="b842c4649e12b18d.png" /></p>
</li>
<li>
<p>如果 b[k]!=b[i],即 b[k−1] 的下一个字符不等于 b[i−1] 的下一个字符。则我们需要找到 b[0,k−1] 的最长前缀子串 b[0,l],且该串最后一个字符的下一个字符为 b[i],即 b[l+1]=b[i]。这个时候,我们可得到 next[i]=l+1。</p>
</li>
</ol>
<h2>问题解决</h2>
<p>我们先给出框架代码如下:</p>
<pre><code class="language-Java">// a,b分别是主串和模式串;n,m分別是主串和模式串的长度
public static int kmp(char[] a, int n, char[] b, int m) {
int[] next = getNexts(b, m);
// 模式串的索引位置
int j = 0;
// i是主串的索引位置
for (int i = 0; i < n; i++) {
// 找到坏字符,也就是a[i]和b[j]
while (j > 0 && a[i] != b[j]) {
// 找到最长可匹配前后缀子串长度k-1,并从k位开始继续匹配
j = next[j - 1] + 1;
}
// 主串与模式串匹配,模式串索引后移
if (a[i] == b[j]) ++j;
// 找到匹配的模式串了,返回主串中开始匹配的字符位置
if (j == m) return i - m + 1;
}
return -1;
}
</code></pre>
<p>next数组的求解属于比较难理解的部分,代码如下:</p>
<pre><code class="language-Java">// b表示模式串,m表示模式串的长度
public static int[] getNexts(char[] b, int m) {
int[] next = new int[m];
next[0] = -1;
// 最长可匹配前后缀子串中,前缀子串的下标
int k = -1;
for (int i = 1; i < m; i++) {
// 情况2,b[k+1]!=b[i],循环找b[0,k]的最长前缀子串,直到b[k+1]=b[i]或k=-1(不存在),next[i]=l+1
while (k != -1 && b[k + 1] != b[i]) {
// 每次都赋值为b[0,k]的最长前缀子串位置
k = next[k];
}
// 情况1,b[k+1]=b[i],next[i]=k+1
if (b[k + 1] == b[i]) ++k;
next[i] = k;
}
return next;
}
</code></pre>
<p>由上述代码可知,KMP算法的时间复杂度为 O(n+m),即两个串都遍历一遍即可;空间复杂度为 O(m),需要申请一个和模式串长度一样的数组。其中 n 为主串长度,m 为模式串长度。</p>
<h1>参考资料</h1>
<p><a href="https://blog.csdn.net/weixin_38073885/article/details/85345215">字符串匹配1———单模式匹配(BF,RK,Sunday,BM,KMP)</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/29" rel="alternate"/><category term="算法"/><category term="字符串匹配"/><category term="2020"/><published>2023-06-30T01:46:47+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/28</id><title>IPTables 学习实践</title><updated>2023-09-29T00:00:48.285066+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2019-06-12</p>
</blockquote>
<h1>概述</h1>
<p>Iptables是一个配置Linux内核防火墙的命令行工具,是Netfilter项目的一部分。Iptables通过封包过滤的方式检测、修改、转发或丢弃IPv4数据包,过滤的方式则采用一系列默认和用户定义的规则。如果匹配到规则,则执行该规则的动作。注意,Iptables的规则是有顺序的,只会执行第一个匹配规则的动作。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/38e96b68-d0df-4596-aba3-186f7ce671dc" alt="8cbddf6ff58c4308.png" /></p>
<h1>概念介绍</h1>
<h2>表(Table)</h2>
<p>Iptables里面有多个表(Table),每个表都定义出自己的默认策略与规则,且每个表的用途都不相同。
Iptables主要包含的表:</p>
<ul>
<li>raw表:用于配置数据包,其中的数据包不会被系统跟踪</li>
<li>filter表:存放所有与防火墙相关操作的默认表</li>
<li>nat表:用于网络地址转换(来源、目的IP、port的转换)</li>
<li>mangle表:用于对特殊封包的修改
<img src="https://github.com/zzy131250/gitblog/assets/7437470/0a45ef67-4dde-40d1-a309-77ad79e59492" alt="c017ffe532d4ad71.png" /></li>
</ul>
<h2>链(Chain)</h2>
<p>Iptables主要包含的链:</p>
<ul>
<li>INPUT链:作用于想要进入本机的封包</li>
<li>OUTPUT链:作用于本机要发送的封包</li>
<li>FORWARD链:作用于要转发的封包</li>
<li>PREROUTING链:作用于路由判断之前</li>
<li>POSTROUTING链:作用于路由判断之后</li>
</ul>
<p>Iptables封包过滤过程(表与链的生效时机)如下图:
<img src="https://github.com/zzy131250/gitblog/assets/7437470/c48f2337-6f74-4eb9-96dc-05eb07645530" alt="f0c53008b78b4655.jpg" /></p>
<h2>规则(Rule)</h2>
<p>位于Iptables上的一系列匹配规则,这些规则包括匹配条件与执行目标(跳转到链、内置目标ACCEPT,DROP,QUEUE和RETURN、扩展目标REJECT和LOG)。
在执行目标为跳转到链时,如果目标链的规则不能提供完全匹配,则会返回到调用链继续寻找匹配规则。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/74a0b7c7-b15a-4bcf-b271-1c1f095ee387" alt="38268c476c086c06.jpg" /></p>
<h2>模块(Module)</h2>
<p>模块可以用来扩展Iptables,如conntrack链接跟踪等。</p>
<h1>实践</h1>
<p>本实践通过配置Iptables规则达到Docker端口映射的目的。实践的机器为CentOS 7.6虚拟机,预先安装了Iptables(v1.4.21)和Docker(v18.09.6)。
Iptables规则可通过iptables -S -t [table]命令查看,最初的Iptables规则列表如下:</p>
<pre><code>*nat
-P PREROUTING ACCEPT # PREROUTING链的默认规则为ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER # 用户自定义的链
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE # 对源地址为172.17.0.0/16网段,出口网桥不是docker0的数据包做动态IP的SNAT
-A DOCKER -i docker0 -j RETURN
*filter
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
</code></pre>
<p>通过 docker run -p 8080:8080 命令,我们添加了端口映射。现在,我们可以直接通过 curl <a href="http://10.0.2.15:8080">http://10.0.2.15:8080</a> 命令访问容器中的服务。其中,10.0.2.15是CentOS宿主机的IP。再次查看Iptables,规则列表变为:</p>
<pre><code>*nat
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:8080
*filter
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
</code></pre>
<p>我们发现多了三条规则:</p>
<pre><code>*nat
# 对源地址为172.17.0.2/32,目的地址为172.17.0.2/32,目的端口为8080的TCP包做动态IP SNAT
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
# 对入口网桥不是docker0,目的端口为8080的TCP包做DNAT,目的地址为172.17.0.2:8080
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:8080
*filter
# 对目的地址为172.17.0.2/32,入口网桥不是docker0,出口网桥是docker0,目的端口是8080的TCP包做ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
</code></pre>
<p>下面我们依次手动添加Iptables规则,命令如下:</p>
<pre><code>iptables -t nat -A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:8080
iptables -t filter -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
</code></pre>
<p>最后,通过curl <a href="http://10.0.2.15:8080">http://10.0.2.15:8080</a> 命令,我们成功访问到了容器中的服务,说明端口映射成功。</p>
<h1>参考资料</h1>
<ol>
<li><a href="http://cn.linux.vbird.org/linux_server/0250simple_firewall.php">鸟哥的Linux私房菜</a></li>
<li><a href="https://wiki.archlinux.org/title/Iptables">Iptables</a></li>
</ol>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/28" rel="alternate"/><category term="网络"/><category term="IPTables"/><category term="2019"/><published>2023-06-30T01:45:40+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/27</id><title>做一只爬虫需要考虑的</title><updated>2023-09-29T00:00:48.393374+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2019-04-10</p>
</blockquote>
<h1>前言</h1>
<p>整理一下做一只可靠的爬虫需要考虑的因素。
一次典型的爬取过程包括下载一个 URL 返回的页面,解析页面中的目标数据,将数据存储起来。那么,可以从网络、解析、存储三个方面来讨论分析。另外,爬虫还需要应对服务端的反爬虫策略。</p>
<h1>网络</h1>
<p>爬虫需要网络访问,网络下载速度越快,那么爬取效率会越高。当然,一般情况下,由于网站的反爬虫策略,爬虫对于网络的要求并不是很高,带宽不需要很大。因为,通常情况下反爬虫策略不允许短时间内的大量访问。
当然,对于客户端而言,最佳实践当然是使用单独的线程或进程进行异步的网络访问,因为 IO 操作相对于 CPU 来说相当耗时。</p>
<h1>解析</h1>
<p>网页的解析,就是从网页中提取需要的数据。主要的解析方式包括正则、Xpath、CSS 选择器等。对于爬虫来说,解析出需要的数据即可,根据不同的网页,可以挑选不同的方式。</p>
<h1>存储</h1>
<p>数据存储主要可分为文件和数据库,如果爬虫爬取的数据量不是很大,那么可以使用文件进行简单存储。而如果数据量达到一定规模,则优先考虑数据库。
数据库又分为关系型数据库,如 MySQL,和非关系型数据库,如 MongoDB。如何选择,则主要看使用场景,两者的对比可参考:<a href="https://www.mongodb.com/zh-cn/compare/mongodb-mysql">MongoDB and MySQL Compared</a>。</p>
<h1>反爬虫策略应对</h1>
<h2>UA识别</h2>
<p>对于通过 User-Agent 识别爬虫的策略,可以伪造 UA,并使用 UA 池,随机切换。另外可以伪装成浏览器爬虫,使用特定的 UA 爬取数据。</p>
<h2>验证码</h2>
<p>对于图片验证码,可以考虑使用 OCR 识别;对于滑块之类的验证手段,可以考虑 Selenium 模拟滑动。验证码是比较难以破解的反爬虫手段,目前也没有完美的方式。</p>
<h2>频率限制</h2>
<p>对于服务端的访问频率限制,可以随机 sleep 一段时间再爬取。</p>
<h2>动态网页</h2>
<p>对于动态网页,可以使用两种方式。
一是直接找到 Ajax 请求的 URL,进行爬取;二是通过 Selenium + 模拟浏览器,首先渲染出页面,再进行爬取。</p>
<h2>IP封锁</h2>
<p>对于 IP 封锁,可使用 IP 代理池,随机切换 IP 爬取数据。</p>
<h2>Cookie验证</h2>
<p>对于需要登录验证的网站,可以先使用网页登录,然后拿到 Cookie,进行爬取。或者使用 Selenium + 模拟浏览器直接登录,然后爬取数据。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/27" rel="alternate"/><category term="爬虫"/><category term="2019"/><published>2023-06-29T15:01:03+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/26</id><title>JDK 动态代理</title><updated>2023-09-29T00:00:48.668079+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2018-09-02</p>
</blockquote>
<h1>前言</h1>
<p>JDK 动态代理可以让我们很容易地实现代理模式。通过解析动态代理的实现机制,我们可以更好地使用它。</p>
<h1>一个例子</h1>
<p>我们先来写个简单的例子,先定义两个接口 Subject 和 Subject2。</p>
<pre><code class="language-Java">public interface Subject {
void hello();
}
public interface Subject2 {
void hello2();
}
</code></pre>
<p>再定义一个实现类来实现上面的两个接口。</p>
<pre><code class="language-Java">public class SubjectImpl implements Subject, Subject2 {
@Override
public void hello() {
System.out.println("hello world");
}
@Override
public void hello2() {
System.out.println("hello world, two");
}
}
</code></pre>
<p>下面,我们还得实现 InvocationHandler 接口,这个接口有一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。我们在其中加入了处理逻辑,用于在实际调用方法之前打印一句 log,这也是动态代理常用的一个场景。</p>
<pre><code class="language-Java">public class IHandler implements InvocationHandler {
private Object object;
IHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("log: before hello");
method.invoke(object, args);
return null;
}
}
</code></pre>
<p>最后,我们写一个 main 方法,来测试动态代理。</p>
<pre><code class="language-Java">public static void main(String[] args) {
Subject subject = new SubjectImpl();
InvocationHandler handler = new IHandler(subject);
Subject s = (Subject) Proxy.newProxyInstance(IHandler.class.getClassLoader(),
new Class<?>[]{Subject.class, Subject2.class}, handler);
Subject2 s2 = (Subject2) Proxy.newProxyInstance(IHandler.class.getClassLoader(),
new Class<?>[]{Subject.class, Subject2.class}, handler);
s.hello();
s2.hello2();
}
</code></pre>
<p>运行之后,可以看到控制台的输出如下:</p>
<pre><code class="language-Java">log: before hello
hello world
log: before hello
hello world, two
</code></pre>
<p>这里有两个注意点:</p>
<ol>
<li>在 invoke 方法中,不能使用参数中的 proxy 来调用方法,这样会导致循环调用,最终导致栈溢出。</li>
<li>在 main 方法中, subject 的对象声明可以是 Subject、Subject2、SubjectImpl中的任意一个,因为这里不是根据对象的声明来调用方法。</li>
</ol>
<p>附上类图供参考。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/b83e2f46-8926-42a8-ad25-336fc64d4c2f" alt="371ac0890df7772b.png" /></p>
<h1>原理分析</h1>
<p>下面,我们来分析一下动态代理的源码实现,这里我们使用的是 JDK 1.8.0_144 版本。我们先来看下 Proxy.newProxyInstance() 这个方法,这个方法接收一个 ClassLoader,一组接口和一个 InvocationHandler。</p>
<pre><code class="language-Java">public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
...
// 根据类加载器和接口获取代理类
Class<?> cl = getProxyClass0(loader, intfs);
...
// 获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
// 使用构造方法新建一个实例,并将 InvocationHandler 参数传入
return cons.newInstance(new Object[]{h});
...
}
</code></pre>
<p>这里的关键是 getProxyClass0() 这个方法,我们看到方法里面是从一个 cache 中获取代理类。这个 cache 的作用是缓存已经生成的代理类以便后续使用,如果没有符合要求的代理类,则会根据该 cache 构造方法传入的 ProxyClassFactory 生成一个我们需要的代理类。下面我们看下 ProxyClassFactory 的实现,它主要实现了一个 apply() 方法,来生成代理类。</p>
<pre><code class="language-Java">public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
// 为代理类命名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 动态生成代理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
...
}
</code></pre>
<p>继续看 ProxyGenerator.generateProxyClass() 方法的实现。里面主要调用了 ProxyGenerator 的 generateClassFile() 方法,而该方法主要的作用是为代理类添加代理方法,将他设置为 Proxy 类的子类,然后转成字节码数组并返回。
在获取代理类之后,我们调用构造方法,生成了代理类实例对象。想看下我们生成的代理类长什么样吗?我们可以使用 IntelliJ IDEA 并在 main 方法第一行加一句:</p>
<pre><code class="language-Java">System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
</code></pre>
<p>这样,我们就可以在项目目录下的 com.sun.proxy 包中看到动态生成的代理类了。</p>
<pre><code class="language-Java">public final class $Proxy0 extends Proxy implements Subject, Subject2 {
public final void hello2() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void hello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
</code></pre>
<p>如我们所料,生成的 $Proxy0 类继承了 Proxy 类,并且实现了 Subject、Subject2 接口。而方法则通过调用父类 Proxy 的 InvocationHandler 的 invoke() 方法来实现。</p>
<h1>参考资料</h1>
<p><a href="https://developer.ibm.com/languages/java/">Java 动态代理机制分析及扩展,第 1 部分</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/26" rel="alternate"/><category term="Java"/><category term="动态代理"/><category term="2018"/><published>2023-06-29T14:02:30+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/25</id><title>技术脑图整理</title><updated>2023-09-29T00:00:48.780723+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2018-02-23</p>
</blockquote>
<h1>前言</h1>
<p>这是我看书的笔记做成的脑图,整理在这里,会经常更新。</p>
<h1>脑图</h1>
<h2>Java SE</h2>
<p><a href="http://naotu.baidu.com/file/6133290b534f386f5dfd4d87c271344a?token=a1df85c8c36a9abb">周志明. 深入理解Java虚拟机[M]. 机械工业出版社, 2011.</a>
<img src="https://github.com/zzy131250/gitblog/assets/7437470/c7832042-1f05-4324-9732-799dd5b84bd6" alt="e954472eada78280.jpg" /></p>
<p><a href="http://naotu.baidu.com/file/06edfb64478a01834f02d9d68c89666f?token=24a9c6171376724a">BrianGoetz, 戈茨, 童云兰. Java并发编程实战[M]. 机械工业出版社, 2012.</a>
<img src="https://github.com/zzy131250/gitblog/assets/7437470/8ed68215-3fc5-447d-aeca-7b11eae6299d" alt="cf20016a0f53de65.jpg" /></p>
<h2>Java EE</h2>
<p><a href="http://naotu.baidu.com/file/687f5efea4057ad18cfe68906708ddb8?token=d1219e67e943a79c">王福强. Spring揭秘[M]. 人民邮电出版社, 2009.</a>
<img src="https://github.com/zzy131250/gitblog/assets/7437470/56170efb-397e-4bdc-8017-65f70a4f63cf" alt="427a20abfa83d3a5.jpg" /></p>
<h2>服务器</h2>
<p><a href="http://naotu.baidu.com/file/b389d807cb8115387eed9ad0535ae18f?token=901af3903971ac59">陶辉. 深入理解Nginx.第2版[M]. 机械工业出版社, 2016.</a>
<img src="https://github.com/zzy131250/gitblog/assets/7437470/74b0ca8e-b5b6-4fff-b887-e0236603d6c8" alt="5798f9ab4dc297a1.jpg" /></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/25" rel="alternate"/><category term="读书笔记"/><category term="2018"/><published>2023-06-29T13:57:20+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/24</id><title>马尔科夫模型</title><updated>2023-09-29T00:00:48.887403+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-09-07</p>
</blockquote>
<h1>要素</h1>
<ul>
<li>状态集合</li>
<li>状态转换概率矩阵</li>
</ul>
<h1>前提</h1>
<h2>马尔科夫假设</h2>
<p>某个状态出现的概率仅依赖于前m个状态,称为m阶马尔科夫模型;特别地,若m=1,则称此模型为一阶马尔科夫模型。</p>
<h2>贝叶斯公式(条件概率)</h2>
<h1>一阶马尔科夫模型的应用</h1>
<h2>给定模型,求某序列出现的概率</h2>
<h2>给定状态集合和某序列,求模型的参数</h2>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/24" rel="alternate"/><category term="WIP"/><category term="2017"/><category term="NLP"/><published>2023-06-29T13:55:55+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/23</id><title>反向传播</title><updated>2023-09-29T00:00:48.980762+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-08-19</p>
</blockquote>
<h1>前言</h1>
<p>反向传播算法是一种训练人工神经网络的常用方法。它通过计算网络中所有权重的梯度,并对权重进行迭代更新,以最小化损失函数。
根据上面的介绍,我们可以得知,反向传播算法的关键,是找出每个权重的梯度,然后可以使用前面讲到的<a href="https://github.com/zzy131250/gitblog/issues/21">梯度下降方法</a>,进行权重的更新。</p>
<h1>三层网络的例子</h1>
<p>我们先来看最基本的三层网络。如图,这三层网络分别称为输入层、隐藏层、输出层。对于隐藏层和输出层的每个神经元,输入是上一层输出值的加权和,输出是一个函数值。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/c35b1efd-482b-4bda-95b6-2682ce7ea7cc" alt="413c53aca59f366c.jpg" /></p>
<h2>正向计算输出</h2>
<h2>反向更新权重</h2>
<h1>扩展到多层网络</h1>
<p>我们发现,无论网络有几层,我们都可以通过链式法则计算出结果对所有参数的偏导数,进而使用梯度下降,求得最优参数组合。</p>
<h1>参考资料</h1>
<p><a href="https://www.zybuluo.com/hanbingtao/note/476663">零基础入门深度学习(3) - 神经网络和反向传播算法</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/23" rel="alternate"/><category term="WIP"/><category term="2017"/><category term="深度学习"/><published>2023-06-29T13:54:42+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/22</id><title>动态规划(二)——最长递增子序列(LIS)</title><updated>2023-09-29T00:00:49.061887+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-08-06</p>
</blockquote>
<h1>前言</h1>
<p>在遥远的上一篇,我们介绍了动态规划的第一个问题——<a href="https://github.com/zzy131250/gitblog/issues/13">背包问题</a>。这篇,我们接着介绍另一个初级问题——最长递增子序列(LIS)。</p>
<h1>问题简述</h1>
<p>给定一个数字序列 $a_{1},a_{2},…,a_{n}$ ,求其中最长的递增子序列长度。举个栗子,现在有序列 2,3,5,4,1,6 ,那么最长递增子序列就是 2,3,5,6 和 2,3,4,6 ,长度为4,我们不关注子序列的元素在原序列中是否相邻。</p>
<h1>问题分析</h1>
<p>根据上一篇的经验,要解决动态规划问题,关键是找出状态及状态转移方程。现在,我们循着这个思路,尝试解决。
首先是状态,我们可以怎样定义状态呢?首先想到的,是把前i个数字序列的LIS长度d[i]作为状态。找到状态,接着找状态转移方程。假设我们已经找到了d[1],d[2],…,d[i],如何找出d[i+1]呢?我们发现,要确定d[i+1],需要知道 $a_{i+1}$ 与前面数字的大小。这就带来一个问题——我们在记录d[i]时,并没有记录d[i]对应的末尾数字,这让我们陷入困境。
我们需要更新状态的定义。
根据上面的分析,我们把状态d[i]定义为:前i个数字序列,且以 $a_{i}$ 作为结尾的LIS长度。那么,d[i+1]就是d[1],d[2],…,d[i]中末尾数字小于 $a_{i+1}$ 的最大值,即d[i+1]=max{d[k]| $a_{k}\textless a_{i+1}$ ,k∈(1,i)}。
在给出代码之前,先插播一下我关于使用循环还是递归的一些看法。在上一篇中,我们给出了递归与循环两种方式的实现,但是个人感觉递归更加容易理解一点,因为那里有硬性约束——背包容量,适合用递归自顶向下考虑;而这里,我们没有什么硬性约束,且为了得到一个状态,需要预先知道前面所有的状态值,所以,适合用循环自底向上考虑。
下面我们给出代码。</p>
<h1>问题解决</h1>
<pre><code class="language-Java">public static int LIS(int[] nums, int index) {
int[] d = new int[nums.length+1];
for (int i = 1; i <= nums.length; i++) {
// d[i]表示以第i个数字结尾的最长递增子序列长度
if (i == 1) d[i] = 1;
// 遍历d[j],找出最大的d[j],且满足第j个数字小于第i个数字
// 这样,d[i]=d[j]+1,否则d[i]=1
for (int j = 1; j < i; j++) {
if (nums[j-1] < nums[i-1] && d[j]+1 > d[i]) d[i] = d[j]+1;
}
d[i] = d[i] > 1 ? d[i] : 1;
}
// 遍历各个最长递增子序列长度值,返回前index个数字中最长的递增子序列长度
int max = 0;
for (int i = 1; i <= index; i++) {
if (max < d[i]) max = d[i];
}
return max;
}
</code></pre>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/22" rel="alternate"/><category term="2017"/><category term="动态规划"/><category term="算法"/><published>2023-06-29T13:53:23+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/21</id><title>从最小二乘法到梯度下降法(二)</title><updated>2023-09-29T00:00:49.149406+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-28</p>
</blockquote>
<h1>前言</h1>
<p><a href="https://github.com/zzy131250/gitblog/issues/20">上一篇</a>我们介绍了最小二乘法,这是一种数学优化方法,常用于拟合回归。它的基本思想就是通过令参数的偏导数为0,计算残差平方和的最小值。但是这种方法有一定的局限性,就是不适合使用计算机进行计算,因为计算机不会解方程。
那么,有没有方便计算机进行计算的方法呢?答案当然是有的。计算机虽然不会解方程,但是它可以通过自己强大的计算能力,把最终答案一步一步试出来。这就是梯度下降法。</p>
<h1>线性回归的例子</h1>
<h1>扩展到多重回归</h1>
<h1>随机梯度下降(SGD)</h1>
<h1>小批量梯度下降(MBGD)</h1>
<p>上述两种梯度下降方法各有优势,而小批量梯度下降则将两者结合起来。顾名思义,该方法将样本分成多个批次,每个批次都有多个样本数据。在一次梯度函数 ∇F 的计算过程中,使用一批样本数据。跟BGD相比,MBGD计算梯度函数时样本量较少,效率高;跟SGD相比,MBGD使用小批量计算梯度,减小了梯度方向的误差,使得收敛速度更快。</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/21" rel="alternate"/><category term="WIP"/><category term="2017"/><category term="优化"/><category term="深度学习"/><published>2023-06-29T05:16:25+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/20</id><title>从最小二乘法到梯度下降法(一)</title><updated>2023-09-29T00:00:49.227469+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-24</p>
</blockquote>
<h1>前言</h1>
<p><a href="https://zh.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95">最小二乘法</a>是一种数学优化方法,它通过计算数据残差的平方和并使之最小,从而得到数据的最佳函数匹配。</p>
<h1>线性回归的例子</h1>
<h1>扩展到多重回归</h1>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/20" rel="alternate"/><category term="WIP"/><category term="2017"/><category term="优化"/><category term="深度学习"/><published>2023-06-29T01:49:43+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/19</id><title>记一次异常数据过滤算法的优化</title><updated>2023-09-29T00:00:49.331226+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-13</p>
</blockquote>
<p>这段时间公司给了个新的需求:对现有的小区均价异常数据过滤算法进行优化,因为现在的过滤算法过滤了大量正常数据,导致均价变化幅度很大,引起客户的怀疑。</p>
<h1>算法目标</h1>
<p>过滤算法的目标是过滤小区房子的错误数据。由于我们的数据来源是中介网站,可以认为错误数据不会很多。另外,如果有错误数据,也认为这是很容易辨识出来的数据。</p>
<h1>过滤算法V1.0</h1>
<p>现有的过滤算法的基本思想是计算幅度。先将小区中房子的成交价按照时间排序,并选取一个月的价格序列,计算相邻价格之间的变化幅度,按照一定的规则确定异常数据。规则如下:</p>
<pre><code class="language-Python"># 对所有的房子价格求幅度
for n in range(1, len(price_list)-1):
# 计算前项的幅度
fn1 = price_list[n] / price_list[n-1] - 1
# 计算后项的幅度
fn2 = 1 - price_list[n] / price_list[n+1]
# 计算平均的幅度
fn3 = (price_list[n-1] + price_list[n+1]) / 2 / price_list[n]
# 如果满足下式,则n必为噪点
if abs(fn1) > a1 and abs(fn2) > b1 and abs(fn3) > c1 and fn1*fn2 < 0:
noise_index_list.append(n)
continue
# 如果满足下式,则n可能为噪点
if abs(fn1) > a2 and abs(fn2) < b2 and abs(fn3) < c2:
# 计算n+1的相关指标
x = n + 1
if x < len(price_list)-1:
fx1 = price_list[x] / price_list[x-1] - 1
fx2 = 1 - price_list[x] / price_list[x+1]
fx3 = (price_list[x-1] + price_list[x+1]) / 2 / price_list[x]
# 如果满足下式,则n为噪点
if abs(fx1) < a2 and abs(fx2) > b2 and abs(fx3) > c2 and fn1*fx2 < 0:
noise_index_list.append(n)
</code></pre>
<p>上述算法是有一定说服力的,如果某个价格B较前一个价格A上升很多,后一个价格C较B又下降很多,那么可以确定B是异常数据。但是这个算法有一个前提,就是数据整体呈上升趋势。当然,假定房价整体呈上升趋势,这也是说得过去的。
通过分析数据,发现同一个小区的房价数据虽然大体处于同一水平,但有些有明显的分化迹象。比如,某个小区一些房子单价在6万左右,另一些在4万左右。这样的小区,如果单纯地按照上述算法过滤价格,那么很可能将大量正常数据过滤掉。</p>
<h1>过滤算法V2.0</h1>
<p>基于上面的发现,我考虑能否先将价格差异明显的小区均价分类,然后每个类内部再使用过滤算法进行过滤。于是,我想到了聚类。
对于这样的聚类场景,首先想到的,当然是<a href="https://en.wikipedia.org/wiki/Affinity_propagation">AP算法</a>,因为不知道聚类的个数。但是,在简单尝试对几个小区聚类之后,我放弃了,因为聚类结果不理想,类太多。在这个场景下,只有非常明显的价格差异,我们才进行分类,类过多毫无用处。我还是回到<a href="https://zh.wikipedia.org/wiki/K-%E5%B9%B3%E5%9D%87%E7%AE%97%E6%B3%95">K-Means算法</a>,不过,做了一些优化。先计算出各个聚类个数下(从5递减)的聚类中心,如果某次聚类下聚类中心相距较远,那么可以认为该聚类个数比较合理;相反,如果聚类中心相距很近,那么减少聚类个数。
在加入分类步骤后再过滤数据,使得很多正常数据得以保留,算法也更合理。但是,仍会有一些正常数据被删掉。继续分析被删掉数据,发现它们大多是与相邻聚类中心都很远的数据。比如,一个聚类中心为5万,这个类中有相邻的三个数据4万、6万、4万,它们虽然属于同一类,但是都处于边缘,导致计算它们的幅度时,仍然超出了限制,6万被删除。
这不禁让我回过头来思考,使用相邻价格的变化幅度来过滤数据的正确性。首先,在这个场景下,相邻数据的上升趋势不明显;另外,我们考虑的是房子的成交单价,一般来说,一套房子只能成交一次,相邻的价格就是两套房子的成交单价,它们其实没有那么多变化幅度上的关联。</p>
<h1>过滤算法V3.0</h1>
<p>根据上面的思考,再结合我们这个场景下的过滤目标,我决定改变原有的计算变化幅度的算法,转而寻找新的算法。通过查阅网上资料,我发现了用于去除噪声的<a href="https://baike.baidu.com/item/%E6%8B%89%E4%BE%9D%E8%BE%BE%E5%87%86%E5%88%99">拉依达准则</a>:假设一个集合,均值为μ,按贝塞尔公式计算出标准偏差为σ,那么,我们可以认为大部分数据都会在(μ-3σ, μ+3σ)之间,如果一个数据超出这个范围,我们就认为它是异常数据。拉依达准则主要考虑数据与均值的距离,而不是相邻点的幅度,这比较符合我们的使用场景。</p>
<h1>结果</h1>
<p>至此,我们得到了改进的算法:</p>
<ol>
<li>通过价格聚类,将小区价格分类</li>
<li>对每个类,计算数据与类中心的距离(拉依达准则),决定是否过滤</li>
</ol>
<p>目前,这个算法取得了最好的效果,符合过滤的预期。当然,还是有改进的余地,如果大家有什么好的建议,请不吝赐教!</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/19" rel="alternate"/><category term="2017"/><category term="算法"/><category term="优化"/><published>2023-06-29T01:48:25+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/18</id><title>布隆过滤器</title><updated>2023-09-29T00:00:49.438308+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-09</p>
</blockquote>
<h1>前言</h1>
<p>在现实生活中,我们常会遇到需要判断某元素是否在一个集合中的问题。比如,网络爬虫需要判断一个网址是否已经被爬取过、或者需要检查某个邮件地址是否用于发送垃圾邮件。
我们可以想到的方法可能是把他们保存起来,保存到数组、链表、树中,并由最优的搜索算法遍历。但随着数据个数的增长,搜索的代价逐渐升高(最好的情况是O(logn)),且空间占用会扩大。这时,我们想到哈希函数,通过把数据映射到哈希值,搜索速度提升(O(n/k)),而且数据可以得到压缩。那么,我们要问,如果数据个数再增长呢?哈希值存不下呢?这时,为了保证可用性,我们不得不牺牲掉一些准确性,这就是布隆过滤器的思想。</p>
<h1>布隆过滤器</h1>
<h2>原理介绍</h2>
<p>布隆过滤器由布隆在1970年提出,它由一个很长的二进制数组和一系列哈希函数组成。通过在数组上记录每个元素的指纹,来记录需要过滤的元素的信息。
假设现在有3个元素需要加入过滤器,同时有3个哈希函数。我们先将过滤器初始化,即全部置0。然后对每个元素通过每个哈希函数求哈希值,并将这9个哈希值对应的数组上的点置1。全部操作完成后,布隆过滤器就完成了。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/6154ca0b-19e1-4f78-a682-0f7e8739cd26" alt="2b6893614fb225ae.jpg" /></p>
<p>在实际使用中,用哈希函数对需要判断的元素求哈希值,如果所求得的哈希值在数组对应位置上出现0,则可以确定该元素一定不是需要过滤的元素。但是,如果发现哈希值对应到数组上都为1,那么,该元素有一定概率是不需要被过滤的,这也是导致布隆过滤器误删的原因。</p>
<h2>误差计算</h2>
<p>我们现在来计算一下布隆过滤器误删元素的概率。假设数组长度为m,哈希函数个数为h,需要过滤的元素个数为k。
我们先在过滤器中插入元素。一个元素在一个哈希函数作用下,造成数组中某位被置为1的概率为 $\frac{1}{m}$ ,因而该位没有被置1的概率为:
$$1-\frac{1}{m}$$
则一个元素在所有哈希函数作用后,该位仍为0的概率为:
$$\left(1-\frac{1}{m}\right)^{h}$$
接下来,把所有需要过滤的元素插入过滤器后,该位为0的概率变为:
$$\left(1-\frac{1}{m}\right)^{hk}$$
因而在布隆过滤器完成时,某位被置为1的概率为:
$$1-\left(1-\frac{1}{m}\right)^{hk}$$
现在我们使用完成的布隆过滤器过滤元素。对于被检验的某个元素,得到它的哈希值对应数组位置都被置1的概率为:
$$\left[1-\left(1-\frac{1}{m}\right)^{hk}\right]^{h}$$
即该元素被删掉的概率。
我们知道,在m很大时, $\left(1-\frac{1}{m}\right)^{hk} \approx e^{-hk/m}$ ,则原概率约等于:
$$\left(1-e^{-hk/m}\right)^{h}$$
我们可以发现,被误删的概率随着数组长度(m)的增大而减小,随着需要过滤元素个数(k)的增大而增大。</p>
<h1>参考资料</h1>
<ol>
<li><a href="https://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8">布隆过滤器</a></li>
<li><a href="http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html">布隆过滤器 (Bloom Filter) 详解</a></li>
</ol>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/18" rel="alternate"/><category term="2017"/><category term="算法"/><category term="哈希"/><published>2023-06-28T15:06:02+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/17</id><title>Python 库推荐</title><updated>2023-09-29T00:00:49.525086+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-02</p>
</blockquote>
<h1>前言</h1>
<p>工欲善其事,必先利其器。Python作为强大的脚本语言,其很大的优势就在于众多优秀的库。这里推荐一些个人觉得很好用的库,本文持续更新。</p>
<h1><a href="https://pypi.org/project/requests/">requests</a></h1>
<p>易用的HTTP库。”Python HTTP for Humans.”这是requests的口号。</p>
<h1><a href="https://pypi.org/project/BeautifulSoup/">beautifulsoup</a></h1>
<p>好用的HTML解析工具。
<a href="https://beautifulsoup.readthedocs.io/zh_CN/latest/">使用手册</a></p>
<h1><a href="https://pypi.org/project/python-dateutil/">dateutil</a></h1>
<p>日期处理工具,令人激动的是,它的parse函数可以自动识别多种字符串,如”2017-01-01”、”01/01/2017”、”2017.01.01”,并直接转成datetime,很方便!另外,它还提供rrule函数,用于根据指定条件生成日期序列。
<a href="https://dateutil.readthedocs.io/en/latest/">使用手册</a></p>
<h1>profile(built-in)</h1>
<p>这是Python的一个内建函数,可以用于进行性能分析。</p>
<h1><a href="https://pypi.org/project/pyenv/">pyenv</a></h1>
<p>Linux下的Python版本管理神器!简单好用,可以指定某个目录使用的Python版本。
<a href="https://github.com/pyenv/pyenv/wiki">使用手册</a></p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/17" rel="alternate"/><category term="2017"/><category term="Python"/><category term="库"/><published>2023-06-28T15:05:03+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/16</id><title>IO 复用</title><updated>2023-09-29T00:00:49.633676+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-07-01</p>
</blockquote>
<h1>前言</h1>
<p>阻塞、非阻塞、同步、异步,这些IO名词经常听到,但是很容易混淆,这里记录一下他们的区别与联系。</p>
<h1>IO 模型概览</h1>
<p>上述词语都和IO编程相关,我们先来了解一下IO模型。依据《UNIX网络编程》的说法,IO模型共分为五种,分别为:</p>
<ul>
<li>阻塞IO</li>
<li>非阻塞IO</li>
<li>IO复用</li>
<li>信号驱动IO</li>
<li>异步IO</li>
</ul>
<p>下面分别简单介绍之。</p>
<h2>阻塞IO模型</h2>
<p>这是最基本的IO模型,进程通过recvfrom系统调用向内核请求数据,在内核准备好数据并返回给进程之前,进程一直阻塞等待。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/de118e8a-7738-42e5-a471-4678b6ff5dea" alt="c2987e344eb74683.png" /></p>
<h2>非阻塞IO模型</h2>
<p>在非阻塞IO模型中,进程不是被动等待数据返回,而是每隔一段时间就使用recvfrom系统调用询问(轮询)内核数据是否准备好。若还未准备好,则内核会返回一个EWOULDBLOCK错误;若准备好,则进程执行recvfrom系统调用。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/32b4601e-b2e5-4db7-9957-8e4d4f0dd0e2" alt="517ed87866a96306.png" /></p>
<h2>IO复用模型</h2>
<p>在IO复用模型中,多个进程可以同时请求数据并阻塞,使用select或poll系统调用,由select或poll代为请求内核的系统调用。当某个进程的数据准备好之后,即通知该进程执行recvfrom系统调用,其他进程继续等待。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/dba75fdc-b7c4-48c9-89e6-8ff78be4d874" alt="5e9e78495bae8a2a.png" /></p>
<h2>信号驱动IO模型</h2>
<p>通过系统调用sigaction,为进程安装一个信号处理程序,进程可以非阻塞地继续执行。当数据准备好,内核为进程生成一个信号,通知进程,然后进程执行recvfrom系统调用。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/f6c7ccbb-b47f-4d5b-ad4d-9175d763dfff" alt="d382490c447f9363.png" /></p>
<h2>异步IO模型</h2>
<p>异步IO与信号驱动IO类似,都是由内核通知进程,但是区别在于:信号驱动IO是由内核通知进程何时可以启动一个IO操作,而异步IO是由内核通知进程IO操作何时完成。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/5b7b3bd6-67f3-4199-9f0e-35ee3c04288a" alt="bae9f8aa06e61d9b.png" /></p>
<h1>IO模型比较</h1>
<p>前四种IO模型的主要区别都在第一阶段,而第二阶段,在数据从内核拷贝到用户空间时,都是阻塞于recvfrom调用;异步IO的两个阶段都不同于前四种模型。
<img src="https://github.com/zzy131250/gitblog/assets/7437470/a6aecea6-5695-41f8-8114-c55c5d69980f" alt="0a91ca3466b600c7.png" /></p>
<h1>同步IO与异步IO</h1>
<p>Posix.1定义:</p>
<ul>
<li>同步IO操作引起请求进程阻塞,直至IO操作完成</li>
<li>异步IO操作不引起请求进程阻塞</li>
</ul>
<p>根据上面的定义,前四种模型都是同步模型,而异步IO模型属于异步模型。</p>
<h1>总结</h1>
<p>同步、异步的区别是返回结果的方式:同步是直接返回结果;异步是通过通知、回调等方式返回结果。
阻塞、非阻塞都描述同步IO模型,他们的区别是进程等待结果时的状态:阻塞是进程挂起等待;非阻塞是进程继续执行,不挂起,并进行轮询。</p>
<h1>参考资料</h1>
<ol>
<li><a href="https://m.douban.com/book/subject/1500149/">UNIX网络编程</a></li>
</ol>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/16" rel="alternate"/><category term="2017"/><category term="IO"/><category term="异步"/><published>2023-06-28T14:52:23+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/15</id><title>目标遐想</title><updated>2023-09-29T00:00:49.724242+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-05-14</p>
</blockquote>
<p>一直认为自己是一个目标明确的人,但有时候还是会对目标有些疑惑。我想,不如在这里记录下自己的思考,让头脑不再模糊。</p>
<p>记得在刚进大学那会儿,觉得世界好大,觉得自己充满好奇,充满活力,什么都想尝试。那时候报了好多社团,迫切地想要拥抱世界,拥抱大学。然而,接下来的时间,我经历了一段迷茫期,觉得对什么都提不起兴趣,觉得人生大概就这样了。我突然很怀念高中那段时光,虽然那时候很累、很苦,但是有目标,有奋斗的动力,愿意付出所有。有朋友说,他不喜欢高中死读书的人,这些人不有趣,不懂得享受生活。但我的看法是,既然有目标,有精力,就为了目标倾尽全力,为了以后不后悔。这在某种程度上也算把自己感动,至少自己不会觉得可惜。</p>
<p>所以,我想我大概是缺了一个目标。</p>
<p>我可能是目的论的拥趸,觉得没有目标,就没有了意义。说到意义,或许,人生本来就没有意义,与世无争也是一种选择。但我总觉得,我拥有了只会有一次的生命,如果不好好珍惜,那该有多可惜啊!</p>
<p>那么,我该把什么当作目标呢?</p>
<p>共产主义的目标是为了全人类的解放,这个目标对我来说,太大,不好把握。功利主义的目标是为最多数人谋取最大利益,对于个人来说,功利主义免不了自私的嫌疑,我不敢苟同。自由主义追求个人的自由,我想,这就是我想要的。</p>
<p>关于自由。这里的自由,是很高的要求,是由自己的理性制定的准则,不是迫于压力,或是被欲望指使。在某种程度上,自由即自律。过去,我也尽量做到自律,但总是容易迷失,不知道为何自律,现在,我明确了目的。</p>
<p>另外,我还想把追求幸福当作个人的目标。当然,这里的幸福,也是种局限的幸福。</p>
<p>首先,它是一种长远的幸福。如果把人生当作一次登山,那么,登上山顶的那一刻,就是最幸福的时刻。但是,你会想让那个时刻在何时出现呢?我们来设想一下,如果那一刻出现在你20岁的时候,那肯定有很多人夸你年少成才,你可以获得极大的荣耀。可是,那之后呢?那之后,你的人生会持续走下坡。相反,如果那个时刻出现在你40岁的时刻,那么在你40岁之前,你的人生都是一个上坡,你会持续的体会到取得成就的幸福感。即使在以后慢慢下坡,你也有理由说服自己,该退休了,这是理所当然的。当然,如果你的人生,不止一次上下坡,而是像过山车一样,上下起伏,我认为那样也是很好的,因为至少有一半时间是上坡。</p>
<p>其次,它是一种高层次的幸福。根据马斯洛的需求层次理论,高层次的需求包含社交需要、尊重需要和自我实现的需要。同样,我所追求的幸福是人际关系良好、自我尊重、自我实现时的幸福。</p>
<p>这段时间有一首很火的小诗,说每个人都有自己的时区,生命就是等待正确的行动时机。我的看法是,光等待是不够的,要行动起来,并等待时机,不然这只会是那些站在原地却幻想成功的人的鸡汤。我喜欢一句相似的话,“但行好事,莫问前程。”我想,这更能催人奋进,也更可能让人获得幸福。</p>
<p>日剧《四重奏》里面,有朱有句台词,“人生轻而易举”。每个人有每个人过自己人生的权利,但是,我不要我的人生轻而易举。</p>
<p>最后,正好趁着母亲节,祝妈妈节日快乐!</p>
<p>2017.05.14 南京</p>
<h1>更新</h1>
<p>一直觉得,似乎自由与幸福就可以是我追求的全部,但最近一些事,似乎一直在印证,还有些东西,也是需要追求的。</p>
<p>这个东西,我觉得是责任。</p>
<p>自由是“从心所欲,不逾矩”,而责任,可以归为“矩”的一部分。从这方面看,目标的排序应该是责任、自由、幸福。</p>
<p>如果不考虑责任,或许会更加幸福。但是,这样的幸福,会变成“不能承受的生命之轻”。不考虑责任,只求自由和幸福,显得自私。</p>
<p>适逢海军成立70周年,祝人民海军生日快乐!</p>
<p>2019.04.23 南京</p>
]]></content><link href="https://github.com/zzy131250/gitblog/issues/15" rel="alternate"/><category term="2017"/><category term="目标"/><published>2023-06-28T14:11:48+00:00</published></entry><entry><id>https://github.com/zzy131250/gitblog/issues/14</id><title>Paxos 算法学习笔记</title><updated>2023-09-29T00:00:49.819473+00:00</updated><content type="html"><![CDATA[<blockquote>
<p>搬运自老博客,发表时间:2017-04-30</p>
</blockquote>
<h1>前言</h1>
<p>前段时间实习,是做一个分布式的数据库系统,当然只是拿开源的分布式系统来使用一下。那时候,简单看了下分布式的基础算法——Paxos算法。最近看书又遇到了,打算好好学习一下,但是发现书上讲的好难懂,于是上网找,发现了土豆(更新:土豆上该视频已下架,链接改为b站的视频)上李海磊老师讲解Paxos的视频,讲得很到位,这里记录一下主要内容。</p>
<h1>Paxos 算法</h1>
<h2>简介</h2>
<p>Paxos算法由Lamport于1990年提出,它是一种基于消息传递且具有高度容错特性的一致性算法。Paxos算法用来确定一个不可变变量的取值,它可以是任意取值,且一旦取值确定,就不再更改,且可被获取。在分布式系统中,可以把它理解为协商一个一致的数据操作序列。这在分布式系统中很重要,因为分布式系统的数据通常有多个副本。如何通过协调,让多个副本执行相同的操作序列,进而保证数据一致性,是分布式系统要解决的基本问题。</p>
<h2>问题定义</h2>
<p>可以把Paxos算法要解决的问题定义为:设计一个系统来存储名为var的取值,这个系统由acceptor来接收var值,由proposer发出var值。系统需要保证var的取值具有一致性,并需要保证具有容错性。这里不考虑acceptor故障丢失var信息问题和拜占庭将军问题。
解决这个问题,关键在以下四个方面:</p>
<ol>
<li>管理多个proposer的并发执行</li>
<li>保证var取值的不可变性</li>
<li>容忍proposer机器故障</li>
<li>容忍半数以下acceptor机器故障</li>
</ol>
<h1>方案一</h1>
<p>方案一先从简单的情况着手,我们先考虑系统由单个acceptor组成。这种情况下,可以通过类似互斥锁的机制来<strong>管理proposer的并发执行</strong>。proposer须先申请到acceptor的互斥访问权,然后再请求acceptor接受自己的值。acceptor负责发放互斥访问权,并接受得到互斥访问权的proposer发出的值。一旦acceptor接受了某个proposer的取值,就认为var值被确定,其他proposer<strong>不再更改var值</strong>。</p>
<h2>具体实现</h2>
<p>Acceptor:</p>
<ol>
<li>保存一个变量var和一个互斥锁lock</li>
<li>prepare()方法加锁,并返回当前var值</li>
<li>release()方法解锁,回收互斥访问权</li>
<li>accept(var, V),如果已经加锁,且var值为空,则设置var为V并释放锁</li>
</ol>
<p>Proposer(两阶段):</p>
<ul>
<li>第一阶段:通过Acceptor::prepare()尝试获取互斥访问权和var值,如果无法获取,则结束</li>
<li>第二阶段:根据var值选择执行方案。如果var值为空,则通过Acceptor::accept(var, V)提交V值;如果var值不为空,则释放锁,获得var值</li>
</ul>
<h2>问题</h2>
<p>如果proposer在获得锁之后,释放锁之前发生故障,则系统将进入死锁。该方案不能容忍proposer机器故障。</p>
<h1>方案二</h1>
<p>为了解决方案一中的死锁问题,<strong>容忍proposer机器故障</strong>,我们引入抢占式访问权。acceptor可以让某个proposer的访问权失效,不再允许其访问,并将访问权重新发放给其他proposer。
为了实现这个目标,我们要求proposer在申请访问权的时候指定编号epoch,越大的epoch越新。acceptor采用“喜新厌旧”的原则,一旦收到更大的epoch,则令旧的访问权失效,然后给最新的epoch发放访问权,并只接受它提交的值。这样会导致拥有旧epoch的proposer无法运行,拥有新epoch的proposer将开始运行。为了保持一致性,不同epoch的proposer之间采用“后者认同前者”的原则,即如果acceptor上已设置了var值,则新的proposer不再更改,并且认同这个取值;如果acceptor上var值为空,proposer才提交自己的值。</p>
<h2>具体实现</h2>
<p>Acceptor:</p>
<ol>
<li>保存var的取值与var对应的accepted_epoch值,并保存最新发放访问权的lastest_epoch值</li>
<li>prepare(epoch)方法先判断参数epoch是否大于自己保存的lastest_epoch,如果大于则更新lastest_epoch为参数epoch值,并返回var的取值;否则返回错误</li>
<li>accept(var, epoch, V)方法先判断参数epoch是否为记录的lastest_epoch值,若相等则更新acceptor的var值与accepted_epoch值;否则返回错误</li>