-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathatom.xml
1046 lines (880 loc) · 192 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>SA-Logs</title>
<subtitle>拥抱开源,分享经验!</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://salogs.com/"/>
<updated>2018-08-16T03:16:10.000Z</updated>
<id>https://salogs.com/</id>
<author>
<name>zhouyq</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Git实用技巧和命令</title>
<link href="https://salogs.com/news/2018/08/16/useful-git-tips-commands/"/>
<id>https://salogs.com/news/2018/08/16/useful-git-tips-commands/</id>
<published>2018-08-16T03:11:50.000Z</published>
<updated>2018-08-16T03:16:10.000Z</updated>
<content type="html"><![CDATA[<p>Git是一个非常强大的工具,它包含丰富的工具用以维护项目。本文我们将会看见一些Git日常使用过程中的实用技巧和命令。希望其中的一些内容能够对读者有所帮助。</p>
<h2 id="Git-diff"><a href="#Git-diff" class="headerlink" title="Git diff"></a>Git diff</h2><p>通常情况下,我们会在自己的独立分支中完成需求开发,此时就会有需求将自己的分支和其他分支进行对比。这个功能可以通过</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git diff branch1 branch</span><br></pre></td></tr></table></figure>
<p>命令来实现。</p>
<p>如果希望对比暂存区和当前的HEAD,那么使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git diff --cached</span><br></pre></td></tr></table></figure>
<p>命令会非常方便。普通的<code>git diff</code>命令默认对比的是没有加到索引中的文件。</p>
<p>恢复暂存区</p>
<p>如果已经将一些文件添加到暂存区后又后悔了,Git提供了多个命令来实现这个功能,具体需要根据当时情况而定。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git rm path/to/file --cached</span><br></pre></td></tr></table></figure>
<p>命令将文件从暂存区索引中删除,但是仍然会将文件保留在工作目录。这比直接使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git rm file -f</span><br></pre></td></tr></table></figure>
<p>命令完全删除文件会安全一点。</p>
<h2 id="Git-reset"><a href="#Git-reset" class="headerlink" title="Git reset"></a>Git reset</h2><p>如果希望恢复一些已经提交的改动,我们可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git reset</span><br></pre></td></tr></table></figure>
<p>命令。该命令有许多不同的行为,因此需要按照实际场景进行使用。</p>
<p>如果希望的是去除所有修改,包括索引中的内容和工作目录中的修改,那么可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git reset --hard</span><br></pre></td></tr></table></figure>
<p>如果仅仅是希望重置索引,那么可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git reset --mixed</span><br></pre></td></tr></table></figure>
<p>命令,这也是git reset命令的默认行为。混合的重置会保留当前工作目录中的改动。最后,如果仅仅希望修改分支的HEAD,可以通过</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git reset --soft</span><br></pre></td></tr></table></figure>
<p>实现。</p>
<p>当运行git reset命令的时候,我们可以指定多个目标文件作为参数传入。当然可以通过</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git reset --hard COMMIT_ID</span><br></pre></td></tr></table></figure>
<p>恢复到指定的提交版本。</p>
<h2 id="Git-stash"><a href="#Git-stash" class="headerlink" title="Git stash"></a>Git stash</h2><p>大家应该对git stash命令并不陌生,它可以通过git stash pop命令方便的将之前的改动恢复回来。然而,如果工作目录中有未追踪的文件,默认情况下是不会将其存入临时储藏区的。为了能够临时保存未追踪的文件,可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git stash --include-untracked</span><br></pre></td></tr></table></figure>
<p>命令。另外一个非常有用的命令是</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git stash list</span><br></pre></td></tr></table></figure>
<p>它能列出临时储藏区中的内容。</p>
<h2 id="历史记录"><a href="#历史记录" class="headerlink" title="历史记录"></a>历史记录</h2><p>Git自带了非常强大的工具来查看项目以及特定文件的变更情况。我个人非常喜欢用其中的一个命令:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git log --graph --decorate --oneline</span><br></pre></td></tr></table></figure>
<p>用以展示经过修饰的提交历史。这个命令非常冗长,因此我建议可以为它创建一个别名(这可能是所有技巧中最有用的,因为许多命令都比较难记)。git log命令可以显示HEAD、所有提交的ID以及分支信息。有了这些信息之后,我们可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git show COMMIT_ID/HEAD/BRANCH</span><br></pre></td></tr></table></figure>
<p>命令来显示更详细的信息。</p>
<p>有的时候我们需要了解谁对一个文件做了哪些改动,这正是</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git blame path/to/file</span><br></pre></td></tr></table></figure>
<p>命令所提供的功能。</p>
<p>之前提到过git diff命令,它也是一个查看历史的工具。例如,如果需要对比当前HEAD和前两个提交,可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git diff HEAD HEAD~2</span><br></pre></td></tr></table></figure>
<p>为了能够展示每个提交中的更详细的更新信息,可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git log --patch</span><br></pre></td></tr></table></figure>
<p>命令。如果只想要看包含关键字“apple”的提交,使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git log --grep apples --oneline</span><br></pre></td></tr></table></figure>
<p>命令。</p>
<p>需要查看历史提交记录中两个点之间的提交历史,我们可以用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git log HEAD~5..HEAD^ --oneline</span><br></pre></td></tr></table></figure>
<p>命令,对于分支可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git log branch_name..master --oneline</span><br></pre></td></tr></table></figure>
<p>修复错误提交</p>
<p><strong>注意:以下一些命令会修改提交历史,使用前请确保了解后再执行。</strong></p>
<p>当提交出错时,我们可能会希望能够修改提交历史。我不建议修改已经推送到远程仓库的提交历史(即使git允许这样做),但是对于本地仓库的提交历史,我个人认为还是可以修改的。通过</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git commit --amend</span><br></pre></td></tr></table></figure>
<p>命令可以删除前一次提交,并创建一个新的提交记录以替代之前的提交。</p>
<p>另一个我最喜欢的git使用技巧是交互式变基(rebase)。它可以用来编辑提交信息,或者将多个提交压缩成一个提交,这也是我最喜欢的一个功能。为了在远程仓库origin的master分支之后的所有提交上执行交互式变基,可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git rebase -i origin/master</span><br></pre></td></tr></table></figure>
<p>该命令会显示提交列表和可执行操作的详细描述。例如以下操作将会把多个提交压缩成一个:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">1 pick 80f2a48 Add feature X</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">2 squash 2c74ea2 Add junit tests for feature X</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">3 squash 4k81nm5 Bugfix for feature X</span><br></pre></td></tr></table></figure>
<p>最终的结果会是生成一个提交消息为“Add feature X”的提交。</p>
<p>如果需要恢复一个有问题的提交,我们可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git revert COMMIT_ID</span><br></pre></td></tr></table></figure>
<p>该命令会创建一个新的提交,让当前项目状态恢复到指定提交之前。</p>
<p>如果我们在修复问题时出现了误操作,例如不小心删除了不应该删除的文件。我们还是可以从版本库中恢复回来,因为git保存了所有修改的版本,包括被移除的提交。git reflog命令就是用来实现这个功能的。</p>
<h2 id="挑拣提交(cherry-pick)"><a href="#挑拣提交(cherry-pick)" class="headerlink" title="挑拣提交(cherry-pick)"></a>挑拣提交(cherry-pick)</h2><p>假设我们和同事在各自单独的分支上进行开发,同事有一个重要的提交我们也想应用到自己的分支上来,但是不需要对方分支的其他提交。这时我们可以使用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git cherry-pick COMMIT_ID</span><br></pre></td></tr></table></figure>
<h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>以上都是我最喜欢的git实用技巧。希望你也能从中学到一些新知识。这些都是我在日常使用中发现非常有用的命令,它们对我的日常工作非常有用。如果你也有类似常用的git实用技巧或者常用命令,可以分享出来大家一起交流。</p>
<p><strong>查看英文原文:</strong><a href="https://thecuriousdev.org/useful-git-tips-commands/" target="_blank" rel="noopener">Useful Git Tips and Commands</a></p>
<p><strong>中文原文:</strong> <a href="http://www.infoq.com/cn/news/2018/08/git-techinique-commands-trick" target="_blank" rel="noopener">Git实用技巧和命令</a></p>
]]></content>
<summary type="html">
<p>Git是一个非常强大的工具,它包含丰富的工具用以维护项目。本文我们将会看见一些Git日常使用过程中的实用技巧和命令。希望其中的一些内容能够对读者有所帮助。</p>
<h2 id="Git-diff"><a href="#Git-diff" class="headerlink
</summary>
<category term="Git" scheme="https://salogs.com/tags/Git/"/>
</entry>
<entry>
<title>迁移到Serverless之前应该想清楚的几件事</title>
<link href="https://salogs.com/news/2018/08/06/What-to-Know-Before-Making-the-Leap/"/>
<id>https://salogs.com/news/2018/08/06/What-to-Know-Before-Making-the-Leap/</id>
<published>2018-08-06T06:48:53.000Z</published>
<updated>2018-08-06T07:02:57.000Z</updated>
<content type="html"><![CDATA[<p><img src="3337e40d-organic-1280537_640.jpg" alt=""></p>
<p>这篇文章是Serverless系列的第三篇文章,这一系列文章是为了探索Serverless的本质。</p>
<p>第一篇:<a href="https://salogs.com/news/2018/08/06/What-is-the-essence-of-Serverless/">Serverless的本质是什么?</a></p>
<p>第二篇:<a href="https://salogs.com/news/2018/08/06/Serverless-Impacts-on-Business-Process-and-Culture/">Serverless 对商业、流程及文化的影响</a></p>
<p>Serverless技术承诺说可以消除IT运维相关的大部分繁重工作,第三方服务商会承担起更多服务器维护相关的管理工作,而不像企业组织为容器编排和微服务平台所做的那样。使用Serverless平台可以节省资源,其中部分原因是因为Serverless允许组织通过 “按使用量付费” 的模式来使用服务器。</p>
<p>迁移到Serverless资源有许多方法,有对的也有错的。在迁移之前,请记住以下几点。</p>
<h2 id="不要被Serverless这个词迷惑"><a href="#不要被Serverless这个词迷惑" class="headerlink" title="不要被Serverless这个词迷惑"></a>不要被Serverless这个词迷惑</h2><p>DevOps的团队成员可能已经听说过Serverless,并希望知道迁移后这个平台可能为他们的组织提供哪些功能,但他们可能一开始就会对Serverless这个词产生疑惑,Serverless意思是无服务器的。但实际上,”Serverless” 应用程序仍在服务器上运行。</p>
<p>“Serverless” 这个词的使用可以追溯到至少2012年,当时Ken Fromm在一篇ReadWrite论文 “<a href="https://readwrite.com/2012/10/15/why-the-future-of-software-and-apps-is-serverless/" target="_blank" rel="noopener">为什么软件和应用程序的未来是无服务器</a>” 中使用了这个术语。一个更精确,合适的定义出现在2016年一篇开创性的博客文章中,由Symphonia的合作伙伴兼联合创始人Mike Roberts在Martin Fowler的网站上提出。他写到Serverless架构是 “<strong>将第三方 <code>后端即服务</code>(BaaS,Backend as a Service)服务,和包含自定义的服务代码的容器整合在一起的应用,并将应用运行在 ‘功能即服务(Function as a Service,FaaS)’平台上。</strong>”</p>
<p>Roberts写道:“通过整合实践这些想法以及相关的类似于单页面应用程序(SPA)的想法,这种架构消除了传统的对永远在线服务器组件的大部分需求。Serverless架构帮助企业降低运营成本,复杂性和工程准备时间,但代价是增加了对供应商的依赖和相对不成熟的支持服务的依赖。”</p>
<p>以上定义概括了许多供应商提供的Serverless产品,包括亚马逊网络服务(AWS)的<a href="https://aws.amazon.com/lambda/" target="_blank" rel="noopener">Lambda</a>,它被广泛使用并提供BaaS(Backend as a Service)和FaaS(Function as a Service)服务。其他Serverless产品包括 <a href="https://cloud.google.com/functions/" target="_blank" rel="noopener">Cloud Functions</a>(Google),<a href="https://www.iron.io/platform/ironworker/" target="_blank" rel="noopener">IronWorker</a>(Iron.io),Manta Functions(Joyent),<a href="https://new-console.ng.bluemix.net/openwhisk/" target="_blank" rel="noopener">OpenWhisk</a>(IBM),<a href="https://www.pubnub.com/" target="_blank" rel="noopener">PubNub BLOCKS</a>(PubNub)以及<a href="https://github.com/bfirsh/serverless-docker" target="_blank" rel="noopener">Serverless Docker</a>(Docker)。</p>
<h2 id="了解Serverless的优缺点"><a href="#了解Serverless的优缺点" class="headerlink" title="了解Serverless的优缺点"></a>了解Serverless的优缺点</h2><p>Serverless的优点在于它能够通过将服务器和相关的基础架构管理任务外包给第三方来帮助公司节省资源。例如,开发人员团队只需要开发应用程序,而Serverless提供商则在异地服务器上完成所有数据维护工作。</p>
<p>Cloud Academy的产品营销经理Albert Qian表示: “Serverless的易用性可以提高开发人员的效率,Serverless计算使开发人员不用接触基础设施,他们可以完全只专注开发单一功能”。</p>
<p>Serverless供应商允许组织根据他们的需求进行扩展或缩小,不再担心冗余的服务器容量。 </p>
<p>“Serverless架构让云服务供应商管理服务器以及服务器的扩展以满足需求。将Serverless视为平台即服务(PaaS)的抽象,您需要做的就是上传代码,当您需要向上或向下扩展某个功能时,您只需扩展这个单一功能,”Qian说。 “因此,您无需扩展整个系统,容器或应用程序。另一个好处是无服务器具有内置的容错能力和设计的的高可用性。”</p>
<p>因此,企业可以通过依赖第三方执行操作和数据管理任务来节省资金。 “Serverless的最大优势之一是它可以降低成本。使用FaaS作为您的计算层,您只需为正在运行的功能付费,”Qian说。 “尤其是对于利用率较低的工作负载,您会节省很多支出。因为您只需支付处理请求所需的计算资源,而不是为等待请求进入的空闲服务器付费。”</p>
<p>但是,通过将服务器管理移交给第三方,您的组织也失去了对服务器的大部分控制权, 这一点在您确定是否以及何时迁移到Serverless时是需要慎重考虑的。</p>
<p>“由于Serverless解决了资源管理问题,它正在迅速发展。对于不想维护服务器的小公司来说,这是完美的解决方案,”Raygun的高级开发人员Jeremy Scott说。 “您可以在几乎完全不用关心运维问题的前提下将工作完成,并且比使用自己搭建的服务器架构更便宜。但是Serverless也有其局限性,当你交出责任时,你同时也交出控制权和访问权,这样一来,对你的服务的监控和分析会变得非常困难。”</p>
<p>通过这种方式,对在Serverless平台上运行的微服务和容器进行分析可能成为一个问题。 “你必须了解你的软件,否则就有可能在没有数据支持的情况下做出商业决策,”Scott说。 “因此,如果您使用Serverless,那么在某些情况下您可能需要在客户端(例如App或者浏览器)添加监控,或者您可以尝试为Serverless Function添加异常处理。”</p>
<p>虽然具有挑战性,但将分析软件应用于Serverless部署仍然是可行的。如果为Serverless Function添加了异常处理,可以将Function包装在try-catch中然后发送https API请求,那么错误信息就会被记录在错误报告客户端(例如:Raygun[2])。然而,如果是基于API的服务,将无法被监控,另一种解决办法就是观察API的行为方式”,Scott说。</p>
<p>在决定哪些功能可以移植到Serverless架构时,需要考虑到Serverless部署的缺点——不太强大的计算性能。 “Serverless Function有很多限制。任何需要超过3GB内存的功能都必须重新设计否则无法运行,”Cloud Academy的Qian说。 “要确保团队了解这些限制,并确保他们有足够的知识,经验和培训,来决定适合特定工作负载的最佳技术,这一点非常重要。Serverless或任何新技术的‘酷’因素在架构方面有时会笼罩批判性思维,结果可能导致的松散的Function的组合,而不是整体一致的应用程序。”</p>
<h2 id="安全清单"><a href="#安全清单" class="headerlink" title="安全清单"></a>安全清单</h2><p>就像监控和分析一样,在Serverless架构上保护你的数据也十分困难。毕竟服务器的管理和控制都被转移给了第三方。</p>
<p>“例如,Serverless运行时环境不是标准化的,并且被运行的云服务隐藏起来,这使得我们更难以保护云上的数据”,Aqua Security营销副总裁Rani Osnat表示。“对于无服务器,我觉得有几个关键的安全问题,”Osnat说。在进行迁移时,某些安全检查尤其重要。 例如,如果在Serverless Functions中使用了潜在的易被攻击的代码,那么需要在使用之前解决这个问题”,同时对于那些违反政策的FaaS Functions, 我们必须使用强制控制来阻止这些功能的部署。</p>
<p>Osnat说道:“尽管Serverless架构如此具有挑战性,企业组织们仍需要确保他们的Serverless平台在运行时会被监控,以便监测异常。”</p>
<p>Cloud Academy的Qian表示,当迁移到无服务器时,同时也会面对多用户的挑战。 “即使客户的工作负载在虚拟机或容器中是被隔离的,也可能存在安全问题,特别是如果应用程序使用敏感数据。 一个客户代码中的潜在的bug可能会影响另一个应用程序的性能,从而影响客户服务和应用程序的质量。”</p>
<blockquote>
<p>译者:Grace Linktime Cloud公司全栈工程师</p>
<p>原文链接:<a href="https://thenewstack.io/serverless-migration-strategies-what-to-know-before-making-the-leap/" target="_blank" rel="noopener">https://thenewstack.io/serverless-migration-strategies-what-to-know-before-making-the-leap/</a></p>
</blockquote>
]]></content>
<summary type="html">
<p><img src="3337e40d-organic-1280537_640.jpg" alt=""></p>
<p>这篇文章是Serverless系列的第三篇文章,这一系列文章是为了探索Serverless的本质。</p>
<p>第一篇:<a href="https://
</summary>
<category term="serverless" scheme="https://salogs.com/categories/serverless/"/>
</entry>
<entry>
<title>Serverless 对商业、流程及文化的影响</title>
<link href="https://salogs.com/news/2018/08/06/Serverless-Impacts-on-Business-Process-and-Culture/"/>
<id>https://salogs.com/news/2018/08/06/Serverless-Impacts-on-Business-Process-and-Culture/</id>
<published>2018-08-06T04:27:38.000Z</published>
<updated>2018-08-06T06:43:25.000Z</updated>
<content type="html"><![CDATA[<p><img src="7525865c-chu-tai-121706-unsplash.jpg" alt=""></p>
<p>这篇文章是Serverless系列的第二篇文章,这一系列文章是为了探索Serverless的本质。</p>
<p><a href="https://salogs.com/news/2018/08/06/What-is-the-essence-of-Serverless/">第一篇:Serverless的本质是什么?</a></p>
<p>越来越多的企业与创业公司选择Serverless技术,并且Serverless技术发展速度远远快于之前的Container技术,而Container技术则是最近一次被行业广泛采用的新技术。</p>
<p>不论从何种维度分析,都可以看到Serverless这项技术正引起人们的兴趣。</p>
<p><img src="c8535704-twitterreach-serverless-300x220.png" alt=""></p>
<ul>
<li>ServerlessConf以及ServerlssDays这两个活动的参与人和演讲人数量和范围一直在增长扩大。</li>
<li><a href="https://www.developereconomics.com/reports/developer-economics-state-of-the-developer-nation-14th-edition" target="_blank" rel="noopener">Developer Economics最新的季度调查表明</a>,19%的后台人员正在使用Serverless平台,这个数据已经接近于使用虚拟机的人数了。而在6个月前,这个数字还只是16%。</li>
<li><a href="http://get.cloudability.com/ebook-state-of-cloud-2018-thank-you.html?aliId=3136040" target="_blank" rel="noopener">Cloudability的2018年度云状态报告</a>中分析了1500个组织的IT支出,结果显示Serverless的环比增长率为 667%。</li>
<li>TweetReach 估计每小时都有100条左右的推文被标记成serverless</li>
<li>谷歌搜索显示serverless的搜索量在最近12月内稳步增长。</li>
</ul>
<p><img src="68e1b769-google-trends-serverless.png" alt=""></p>
<p>目前只有一些创业公司会在全面使用Serverless,大部分的企业只会在一些特定的项目中采用Serverless技术来测试基础架构的方案,以及测试团队文化是否适合实施Serverless。</p>
<p>尽管Serverless技术只被用于一些独立的项目上进行测试,它们仍引起了多米诺骨牌效应,以一种意想不到的方式影响了公司业务。Serverless缩短了生产时间,引入了cost-for-usage(根据使用量付费)模型并鼓励团队成员更加自主。这些改变影响了企业的运作方式,包括如何选择需要在Serverless上运行的功能,企业研发新产品新功能的意愿,以及如何管理预算,程序员与产品经理如何履行职责等等。</p>
<h2 id="Serverless和微服务"><a href="#Serverless和微服务" class="headerlink" title="Serverless和微服务"></a>Serverless和微服务</h2><p>O’Reilly系列的Microservices Architecture(微服务架构)书中提到 “微服务架构使Serverless更加快速,微服务使一个单体应用代码被拆分成小型的自治服务。因此,企业可以大规模的快速安全的构建解决方案”。</p>
<p>今年年初的<a href="https://blog.algorithmia.com/introduction-to-serverless-microservices/" target="_blank" rel="noopener">一篇博客中</a>,机器学习平台Algorithmia的Mike Anderson演示了一个典型的高科技公司通过微服务将标准发布周期从65天缩短至16天。他写道“<strong>微服务帮助他们解耦了开发流程中的阻塞部分,缕清以及隔离了问题,使他们只关注组件级别的变化。</strong>”</p>
<p>当一个组织的代码是以微服务而不是单体应用的形式组织的时候,才可能使用Serverless技术。现在,企业可以识别出适合在Serverless架构运行的任务,这些任务是一些特定的,无状态的任务。云平台会通过分配计算资源来解决问题,运行服务器来做计算,在数据转换完成后将这些操作任务关闭。</p>
<p>Red Hat产品管理部门的高级主管Rich Sharples 说道,该公司在过去两三年一直关注Serverless技术并且正在开发一个开源的,企业级别的Serverless产品,这个产品可以与Kubernetes一起使用。Red Hat希望今年能够发布整个产品。</p>
<p>Sharples说道 “通过客户,我们发现技术的技术成熟度曲线( The Hyper Curve)飙升的十分迅速,例如微服务,而Serverless攀升的速度更快”</p>
<p>因为微服务的增量交付,灵活,快速交付的特点,微服务受到越来越多企业的欢迎,Sharples说道“但是在我看来,这些使用者在疲于应对分布式系统管理和监控的运维复杂性,这也是为什么Serverless会引起他们的兴趣,通过Serverless,你可以获得微服务的好处,从开发人员角度来看,这也非常简单——仅仅只用修改一些代码。在运维人员看来,Serverless是一种非常适合建立自动化运维系统的模型,例如将回复一个日志事件实现成Serverless方法调用将会非常方便“。Sharples说道从他与企业级用户的日常对话中,可以感受到他们对Serverless的兴趣。</p>
<p>Sharples说道 “<strong>任何短时间运行的,无状态的,可以独立运行的任务都适用于Serverless</strong>”。</p>
<p>开源软件API Gateway Kong的高级解决方案工程师Aaron Miler说道,大部分开始设计Serverless架构的客户基本都采用了自下而上,或者自上而下的方案。使用自下而上方案的客户已经将他们的传统架构转换成微服务架构,并且正在寻找一种方案可以让他们在整个企业以不同方式使用Kong。他们倾向于选择开源技术公司的企业级方案,并且希望通过发掘Serverless方向的工作机会来进入一个新的领域。</p>
<p>Miller说道:“在Kong,的确有一些优秀的Serverless项目引起了那些想要快速跟进新技术的CTO的关注。Expedia的Serverless项目就采用了Kong的技术,Expedia是Serverless的深度用户,这家公司在他们所有日常的API网关都使用了Kong,因为Kong提供了AWS Lamda插件,通过插件,Expedia可以很方便的使用AWS Lamda,例如,每当用户在Expedia上订了一间房或是一张机票时,Expedia都会为客户提供一个可以用于下次消费的折扣券。Serverless技术非常适用于这种场景。”</p>
<p>Miller谈到,虽然减少成本是Serverless的一大优势,但是它更大的优势在于加快了开发速度。“如果团队只用专注于写代码,只用关心函数功能的质量,不用担心这些代码是在Linux哪个版本上运行,这些问题将由别人解决,而团队只用关心如何用Node、Python或是其他Serverless语言写代码,这样一来,每个人都从中收益。它可以让人更加专注于自己的事情同时交付更多的功能。”</p>
<h2 id="哪些功能应该运行在Serverless上呢?"><a href="#哪些功能应该运行在Serverless上呢?" class="headerlink" title="哪些功能应该运行在Serverless上呢?"></a>哪些功能应该运行在Serverless上呢?</h2><p>许多企业和创业公司都找一个简单的例子来测试Serverless的潜力。The New Stack会定期列举一些简单事件作为Serverless用例( <a href="https://thenewstack.io/serverless-architecture-five-design-patterns/" target="_blank" rel="noopener">a number of the low-hanging fruit use cases for serverless</a>)。</p>
<ul>
<li>调整图片大小(或是任何一个需要清理或者操作媒体文件的任务)</li>
<li>需要清理非结构化数据或是将数据转换成某种标准格式的ETL任务</li>
<li>克朗型作业(一种自动化的定期运行的任务)</li>
</ul>
<p>Steve Faulkner曾经帮助媒体公司Bustle公司构建Serverless基础架构,该公司现在所有的运维都在Serverless的架构上运行,只有一个兼职的运维员工, Steve有一个简单的经验法则来帮助企业找到从哪可以开始他们的Serverless旅程。</p>
<p>他问到 “你的CI/CD流水线有哪些缺陷?一个测试你的CI/CD流水线的简单方法:一个非技术人员是否可以对您的网站或文档中的文本或副本进行更改?将更改部署到生产的速度有多快?大多数公司甚至不能这样做。”</p>
<p>Faulkner说道,他见过有个CEO看到网站上有个错别字,改正后通过Git中提交,一小时内更改就在生产中生效了。“这是一个很高的标准,但是这对于找到你的CI/CD的缺陷是一个很好的测试,并且这也是一个Serverless解决问题的案例”。</p>
<p>Kong公司的Miller提出了一种适合使用Serverless的场景,当某个处理任务所需的资源是不可预估的时候,这个任务就很适合使用Serverless。“在任何你不确定计算容量的地方:比如你不确定使用1还是100台服务器,如果没有方式可以预估,那么这个任务就很适合用Serverless处理”。另一种情况则是当你有很高很多的需求,但是预算又非常紧张的时候,Serverless可以帮助你很好的控制成本。</p>
<p>就像是Expedia的用例一样,他也建议技术领导与产品经理以及销售和营销团队一起讨论企业当前如何提高用户的粘性,在此之中或许可以发现一些合适的用例。</p>
<p>这通常是James Beswick的起点,该公司的Indevelo开发商店为用户创建网页和移动应用程序,他们的企业和B2B客户端都是Serverless架构。最初,Beswick在所有标准图像大小调整类型任务中使用Serverless,但在纽约最近一个ServerlessConf中,“我突然意识到我可以将所有的工作都放在无服务器上,”他说。</p>
<p>Beswick说,每次跟新客户讨论时,他通常不会从一开始就讨论Serverless,而是把低廉的使用成本作为他的主要卖点。</p>
<p>“我们必须解释整个事情,”Beswick说。 “首先我们谈论软件需求是什么,然后谈论架构。在架构方面,我们从财务方面入手。我们解释了为什么Serverless更便宜,更易用。”</p>
<p>Beswick说,因为Serverless价格“非常难以估计”,他倾向于给潜在客户一个估价范围。 “网络规模的应用程序非常昂贵,但是你得运行它们”Beswick说。我倾向于表明,如果某个应用程序正在进行10,000次交易,那么每月帐户看起来如何?如果一个事件每个月花费500美元,那么什么可能会导致它突然每月花费5000美元?”</p>
<h2 id="Serverless如何改变业务的?"><a href="#Serverless如何改变业务的?" class="headerlink" title="Serverless如何改变业务的?"></a>Serverless如何改变业务的?</h2><p>从成本的角度出发,促使企业思考Serverless架构可能会对他们未来运行公司的方式产生巨大的影响。</p>
<p>例如,在美国,IT项目可能会根据人力资源和资本支出(capX)进行预算,而不是纯粹的成本分配和运营支出(opX)。许多企业将其IT视为capX,因此获得特定的税收折扣。但是对于云环境,对于Serverless的情况更是如此,其中定价是基于实际使用情况,它全部是opX。这让一些首席财务官感到担忧,他们害怕失去IT项目的税务减免。在进行项目预算时,其他的项目并没有设置成可以降低现有员工的工资成本。</p>
<p>“你可能会为一名数据库员工每年支付12万美元,”Beswick解释说。 “如果您正在运行AWS Aurora,那可能会花费500美元一个月,这要便宜得多,但人们并没有考虑到人员成本,因为它被分成了多个部分。“Beswick说,企业将不得不推出新的会计方法,这不是寻求CapX税务减免,而是一种纯粹的成本分配,人力资源预算将会采用新的方式,而将运维成本划分到项目成本之中。</p>
<p>Serverless的引入也带来了其他的影响。其中一些影响在云技术和SaaS技术中产生,但随着Sevrerless应用的发展,它影响的速度和范围也在增加。</p>
<p>Beswick指出,他的设计店经常被IT以外的业务部门雇用,例如企业的营销和销售团队。例如,SaaS工具只被一个部门使用,但随后数据集成和移动设备集成则意味着跨越多个业务线,应用程序开发必须找到解决方法来限制传统IT部门的参与。 “当你与IT部门合作时,有一系列因素在起作用:他们不在权威,他们对发生的事故感到不满,并且怀疑它是否应该在Serverless中运行。 IT团队在这方面如此落后,“Beswick警告说。</p>
<p>“IT部门正在变懒,他们掉入了不想改变任何事物的陷阱之中。但大多数企业都看到自身行业发生的巨大变化,所以他们不想再依赖IT部门,而是从外部寻求问题的解决方案,”Beswick认为,大多数企业都在处于一个中间过渡时期,新的组织架构会代替传统IT部门的组织结构和作用,而Serverless正在加速这个过程。</p>
<h2 id="Serverless正在改变流程"><a href="#Serverless正在改变流程" class="headerlink" title="Serverless正在改变流程"></a>Serverless正在改变流程</h2><p>Faulkner说道:“我确实看到了Serverless带来的业务流程的变化。 我认为Serverless不仅仅提高了软件开发的速度,还帮助你模块化你的应用程序。 我们通过Serverless的方式重新构建了Bustle。 Serverless架构是模块化的,并且具有许多功能,因此更容易与其他的应用适配。 在去年,我们开始建立可以搭载事件的架构并提供新价值的业务系统。”</p>
<p>Beswick也同意这个观点。 “想想以前的世界,MVP(Minimum Viable products 最小可行性产品)是不可扩展的,它们并不可靠。 如果想法真正被采用并进入生产阶段,那么MVP的大部分工作将被丢弃。 但是现在有了Serverless之后,我们可以轻易的将MVP投入生产来构建高级应用程序,从而解决问题,它变得更灵活。 以前当一个功能或应用规模扩大时,你只能默默祈祷不要发生问题,而现在Serverless使规模缩放变得更稳定。”</p>
<p>Beswick说,通过微服务和Functions,构建MVP所需的很多代码块都已经实现。 通过Serverless(根据使用量付费),企业可以加快开发速度,快速向客户群体发布原型以获得反馈,从而降低风险。 如果他们成功了,他们可以更快地转向生产,而无需重建整个应用。</p>
<p>保险科技创业公司Fabric的联合创始人兼首席技术官Steven Surgnier表示,在他们的业务中使用Serverless架构的确为该公司带来了的业务流程的优势,正如Faulkner和Beswick之前所提到的一样。</p>
<p>“最终都归结于执行力,”Surgnier说。 “作为一家早期的创业公司,我们希望迅速执行并最大限度地降低整体技术的复杂性。 我们使用Serverless技术来减少从我们的想法到客户手中的最终产品的时间。”</p>
<p>Miller和Surgnier都赞同一种观点,即Serverless技术帮助企业留住关键开发人员。 Miller说,企业内部的开发团队通常对测试新技术感兴趣,他们会找到一个项目,分配一个小团队,并给他们几个星期来建立一个原型。 “这些企业通过为开发人员提供有趣的工作来吸引和留住优秀的人才,”Miller解释说。 Surgnier说Serverless对公司和工程师都有好处:“工程师喜欢创造。 因此,通过为他们提供Serverless基础架构和专注于产品的机会,可以让工程师在其职业生涯中创造出更多的产品。”</p>
<h2 id="Serverless是如何改变企业文化的?"><a href="#Serverless是如何改变企业文化的?" class="headerlink" title="Serverless是如何改变企业文化的?"></a>Serverless是如何改变企业文化的?</h2><p>Steve Faulkner说Serverless改变了软件开发的基本流程,在这个过程中培养企业内部的开发人员自主性。</p>
<p>作为Bustle的Serverless的冠军,他花费大部分时间来维护第一批bash脚本,并在之后构建了一个十分成熟的框架。 “我希望人们了解它是如何工作的,但我也坚信任何人都可以随时用一个命令部署生产环境,”他说。</p>
<p>Serverless鼓励这种程序员自主开发的文化。 “如果有的话,一切都会更快,”福克纳说。 “一个完全部署的Lambda的更改可以在30秒内完成。 如果你知道部署到生产需要一些过程,那么它会成为一种精神障碍,并会引入惰性,就像是我现在应该做么?还是我应该等待别人去做这件事?Serverless帮助开发者解除了这种精神障碍。 现在任何人都可以随时部署到任何生产端点,这就会给赋予人们自主权和能力。”</p>
<p>Faulkner说,在Bustle,这已经是文化的一部分,而大公司可能有一种根深蒂固的文化和观念,即鼓励开发者们在签字(上级认可)后才开始做事。 “我想如果你在大企业中使用Serverless,如果文化已经很糟糕,Serverless也无法帮助到你。”</p>
<p>Faulkner表示,Serverless也加速改变了另一种文化,即对轻量级架构决策记录的需求。 “人们需要记录他们正在做的事情,”他急忙说道。 “如果我们的目标是任何时候任何人都可以部署到生产,我们必然会有很多短暂存在的feature branch(Git的功能分支)。那么你需要一个功能标记系统,这样生产中的东西就不会完全暴露给外界。“尽管Faulkner表示这还不是规范,并且是分布式软件应用有着更广泛的趋势,这种议程和需求是由影响无服务器的采用创造了速度和发布生产级变更的能力。</p>
<p>“我最近的观点是,随着应用规模不断扩大,功能标志(feature flag)可能是改善软件开发过程最有用的方法。”Faulkner建议道。</p>
<h2 id="Serverless将会成为行业的兴奋剂"><a href="#Serverless将会成为行业的兴奋剂" class="headerlink" title="Serverless将会成为行业的兴奋剂"></a>Serverless将会成为行业的兴奋剂</h2><p>Indevelo的Beswick认为,在许多企业,IT主管和业务部门经理会对未来的发展存在分歧。 云技术已经引入了新的领域,但Serverless的出现则向前推进了更大的一步。</p>
<p>“我从未像现在这样对一项技术感到如此兴奋,”Beswick说道,Faulkner,Surgnier,Miller和Sharples都表达了同样的观点。 “Serverless使所有事物汇集于一处,”Beswick继续说道。 “Serverless能交付的远超想象, 以后可以将它添加到机器学习,也可以被应用于移动方向。 你需要以一种全新的思维方式去思考,而不是继续维持老旧的思想,这才是Serverless令人兴奋的地方。 我们可以更好地去询问客户的问题,以及如何让每个人更容易解决这些问题。”</p>
<blockquote>
<p>译者:Grace Linktime Cloud公司全栈工程师<br>原文链接:<a href="https://thenewstack.io/serverless-impacts-on-business-process-and-culture/" target="_blank" rel="noopener">https://thenewstack.io/serverless-impacts-on-business-process-and-culture/</a></p>
</blockquote>
]]></content>
<summary type="html">
<p><img src="7525865c-chu-tai-121706-unsplash.jpg" alt=""></p>
<p>这篇文章是Serverless系列的第二篇文章,这一系列文章是为了探索Serverless的本质。</p>
<p><a href="https://
</summary>
<category term="serverless" scheme="https://salogs.com/categories/serverless/"/>
</entry>
<entry>
<title>Serverless的本质是什么?</title>
<link href="https://salogs.com/news/2018/08/06/What-is-the-essence-of-Serverless/"/>
<id>https://salogs.com/news/2018/08/06/What-is-the-essence-of-Serverless/</id>
<published>2018-08-06T03:08:04.000Z</published>
<updated>2018-08-06T06:33:57.000Z</updated>
<content type="html"><![CDATA[<p>Serverless直译为中文是 “无服务器”,但是实际上它仍需要服务器,只不过服务器的管理以及资源分配部分对用户不可见,为避免误导读者,译文中还是将英文保留。</p>
<p>最开始,一台单用户的物理服务器便能满足我们的日常所需,它快速,可靠并且安全,只对管理员负责。但是在实际中配置和扩展都很麻烦。虚拟机的出现满足了灵活性和可扩展性的需求,之后云服务提供商为我们带来了基础架构即服务(IaaS),云平台自助服务也由此诞生。在这片肥沃的土壤中出现AWS(Amazon Web Services),编排,以及基础设施即代码(IaC),之后开始了集装箱化,带来了平台即服务(PaaS)的架构,一切看起来都很顺利……但程序员仍想要更多的功能,如独立于编程语言(language agnostic)的端点,服务器的水平伸缩能力,以及可以实时支付服务使用量的能力。</p>
<p>为了满足这些需求,Serverless计算应运而生,Serverless计算也被称为功能即服务(FaaS)。运行时只会执行程序但不会存储数据。这意味着像AWS(Amazon Web Service),谷歌云以及微软Azure云这样的云服务提供商会动态的管理资源的分配和分布。</p>
<p><strong>Serverless是付完即走</strong>,基于实际的消费而不是基于预测的预付款进行收费的。这本是基础设施应该有的样子,在2018年终于出现在我们面前。</p>
<h2 id="这并不是魔法"><a href="#这并不是魔法" class="headerlink" title="这并不是魔法"></a>这并不是魔法</h2><p>首先,这个名字非常有误导性。 Serveless(无服务器)计算仍然需要服务器,它并不是什么神秘魔法。</p>
<p>这个术语的出现是因为<strong>服务器的管理以及容量规划都被隐藏起来了</strong>。Serverless被称作是无服务的是因为用户/程序员无需关心甚至意识到基础架构的存在——服务器被完全抽象出去了。Serveless代码可以和传统方式部署的代码一起使用,例如微服务,甚至一个应用程序可以完全不需要配置服务器,只需要以无服务器的方式编写即可。</p>
<p><strong>Serveless 真正的价值不在于节省了成本,而在于节省了时间</strong>。</p>
<p>Bitnami的现任云技术高级总监Sebastien Goasguen说到 “我会把Serverles 想像成一个具有微型PaaS的像胶水一样的软件,它最大的优点就是可以从云中发生的事件中调用函数(即胶水)“,Goasguen描述了一个场景,例如,将图像放在AWS(Amazon Web Service)的storage bucket中存储,然后调用函数来调整该图像的大小。Serverless系统会获取这段代码并且自动注入到运行时环境(服务器或者容器),之后将这个函数暴露出来以便调用。</p>
<h2 id="那么,讲完了什么是Serverless,然后呢?"><a href="#那么,讲完了什么是Serverless,然后呢?" class="headerlink" title="那么,讲完了什么是Serverless,然后呢?"></a>那么,讲完了什么是Serverless,然后呢?</h2><p><strong>传统云计算和Serverless云计算最主要的区别在于客户是否需要为未被使用或者未被充分使用的资源支付费用。</strong>以前,无论是内部数据中心还是云上,我们都需要提前预测容量和资源需求,并且提前准备好。在之前的例子中,这意味着我们提前启动AWS服务器以便随时执行这项调整镜像大小的服务。而在Serverless配置中,你只需要调整代码执行的时机,即只在函数被调用时候执行。</p>
<p>Serverless计算服务将您的函数作为输入,执行逻辑,返回输出,之后关闭。你只需要为函数实际执行所消耗的资源付费。</p>
<p>即用即付(Pay-as-you-play),并且只用为你实际使用的资源付费,这显然是件好事,但是Goasguen以及其他Cloud Native的专家强调Serverless真正的价值在于时间效率,而不是成本效率。</p>
<h2 id="像时间机器一样?"><a href="#像时间机器一样?" class="headerlink" title="像时间机器一样?"></a>像时间机器一样?</h2><p>Serverless就像一扇通往未来的门,它和其他即服务(other as-a-service)的技术一样,为公司提供了很多工具,可以让公司专注于构建使用如AI,机器学习等尖端技术的应用程序。而不是浪费时间精力在不停的构建,重建必需的基础设施。</p>
<p>Serverless其他的时间机器功能在于缩短了从代码开发到投入生产的时间。它真正实现了“这是我的代码,现在立刻运行它”。不会因为基础设施产生延迟。</p>
<p>Oracle公司负责Serverless业务的副总裁Chad Arimura说到“Serverless的基本思想是程序员只需要写代码然后推送到Serverless的服务就足够了,其余的事情都由这个服务来处理”。他还补充道,像数据库和数据存储这类的依赖关系也被当作服务无缝集成到Serverless底下。</p>
<p>Arimura说到“在Serverless背后,专业的团队和自动化工作相结合,大规模的操作这些系统,以便开发人员无需考虑这些问题,这项技术看起来就像是魔法一样,这也是为什么这项技术被炒作的如此厉害,正因为它带给了我们如此美妙的体验”。</p>
<h2 id="感受-FaaS(功能即服务)平台的强大"><a href="#感受-FaaS(功能即服务)平台的强大" class="headerlink" title="感受 FaaS(功能即服务)平台的强大"></a>感受 FaaS(功能即服务)平台的强大</h2><p>虽然Docker技术简化了分布式应用打包和依赖管理的问题。Kubernetes帮助企业在生产中运行这些应用。但是使用它们并不简单易用。Dcokerfile、基础设施细节、Kubernets manifests文件,这些即便对于程序员听众来说,也异常复杂。</p>
<p>Serverless计算的核心就是作为一个功能即服务平台。从本质上讲亚马逊的AWS Lambda以及Google Cloud Function就是Serverless的实现。它们处理了资源管理,负载均衡以及多线程,开发人员就可以只关注他们的代码,而企业只用关心他们的目标。</p>
<p>iguaz.io的创始人兼CTO Yaron Haviv提到,一个持续的数据平台是为提升云上的性能而设计的,将一个新的技术栈按照FaaS(功能即服务)的工作流运行一遍,步骤如下:</p>
<ol>
<li>Serverless平台提取了功能代码——即FaaS(功能即服务)的功能部分——以及所有依赖项(如必需的库,内存的数量,属性等等),构建成一个集装箱化的应用程序包,通常采用Docker镜像的形式。</li>
<li>当另一个平台服务,例如对象存储或数据库想要触发这个功能,或者一个外部的http请求想要调用这个功能,Serverless平台会将这个请求转发到一个可用的功能微服务。如果当前没有可用的微服务,那么它将部署“冷启动”(cold start)一个这样的实例。</li>
<li>Serverless平台需要负责在微服务失败的时候恢复它,自动扩展以适应需求,记录和监控功能活动,并且在代码被修改后进行实时滚动升级。专门有人来管理平台服务,这样程序员就可以专注于“功能”方面。</li>
</ol>
<h2 id="那么Severless的缺点是什么呢?"><a href="#那么Severless的缺点是什么呢?" class="headerlink" title="那么Severless的缺点是什么呢?"></a>那么Severless的缺点是什么呢?</h2><p>除了以上列出的优点之外,FaaS(功能即服务)也存在一些潜在的缺点。专家提到,云服务提供商通常会降低那些不经常的运行环境的资源,他们也会限制你的可用资源的总量,由此带来延迟以及低性能问题。并且由于任何云计算工作流事实上都会运行在一个公有云环境,而你无法控制或者进入这些云环境,导致监控,调试,以及安全性都无法保障。</p>
<p>亚马逊Lambda已成称为Serverless计算的代名词,这也是大多数人可以完全理解一种Servless的模式。尽管Lambda已经开辟了Serverlss的前路,它也涵盖了所有Serverless的缺点:缓慢的冷启动(cold start),低性能,短时间存在的功能,以及一组封闭的触发器。目前普遍认为,所有的Severless平台都具有这些限制性,但事实上这些限制性可以在平台的实施中避免。值得注意的是有一些更新的Serverless平台比如 <a href="https://nuclio.io/" target="_blank" rel="noopener">Nuclio</a>,它演变出了很多更广泛的用例,这些更新的平台具有更少的限制,更高的性能,并且可以运行在多个云服务上甚至是内部环境。</p>
<p>Haviv说道“极客们喜欢Serverless,但是企业们仍在试水——他们甚至还没有熟悉Docker/Kubernetes,Serverless就开始出现了”,就如任何新技术一样,一开始总是由一些聪敏,愿意冒险的早期受众开始试用,之后慢慢面向大众。而大众往往需要在看到这项技术是值得信赖,并且有可靠证据证明它能解决性能问题,安全问题等等问题之后才会采用这项技术。</p>
<p>鉴于Serverlss这项技术几乎每天都在发展壮大,所以并非所有的方面都可以令人满意。虽然称不上是个缺点,但是这一点的确令整个董事会坐立不安。Haviv 指出“由于数字化转型”正影响着当代企业,创新者们以及Serverless一类的某某即服务(Other As A Service)技术正在威胁着现任者,让企业变的更加敏捷以及更加愿意承担风险。最有趣的是尽管Serverless比Docker/Kubernetes技术更新,但如果将Docker/Kubernetes的复杂性抽象出来,Serverless会更快更容易的被采用。 </p>
<h2 id="如何知道Serverless是否适合你的公司"><a href="#如何知道Serverless是否适合你的公司" class="headerlink" title="如何知道Serverless是否适合你的公司"></a>如何知道Serverless是否适合你的公司</h2><p>这里不是讨论你的公司到底适不适合Serverless,这里只是讨论一下那些最先采用Serverless技术的公司。</p>
<p>Oracle的Arimura说道“表面上来说,任何编写软件的公司和组织都非常适合采用Serverless”,不过,就当前的文化以及离达成全面“原生云”目标的距离而言,采用Serverless将会使这个过程变得更加艰难。换句话说,<strong>如果一个公司没有使用过任何的公有云,没有任何Docker/Kubernetes的实施经历。那么他们就不该从Serverless开始</strong>。</p>
<p>“这是一种全新的架构,需要完全不同的思维方式,最简单的例子就是如果要把一个单体应用拆分成10个微服务,100个函数,并且每个都具有独立的部署周期以及复杂的依赖图,配合成熟健壮的CI/CD以及自动化的系统。把这些和Serverlss一起使用的时候,将会大幅提升敏捷性和创新性,但是如果仅仅只有其中一个,那么带来的损失远大于收益。”</p>
<p>Arimura继续说道 “这就是为什么Devops(运维人员)不会变成noOps(无运维人员),因为这是一条完全错误的方向。“事实上,Serverless的出现使得DevOps比以往更重要。</p>
<p>事实上,Bitnami的Goasguen指出,他观察到的大部分使用Serverless的公司都是以程序员为中心的组织,尤其是使用AWS Lamba服务的,这些公司原本就使用AWS并且用AWS将服务连接在一起。 Goasguen说道“所以很有可能,如果你不使用AWS,那么你便不需要Serverless,但是你始终需要保持警惕,开始评估,辨别企业中哪些事件源可以被用来建立完整的应用程序管道。</p>
<h2 id="企业试水Serverless的最佳途径?"><a href="#企业试水Serverless的最佳途径?" class="headerlink" title="企业试水Serverless的最佳途径?"></a>企业试水Serverless的最佳途径?</h2><p>Arimura建议道“如果一个企业仍处在适应DevOps的阶段,那么它不需要一开始就将一个大型的单体应用完全拆分转换成微服务或者功能,或是为了学习一个新架构就在一个重要的新项目中使用这项技术。最好是从小处着手,例如创建一些自动化的任务,或是事件驱动的用例。”</p>
<p>本系列Serverless的文章可以帮助您的公司开始使用这项技术。</p>
<blockquote>
<p>译者:Grace Linktime Cloud公司全栈工程师</p>
<p>原文:<a href="https://thenewstack.io/serverless-101-how-to-get-serverless-started-in-the-enterprise/" target="_blank" rel="noopener">https://thenewstack.io/serverless-101-how-to-get-serverless-started-in-the-enterprise/</a></p>
</blockquote>
]]></content>
<summary type="html">
<p>Serverless直译为中文是 “无服务器”,但是实际上它仍需要服务器,只不过服务器的管理以及资源分配部分对用户不可见,为避免误导读者,译文中还是将英文保留。</p>
<p>最开始,一台单用户的物理服务器便能满足我们的日常所需,它快速,可靠并且安全,只对管理员负责。但是在
</summary>
<category term="serverless" scheme="https://salogs.com/categories/serverless/"/>
</entry>
<entry>
<title>Kubernetes 是如何成为传统应用迁移的终极方案的</title>
<link href="https://salogs.com/news/2018/08/01/how-kubernetes-became-solution-migrating-legacy-applications/"/>
<id>https://salogs.com/news/2018/08/01/how-kubernetes-became-solution-migrating-legacy-applications/</id>
<published>2018-08-01T07:49:40.000Z</published>
<updated>2018-08-01T08:18:49.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>其实你根本就没有必要将自己的单体应用改写为现代的版本,使用云原生技术可以将之优雅的转变为微服务架构。</p>
</blockquote>
<h1 id="计算机基础设施的变迁史"><a href="#计算机基础设施的变迁史" class="headerlink" title="计算机基础设施的变迁史"></a>计算机基础设施的变迁史</h1><p>在互联网的早期,如果你来运行应用程序的话,需要购买或租用硬件。无论它是机架式的还是塔式的服务器,这不重要,重要的是每一个应用都需要一台服务器来运行,所以是非常昂贵的。在2001年,VMware推出了虚拟化软件,允许用户在同一硬件上运行多个应用程序。这也就意味着将一台服务器分割为多个,而且分割后的服务器是完全可以独立运行应用程序的。这对于用户的成本来说,是极大的节约。</p>
<p>时间过得很快,转眼就是2006年,亚马逊普及了基础设施即服务(IaaS),因为推出了颠覆性服务——AWS的弹性计算云(EC2),你不再需要购买你自己的硬件。你甚至都不需要去担心管理那些运行你的应用程序的虚拟机。而你实际上正在租用运行你的服务所需的计算环境和底层基础架构。你按小时来付费,像租用开会用的会议室一样。这样就可以让使用它们的公司,能够充分的优化自己的资源以节约成本,只需购买自己所需要的计算资源即可。要知道,IaaS 的出现是具有革命性的变革的,它的直接结果就是让计算的成本下降很多。</p>
<p>三年之后,Heroku提出了平台即服务(PaaS)的想法。PaaS 通过屏蔽了需要管理虚拟机操作系统重新抽象了一层。Heroku神奇地简化了部署应用程序的新版本;让应用变成了只需要输入 git push heroku 这样简单的命令即可。在Heroku当时诞生了很多现在知名的互联网公司。</p>
<p>这些进步使得以任何规模(大或小)部署应用程序变得更加容易和成本低廉。这就直接导致了许多的创新出现,并极大的推动了企业在处理基础设施上的实质转变,从而将其从资本支出转移到可变的运营支出。</p>
<p>以上所有看起来一切都很美好,不知道读者看到这里是否意识到?这其中有一个非常大的问题。那就是所有的这些技术提供商都是闭源的、专有的公司。这就存在一个现象:单独厂家锁定!在各个环境之间移植应用程序是非常困难的事情。混合和匹配内部部署和基于云的应用程序几乎是不可能的。</p>
<p>而这个大问题就需要开源来解决!不过也是时机正好。这次依旧是基于Linux之上,即 Docker 和 Kubernetes,而后者更是被人们称之为云时代的Linux。</p>
<h1 id="开源来拯救世界"><a href="#开源来拯救世界" class="headerlink" title="开源来拯救世界"></a>开源来拯救世界</h1><p>Docker在2013年出现,将容器的概念迅速扩散。正如当年集装箱点燃了全球的货运革命一样,当时的船运公司使用这种大型的金属集装箱替代了过去纷杂的货运装置,以适应在卡车、船舶、铁路三者之间匹配。<strong>装什么无所谓,重要的是装载本身有了标准。</strong></p>
<p>和现实世界的集装箱运输一样,Docker 容器创建了对于应用最为基本的封装,使之可以运行在任何的基础设施平台上。一时之间,Linux容器风靡世界。到今天为止,几乎所有的企业都有意愿将他们的应用跑在容器之上,即使是他们自己的内部的服务器,也同样在考虑。尽管容器仅仅是管理现代的应用程序的一种更好的方式,因为它们通常被分割成无数的组件(微服务),但仍然需要能够在服务器之间进行容易的移植和访问。</p>
<p>技术的本质,通常是解决了一个问题,而又引入新的问题,如此反复。Linux容器也不例外,它虽然封装的很好,但是它给DevOps团队带来前所未有的挑战,它在应用程序的移植、部署的活动中增加的了更多动态的东西。于是Kubernetes应时而生。</p>
<p>2015年,Google 以开源的方式发布了Kubernetes项目,它是Google内部系统Borg的实现。Google 联合Linux基金会成立了云原生计算基金会(CNCF),Kubernetes是此基金会下的第一个种子项目,Kubernetes迅速成为历史上发展最快的开源项目之一,目前拥有数十家公司和组织中成千上万的贡献者。</p>
<p>是什么让Kubernetes如此不可思议的快速增长?恐怕最主要的原因莫过于Kubernetes是Google内部集群系统Borg的再现。这个世界上还很少有哪家的数据中心有Google的规模,Borg 系统平均每周会推出大约20亿个容器,这也就是说每秒钟都会新启动3300个容器。大家可以想象一下高峰期的时候,会比这个数字大得多。是的,没错,Kubernetes就是诞生在这样一个强大的系统之上的,经历过严峻考验的、以应对比这个规模更大的负载!</p>
<p><img src="1_cncf-history.png" alt=""></p>
<p>在确定了 Docker 和 Kubernetes 作为核心之后,CNCF扩展了更多的云原生项目,到目前为止,CNCF拥有大大小小的项目数量达到了369,其中较为重要和成熟的项目有:<a href="https://kubernetes.io/" target="_blank" rel="noopener">Kubernetes</a>, <a href="https://prometheus.io/" target="_blank" rel="noopener">Prometheus</a>, <a href="http://opentracing.io/" target="_blank" rel="noopener">OpenTracing</a>, <a href="https://www.fluentd.org/" target="_blank" rel="noopener">Fluentd</a>, <a href="https://linkerd.io/" target="_blank" rel="noopener">Linkerd</a>, <a href="https://grpc.io/" target="_blank" rel="noopener">gRPC</a>, <a href="https://coredns.io/" target="_blank" rel="noopener">CoreDNS</a>, <a href="https://coreos.com/rkt/" target="_blank" rel="noopener">rkt</a>, <a href="https://containerd.io/" target="_blank" rel="noopener">containerd</a>, <a href="https://www.cncf.io/blog/2017/05/23/cncf-hosts-container-networking-interface-cni/" target="_blank" rel="noopener">Container Networking Interface</a>, <a href="https://github.com/coredns?language=css" target="_blank" rel="noopener">CoreDNS</a>, <a href="https://envoy.com/" target="_blank" rel="noopener">Envoy</a>, <a href="https://github.com/jaegertracing/jaeger" target="_blank" rel="noopener">Jaeger</a>, <a href="https://github.com/theupdateframework/notary" target="_blank" rel="noopener">Notary</a>, <a href="https://github.com/theupdateframework/specification" target="_blank" rel="noopener">The Update Framework</a>, <a href="https://rook.io/" target="_blank" rel="noopener">Rook</a>, 和 <a href="http://vitess.io/" target="_blank" rel="noopener">Vitess</a>.</p>
<p><img src="2_cncf-landscape.jpg" alt=""></p>
<p>然而,作为后起之秀的CNCF,从以往过去的开源项目汲取了很多的经验,CNCF一直非常谨慎,以确保只选择那些能够很好地协同工作并能满足企业和创业公司需求的技术。而且这些技术正在大量采用。</p>
<p>企业拥抱开源技术最大的原因之一就是避免供应商锁定,并实现在多个云平台之间,以及云平台和私有的基础设施之间的容器的平滑移植。基于开源,最终用户可以很容器的切换供应商,也可以使用混合的云平台。如果用户拥有足够的技术能力,甚至都可以自己来进行管理。</p>
<h2 id="将单体应用分片"><a href="#将单体应用分片" class="headerlink" title="将单体应用分片"></a>将单体应用分片</h2><p>Kubernetes和Docker 不仅增强了用户管理大规模负载的能力,而且还能够将大型的、单体应用更加容易的分割为更加易于管理的微服务。分割之后的服务,具备自我管理的功能,可以随时根据需要进行伸缩。微服务还支持更快的部署和更快的迭代,以符合现代持续集成实践。基于Kubernetes的业务流程可以通过动态管理和调度这些微服务来提高效率和资源利用率。它还增加了非凡的弹性水平。用户根本不必担心容器故障,且可以继续按需求进行伸缩。</p>
<p>Kubernetes 迅速成为云原生编排系统的首选,它也成为了开源历史上速度最快的开发项目之一。并得到包括AWS,微软,红帽,SUSE等在内的主要厂商的支持。</p>
<p>所有这些都对企业有直接的影响。具Puppet公司举行的2016年度DevOps形势报告称,高性能的云原生架构可以有更频繁的开发,更短的交付周期,更低的故障率以及更快的故障恢复。这意味着功能可以更快地推向市场,项目可以更快地发挥作用,而工程和开发团队的等待时间则少得多。在今天,如果您要从头开始构建新的应用程序,那么云原生应用程序体系结构就是实现它的最佳途径。同样重要的是,云原生会思考如何利用现有(棕地)应用程序提供了一个路线图,并将它们慢慢转化为运行在容器和Kubernetes上的更为高效和更具弹性的基于微服务的架构。棕地、单体应用其实是构成当今所有软件产品的大部分内容。</p>
<p>单体应用是站在云原生应用的对立面的,它们意味着的陈旧、代价昂贵、臃肿、紧耦合、且不够稳定。问题在于:<strong>如何将这些单体应用拆分为微服务架构?</strong> 用户或许考虑过重写所有的那些大型的旧版应用,而现实的情况告诉你:<strong>大多数重写都以失败告终。</strong>你试图重写的第一个系统,即使在你试图替换它时也是存在的,而且一直都在进化。有时候,第一个系统发展得太快,你永远无法赶上。</p>
<p>当然,用户也可以采用更加有效的方法来解决此问题。首先,停止向现有的单体应用程序添加重要的新功能。这里有一个“提升和转移”的概念,也就意味着用户可以将一个需要几GB内存的应用使用容器来封装。是不是很简单!</p>
<h1 id="一个将单体应用转换为容器的典型实例"><a href="#一个将单体应用转换为容器的典型实例" class="headerlink" title="一个将单体应用转换为容器的典型实例"></a>一个将单体应用转换为容器的典型实例</h1><p><a href="https://www.youtube.com/watch?v=dUHr2ukGN5w&list=PLbzoR-pLrL6oNLRbC03ziuX0JWzQFjnth&index=28" target="_blank" rel="noopener">Ticketmaster</a> 就是实现单体应用转换为容器典型的实例。它的代码运行在非常古老的<a href="https://en.wikipedia.org/wiki/PDP-11" target="_blank" rel="noopener">PDP-11</a>中,首先将创建了一个PDP-11的模拟器,然后使其在Docker中运行,这样就间接的将应用也容器化了。我们知道Kubernetes有一项功能叫做<a href="https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/" target="_blank" rel="noopener">Stateful set</a>(也就是过去的PetSets),此功能可以将某些容器固定的运行在特定的机器中,以确保它能够保持正常运行。</p>
<p>另外,Ticketmaster 还有一个特殊的问题:每当开始售票的时候,当所有人都来访问的时候,这无异于就是发动了分布式拒绝服务(DDoS)攻击。该公司需要一套前端服务器,可以扩展和处理这种需求,而不是试图将其写入他们的旧版应用程序中。最终的解决办法是,在旧版应用程序的前面部署了一套新的基于容器的微服务,进而最大限度地减少了传统体系结构中的持续蔓延。</p>
<p>当你试图将工作负载从旧版程序迁移到容器的时候,可能还想将某些功能从应用程序转移到微服务中,又或者是使用微服务来添加新的功能,而不是添加到旧的代码库中。举个例子,如果您想添加<a href="https://en.wikipedia.org/wiki/OAuth" target="_blank" rel="noopener">OAuth</a>这项功能,可能只需一个简单的<a href="https://opensource.com/tags/nodejs" target="_blank" rel="noopener">Node.js</a>应用程序可以放在你的旧版应用程序前面即可。如果你有一个高性能敏感的任务,你可以在Golang中编写它,并将其设置为一个驻留的单体应用前,使得API驱动的服务能够生效。仍然可以将API调用返回到你现有的单体应用。</p>
<p>这些新功能可以由不同的团队用更现代的语言编写,这些团队可以使用自己的一套库和依赖关系,并开始拆分原有的单体应用。</p>
<p>来自北卡罗来纳的<a href="https://www.key.com/personal/index.jsp" target="_blank" rel="noopener">KeyBanc</a>无疑是这方面最为生动的案例,它在传统的Java应用程序之前部署Node.js应用程序服务器来处理移动客户端。这比为旧版单体应用添加代码,显得更为简单,也更高效,而且有助于确保其基础架构在未来也能良性发展。</p>
<h1 id="真正的云原生是众多项目互补的集合"><a href="#真正的云原生是众多项目互补的集合" class="headerlink" title="真正的云原生是众多项目互补的集合"></a>真正的云原生是众多项目互补的集合</h1><p>如果作为用户的你正在打算拥抱云原生技术的话,那么就应该考虑一下项目互补来交付核心的功能。举个例子,在云原生的环境中,最重要的莫过于监视、追踪和日志。此时就可以考虑诸如Prometheus、OpenTracing 和 Fluentd,Linkerd 是一个service mesh,其可以支持更复杂的路由。gRPC是一个非常高性能的API系统,完全可以替代原有的JSON,且获得更高性能。CoreDNS是一个服务发现平台。所有这些项目均是CNCF的一分子,在未来CNCF会增加更多的项目,为拥抱云原生技术的用户拥有更多选择。</p>
<h1 id="无论是绿地、棕地,还是其它什么地,均可云原生"><a href="#无论是绿地、棕地,还是其它什么地,均可云原生" class="headerlink" title="无论是绿地、棕地,还是其它什么地,均可云原生"></a>无论是绿地、棕地,还是其它什么地,均可云原生</h1><p>作为开发者的你,或许此时会考虑迁移旧有的单体应道到云原生的微服务,那么恭喜你,你根本毋须完全另起炉灶新写一个,或者重新改写。诸如 Kubernetes 这样的CNCF技术非常拥抱旧有的应用程序,这对于所有的公司和企业来说,都是应该走的进化之路。用户既可以使用Kubernetes来构建一套全新的应用,也可以逐步的将原有的单体应用转化为微服务应用,以在不久的未来担当重要业务的支撑。</p>
<blockquote>
<p><strong>注解:</strong> 关于绿地(greenfeild)棕地(brownfeild)的解释: 绿地是指软件完全是不受任何原有系统干扰的新的项目,而棕地则表示,在开发新的系统时,要处处引进和考虑旧的系统。</p>
</blockquote>
<p>关于原作者</p>
<blockquote>
<p>Swapnil Bhartiya 是一名专业的记者和编辑,有超过报道企业开源12年的职业经历。</p>
<p>本文由作者Swapnil Bhartiya 发表在Opensource.com上:<a href="https://opensource.com/article/18/2/how-kubernetes-became-solution-migrating-legacy-applications" target="_blank" rel="noopener">How Kubernetes became the solution for migrating legacy applications</a>。由开源之道精心翻译共享。本文在Creative Commons BY-SA 4.0许可证下发布。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<p>其实你根本就没有必要将自己的单体应用改写为现代的版本,使用云原生技术可以将之优雅的转变为微服务架构。</p>
</blockquote>
<h1 id="计算机基础设施的变迁史"><a href="#计算机基础设施的变迁史" class="head
</summary>
<category term="Kubernetes" scheme="https://salogs.com/categories/Kubernetes/"/>
<category term="Kubernetes" scheme="https://salogs.com/tags/Kubernetes/"/>
</entry>
<entry>
<title>CentOS 7.x 安装 Dell OMSA 与监控</title>
<link href="https://salogs.com/news/2018/06/21/install-dell-omsa/"/>
<id>https://salogs.com/news/2018/06/21/install-dell-omsa/</id>
<published>2018-06-21T06:44:44.000Z</published>
<updated>2018-06-21T08:00:33.000Z</updated>
<content type="html"><![CDATA[<p>各大厂商的服务器都有配到的硬件管理及监控软件,本文主要针对Dell服务器,讲解它的管理与监控。</p>
<p>Dell服务器的 OMSA (OpenManage Server Administrator )是安装在操作系统中的一套软件,提供硬件监控、驱动程序的升级等操作。</p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><ul>
<li>配置仓库</li>
</ul>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl <span class="_">-s</span> http://linux.dell.com/repo/hardware/dsu/bootstrap.cgi | bash</span><br></pre></td></tr></table></figure>
<ul>
<li>安装DSU(Dell EMC System Update)<blockquote>
<p>DSU 是为操作更新驱动与软件的服务。</p>
</blockquote>
</li>
</ul>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum install dell-system-update</span><br></pre></td></tr></table></figure>
<ul>
<li>安装并启动OMSA</li>
</ul>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum install srvadmin-all -y</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置开机启动</span></span><br><span class="line">/opt/dell/srvadmin/sbin/srvadmin-services.sh <span class="built_in">enable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动服务</span></span><br><span class="line">/opt/dell/srvadmin/sbin/srvadmin-services.sh start</span><br><span class="line">Starting instsvcdrv (via systemctl): [ 确定 ]</span><br><span class="line">Starting dataeng (via systemctl): [ 确定 ]</span><br><span class="line">Starting dsm_om_connsvc (via systemctl): [ 确定 ]</span><br></pre></td></tr></table></figure>
<h2 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h2><p>安装完OMSA后,可以通过访问 https://<本机IP>:000 通过浏览器的方式查看硬件信息,如果机器成百上千台,这种方式的工作效率是非常低的,因此本文介绍通过<code>nagios</code>插件 <a href="http://folk.uio.no/trondham/software/check_openmanage.html" target="_blank" rel="noopener">check_openmanage</a> 的方式通过命令行检查,当然这个插件可以结合nagios和zabbix使用,有兴趣的同学可以深入学习一下。 下文只简单介绍使用方法</p>
<h3 id="下载check-openmanage"><a href="#下载check-openmanage" class="headerlink" title="下载check_openmanage"></a>下载check_openmanage</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget http://folk.uio.no/trondham/software/files/check_openmanage-3.7.12.tar.gz</span><br></pre></td></tr></table></figure>
<h3 id="使用check-openmanage"><a href="#使用check-openmanage" class="headerlink" title="使用check_openmanage"></a>使用check_openmanage</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">./check_openmanage -h</span><br><span class="line">Usage: check_openmanage [OPTION]...</span><br><span class="line"></span><br><span class="line">GENERAL OPTIONS:</span><br><span class="line"></span><br><span class="line"> <span class="_">-f</span>, --config Specify configuration file</span><br><span class="line"> -p, --perfdata Output performance data [default=no]</span><br><span class="line"> -t, --timeout Plugin timeout <span class="keyword">in</span> seconds [default=30]</span><br><span class="line"> -c, --critical Custom temperature critical limits</span><br><span class="line"> -w, --warning Custom temperature warning limits</span><br><span class="line"> -F, --fahrenheit Use Fahrenheit as temperature unit</span><br><span class="line"> <span class="_">-d</span>, --debug Debug output, reports everything</span><br><span class="line"> -h, --help Display this <span class="built_in">help</span> text</span><br><span class="line"> -V, --version Display version info</span><br><span class="line"></span><br><span class="line">SNMP OPTIONS:</span><br><span class="line"></span><br><span class="line"> -H, --hostname Hostname or IP (required <span class="keyword">for</span> SNMP)</span><br><span class="line"> -C, --community SNMP community string [default=public]</span><br><span class="line"> -P, --protocol SNMP protocol version [default=2c]</span><br><span class="line"> --port SNMP port number [default=161]</span><br><span class="line"> -6, --ipv6 Use IPv6 instead of IPv4 [default=no]</span><br><span class="line"> --tcp Use TCP instead of UDP [default=no]</span><br><span class="line"></span><br><span class="line">OUTPUT OPTIONS:</span><br><span class="line"></span><br><span class="line"> -i, --info Prefix any alerts with the service tag</span><br><span class="line"> <span class="_">-e</span>, --extinfo Append system info to alerts</span><br><span class="line"> <span class="_">-s</span>, --state Prefix alerts with alert state</span><br><span class="line"> -S, --short-state Prefix alerts with alert state abbreviated</span><br><span class="line"> -o, --okinfo Verbosity when check result is OK</span><br><span class="line"> -B, --show-blacklist Show blacklistings <span class="keyword">in</span> OK output</span><br><span class="line"> -I, --htmlinfo HTML output with clickable links</span><br><span class="line"></span><br><span class="line">CHECK CONTROL AND BLACKLISTING:</span><br><span class="line"></span><br><span class="line"> <span class="_">-a</span>, --all Check everything, even <span class="built_in">log</span> content</span><br><span class="line"> -b, --blacklist Blacklist missing and/or failed components</span><br><span class="line"> --only Only check a certain component or alert <span class="built_in">type</span></span><br><span class="line"> --check Fine-tune <span class="built_in">which</span> components are checked</span><br><span class="line"> --no-storage Don<span class="string">'t check storage</span></span><br><span class="line"><span class="string"> --vdisk-critical Make any alerts on virtual disks critical</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">For more information and advanced options, see the manual page or URL:</span></span><br><span class="line"><span class="string"> http://folk.uio.no/trondham/software/check_openmanage.html</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>各大厂商的服务器都有配到的硬件管理及监控软件,本文主要针对Dell服务器,讲解它的管理与监控。</p>
<p>Dell服务器的 OMSA (OpenManage Server Administrator )是安装在操作系统中的一套软件,提供硬件监控、驱动程序的升级等操作。<
</summary>
<category term="linux" scheme="https://salogs.com/categories/linux/"/>
<category term="monitor" scheme="https://salogs.com/categories/linux/monitor/"/>
<category term="Hardware" scheme="https://salogs.com/tags/Hardware/"/>
<category term="monitor" scheme="https://salogs.com/tags/monitor/"/>
</entry>
<entry>
<title>开源PaaS Rainbond不完全评测01 —— 安装篇</title>
<link href="https://salogs.com/news/2018/06/21/rainbond-install-s1/"/>
<id>https://salogs.com/news/2018/06/21/rainbond-install-s1/</id>
<published>2018-06-21T04:50:57.000Z</published>
<updated>2018-06-21T06:53:15.000Z</updated>
<summary type="html">
</summary>
</entry>
<entry>
<title>Mac 安装与使用Shadowsocks</title>
<link href="https://salogs.com/news/2016/11/02/mac-shadowsocks/"/>
<id>https://salogs.com/news/2016/11/02/mac-shadowsocks/</id>
<published>2016-11-02T04:31:17.000Z</published>
<updated>2016-11-02T08:37:15.000Z</updated>
<content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>A secure socks5 proxy,designed to protect your Internet traffic.—— 来自 <a href="https://shadowsocks.org/en/index.html" target="_blank" rel="noopener">shadowsocks.org</a></p>
<h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul>
<li><strong>速度快</strong> 程序基于异步I/O和事件驱动研发</li>
<li><strong>灵活的加密方式</strong> 使用行业级加密算法加密。 灵活支持自定义算法。</li>
<li><strong>移动设备支持</strong> 针对移动和无线网络进行优化,没有任何保持活动的连接请求。</li>
<li><strong>跨平台</strong> 支持主流平台,包括Windows, Linux, Mac, Android, iOS, 和 OpenWRT。</li>
<li><strong>开放源码</strong> 完全免费和开源。全球开发者社区解决程序bug,免费代码,长效支持!</li>
<li><strong>易于开发</strong> 可以通过 pip, aur, freshports 和其他包管理系统实现简单开发</li>
</ul>
<h2 id="安装Shadowsocks-Server"><a href="#安装Shadowsocks-Server" class="headerlink" title="安装Shadowsocks-Server"></a>安装Shadowsocks-Server</h2><p>这里只介绍Ubuntu/Debian系统安装 Server 端程序</p>
<p>先添加 GPG public key :<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ wget -O- http://shadowsocks.org/debian/1D27208A.gpg | sudo apt-key add -</span><br></pre></td></tr></table></figure></p>
<p>配置包仓库地址<br>Debian Wheezy, Ubuntu 12.04 系统 libssl > 1.0.0 执行如下命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">echo</span> <span class="string">"deb http://shadowsocks.org/debian wheezy main"</span> >> /etc/apt/sources.list</span><br></pre></td></tr></table></figure>
<p>如果操作系统是 Debian Squeeze, Ubuntu 11.04, libssl > 0.9.8, but < 1.0.0 执行下面命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">echo</span> <span class="string">"deb http://shadowsocks.org/debian squeeze main"</span> >> /etc/apt/sources.list</span><br></pre></td></tr></table></figure>
<p>然后安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ apt-get update</span><br><span class="line">$ apt-get install shadowsocks-libev</span><br></pre></td></tr></table></figure>
<h2 id="Mac系统安装-Shadowsocks-Client"><a href="#Mac系统安装-Shadowsocks-Client" class="headerlink" title="Mac系统安装 Shadowsocks-Client"></a>Mac系统安装 Shadowsocks-Client</h2><ul>
<li><p>安装GUI 客户端<br>下载地址:<a href="https://github.com/shadowsocks/ShadowsocksX-NG/releases" target="_blank" rel="noopener">https://github.com/shadowsocks/ShadowsocksX-NG/releases</a></p>
</li>
<li><p>通过命令行安装</p>
</li>
</ul>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">brew install shadowsocks-libev</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置开机启动</span></span><br><span class="line"> brew services start shadowsocks-libev</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者先前台跑一次程序</span></span><br><span class="line">/usr/<span class="built_in">local</span>/opt/shadowsocks-libev/bin/ss-local -c /usr/<span class="built_in">local</span>/etc/shadowsocks-libev.json</span><br></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>A secure socks5 proxy,designed to protect your Internet traffic.—— 来自
</summary>
<category term="shadowsocks" scheme="https://salogs.com/tags/shadowsocks/"/>
</entry>
<entry>
<title>Hexo网站优化之SEO</title>
<link href="https://salogs.com/news/2016/09/06/nexo-seo/"/>
<id>https://salogs.com/news/2016/09/06/nexo-seo/</id>
<published>2016-09-06T04:01:23.000Z</published>
<updated>2016-09-06T04:11:51.000Z</updated>
<content type="html"><![CDATA[<p>SEO (Search Engine Optimization),即搜索引擎优化。对网站做SEO优化,有利于提高搜索引擎的收录速度及网页排名。下面讲解一些简单的SEO优化方法,主要针对Hexo网站。</p>
<h1 id="SEO优化之title"><a href="#SEO优化之title" class="headerlink" title="SEO优化之title"></a>SEO优化之title</h1><p>编辑站点目录下的<code>themes/layout/index.swig</code>文件,将下面的代码<br><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">{% block title %} {{ config.title }} {% endlock %}</span><br></pre></td></tr></table></figure></p>
<p>改成</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">{% block title %} {{ config.title }} - {{ theme.description }} {% endlock %}</span><br></pre></td></tr></table></figure>
<p>这时将网站的描述及关键词加入了网站的title中,更有利于详细地描述网站。</p>
<h1 id="添加robots-txt"><a href="#添加robots-txt" class="headerlink" title="添加robots.txt"></a>添加robots.txt</h1><p>robots.txt是一种存放于网站根目录下的ASCII编码的文本文件,它的作用是告诉搜索引擎此网站中哪些内容是可以被爬取的,哪些是禁止爬取的。robots.txt应该放在站点目录下的source文件中,网站生成后在网站的根目录(<code>站点目录/public/</code>)下。</p>
<p>我的robots.txt文件内容如下<br><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">User-agent: *</span><br><span class="line">Allow: /</span><br><span class="line">Allow: /archives/</span><br><span class="line">Allow: /categories/</span><br><span class="line">Allow: /about/</span><br><span class="line"></span><br><span class="line">Disallow: /vendors/</span><br><span class="line">Disallow: /js/</span><br><span class="line">Disallow: /css/</span><br><span class="line">Disallow: /fonts/</span><br><span class="line">Disallow: /vendors/</span><br><span class="line">Disallow: /fancybox/</span><br></pre></td></tr></table></figure></p>
<h1 id="添加sitemap"><a href="#添加sitemap" class="headerlink" title="添加sitemap"></a>添加sitemap</h1><p>Sitemap即网站地图,它的作用在于便于搜索引擎更加智能地抓取网站。最简单和常见的sitemap形式,是XML文件,在其中列出网站中的网址以及关于每个网址的其他元数据(上次更新时间、更新的频率及相对其他网址重要程度等)。</p>
<h2 id="Step-1-安装sitemap生成插件"><a href="#Step-1-安装sitemap生成插件" class="headerlink" title="Step 1: 安装sitemap生成插件"></a>Step 1: 安装sitemap生成插件</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm install hexo-generator-sitemap --save</span><br><span class="line">npm install hexo-generator-baidu-sitemap --save</span><br></pre></td></tr></table></figure>
<h2 id="Step-2-编辑站点目录下的-config-yml,添加"><a href="#Step-2-编辑站点目录下的-config-yml,添加" class="headerlink" title="Step 2: 编辑站点目录下的_config.yml,添加"></a>Step 2: 编辑站点目录下的_config.yml,添加</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># hexo sitemap网站地图</span><br><span class="line">sitemap:</span><br><span class="line">path: sitemap.xml</span><br><span class="line">baidusitemap:</span><br><span class="line">path: baidusitemap.xml</span><br></pre></td></tr></table></figure>
<h2 id="Step-3-在robots-txt文件中添加"><a href="#Step-3-在robots-txt文件中添加" class="headerlink" title="Step 3: 在robots.txt文件中添加"></a>Step 3: 在robots.txt文件中添加</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Sitemap: http://www.jeyzhang.com/sitemap.xml</span><br><span class="line">Sitemap: http://www.jeyzhang.com/baidusitemap.xml</span><br></pre></td></tr></table></figure>
<blockquote>
<p>本文转自:<a href="http://www.jeyzhang.com/hexo-website-seo.html" target="_blank" rel="noopener">http://www.jeyzhang.com/hexo-website-seo.html</a></p>
</blockquote>
]]></content>
<summary type="html">
<p>SEO (Search Engine Optimization),即搜索引擎优化。对网站做SEO优化,有利于提高搜索引擎的收录速度及网页排名。下面讲解一些简单的SEO优化方法,主要针对Hexo网站。</p>
<h1 id="SEO优化之title"><a href="#SE
</summary>
<category term="经验分析" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E6%9E%90/"/>
<category term="hexo,seo" scheme="https://salogs.com/tags/hexo-seo/"/>
</entry>
<entry>
<title>GitHub开源的MySQL在线更改Schema工具</title>
<link href="https://salogs.com/news/2016/08/03/gitHub-gh-ost-online-change-schema-tool/"/>
<id>https://salogs.com/news/2016/08/03/gitHub-gh-ost-online-change-schema-tool/</id>
<published>2016-08-03T07:46:26.000Z</published>
<updated>2016-08-05T03:12:32.000Z</updated>
<content type="html"><![CDATA[<p>MySQL在线更改schema的工具很多,如Percona的<a href="https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html" target="_blank" rel="noopener">pt-online-schema-change</a>、 Facebook的 <a href="https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/" target="_blank" rel="noopener">OSC</a> 和 <a href="https://github.com/soundcloud/lhm" target="_blank" rel="noopener">LHM</a> 等,但这些都是基于触发器(Trigger)的,今天咱们介绍的 <code>gh-ost</code> 号称是不需要触发器(Triggerless)支持的在线更改表结构的工具。</p>
<p><img src="/img/gh-ost.png" alt="gh-ost"></p>
<p>原文地址:<a href="http://githubengineering.com/gh-ost-github-s-online-migration-tool-for-mysql/" target="_blank" rel="noopener">gh-ost: GitHub’s online schema migration tool for MySQL</a></p>
<blockquote>
<p>本文先介绍一下当前业界已经存在的这些工具的使用场景和原理,然后再详细介绍 <code>gh-ost</code> 的工作原理和特性。</p>
</blockquote>
<p>今天我们开源了GitHub内部使用的一款 不需要触发器支持的 MySQL 在线更改表结构的工具 <a href="http://github.com/github/gh-ost" target="_blank" rel="noopener">gh-ost</a></p>
<p>开发 <code>gh-ost</code> 是为了应付GitHub在生产环境中面临的持续的、不断变化的在线修改表结构的需求。<code>gh-ost</code> 通过提供低影响、可控、可审计和操作友好的解决方案改变了现有的在线迁移表工具的工作模式。</p>
<p>MySQL表迁移及结构更改操作是业界众所周知的问题,2009年以来已经可以通过在线(不停服务)变更的工具来解决。迅速增长,快速迭代的产品往往需要频繁的需改数据库的结构。增加/更改/删除/ 字段和索引等等,这些操作在MySQL中默认都会锁表,影响线上的服务。 向这种数据库结构层面的变更我们每天都会面临多次,当然这种操作不应该影响用户的正常服务。</p>
<p>在开始介绍 <code>gh-ost</code> 工具之前,咱们先来看一下当前现有的这些工具的解决方案。</p>
<h1 id="在线修改表结构,已存在的场景"><a href="#在线修改表结构,已存在的场景" class="headerlink" title="在线修改表结构,已存在的场景"></a>在线修改表结构,已存在的场景</h1><p>如今,在线修改表结构可以通过下面的三种方式来完成:</p>
<ul>
<li>在从库上修改表结构,操作会在其他的从库上生效,将结构变更了的从库设置为主库</li>
<li>使用 MySQL InnoDB 存储引擎提供的在线DDL特性</li>
<li>使用在线修改表结构的工具。现在最流行的是 <a href="https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html" target="_blank" rel="noopener">pt-online-schema-change</a> 和 Facebook 的 <a href="https://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932/" target="_blank" rel="noopener">OSC</a>;当然还有 <a href="https://github.com/soundcloud/lhm" target="_blank" rel="noopener">LHM</a> 和比较原始的 <a href="http://shlomi-noach.github.io/openarkkit/oak-online-alter-table.html" target="_blank" rel="noopener">oak-online-alter-table</a> 工具。 </li>
</ul>
<p>其他的还包括 Galera 集群的Schema滚动更新,以及一些其他的非InnoDB的存储引擎等待,在 GitHub 我们使用通用的 主-从 架构 和 InnoDB 存储引擎。</p>
<p>为什么我们决定开始一个新的解决方案,而不是使用上面的提到的这些呢?现有的每种解决方案都有其局限性,下文会对这些方式的普遍问题简单的说明一下,但会对基于触发器的在线变更工具的问题进行详细说明。</p>
<ul>
<li><p>基于主从复制的迁移方式需要很多的前置工作,如:大量的主机,较长的传输时间,复杂的管理等等。变更操作需要在一个指定的从库上或者基于sub-tree的主从结构中执行。需要的情况也比较多,如:主机宕机、主机从早先的备份中恢复数据、新主机加入到集群等等,所有这些情况都有可能对我们的操作造成影响。最要命的是可能这些操作一天要进行很多次,如果使用这种方法我们操作人员每天的效率是非常高的(译者注:现如今很少有人用这种方式了吧)</p>
</li>
<li><p>MySQL针对Innodb存储引擎的在线DDL操作在开始之前都需要一个短时间排它锁(exclusive)来准备环境,所以alter命令发出后,会首先等待该表上的其它操作完成,在alter命令之后的请求会出现等待waiting meta data lock。同样在ddl结束之前,也要等待alter期间所有的事务完成,也会堵塞一小段时间,这对于繁忙的数据库服务来说危险系数是非常高的。另外DDL操作不能中断,如果中途kill掉,会造成长时间的事务回滚,还有可能造成元数据的损坏。它操作起来并不那么的Nice,不能限流和暂停,在大负载的环境中甚至会影响正常的业务。</p>
</li>
<li><p>我们用了很多年的 <code>pt-online-schema-change</code> 工具。然而随着我们不断增长的业务和流量,我们遇到了很多的问题,我们必须考虑在操作中的哪些 <code>危险操作</code> (译者注:pt工具集的文档中经常会有一些危险提示)。某些操作必须避开高峰时段来进行,否则MySQL可能就挂了。所有现存的在线表结构修改的工具都是利用了MySQL的触发器来执行的,这种方式有一些潜藏的问题。</p>
</li>
</ul>
<h1 id="基于触发器的在线修改有哪些问题呢?"><a href="#基于触发器的在线修改有哪些问题呢?" class="headerlink" title="基于触发器的在线修改有哪些问题呢?"></a>基于触发器的在线修改有哪些问题呢?</h1><p>所有在线表结构修改工具的操作方式都类似:创建与原表结构一致的临时表,该临时表已经是按要求修改后的表结构了,缓慢增量的从原表中复制数据,同时记录原表的更改(所有的 INSERT, DELETE, UPDATE 操作) 并应用到临时表。当工具确认表数据已经同步完成,它会进行替换工作,将临时表更名为原表。</p>
<p><code>pt-online-schema-change</code>, <code>LHM</code> 和 <code>oak-online-alter-table</code> 这些工具都使用同步的方式,当原表有变更操作时利用一些事务的间隙时间将这些变化同步到临时表。Facebook 的工具使用异步的方式将变更写入到changelog表中,然后重复的将changelog表的变更应用到临时表。所有的这些工具都使用触发器来识别原表的变更操作。</p>
<p>当表中的每一行数据有 INSERT, DELETE, UPDATE 操作时都会调用存储的触发器。一个触发器可能在一个事务空间中包含一系列查询操作。这样就会造成一个原子操作不单会在原表执行,还会调用相应的触发器执行多个操作。</p>
<p>在基于触发器迁移实践中,遇到了如下的问题:</p>
<ul>
<li><p>触发器是以解释型代码的方式保存的。MySQL 不会预编译这些代码。 会在每次的事务空间中被调用,它们被添加到被操作的表的每个查询行为之前的分析和解释器中。</p>
</li>
<li><p>锁表: 触发器在原始表查询中共享相同的事务空间,而这些查询在这张表中会有竞争锁,触发器在另外一张表会独占竞争锁。在这种极端情况下,同步方式的锁争夺直接关系到主库的并发写性能。以我们的经验来说,在生产环境中当竞争锁接近或者结束时,数据库可能会由于竞争锁而被阻塞住。触发锁的另一个方面是创建或销毁时所需要的元数据锁。我们曾经遇到过在繁忙的表中当表结构修改完成后,删除触发器可能需要数秒到分钟的时间。</p>
</li>
</ul>
<ul>
<li><p>不可信:当主库的负载上升时,我们希望降速或者暂停操作,但基于触发器的操作并不能这么做。虽然它可以暂停行复制操作,但却不能暂停出触发器,如果删除触发器可能会造成数据丢失,因此触发器需要在整个操作过程中都要存在。在我们比较繁忙的服务器中就遇到过由于触发器占用CPU资源而将主库拖死的例子。</p>
</li>
<li><p>并发迁移: 我们或者其他的人可能比较关注多个同时修改表结构(不同的表)的场景。鉴于上述触发器的开销,我们没有兴趣同时对多个表进行在线修改操作,我们也不确定是否有人在生产环境中这样做过。</p>
</li>
<li><p>测试:我们修改表结构可能只是为了测试,或者评估其负载开销。基于触发器的表结构修改操作只能通过基于语句复制的方式来进行模拟实验,离真实的主库操作还有一定的距离,不能真实的反映实际情况。</p>
</li>
</ul>
<h1 id="gh-ost"><a href="#gh-ost" class="headerlink" title="gh-ost"></a>gh-ost</h1><p><code>gh-ost</code> GitHub 的在线 Schema 修改工具,下面工作原理图:</p>
<p> <img src="/img/gh-ost2.png" alt="gh-ost2"></p>
<p><code>gh-ost</code> 具有如下特性:</p>
<ul>
<li>无触发器</li>
<li>轻量级</li>
<li>可暂停</li>
<li>可动态控制</li>
<li>可审计</li>
<li>可测试</li>
<li>值得信赖 :blush:</li>
</ul>
<h1 id="无触发器"><a href="#无触发器" class="headerlink" title="无触发器"></a>无触发器</h1><p><code>gh-ost</code> 没有使用触发器。它通过分析binlog日志的形式来监听表中的数据变更。因此它的工作模式是异步的,只有当原始表的更改被提交后才会将变更同步到临时表(ghost table)</p>
<p><code>gh-ost</code> 要求binlog是RBR格式 ( 基于行的复制);然而也不是说你就不能在基于SBR(基于语句的复制)日志格式的主库上执行在线变更操作。实际上是可以的。gh-ost 可以将从库的 SBR日志转换为RBR日志,只需要重新配置就可以了。</p>
<h1 id="轻量级"><a href="#轻量级" class="headerlink" title="轻量级"></a>轻量级</h1><p>由于没有使用触发器,因此在操作的过程中对主库的影响是最小的。当然在操作的过程中也不用担心并发和锁的问题。 变更操作都是以流的形式顺序的写到binlog文件中,gh-ost只是读取他们并应用到gh-ost表中。实际上,gh-ost 通过读取binlog的写事件来进行顺序的行复制操作。因此,主库只会有一个单独连接顺序的将数据写入到临时表(ghost table)。这和ETL操作有很大的不同。</p>
<h1 id="可暂停"><a href="#可暂停" class="headerlink" title="可暂停"></a>可暂停</h1><p>所有的写操作都是由gh-ost控制的,并且以异步的方式读取binlog,当限速的时候,gh-ost可以暂停向主库写入数据,限速意味着不会在主库进行复制,也不会有行更新。当限速时gh-ost会创建一个内部的跟踪(tracking)表,以最小的系统开销向这个表中写入心跳事件</p>
<p>gh-ost 支持多种方式的限速:</p>
<ul>
<li>负载: 为熟悉 <code>pt-online-schema-change</code> 工具的用户提供了类似的功能,可以设置MySQL中的状态阈值,如 Threads_running=30</li>
<li>复制延迟: <code>gh-ost</code> 内置了心跳机制,可以指定不同的从库,从而对主从的复制延迟时间进行监控,如果达到了设定的延迟阈值程序会自动进入限速模式。</li>
<li>查询: 用户可以可以设置一个限流SQL,比如 <code>SELECT HOUR(NOW()) BETWEEN 8 and 17</code> 这样就可以动态的设置限流时间。</li>
<li>标示文件: 可以通过创建一个标示文件来让程序限速,当删除文件后可以恢复正常操作。</li>
<li>用户命令: 可以动态的连接到 <code>gh-ost</code> (下文会提到) 通过网络连接的方式实现限速。</li>
</ul>
<h1 id="可动态控制"><a href="#可动态控制" class="headerlink" title="可动态控制"></a>可动态控制</h1><p>现在的工具,当执行操作的过程中发现负载上升了,DBA不得不终止操作,重新配置参数,如 chunk-size,然后重新执行操作命令,我们发现这种方式效率非常低。</p>
<p><code>gh-ost</code> 可以通过 unix socket 文件或者TCP端口(可配置)的方式来监听请求,操作者可以在命令运行后更改相应的参数,参考下面的例子:</p>
<ul>
<li><code>echo throttle | socat - /tmp/gh-ost.sock</code> 打开限速,同样的,可以使用 <code>no-throttle</code> 来关闭限流。</li>
<li>改变执行参数: <code>chunk-size=1500</code>, <code>max-lag-millis=2000</code>, <code>max-load=Thread_running=30</code> 这些参数都可以在运行时变更。</li>
</ul>
<h1 id="可审计"><a href="#可审计" class="headerlink" title="可审计"></a>可审计</h1><p>同样的,使用上文提到的程序接口可以获取 <code>gh-ost</code> 的状态。<code>gh-ost</code> 可以报告当前的进度,主要参数的配置以及当前服务器的标示等等。这些信息都可以通过网络接口取到,相对于传统的tail日志的方式要灵活很多。</p>
<h1 id="可测试"><a href="#可测试" class="headerlink" title="可测试"></a>可测试</h1><p>因为日志文件和主库负载关系不大,因此在从库上执行修改表结构的操作可以更真实的体现出这些操作锁产生的实际影响。(虽然不是十分理想,后续我们会做优化工作)。</p>
<p><code>gh-ost</code> 內建支持测试功能,通过使用 <code>--test-on-replica</code> 的参数来指定: 它可以在从库上进行变更操作,在操作结束时<code>gh-ost</code> 将会停止复制,交换表,反向交换表,保留2个表并保持同步,停止复制。可以在空闲时候测试和比较两个表的数据情况。</p>
<p>这是我们在GitHub的生产环境中的测试:我们生产环境中有多个从库;部分从库并不是为用户提供服务的,而是用来对所有表运行的连续覆盖迁移测试。我们生产环境中的表,小的可能没有数据,大的会达到数百GB,我们只是做个标记,并不会正在的修改表结构(engine=innodb)。当每一个迁移结束后会停止复制,我们会对原表和临时表的数据进行完整的checksum确保他们的数据一致性。然后我们会恢复复制,再去操作下一张表。我们的生产环境的从库中已经通过 gh-ost 成功的操作了很多表。</p>
<h1 id="值得信赖"><a href="#值得信赖" class="headerlink" title="值得信赖"></a>值得信赖</h1><p>上文提到说了这么多,都是为了提高大家对 <code>gh-ost</code> 的信任程度。毕竟在业界它还是一个新手,类似的工具已经存在了很多年了。</p>
<ul>
<li><p>在第一次试手之前我们建议用户先在从库上测试,校验数据的一致性。我们已经在从库上成功的进行了数以千计的迁移操作。</p>
</li>
<li><p>如果在主库上使用 <code>gh-ost</code> 用户可以实时观察主库的负载情况,如果发现负载变化很大,可以通过上文提到的多种形式进行限速,直到负载恢复正常,然后再通过命令微调参数,这样可以动态的控制操作风险。</p>
</li>
<li><p>如果迁移操作开始后预完成计时间(ETA)显示要到夜里2点才能完成,结束时候需要切换表,你是不是要留下来盯着?你可以通过标记文件让gh-ost推迟切换操作。gh-ost 会完成行复制,但并不会切换表,它会持续的将原表的数据更新操作同步到临时表中。你第二天来到办公室,删除标记文件或者通过接口 <code>echo unpostpone</code> 告诉gh-ost开始切换表。我们不想让我们的软件把使用者绑住,它应该是为我们拜托束缚。</p>
</li>
<li><p>说到 ETA, <code>--exact-rowcount</code> 参数你可能会喜欢。相对于一条漫长的 <code>SELECT COUNT(*)</code> 语句,gh-ost 会预估出迁移操作所需要花费的时间,还会根据当前迁移的工作状况更新预估时间。虽然ETA的时间随时更改,但进度百分比的显示是准确的。</p>
</li>
</ul>
<h1 id="gh-ost-操作模式"><a href="#gh-ost-操作模式" class="headerlink" title="gh-ost 操作模式"></a>gh-ost 操作模式</h1><p>gh-ost 可以同时连接多个服务器,为了获取二进制的数据流,它会作为一个从库,将数据从一个库复制到另外一个。它有各种不同的操作模式,这取决于你的设置,配置,和要运行迁移环境。</p>
<p><img src="/img/gh-ost3.png" alt="gh-ost3"></p>
<h2 id="a-连接到从库,在主库做迁移"><a href="#a-连接到从库,在主库做迁移" class="headerlink" title="a. 连接到从库,在主库做迁移"></a>a. 连接到从库,在主库做迁移</h2><p>这是 gh-ost 默认的工作方式。gh-ost 将会检查从库状态,找到集群结构中的主库并连接,接下来进行迁移操作:</p>
<ul>
<li>行数据在主库上读写</li>
<li>读取从库的二进制日志,将变更应用到主库</li>
<li>在从库收集表格式,字段&索引,行数等信息</li>
<li>在从库上读取内部的变更事件(如心跳事件)</li>
<li>在主库切换表</li>
</ul>
<p>如果你的主库的日志格式是 SBR,工具也可以正常工作。但从库必须启用二级制日志(log_bin, log_slave_updates) 并且设置 <code>binlog_format=ROW</code> ( gh-ost 是读取从库的二级制文件)。</p>
<p>如果直接在主库上操作,当然也需要二进制日志格式是RBR。</p>
<h2 id="b-连接到主库"><a href="#b-连接到主库" class="headerlink" title="b. 连接到主库"></a>b. 连接到主库</h2><p>如果你没有从库,或者不想使用从库,你可以直接在主库上操作。<code>gh-ost</code> 将会直接在主库上进行所有操作。你需要持续关注复制延迟问题。</p>
<ul>
<li>你的主库的二进制日志必须是 RBR 格式。</li>
<li>在这个模式中你必须指定 <code>--allow-on-master</code> 参数</li>
</ul>
<h2 id="c-在从库迁移-测试"><a href="#c-在从库迁移-测试" class="headerlink" title="c. 在从库迁移/测试"></a>c. 在从库迁移/测试</h2><p>该模式会在从库执行迁移操作。gh-ost 会简单的连接到主库,此后所有的操作都在从库执行,不会对主库进行任何的改动。整个操作过程中,gh-ost 将控制速度保证从库可以及时的进行数据同步</p>
<ul>
<li><code>--migrate-on-replica</code> 表示 gh-ost 会直接在从库上进行迁移操作。即使在复制运行阶段也可以进行表的切换操作。</li>
<li><code>--test-on-replica</code> 表示 迁移操作只是为了测试在切换之前复制会停止,然后会进行切换操作,然后在切换回来,你的原始表最终还是原始表。两个表都会保存下来,复制操作是停止的。你可以对这两个表进行一致性检查等测试操作。</li>
</ul>
<h1 id="gh-ost-at-GitHub"><a href="#gh-ost-at-GitHub" class="headerlink" title="gh-ost at GitHub"></a>gh-ost at GitHub</h1><p>我们已经在所有线上所有的数据库在线操作中使用了gh-ost ,我们每天都需要使用它,根据数据库修改需求,可能每天要运行多次。凭借其审计和控制功能我们已经将它集成到了<a href="https://www.pagerduty.com/blog/what-is-chatops/" target="_blank" rel="noopener">ChatOps</a>流程中。我们的工程师可以清醒的了解到迁移操作的进度,而且可以灵活的控制其行为。</p>
<h1 id="开源"><a href="#开源" class="headerlink" title="开源"></a>开源</h1><p>gh-ost 在<a href="https://github.com/github/gh-ost/blob/master/LICENSE" target="_blank" rel="noopener">MIT的许可</a>下发布到了<a href="https://github.com/github/gh-ost" target="_blank" rel="noopener">开源社区</a>。</p>
<p>虽然gh-ost在使用中很稳定,我们还在不断的完善和改进。我们将其开源也欢迎社会各界的朋友能够参与和贡献。随后我们会发布 贡献和建议的页面。</p>
<p>我们会积极的维护 gh-ost 项目,同时希望广大的用户可以尝试和测试这个工具,我们做了很大努力使之更值得信赖。</p>
<h2 id="译者注"><a href="#译者注" class="headerlink" title="译者注"></a>译者注</h2><p>gh-ost 是MySQL业界在线修改表结构工具中的一名新秀,通常我们都是通过Percona的pt-online-schema-change工具来做这项工作,gh-ost的出现给我们带来了一种全新的方式。本文是翻译了一篇gh-ost的介绍文章,还没有尝试过这个工具。欢迎喜欢尝鲜网友谈谈使用感受。</p>
]]></content>
<summary type="html">
<p>MySQL在线更改schema的工具很多,如Percona的<a href="https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html" target="_blank" rel=
</summary>
<category term="数据库工具" scheme="https://salogs.com/categories/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%B7%A5%E5%85%B7/"/>
<category term="MySQL" scheme="https://salogs.com/tags/MySQL/"/>
</entry>
<entry>
<title>配置 salt</title>
<link href="https://salogs.com/news/2015/08/20/configure-salt/"/>
<id>https://salogs.com/news/2015/08/20/configure-salt/</id>
<published>2015-08-20T09:31:26.000Z</published>
<updated>2015-08-20T09:48:08.000Z</updated>
<content type="html"><![CDATA[<h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><h2 id="客户端配置"><a href="#客户端配置" class="headerlink" title="客户端配置"></a>客户端配置</h2><p>客户端配置比较简单,只需要配置一下server的地址就可以了,复杂的配置等到以后遇到的适合在进行介绍</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 配置salt服务端地址</span></span><br><span class="line">vi /etc/salt/minion</span><br><span class="line"></span><br><span class="line"><span class="comment"># 这里也可以写主机名</span></span><br><span class="line">master: 192.168.1.100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改完成后重启服务</span></span><br><span class="line">service salt-minion restart</span><br></pre></td></tr></table></figure>
<h2 id="服务端操作"><a href="#服务端操作" class="headerlink" title="服务端操作"></a>服务端操作</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看有哪些client发起了验证请求 </span></span><br><span class="line">salt-key list</span><br><span class="line"></span><br><span class="line">Accepted Keys:</span><br><span class="line">Denied Keys:</span><br><span class="line">Unaccepted Keys: <span class="comment"># 这里列出了为被接受的client 主机名</span></span><br><span class="line">host01</span><br><span class="line">host02</span><br><span class="line">Rejected Keys:</span><br><span class="line"></span><br><span class="line"><span class="comment"># 接受所有验证请求</span></span><br><span class="line">salt-key -A</span><br><span class="line">The following keys are going to be accepted:</span><br><span class="line">Unaccepted Keys:</span><br><span class="line">host01</span><br><span class="line">host02</span><br><span class="line">Proceed? [n/Y] Y</span><br><span class="line">Key <span class="keyword">for</span> minion host01 accepted.</span><br><span class="line">Key <span class="keyword">for</span> minion host02 accepted.</span><br><span class="line"></span><br><span class="line"><span class="comment"># 再查看一下</span></span><br><span class="line">salt-key list</span><br><span class="line">Accepted Keys: <span class="comment"># 已经接受</span></span><br><span class="line">host01</span><br><span class="line">host02</span><br><span class="line">Denied Keys:</span><br><span class="line">Unaccepted Keys:</span><br><span class="line">Rejected Keys:</span><br></pre></td></tr></table></figure>
<h1 id="简单验证执行命令"><a href="#简单验证执行命令" class="headerlink" title="简单验证执行命令"></a>简单验证执行命令</h1><h2 id="远程执行命令"><a href="#远程执行命令" class="headerlink" title="远程执行命令"></a>远程执行命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">salt aws-bj* cmd.run uptime</span><br><span class="line">host01:</span><br><span class="line"> 09:42:13 up 2:02, 2 users, load average: 0.05, 0.03, 0.05</span><br><span class="line">host02:</span><br><span class="line"> 09:42:12 up 1:28, 1 user, load average: 0.00, 0.01, 0.05</span><br></pre></td></tr></table></figure>
<h1 id="下篇预告"><a href="#下篇预告" class="headerlink" title="下篇预告"></a>下篇预告</h1><ul>
<li><a href="http://salogs.com/news/2015/08/20/group-salt/">03. 将client主机分组</a></li>
</ul>
<h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ul>
<li><a href="http://salogs.com/news/2015/08/20/ubuntu-install-salt/">01. Ubuntu 安装salt</a></li>
<li><a href="http://salogs.com/news/2015/08/20/configure-salt/">02. 配置 salt</a></li>
<li><a href="http://salogs.com/news/2015/08/20/group-salt/">03. 将client主机分组</a></li>
</ul>
]]></content>
<summary type="html">
<h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><h2 id="客户端配置"><a href="#客户端配置" class="headerlink" title="客户端配置"></a>客户端配
</summary>
<category term="运维自动化" scheme="https://salogs.com/categories/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
<category term="salt" scheme="https://salogs.com/tags/salt/"/>
</entry>
<entry>
<title>Ubuntu 安装salt</title>
<link href="https://salogs.com/news/2015/08/20/ubuntu-install-salt/"/>
<id>https://salogs.com/news/2015/08/20/ubuntu-install-salt/</id>
<published>2015-08-20T09:10:26.000Z</published>
<updated>2016-07-19T10:26:27.000Z</updated>
<content type="html"><![CDATA[<h1 id="添加仓库"><a href="#添加仓库" class="headerlink" title="添加仓库"></a>添加仓库</h1><p>最新的salt包会发布在 Ubuntu saltstack PPA。如果你的系统中有<code>add-apt-repository</code> 工具,可以通过一条命令添加仓库并导入PPA key:</p>
<h2 id="通过add-apt-repository添加仓库及PPA-key"><a href="#通过add-apt-repository添加仓库及PPA-key" class="headerlink" title="通过add-apt-repository添加仓库及PPA key"></a>通过add-apt-repository添加仓库及PPA key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">add-apt-repository ppa:saltstack/salt</span><br><span class="line"></span><br><span class="line"> Salt, the remote execution and configuration management tool.</span><br><span class="line"> More info: https://launchpad.net/~saltstack/+archive/ubuntu/salt</span><br><span class="line">Press [ENTER] to <span class="built_in">continue</span> or ctrl-c to cancel adding it</span><br><span class="line"></span><br><span class="line">gpg: keyring `/tmp/tmpys0ah_nb/secring.gpg<span class="string">' created</span></span><br><span class="line"><span class="string">gpg: keyring `/tmp/tmpys0ah_nb/pubring.gpg'</span> created</span><br><span class="line">gpg: requesting key 0E27C0A6 from hkp server keyserver.ubuntu.com</span><br><span class="line">gpg: /tmp/tmpys0ah_nb/trustdb.gpg: trustdb created</span><br><span class="line">gpg: key 0E27C0A6: public key <span class="string">"Launchpad PPA for Salt Stack"</span> imported</span><br><span class="line">gpg: Total number processed: 1</span><br><span class="line">gpg: imported: 1 (RSA: 1)</span><br><span class="line">OK</span><br></pre></td></tr></table></figure>
<blockquote>
<p>如果没有找到add-apt-repository命令可以执行下面的命令进行安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install python-software-properties</span><br></pre></td></tr></table></figure>
<p>可能也需要安装下面的包</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install software-properties-common</span><br></pre></td></tr></table></figure>
</blockquote>
<h2 id="手动添加仓库及PPA-key"><a href="#手动添加仓库及PPA-key" class="headerlink" title="手动添加仓库及PPA key"></a>手动添加仓库及PPA key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> deb http://ppa.launchpad.net/saltstack/salt/ubuntu `lsb_release -sc` main | sudo tee /etc/apt/sources.list.d/saltstack.list</span><br><span class="line">wget -q -O- <span class="string">"http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4759FA960E27C0A6"</span> |apt-key add -</span><br></pre></td></tr></table></figure>
<h2 id="更新仓库元数据"><a href="#更新仓库元数据" class="headerlink" title="更新仓库元数据"></a>更新仓库元数据</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get update</span><br></pre></td></tr></table></figure>
<h1 id="安装包"><a href="#安装包" class="headerlink" title="安装包"></a>安装包</h1><h2 id="服务端"><a href="#服务端" class="headerlink" title="服务端"></a>服务端</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install salt-master salt-minion salt-syndic</span><br></pre></td></tr></table></figure>
<h2 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install salt-minion</span><br></pre></td></tr></table></figure>
<h1 id="ZEROMQ-4"><a href="#ZEROMQ-4" class="headerlink" title="ZEROMQ 4"></a>ZEROMQ 4</h1><p>ZeroMQ 4 在 Ubuntu 14.04 以上版本已经与系统集成。因此Ubuntu 12.04 LTS 之前的版本需要升级到ZEROMQ 4</p>
]]></content>
<summary type="html">
<h1 id="添加仓库"><a href="#添加仓库" class="headerlink" title="添加仓库"></a>添加仓库</h1><p>最新的salt包会发布在 Ubuntu saltstack PPA。如果你的系统中有<code>add-apt-reposi
</summary>
<category term="运维自动化" scheme="https://salogs.com/categories/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
<category term="salt" scheme="https://salogs.com/tags/salt/"/>
</entry>
<entry>
<title>保存iptable规则并开机自动加载</title>
<link href="https://salogs.com/news/2015/08/20/iptables-save/"/>
<id>https://salogs.com/news/2015/08/20/iptables-save/</id>
<published>2015-08-20T08:46:27.000Z</published>
<updated>2015-08-20T09:07:36.000Z</updated>
<content type="html"><![CDATA[<h1 id="iptables-save"><a href="#iptables-save" class="headerlink" title="iptables-save"></a>iptables-save</h1><p>利用iptables-save命令可以将iptable规则保存到一个持久化存储的目录中,不同的系统保存的目录也有所不同(IPv4):</p>
<p>Debian/Ubuntu: <code>iptables-save</code> > /etc/iptables/rules.v4</p>
<p>RHEL/CentOS: <code>iptables-save</code> > /etc/sysconfig/iptables</p>
<p>保存之后,可以通过iptables-restore命令载入(IPv4):</p>
<p>Debian/Ubuntu: <code>iptables-restore</code> < /etc/iptables/rules.v4</p>
<p>RHEL/CentOS: <code>iptables-restore</code> < /etc/sysconfig/iptables</p>
<p>上面是针对IPv5的规则,如果你有使用IPv6的规则,通常需要执行下面对应的IPv6保存和恢复的命令(IPv4:</p>
<p>Debian/Ubuntu: <code>ip6tables-save</code> > /etc/iptables/rules.v6<br>RHEL/CentOS: <code>ip6tables-save</code> > /etc/sysconfig/ip6tables</p>
<p><strong>注意:</strong> 这种方式只是保存规则和恢复的一种方式,并不是说保存规则后下次启动就会自动加载。一定要记住这点,如果要想系统启动后自动加载请看下面的方式。</p>
<h1 id="iptables-persistent-Debian-Ubuntu"><a href="#iptables-persistent-Debian-Ubuntu" class="headerlink" title="iptables-persistent (Debian/Ubuntu)"></a>iptables-persistent (Debian/Ubuntu)</h1><p>从 Ubuntu 10.04 LTS (Lucid) 和 Debian 6.0 (Squeeze) 版本开始,可以通过安装一个名为 “iptables-persistent” 的包,安装后它以守护进程的方式来运行,系统重启后可以自动将保存的内容加载到iptables中。当然前提也是需要先保存规则。</p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install iptables-persistent</span><br></pre></td></tr></table></figure>
<h2 id="保存规则"><a href="#保存规则" class="headerlink" title="保存规则"></a>保存规则</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">service iptables-persistent save</span><br><span class="line">* Saving rules... * IPv4... * IPv6...</span><br><span class="line"></span><br><span class="line">ls -1 /etc/iptables/</span><br><span class="line">rules.v4</span><br><span class="line">rules.v6</span><br><span class="line"></span><br><span class="line">cat /etc/iptables/rules.v4</span><br><span class="line"><span class="comment"># Generated by iptables-save v1.4.21 on Thu Aug 20 08:59:52 2015</span></span><br><span class="line">*filter</span><br><span class="line">:INPUT ACCEPT [5726:774869]</span><br><span class="line">:FORWARD ACCEPT [170:27598]</span><br><span class="line">:OUTPUT ACCEPT [5467:789045]</span><br><span class="line">COMMIT</span><br><span class="line"><span class="comment"># Completed on Thu Aug 20 08:59:52 2015</span></span><br><span class="line"><span class="comment"># Generated by iptables-save v1.4.21 on Thu Aug 20 08:59:52 2015</span></span><br><span class="line">*nat</span><br><span class="line">:PREROUTING ACCEPT [23:1596]</span><br><span class="line">:INPUT ACCEPT [0:0]</span><br><span class="line">:OUTPUT ACCEPT [9:540]</span><br><span class="line">:POSTROUTING ACCEPT [0:0]</span><br><span class="line">-A POSTROUTING <span class="_">-s</span> 10.0.0.0/16 -o eth0 -j MASQUERADE</span><br><span class="line">COMMIT</span><br><span class="line"><span class="comment"># Completed on Thu Aug 20 08:59:52 2015</span></span><br></pre></td></tr></table></figure>
<h1 id="RHEL-和-CentOS-保存规则"><a href="#RHEL-和-CentOS-保存规则" class="headerlink" title="RHEL 和 CentOS 保存规则"></a>RHEL 和 CentOS 保存规则</h1><p>RHEL/CentOS 提供了简单的方式来持久化存储iptables规则,可以直接通过iptables服务的命令来完成:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">chkconfig --list | grep iptables</span><br><span class="line"> iptables 0:off 1:off 2:on 3:on 4:on 5:on 6:off</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果不是开机启动,需要执行下面命令</span></span><br><span class="line">chkconfig iptables on</span><br><span class="line"></span><br><span class="line"><span class="comment"># 保存规则</span></span><br><span class="line">service iptables save</span><br></pre></td></tr></table></figure>
<p>IPv4规则信息会保存到 /etc/sysconfig/iptables 文件中,IPv6 规则保存到 /etc/sysconfig/ip6tables 文件中。 必须执行<code>service iptables save</code> 命令才会保存,保存后系统重启后会自动加载。</p>
]]></content>
<summary type="html">
<h1 id="iptables-save"><a href="#iptables-save" class="headerlink" title="iptables-save"></a>iptables-save</h1><p>利用iptables-save命令可以将iptabl
</summary>
<category term="经验分享" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
<category term="Iptables" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/Iptables/"/>
<category term="iptables" scheme="https://salogs.com/tags/iptables/"/>
</entry>
<entry>
<title>MySQL备份与恢复</title>
<link href="https://salogs.com/news/2015/08/17/database-backup-and-recovery/"/>
<id>https://salogs.com/news/2015/08/17/database-backup-and-recovery/</id>
<published>2015-08-17T06:04:14.000Z</published>
<updated>2015-08-17T06:06:43.000Z</updated>
<content type="html"><![CDATA[<h1 id="数据库备份与恢复"><a href="#数据库备份与恢复" class="headerlink" title="数据库备份与恢复"></a>数据库备份与恢复</h1><h2 id="当前数据库结构"><a href="#当前数据库结构" class="headerlink" title="当前数据库结构"></a>当前数据库结构</h2><p>db01 (主)</p>
<p>db02 (从)</p>
<h2 id="备份策略"><a href="#备份策略" class="headerlink" title="备份策略"></a>备份策略</h2><ul>
<li>每天一次全量备份</li>
<li>每小时一次增量备份</li>
<li>通过热备工具(不锁表,不影响线上应用)备份主库数据</li>
<li>备份数据保存到db01,备份完成scp到db02</li>
<li>保留一个月的增量及全量备份</li>
</ul>
<table>
<thead>
<tr>
<th>时间</th>
<th>备份类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>00:01</td>
<td>全量备份</td>
</tr>
<tr>
<td>01:01</td>
<td>增量备份(当天首次)</td>
</tr>
<tr>
<td>02:01~23:01</td>
<td>增量备份</td>
</tr>
</tbody>
</table>
<blockquote>
<p>随着数据量的增加,全量备份可以做成每周一次,每2~8小时一次增量备份</p>
</blockquote>
<h2 id="备份目录"><a href="#备份目录" class="headerlink" title="备份目录"></a>备份目录</h2><p>db{01,02}:/data/backup/{full,incremental}/</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">backup/</span><br><span class="line">├── full</span><br><span class="line">│ ├── 2015-08-16 <span class="comment"># 每日全量备份</span></span><br><span class="line">│ └── 2015-08-16.log <span class="comment"># 全量备份日志</span></span><br><span class="line">└── incremental </span><br><span class="line"> ├── 2015-08-16_14 <span class="comment"># 每小时增量备份</span></span><br><span class="line"> ├── 2015-08-16_14.log <span class="comment"># 每小时增量备份日志</span></span><br><span class="line"> ├── 2015-08-16_15</span><br><span class="line"> ├── 2015-08-16_15.log</span><br><span class="line"> ├── 2015-08-16_16</span><br><span class="line"> ├── 2015-08-16_16.log</span><br><span class="line"> ├── 2015-08-16_17</span><br><span class="line"> ├── 2015-08-16_17.log</span><br><span class="line"> ├── 2015-08-16_18</span><br><span class="line"> ├── 2015-08-16_18.log</span><br><span class="line"> ├── 2015-08-16_19</span><br><span class="line"> └── 2015-08-16_19.log</span><br></pre></td></tr></table></figure>
<h2 id="备份计划任务"><a href="#备份计划任务" class="headerlink" title="备份计划任务"></a>备份计划任务</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Info : 数据库备份</span></span><br><span class="line"><span class="comment"># Author : zhouyq</span></span><br><span class="line"><span class="comment"># CTime : 2015-08-16</span></span><br><span class="line"><span class="comment"># 全量备份</span></span><br><span class="line">1 0 * * * /bin/bash /root/bin/bakdb.sh full</span><br><span class="line"><span class="comment"># 第一次增量备份</span></span><br><span class="line">1 1 * * * /bin/bash /root/bin/bakdb.sh incremental first</span><br><span class="line"><span class="comment"># 其他时间段增量备份</span></span><br><span class="line">1 2-23 * * * /bin/bash /root/bin/bakdb.sh incremental</span><br></pre></td></tr></table></figure>
<h2 id="备份脚本"><a href="#备份脚本" class="headerlink" title="备份脚本"></a>备份脚本</h2><p>/root/bin/bakdb.sh</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">fullPath=<span class="string">"/data/backup/full"</span></span><br><span class="line">incrPath=<span class="string">"/data/backup/incremental"</span></span><br><span class="line">bakdate=`date +<span class="string">'%F'</span>`</span><br><span class="line">bakhour=`date +<span class="string">'%H'</span>`</span><br><span class="line"></span><br><span class="line">oneHourAgo=`date <span class="_">-d</span> <span class="string">'1 hours ago'</span> +<span class="string">'%F_%H'</span>`</span><br><span class="line"></span><br><span class="line">BakB<span class="keyword">in</span>=<span class="string">"/usr/bin/innobackupex --no-timestamp --user=root --socket /data/db/tmp/mysql.sock --defaults-file=/usr/local/mysql/my.cnf --sleep 100"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># backup function</span></span><br><span class="line"><span class="keyword">function</span> <span class="function"><span class="title">hotbackup</span></span>(){</span><br><span class="line"></span><br><span class="line"> baktype=<span class="variable">$1</span></span><br><span class="line"> logfile=<span class="variable">$2</span></span><br><span class="line"> incrpath=<span class="variable">$3</span></span><br><span class="line"> bakpath=<span class="variable">$4</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> [ <span class="string">"<span class="variable">$baktype</span>"</span> == <span class="string">"full"</span> ];<span class="keyword">then</span></span><br><span class="line"> <span class="variable">$BakBin</span> <span class="variable">$bakpath</span> > <span class="variable">$logfile</span> 2>&1</span><br><span class="line"> <span class="keyword">elif</span> [ <span class="string">"<span class="variable">$baktype</span>"</span> == <span class="string">"incremental"</span> ];<span class="keyword">then</span></span><br><span class="line"> <span class="variable">$BakBin</span> --incremental <span class="variable">$incrpath</span> --incremental-basedir <span class="variable">$bakpath</span> > <span class="variable">$logfile</span> 2>&1</span><br><span class="line"> <span class="keyword">fi</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment"># ============= Main =============</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">$1</span>"</span> == <span class="string">"full"</span> ];<span class="keyword">then</span></span><br><span class="line"> <span class="comment"># 全量备份</span></span><br><span class="line"> hotbackup <span class="string">"full"</span> <span class="string">"<span class="variable">${fullPath}</span>/<span class="variable">${bakdate}</span>.log"</span> <span class="string">"none"</span> <span class="string">"<span class="variable">$fullPath</span>/<span class="variable">$bakdate</span>"</span></span><br><span class="line"> /usr/bin/scp -P 9922 -rp <span class="variable">${fullPath}</span>/<span class="variable">${bakdate}</span>* db02:<span class="variable">${fullPath}</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">elif</span> [ <span class="string">"<span class="variable">$1</span>"</span> == <span class="string">"incremental"</span> ];<span class="keyword">then</span></span><br><span class="line"> <span class="comment"># 判断是否为第一次增量备份,只有第一次增量备份目录指向全量备份</span></span><br><span class="line"> <span class="comment"># 第二次开始增量备份的上一次目录指向第一次增量目录即可</span></span><br><span class="line"> <span class="keyword">if</span> [ <span class="string">"<span class="variable">$2</span>"</span> == <span class="string">"first"</span> ];<span class="keyword">then</span></span><br><span class="line"> hotbackup <span class="string">"incremental"</span> <span class="string">"<span class="variable">${incrPath}</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>.log"</span> <span class="string">"<span class="variable">$incrPath</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>"</span> <span class="string">"<span class="variable">$fullPath</span>/<span class="variable">$bakdate</span>"</span></span><br><span class="line"> /usr/bin/scp -P 9922 -rp <span class="variable">${incrPath}</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>* db02:<span class="variable">${incrPath}</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hotbackup <span class="string">"incremental"</span> <span class="string">"<span class="variable">${incrPath}</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>.log"</span> <span class="string">"<span class="variable">$incrPath</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>"</span> <span class="string">"<span class="variable">$incrPath</span>/<span class="variable">${oneHourAgo}</span>"</span></span><br><span class="line"> /usr/bin/scp -P 9922 -rp <span class="variable">${incrPath}</span>/<span class="variable">${bakdate}</span>_<span class="variable">${bakhour}</span>* db02:<span class="variable">${incrPath}</span></span><br><span class="line"> <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure>
<h2 id="恢复"><a href="#恢复" class="headerlink" title="恢复"></a>恢复</h2><h3 id="全量备份恢复"><a href="#全量备份恢复" class="headerlink" title="全量备份恢复"></a>全量备份恢复</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">innobackupex --user=root --defaults-file=/usr/<span class="built_in">local</span>/mysql/my.cnf --apply-log /data/backup/full/2015-08-16</span><br><span class="line"></span><br><span class="line">innobackupex --user=root --defaults-file=/usr/<span class="built_in">local</span>/mysql/my.cnf --move-back /data/backup/full/2015-08-16</span><br></pre></td></tr></table></figure>
<h3 id="增量备份恢复"><a href="#增量备份恢复" class="headerlink" title="增量备份恢复"></a>增量备份恢复</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">innobackupex --defaults-file=/usr/<span class="built_in">local</span>/mysql/my.cnf --user=root --apply-log --redo-only /data/backup/full/2015-08-16 </span><br><span class="line"> </span><br><span class="line">innobackupex --defaults-file=/usr/<span class="built_in">local</span>/mysql/my.cnf --user=root --apply-log --redo-only /data/backup/full/2015-08-16 --incremental-dir=/data/backup/incremental/2015-08-16_14 </span><br><span class="line"> </span><br><span class="line">innobackupex --defaults-file=/usr/<span class="built_in">local</span>/mysql/my.cnf --user=root --apply-log --redo-only /data/backup/full/2015-08-16 --incremental-dir=/data/backup/incremental/2015-08-16_15</span><br></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<h1 id="数据库备份与恢复"><a href="#数据库备份与恢复" class="headerlink" title="数据库备份与恢复"></a>数据库备份与恢复</h1><h2 id="当前数据库结构"><a href="#当前数据库结构" class="header
</summary>
<category term="经验分享" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
<category term="MySQL" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/MySQL/"/>
<category term="XtraBackup" scheme="https://salogs.com/tags/XtraBackup/"/>
</entry>
<entry>
<title>【转】十条有用的 GO 技术</title>
<link href="https://salogs.com/news/2015/08/04/ten-go-best-practice/"/>
<id>https://salogs.com/news/2015/08/04/ten-go-best-practice/</id>
<published>2015-08-04T01:16:16.000Z</published>
<updated>2015-08-04T03:36:14.000Z</updated>
<content type="html"><![CDATA[<p>原文<a href="http://mikespook.com/2014/07/%e5%8d%81%e6%9d%a1%e6%9c%89%e7%94%a8%e7%9a%84-go-%e6%8a%80%e6%9c%af/" target="_blank" rel="noopener">在此</a>,实用总结。</p>
<hr>
<h1 id="十条有用的-Go-技术"><a href="#十条有用的-Go-技术" class="headerlink" title="十条有用的 Go 技术"></a>十条有用的 Go 技术</h1><p>这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指:<br>某个应用需要适配一个灵活的环境。你不希望每过 3 到 4 个月就不得不将它们全部重构一遍。添加新的特性应当很容易。许多人参与开发该应用,它应当可以被理解,且维护简单。许多人使用该应用,bug 应该容易被发现并且可以快速的修复。我用了很长的时间学到了这些事情。其中的一些很微小,但对于许多事情都会有影响。所有这些都仅仅是建议,具体情况具体对待,并且如果有帮助的话务必告诉我。随时留言:)</p>
<h1 id="1-使用单一的-GOPATH"><a href="#1-使用单一的-GOPATH" class="headerlink" title="1. 使用单一的 GOPATH"></a>1. 使用单一的 GOPATH</h1><p>多个 GOPATH 的情况并不具有弹性。GOPATH 本身就是高度自我完备的(通过导入路径)。有多个 GOPATH 会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个 GOPATH 的情况。所以只使用单一的 GOPATH,这会提升你 Go 的开发进度。</p>
<p>许多人不同意这一观点,接下来我会做一些澄清。像 <a href="https://github.com/coreos/etcd" target="_blank" rel="noopener">etcd</a> 或 <a href="https://camlistore.org/" target="_blank" rel="noopener">camlistore</a> 这样的大项目使用了像 <a href="https://github.com/tools/godep" target="_blank" rel="noopener">godep</a> 这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的 GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的 GOPATH。如果你认为项目需要一个自己的 GOPATH 目录,那么就创建它,否则不要尝试使用多个 GOPATH。它只会拖慢你的进度。</p>
<h1 id="2-将-for-select-封装到函数中"><a href="#2-将-for-select-封装到函数中" class="headerlink" title="2. 将 for-select 封装到函数中"></a>2. 将 for-select 封装到函数中</h1><p>如果在某个条件下,你需要从 for-select 中退出,就需要使用标签。例如:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> </span><br><span class="line">L:</span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> <span class="keyword">select</span> {</span><br><span class="line"> <span class="keyword">case</span> <-time.After(time.Second):</span><br><span class="line"> fmt.Println(<span class="string">"hello"</span>)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">break</span> L</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> fmt.Println(<span class="string">"ending"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如你所见,需要联合break使用标签。这有其用途,不过我不喜欢。这个例子中的 for 循环看起来很小,但是通常它们会更大,而判断break的条件也更为冗长。</p>
<p>如果需要退出循环,我会将 for-select 封装到函数中:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> foo()</span><br><span class="line"> fmt.Println(<span class="string">"ending"</span>)</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> <span class="keyword">select</span> {</span><br><span class="line"> <span class="keyword">case</span> <-time.After(time.Second):</span><br><span class="line"> fmt.Println(<span class="string">"hello"</span>)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你还可以返回一个错误(或任何其他值),也是同样漂亮的,只需要:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">// 阻塞</span><br><span class="line"><span class="keyword">if</span> err := foo(); err != nil {</span><br><span class="line"> // 处理 err</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="3-在初始化结构体时使用带有标签的语法"><a href="#3-在初始化结构体时使用带有标签的语法" class="headerlink" title="3. 在初始化结构体时使用带有标签的语法"></a>3. 在初始化结构体时使用带有标签的语法</h1><p>这是一个无标签语法的例子:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> T <span class="keyword">struct</span> {</span><br><span class="line"> Foo <span class="keyword">string</span></span><br><span class="line"> Bar <span class="keyword">int</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> t := T{<span class="string">"example"</span>, <span class="number">123</span>} <span class="comment">// 无标签语法</span></span><br><span class="line"> fmt.Printf(<span class="string">"t %+v\n"</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>那么如果你添加一个新的字段到T结构体,代码会编译失败:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> T <span class="keyword">struct</span> {</span><br><span class="line"> Foo <span class="keyword">string</span></span><br><span class="line"> Bar <span class="keyword">int</span></span><br><span class="line"> Qux <span class="keyword">string</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> t := T{<span class="string">"example"</span>, <span class="number">123</span>} <span class="comment">// 无法编译</span></span><br><span class="line"> fmt.Printf(<span class="string">"t %+v\n"</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果使用了标签语法,Go 的兼容性规则(<a href="http://golang.org/doc/go1compat" target="_blank" rel="noopener">http://golang.org/doc/go1compat</a>)会处理代码。例如在向net包的类型添加叫做Zone的字段,参见:<a href="http://golang.org/doc/go1.1#library" target="_blank" rel="noopener">http://golang.org/doc/go1.1#library</a>。回到我们的例子,使用标签语法:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">type</span> T struct {</span><br><span class="line"> Foo string</span><br><span class="line"> Bar int</span><br><span class="line"> Qux string</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">func <span class="function"><span class="title">main</span></span>() {</span><br><span class="line"> t := T{Foo: <span class="string">"example"</span>, Qux: 123}</span><br><span class="line"> fmt.Printf(<span class="string">"t %+v\n"</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个编译起来没问题,而且弹性也好。不论你如何添加其他字段到T结构体。你的代码总是能编译,并且在以后的 Go 的版本也可以保证这一点。只要在代码集中执行go vet,就可以发现所有的无标签的语法。</p>
<h1 id="4-将结构体的初始化拆分到多行"><a href="#4-将结构体的初始化拆分到多行" class="headerlink" title="4. 将结构体的初始化拆分到多行"></a>4. 将结构体的初始化拆分到多行</h1><p>如果有两个以上的字段,那么就用多行。它会让你的代码更加容易阅读,也就是说不要:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">T{Foo: <span class="string">"example"</span>, Bar:someLongVariable, Qux:anotherLongVariable, B: forgetToAddThisToo}</span><br></pre></td></tr></table></figure>
<p>而是:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">T{</span><br><span class="line"> Foo: <span class="string">"example"</span>,</span><br><span class="line"> Bar: someLongVariable,</span><br><span class="line"> Qux: anotherLongVariable,</span><br><span class="line"> B: forgetToAddThisToo,</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这有许多好处,首先它容易阅读,其次它使得允许或屏蔽字段初始化变得容易(只要注释或删除它们),最后添加其他字段也更容易(只要添加一行)。</p>
<h1 id="5-为整数常量添加-String-方法"><a href="#5-为整数常量添加-String-方法" class="headerlink" title="5. 为整数常量添加 String() 方法"></a>5. 为整数常量添加 String() 方法</h1><p>如果你利用 iota 来使用自定义的整数枚举类型,务必要为其添加 String() 方法。例如,像这样:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> State <span class="keyword">int</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line"> Running State = <span class="literal">iota</span></span><br><span class="line"> Stopped</span><br><span class="line"> Rebooting</span><br><span class="line"> Terminated</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>如果你创建了这个类型的一个变量,然后输出,会得到一个整数(<a href="http://play.golang.org/p/V5VVFB05HB" target="_blank" rel="noopener">http://play.golang.org/p/V5VVFB05HB</a>):</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> state := Running</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// print: "state 0"</span></span><br><span class="line"> fmt.Println(<span class="string">"state "</span>, state)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>除非你回顾常量定义,否则这里的0看起来毫无意义。只需要为State类型添加String()方法就可以修复这个问题(<a href="http://play.golang.org/p/ewMKl6K302" target="_blank" rel="noopener">http://play.golang.org/p/ewMKl6K302</a>):</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s State)</span> <span class="title">String</span><span class="params">()</span> <span class="title">string</span></span> {</span><br><span class="line"> <span class="keyword">switch</span> s {</span><br><span class="line"> <span class="keyword">case</span> Running:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Running"</span></span><br><span class="line"> <span class="keyword">case</span> Stopped:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Stopped"</span></span><br><span class="line"> <span class="keyword">case</span> Rebooting:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Rebooting"</span></span><br><span class="line"> <span class="keyword">case</span> Terminated:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Terminated"</span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Unknown"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>新的输出是:state: Running。显然现在看起来可读性好了很多。在你调试程序的时候,这会带来更多的便利。同时还可以在实现 MarshalJSON()、UnmarshalJSON() 这类方法的时候使用同样的手段。</p>
<h1 id="6-让-iota-从-a-1-开始增量"><a href="#6-让-iota-从-a-1-开始增量" class="headerlink" title="6. 让 iota 从 a +1 开始增量"></a>6. 让 iota 从 a +1 开始增量</h1><p>在前面的例子中同时也产生了一个我已经遇到过许多次的 bug。假设你有一个新的结构体,有一个State字段:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> T <span class="keyword">struct</span> {</span><br><span class="line"> Name <span class="keyword">string</span></span><br><span class="line"> Port <span class="keyword">int</span></span><br><span class="line"> State State</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在如果基于 T 创建一个新的变量,然后输出,你会得到奇怪的结果(<a href="http://play.golang.org/p/LPG2RF3y39" target="_blank" rel="noopener">http://play.golang.org/p/LPG2RF3y39</a>):</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> t := T{Name: <span class="string">"example"</span>, Port: <span class="number">6666</span>}</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// prints: "t {Name:example Port:6666 State:Running}"</span></span><br><span class="line"> fmt.Printf(<span class="string">"t %+v\n"</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>看到 bug 了吗?State字段没有初始化,Go 默认使用对应类型的零值进行填充。由于State是一个整数,零值也就是0,但在我们的例子中它表示Running。</p>
<p>那么如何知道 State 被初始化了?还是它真得是在Running模式?没有办法区分它们,那么这就会产生未知的、不可预测的 bug。不过,修复这个很容易,只要让 iota 从 +1 开始(<a href="http://play.golang.org/p/VyAq-3OItv" target="_blank" rel="noopener">http://play.golang.org/p/VyAq-3OItv</a>):</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> (</span><br><span class="line"> Running State = <span class="literal">iota</span> + <span class="number">1</span></span><br><span class="line"> Stopped</span><br><span class="line"> Rebooting</span><br><span class="line"> Terminated</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>现在t变量将默认输出Unknown,不是吗? :) </p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> t := T{Name: <span class="string">"example"</span>, Port: <span class="number">6666</span>}</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 输出: "t {Name:example Port:6666 State:Unknown}"</span></span><br><span class="line"> fmt.Printf(<span class="string">"t %+v\n"</span>, t)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>不过让 iota 从零值开始也是一种解决办法。例如,你可以引入一个新的状态叫做Unknown,将其修改为:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> (</span><br><span class="line"> Unknown State = <span class="literal">iota</span></span><br><span class="line"> Running</span><br><span class="line"> Stopped</span><br><span class="line"> Rebooting</span><br><span class="line"> Terminated</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<h1 id="7-返回函数调用"><a href="#7-返回函数调用" class="headerlink" title="7. 返回函数调用"></a>7. 返回函数调用</h1><p>我已经看过很多代码例如(<a href="http://play.golang.org/p/8Rz1EJwFTZ" target="_blank" rel="noopener">http://play.golang.org/p/8Rz1EJwFTZ</a>):</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> {</span><br><span class="line"> v, err := foo()</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">""</span>, err</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> v, <span class="literal">nil</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然而,你只需要:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> {</span><br><span class="line"> <span class="keyword">return</span> foo()</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>更简单也更容易阅读(当然,除非你要对某些内部的值做一些记录)。</p>
<h1 id="8-把-slice、map-等定义为自定义类型"><a href="#8-把-slice、map-等定义为自定义类型" class="headerlink" title="8. 把 slice、map 等定义为自定义类型"></a>8. 把 slice、map 等定义为自定义类型</h1><p>将 slice 或 map 定义成自定义类型可以让代码维护起来更加容易。假设有一个Server类型和一个返回服务器列表的函数:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Server <span class="keyword">struct</span> {</span><br><span class="line"> Name <span class="keyword">string</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ListServers</span><span class="params">()</span> []<span class="title">Server</span></span> {</span><br><span class="line"> <span class="keyword">return</span> []Server{</span><br><span class="line"> {Name: <span class="string">"Server1"</span>},</span><br><span class="line"> {Name: <span class="string">"Server2"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo1"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo2"</span>},</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在假设需要获取某些特定名字的服务器。需要对 ListServers() 做一些改动,增加筛选条件:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ListServers 返回服务器列表。只会返回包含 name 的服务器。空的 name 将会返回所有服务器。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ListServers</span><span class="params">(name <span class="keyword">string</span>)</span> []<span class="title">Server</span></span> {</span><br><span class="line"> servers := []Server{</span><br><span class="line"> {Name: <span class="string">"Server1"</span>},</span><br><span class="line"> {Name: <span class="string">"Server2"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo1"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo2"</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 class="keyword">if</span> name == <span class="string">""</span> {</span><br><span class="line"> <span class="keyword">return</span> servers</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回过滤后的结果</span></span><br><span class="line"> filtered := <span class="built_in">make</span>([]Server, <span class="number">0</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> _, server := <span class="keyword">range</span> servers {</span><br><span class="line"> <span class="keyword">if</span> strings.Contains(server.Name, name) {</span><br><span class="line"> filtered = <span class="built_in">append</span>(filtered, server)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> filtered</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在可以用这个来筛选有字符串Foo的服务器:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> servers := ListServers(<span class="string">"Foo"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 输出:“servers [{Name:Foo1} {Name:Foo2}]”</span></span><br><span class="line"> fmt.Printf(<span class="string">"servers %+v\n"</span>, servers)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>显然这个函数能够正常工作。不过它的弹性并不好。如果你想对服务器集合引入其他逻辑的话会如何呢?例如检查所有服务器的状态,为每个服务器创建一个数据库记录,用其他字段进行筛选等等……</p>
<p>现在引入一个叫做Servers的新类型,并且修改原始版本的 ListServers() 返回这个新类型:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Servers []Server</span><br><span class="line"> </span><br><span class="line"><span class="comment">// ListServers 返回服务器列表</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ListServers</span><span class="params">()</span> <span class="title">Servers</span></span> {</span><br><span class="line"> <span class="keyword">return</span> []Server{</span><br><span class="line"> {Name: <span class="string">"Server1"</span>},</span><br><span class="line"> {Name: <span class="string">"Server2"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo1"</span>},</span><br><span class="line"> {Name: <span class="string">"Foo2"</span>},</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在需要做的是只要为Servers类型添加一个新的Filter()方法:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Filter 返回包含 name 的服务器。空的 name 将会返回所有服务器。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s Servers)</span> <span class="title">Filter</span><span class="params">(name <span class="keyword">string</span>)</span> <span class="title">Servers</span></span> {</span><br><span class="line"> filtered := <span class="built_in">make</span>(Servers, <span class="number">0</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> _, server := <span class="keyword">range</span> s {</span><br><span class="line"> <span class="keyword">if</span> strings.Contains(server.Name, name) {</span><br><span class="line"> filtered = <span class="built_in">append</span>(filtered, server)</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="keyword">return</span> filtered</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在可以针对字符串Foo筛选服务器:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> servers := ListServers()</span><br><span class="line"> servers = servers.Filter(<span class="string">"Foo"</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"servers %+v\n"</span>, servers)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>哈!看到你的代码是多么的简单了吗?还想对服务器的状态进行检查?或者为每个服务器添加一条数据库记录?没问题,添加以下新方法即可:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s Servers)</span> <span class="title">Check</span><span class="params">()</span></span></span><br><span class="line"><span class="function"><span class="title">func</span> <span class="params">(s Servers)</span> <span class="title">AddRecord</span><span class="params">()</span></span></span><br><span class="line"><span class="function"><span class="title">func</span> <span class="params">(s Servers)</span> <span class="title">Len</span><span class="params">()</span></span></span><br><span class="line"><span class="function">...</span></span><br></pre></td></tr></table></figure>
<h1 id="9-withContext-封装函数"><a href="#9-withContext-封装函数" class="headerlink" title="9. withContext 封装函数"></a>9. withContext 封装函数</h1><p>有时对于函数会有一些重复劳动,例如锁/解锁,初始化一个新的局部上下文,准备初始化变量等等……这里有一个例子:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">()</span></span> {</span><br><span class="line"> mu.Lock()</span><br><span class="line"> <span class="keyword">defer</span> mu.Unlock()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// foo 相关的工作</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span></span> {</span><br><span class="line"> mu.Lock()</span><br><span class="line"> <span class="keyword">defer</span> mu.Unlock()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// bar 相关的工作</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">qux</span><span class="params">()</span></span> {</span><br><span class="line"> mu.Lock()</span><br><span class="line"> <span class="keyword">defer</span> mu.Unlock()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// qux 相关的工作</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果你想要修改某个内容,你需要对所有的都进行修改。如果它是一个常见的任务,那么最好创建一个叫做withContext的函数。这个函数的输入参数是另一个函数,并用调用者提供的上下文来调用它:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">withLockContext</span><span class="params">(fn <span class="keyword">func</span>()</span>)</span> {</span><br><span class="line"> mu.Lock</span><br><span class="line"> <span class="keyword">defer</span> mu.Unlock()</span><br><span class="line"> </span><br><span class="line"> fn()</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>只需要将之前的函数用这个进行封装:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">()</span></span> {</span><br><span class="line"> withLockContext(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// foo 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span></span> {</span><br><span class="line"> withLockContext(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// bar 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">qux</span><span class="params">()</span></span> {</span><br><span class="line"> withLockContext(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// qux 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>不要光想着加锁的情形。对此来说最好的用例是数据库链接。现在对 withContext 函数作一些小小的改动:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">withDBContext</span><span class="params">(fn <span class="keyword">func</span>(db DB)</span> <span class="title">error</span>) <span class="title">error</span></span> {</span><br><span class="line"> <span class="comment">// 从连接池获取一个数据库连接</span></span><br><span class="line"> dbConn := NewDB()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> fn(dbConn)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如你所见,它获取一个连接,然后传递给提供的参数,并且在调用函数的时候返回错误。你需要做的只是:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">()</span></span> {</span><br><span class="line"> withDBContext(<span class="function"><span class="keyword">func</span><span class="params">(db *DB)</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="comment">// foo 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bar</span><span class="params">()</span></span> {</span><br><span class="line"> withDBContext(<span class="function"><span class="keyword">func</span><span class="params">(db *DB)</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="comment">// bar 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">qux</span><span class="params">()</span></span> {</span><br><span class="line"> withDBContext(<span class="function"><span class="keyword">func</span><span class="params">(db *DB)</span> <span class="title">error</span></span> {</span><br><span class="line"> <span class="comment">// qux 相关工作</span></span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你在考虑一个不同的场景,例如作一些预初始化?没问题,只需要将它们加到withDBContext就可以了。这对于测试也同样有效。</p>
<p>这个方法有个缺陷,它增加了缩进并且更难阅读。再次提示,永远寻找最简单的解决方案。</p>
<h1 id="10-为访问-map-增加-setter,getters"><a href="#10-为访问-map-增加-setter,getters" class="headerlink" title="10. 为访问 map 增加 setter,getters"></a>10. 为访问 map 增加 setter,getters</h1><p>如果你重度使用 map 读写数据,那么就为其添加 getter 和 setter 吧。通过 getter 和 setter 你可以将逻辑封分别装到函数里。这里最常见的错误就是并发访问。如果你在某个 goroutein 里有这样的代码:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">m[<span class="string">"foo"</span>] = bar</span><br></pre></td></tr></table></figure>
<p>还有这个:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="built_in">delete</span>(m, <span class="string">"foo"</span>)</span><br></pre></td></tr></table></figure>
<p>会发生什么?你们中的大多数应当已经非常熟悉这样的竞态了。简单来说这个竞态是由于 map 默认并非线程安全。不过你可以用互斥量来保护它们:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line">m[<span class="string">"foo"</span>] = <span class="string">"bar"</span></span><br><span class="line">mu.Unlock()</span><br></pre></td></tr></table></figure>
<p>以及:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line"><span class="built_in">delete</span>(m, <span class="string">"foo"</span>)</span><br><span class="line">mu.Unlock()</span><br></pre></td></tr></table></figure>
<p>假设你在其他地方也使用这个 map。你必须把互斥量放得到处都是!然而通过 getter 和 setter 函数就可以很容易的避免这个问题:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Put</span><span class="params">(key, value <span class="keyword">string</span>)</span></span> {</span><br><span class="line"> mu.Lock()</span><br><span class="line"> m[key] = value</span><br><span class="line"> mu.Unlock()</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Delete</span><span class="params">(key <span class="keyword">string</span>)</span></span> {</span><br><span class="line"> mu.Lock()</span><br><span class="line"> <span class="built_in">delete</span>(m, key)</span><br><span class="line"> mu.Unlock()</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用接口可以对这一过程做进一步的改进。你可以将实现完全隐藏起来。只使用一个简单的、设计良好的接口,然后让包的用户使用它们:</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Storage <span class="keyword">interface</span> {</span><br><span class="line"> Delete(key <span class="keyword">string</span>)</span><br><span class="line"> Get(key <span class="keyword">string</span>) <span class="keyword">string</span></span><br><span class="line"> Put(key, value <span class="keyword">string</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这只是个例子,不过你应该能体会到。对于底层的实现使用什么都没关系。不光是使用接口本身很简单,而且还解决了暴露内部数据结构带来的大量的问题。</p>
<p>但是得承认,有时只是为了同时对若干个变量加锁就使用接口会有些过分。理解你的程序,并且在你需要的时候使用这些改进。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>抽象永远都不是容易的事情。有时,最简单的就是你已经实现的方法。要知道,不要让你的代码看起来很聪明。Go 天生就是个简单的语言,在大多数情况下只会有一种方法来作某事。简单是力量的源泉,也是为什么在人的层面它表现的如此有弹性。</p>
<p>如果必要的话,使用这些基数。例如将[]Server转化为Servers是另一种抽象,仅在你有一个合理的理由的情况下这么做。不过有一些技术,如 iota 从 1 开始计数总是有用的。再次提醒,永远保持简单。</p>
<p>特别感谢 Cihangir Savas、Andrew Gerrand、Ben Johnson 和 Damian Gryski 提供的极具价值的反馈和建议。</p>
]]></content>
<summary type="html">
<p>原文<a href="http://mikespook.com/2014/07/%e5%8d%81%e6%9d%a1%e6%9c%89%e7%94%a8%e7%9a%84-go-%e6%8a%80%e6%9c%af/" target="_blank" rel="noopen
</summary>
<category term="经验分享" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
<category term="Go" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/Go/"/>
<category term="Go" scheme="https://salogs.com/tags/Go/"/>
</entry>
<entry>
<title>阿里云VPC网络无EIP的主机上网问题</title>
<link href="https://salogs.com/news/2015/08/02/aliyun-vpc-snat-md/"/>
<id>https://salogs.com/news/2015/08/02/aliyun-vpc-snat-md/</id>
<published>2015-08-02T13:38:58.000Z</published>
<updated>2018-06-19T02:48:22.000Z</updated>
<content type="html"><![CDATA[<h1 id="问题由来"><a href="#问题由来" class="headerlink" title="问题由来"></a>问题由来</h1><p>阿里云的VPC与其他基于OpenStack的IaaS不同,他的路由只是作为多网段的路由交换,不提供内到外的路由,因此在VPC内的主机除非绑定EIP,否则是无法连接公网的。通过工单询问客服,得到的结论是通过在路由器上添加一个路由,通过一个绑定EIP的主机做NAT上网,通过设置iptables的方式来实现。</p>
<h1 id="VPC结构图"><a href="#VPC结构图" class="headerlink" title="VPC结构图"></a>VPC结构图</h1><p><img src="/img/aliyun-vpc-snat.jpg" alt=""></p>
<h1 id="虚拟路由器配置"><a href="#虚拟路由器配置" class="headerlink" title="虚拟路由器配置"></a>虚拟路由器配置</h1><h2 id="添加路由"><a href="#添加路由" class="headerlink" title="添加路由"></a>添加路由</h2><p>为了让内网服务器借助EIP访问公网,所以设置所有目标地址0.0.0.0/0下一跳都转发到绑定了公网IP的ECS实例上。这里的下一跳ECS不支持搜索,需要提前记号名称:</p>
<p><img src="/img/aliyun-vpc-snat-2.jpg" alt=""></p>
<h1 id="绑定EIP的主机配置"><a href="#绑定EIP的主机配置" class="headerlink" title="绑定EIP的主机配置"></a>绑定EIP的主机配置</h1><h2 id="iptables添加SNAT规则"><a href="#iptables添加SNAT规则" class="headerlink" title="iptables添加SNAT规则"></a>iptables添加SNAT规则</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">iptables -t nat -A POSTROUTING <span class="_">-s</span> 192.168.2.0/24 -o eth0 -j MASQUERADE</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意:</strong> ubuntu 14.04 系统保存iptables设置需要安装iptables-persistent包,然后通过 <code>service iptables-persistent save</code> 的方式保存配置,安装完iptables-persistent后该服务随系统一起启动并会把保存的配置应用</p>
</blockquote>
<h2 id="开启IP转发"><a href="#开启IP转发" class="headerlink" title="开启IP转发"></a>开启IP转发</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"net.ipv4.ip_forward=1"</span> >> /etc/sysctl.conf && sysctl -p</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="问题由来"><a href="#问题由来" class="headerlink" title="问题由来"></a>问题由来</h1><p>阿里云的VPC与其他基于OpenStack的IaaS不同,他的路由只是作为多网段的路由交换,不提供内到外的路由,因此在VPC
</summary>
<category term="经验分享" scheme="https://salogs.com/categories/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
<category term="route" scheme="https://salogs.com/tags/route/"/>
<category term="IaaS" scheme="https://salogs.com/tags/IaaS/"/>
</entry>
<entry>
<title>shell 数组应用实例</title>
<link href="https://salogs.com/news/2015/08/02/shell-array-demo/"/>
<id>https://salogs.com/news/2015/08/02/shell-array-demo/</id>
<published>2015-08-02T10:01:37.000Z</published>
<updated>2015-08-02T10:50:47.000Z</updated>
<content type="html"><![CDATA[<h1 id="数组定义"><a href="#数组定义" class="headerlink" title="数组定义"></a>数组定义</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 定义一个空数组</span></span><br><span class="line">Result=()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义并给数组赋值</span></span><br><span class="line">arr=(a b c d e)</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>说明:</strong> </p>
<ul>
<li><p>默认数组中的元素是以空格分隔的,如果元素是包含空格的字符串,最好用双引号括起来</p>
</li>
<li><p>shell中的默认分隔符可以通过修改 $IFS变量来设置</p>
</li>
</ul>
</blockquote>
<h1 id="数组读取-删除"><a href="#数组读取-删除" class="headerlink" title="数组读取/删除"></a>数组读取/删除</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 初始化并赋值数组</span></span><br><span class="line">arr=(a b c d e)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算长度</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${#arr[@]}</span> <span class="comment"># 结果: 5</span></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${#arr[*]}</span> <span class="comment"># 结果: 5</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 取出所有数据</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr[@]}</span> <span class="comment"># 结果: a b c d e</span></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr[*]}</span> <span class="comment"># 结果: a b c d e</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 取出第二个元素的数据</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr[1]}</span> <span class="comment"># 结果: b</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 遍历数组</span></span><br><span class="line">filelist=(`ls`)</span><br><span class="line"><span class="keyword">for</span> file <span class="keyword">in</span> <span class="variable">${filelist[@]}</span>;<span class="keyword">do</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$file</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除第二个元素</span></span><br><span class="line"><span class="built_in">unset</span> arr[1]</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr[*]}</span> <span class="comment"># 结果: a c d e</span></span><br></pre></td></tr></table></figure>
<h1 id="切片-元素替换"><a href="#切片-元素替换" class="headerlink" title="切片/元素替换"></a>切片/元素替换</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 切片</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始化并赋值数组</span></span><br><span class="line">arr=(a b c d e)</span><br><span class="line"></span><br><span class="line">arr2=(<span class="variable">${arr[@]:0:3}</span>)</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr2[@]}</span> <span class="comment"># 结果 a b c</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 class="comment"># 初始化并赋值数组</span></span><br><span class="line">arr=(a b c d e)</span><br><span class="line"><span class="built_in">echo</span> <span class="variable">${arr[@]/a/aaa}</span></span><br></pre></td></tr></table></figure>
<blockquote>
<ul>
<li><strong>切片(分片):</strong> 直接通过 ${数组名[@或*]:起始位置:长度} 切片原先数组,返回是字符串,中间用“空格”分开,因此如果加上”()”,将得到切片数组,上面例子:c 就是一个新数据。</li>
</ul>
<ul>
<li><strong>替换:</strong> ${数组名[@或*]/查找字符/替换字符} 该操作不会改变原先数组内容,如果需要修改,请重新定义变量并赋值。</li>
</ul>
</blockquote>
<h1 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h1><blockquote>
<p>在一个多域名的web server环境中,通过分析访问日志,统计最近8小时有用户访问的域名(去重),并显示。</p>
<p>日志格式:X-Forworld-IP User-IP YYYY-MM-DD HH:mm:ss method “URL” HTTP响应码 服务器处理时间 返回大小 “Refer” “浏览器信息” “虚拟主机域名” 真实处理请求的主机</p>
</blockquote>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">AWKB<span class="keyword">in</span>=<span class="string">"/usr/bin/awk"</span></span><br><span class="line">EGREPB<span class="keyword">in</span>=<span class="string">"/bin/egrep"</span></span><br><span class="line">SORTB<span class="keyword">in</span>=<span class="string">"/usr/bin/sort"</span></span><br><span class="line">SEDB<span class="keyword">in</span>=<span class="string">"/bin/sed"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 虚拟主机</span></span><br><span class="line">VSName=<span class="variable">$1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 时间段</span></span><br><span class="line">TimePeriod=<span class="variable">$2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 过滤字符串</span></span><br><span class="line">FilterKeys=<span class="string">''</span><span class="string">'DNSPod-Monitor|JianKongBao'</span><span class="string">''</span></span><br><span class="line"><span class="comment"># 日志目录</span></span><br><span class="line">LOGPath=<span class="string">"/logs/nginx"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义最终输出的数组变量</span></span><br><span class="line">Result=()</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (( TimePeriod > 0 ))</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line"> LogTime=`date <span class="_">-d</span> <span class="string">"- <span class="variable">${TimePeriod}</span>hours"</span> +%Y-%m-%d-%H`</span><br><span class="line"> LogFile=<span class="variable">${VSName}</span>_<span class="variable">${LogTime}</span>.log</span><br><span class="line"></span><br><span class="line"> Result=(<span class="variable">${Result[@]}</span> `<span class="variable">$AWKBin</span> <span class="string">'{if($5!~/HEAD/ && $5!~/\"\"/ ) print $0}'</span> <span class="variable">${LOGPath}</span>/<span class="variable">${LogFile}</span> | \</span><br><span class="line"> <span class="variable">$EGREPBin</span> -v <span class="variable">$FilterKeys</span> | <span class="variable">$AWKBin</span> <span class="string">'{print $NF}'</span> | <span class="variable">$AWKBin</span> -F <span class="string">'@'</span> <span class="string">'{print $1}'</span> |\</span><br><span class="line"> <span class="variable">$SORTBin</span> -u`)</span><br><span class="line"></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"第<span class="variable">$TimePeriod</span> 个日志:<span class="variable">${Result[*]}</span>"</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> ((TimePeriod--))</span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"未去重:<span class="variable">${Result[*]}</span>"</span></span><br><span class="line"></span><br><span class="line">Result=($(awk -vRS=<span class="string">' '</span> <span class="string">'!a[$1]++'</span> <<< <span class="variable">${Result[@]}</span>))</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"去重以后:<span class="variable">${Result[*]}</span>"</span></span><br></pre></td></tr></table></figure>
]]></content>