-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
2250 lines (2037 loc) · 385 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>WebFuse</title>
<link href="/atom.xml" rel="self"/>
<link href="https://webfuse.cn/"/>
<updated>2020-04-30T03:15:21.152Z</updated>
<id>https://webfuse.cn/</id>
<author>
<name>Jesen Kwan</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>从0开始用SpringCloud搭建微服务系统【五】</title>
<link href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/"/>
<id>https://webfuse.cn/2020/04/30/从0开始用SpringCloud搭建微服务系统【五】/</id>
<published>2020-04-30T02:20:21.000Z</published>
<updated>2020-04-30T03:15:21.152Z</updated>
<content type="html"><![CDATA[<h2 id="调用链监控"><a href="#调用链监控" class="headerlink" title="调用链监控"></a>调用链监控</h2><p>如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。</p>
<p>链路追踪目的:解决错综复杂的服务调用中链路的查看。排查慢服务。</p>
<p>市面上链路追踪产品,大部分基于<a href="http://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/36356.pdf" target="_blank" rel="external">google的Dapper论文</a>。</p>
<h3 id="链路追踪要考虑的几个问题"><a href="#链路追踪要考虑的几个问题" class="headerlink" title="链路追踪要考虑的几个问题"></a>链路追踪要考虑的几个问题</h3><ol>
<li>探针的性能消耗。尽量不影响 服务本尊。</li>
<li>易用。开发可以很快接入,别浪费太多精力。</li>
<li>数据分析。要实时分析。维度足够。</li>
</ol>
<h3 id="Sleuth"><a href="#Sleuth" class="headerlink" title="Sleuth"></a>Sleuth</h3><p>Sleuth是Spring cloud的分布式跟踪解决方案。</p>
<ol>
<li><p>span(跨度),基本工作单元。一次链路调用,创建一个span,span用一个64位id唯一标识。包括:id,描述,时间戳事件,spanId,span父id。</p>
<p>span被启动和停止时,记录了时间信息,初始化span叫:root span,它的span id和trace id相等。</p>
</li>
<li><p>trace(跟踪),一组共享“root span”的span组成的树状结构 称为 trace,trace也有一个64位ID,trace中所有span共享一个trace id。类似于一颗 span 树。</p>
</li>
<li><p>annotation(标签),annotation用来记录事件的存在,其中,核心annotation用来定义请求的开始和结束。</p>
<ul>
<li>CS(Client Send客户端发起请求)。客户端发起请求描述了span开始。</li>
<li>SR(Server Received服务端接到请求)。服务端获得请求并准备处理它。SR-CS=网络延迟。</li>
<li>SS(Server Send服务器端处理完成,并将结果发送给客户端)。表示服务器完成请求处理,响应客户端时。SS-SR=服务器处理请求的时间。</li>
<li>CR(Client Received 客户端接受服务端信息)。span结束的标识。客户端接收到服务器的响应。CR-CS=客户端发出请求到服务器响应的总时间。</li>
</ul>
</li>
</ol>
<p>其实数据结构是一颗树,从root span 开始。</p>
<img src="/2020/04/30/从0开始用SpringCloud搭建微服务系统【五】/sleuth.png" alt="sleuth.png" title="">
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>在每个需要被监控的系统的pom中引入:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment"><!-- 引入sleuth依赖 --></span></div><div class="line"> <span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-sleuth<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure>
<p>启动后,日志中可以看到类似于:<code>[account-service,,,]</code>的数据,说明如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">[服务名称,traceId(一条请求调用链中 唯一ID),spanID(基本的工作单元,获取数据等),是否让zipkin收集和展示此信息]</div></pre></td></tr></table></figure>
<h3 id="zipkin"><a href="#zipkin" class="headerlink" title="zipkin"></a>zipkin</h3><p>zipkin是twitter开源的分布式跟踪系统。</p>
<p>原理收集系统的时序数据,从而追踪微服务架构中系统延时等问题。还有一个友好的界面。</p>
<p>由4个部分组成:Collector(采集器)、Storage(存储器)、Restful API(接口)、Web UI</p>
<h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>sleuth收集跟踪信息通过http请求发送给zipkin server,zipkin将跟踪信息存储,以及提供RESTful API接口,zipkin ui通过调用api进行数据展示。</p>
<p>默认内存存储,可以用mysql,ES等存储。</p>
<h4 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h4><p>在每个需要被监控的系统的pom中引入:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment"><!-- zipkin --></span></div><div class="line"> <span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-zipkin<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure>
<p>在每个需要监听的服务的配置文件中加入:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="attr">spring:</span></div><div class="line"> <span class="comment">#zipkin</span></div><div class="line"><span class="attr"> zipkin:</span></div><div class="line"><span class="attr"> base-url:</span> <span class="attr">http://localhost:9411/</span></div><div class="line"> <span class="comment">#采样比例1</span></div><div class="line"><span class="attr"> sleuth:</span></div><div class="line"><span class="attr"> sampler:</span></div><div class="line"><span class="attr"> rate:</span> <span class="number">1</span></div></pre></td></tr></table></figure>
<p>根据<a href="https://zipkin.io/pages/quickstart.html" target="_blank" rel="external">zipkin官网中的Quickstart</a>,启动zipkin。</p>
<h2 id="监控告警"><a href="#监控告警" class="headerlink" title="监控告警"></a>监控告警</h2><h3 id="使用-Spring-Boot2-x-Actuator-监控应用"><a href="#使用-Spring-Boot2-x-Actuator-监控应用" class="headerlink" title="使用 Spring Boot2.x Actuator 监控应用"></a>使用 Spring Boot2.x Actuator 监控应用</h3><p>在项目的 POM 中引入</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-actuator<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"><span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure>
<h4 id="Endpoints"><a href="#Endpoints" class="headerlink" title="Endpoints"></a>Endpoints</h4><p>可以通过<code>/actuator</code>查看所有的暴露端点。</p>
<h5 id="暴露配置"><a href="#暴露配置" class="headerlink" title="暴露配置"></a>暴露配置</h5><figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="attr">management:</span></div><div class="line"><span class="attr"> endpoints:</span></div><div class="line"><span class="attr"> web:</span></div><div class="line"><span class="attr"> exposure:</span></div><div class="line"><span class="attr"> include:</span> <span class="string">'*'</span> <span class="comment"># 通过HTTP暴露所有的端点</span></div><div class="line"><span class="attr"> base-path:</span> <span class="string">'/actuator'</span></div><div class="line"><span class="attr"> jmx:</span></div><div class="line"><span class="attr"> exposure:</span></div><div class="line"><span class="attr"> include:</span> <span class="string">'*'</span> <span class="comment"># 通过JMX暴露所有的端点</span></div></pre></td></tr></table></figure>
<h5 id="health端点"><a href="#health端点" class="headerlink" title="health端点"></a>health端点</h5><p>health端点会聚合你程序的健康指标,来检查程序的健康情况。端点公开的应用健康信息取决于:<code>management.endpoint.health.show-details=always</code></p>
<p>show-details的值:</p>
<ul>
<li>never 不展示详细信息,up或者down的状态,默认配置</li>
<li>when-authorized 详细信息将会展示给通过认证的用户。授权的角色可以通过 management.endpoint.health.roles配置</li>
<li>always 对所有用户暴露详细信息</li>
</ul>
<h5 id="metrics端点"><a href="#metrics端点" class="headerlink" title="metrics端点"></a>metrics端点</h5><p>metrics端点用来返回当前应用的各类重要度量指标,比如:内存信息、线程信息、垃圾回收信息、tomcat、数据库连接池等。</p>
<p>调用:<code>"http://localhost:8080/actuator/metrics/{requiredMetricName}"</code> 获得各个指标。如:<code>http://localhost:8080/actuator/metrics/jvm.memory.max</code></p>
<h5 id="loggers端点"><a href="#loggers端点" class="headerlink" title="loggers端点"></a>loggers端点</h5><p>loggers 端点暴露了我们程序内部配置的所有logger的信息。</p>
<p>调用:<code>http://localhost:8080/actuator/loggers</code>获得所有的logger信息。</p>
<p>可以调用对应的POST接口,修改日志级别等。</p>
<h5 id="info端点"><a href="#info端点" class="headerlink" title="info端点"></a>info端点</h5><p>info端点可以用来展示你程序的信息。我理解过来就是一些程序的基础信息。并且你可以按照自己的需求在配置文件 application.properties中个性化配置。</p>
<h5 id="beans端点"><a href="#beans端点" class="headerlink" title="beans端点"></a>beans端点</h5><p>beans端点会返回Spring 容器中所有bean的别名、类型、是否单例、依赖等信息。</p>
<h5 id="heapdump端点"><a href="#heapdump端点" class="headerlink" title="heapdump端点"></a>heapdump端点</h5><p>访问:<code>http://localhost:8080/actuator/heapdump</code>会自动生成一个 Jvm 的堆文件 heapdump。我们可以使用 JDK 自带的 Jvm监控工具,VisualVM 打开此文件查看内存快照。</p>
<h5 id="threaddump-端点"><a href="#threaddump-端点" class="headerlink" title="threaddump 端点"></a>threaddump 端点</h5><p>主要展示了线程名、线程ID、线程的状态、是否等待锁资源、线程堆栈等信息。就是可能查看起来不太直观。访问:<code>http://localhost:8080/actuator/threaddump</code></p>
<h5 id="shutdown端点"><a href="#shutdown端点" class="headerlink" title="shutdown端点"></a>shutdown端点</h5><p>这个端点属于操作控制类端点,可以优雅关闭 Spring Boot 应用。要使用这个功能首先需要在配置文件中开启:<br><figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="attr">management:</span></div><div class="line"><span class="attr"> endpoint:</span></div><div class="line"><span class="attr"> shutdown:</span></div><div class="line"><span class="attr"> enabled:</span> <span class="literal">true</span></div></pre></td></tr></table></figure></p>
<p>由于 shutdown 接口默认只支持 POST 请求。</p>
<h5 id="env端点"><a href="#env端点" class="headerlink" title="env端点"></a>env端点</h5><p>获取应用所有可用的环境属性报告</p>
<h5 id="mappings端点"><a href="#mappings端点" class="headerlink" title="mappings端点"></a>mappings端点</h5><p>获取应用所有Spring Web的控制器映射关系报告</p>
<h5 id="configprops端点"><a href="#configprops端点" class="headerlink" title="configprops端点"></a>configprops端点</h5><p>获取应用中配置的属性信息报告</p>
<h2 id="SpringCloud-Admin健康检查"><a href="#SpringCloud-Admin健康检查" class="headerlink" title="SpringCloud Admin健康检查"></a>SpringCloud Admin健康检查</h2><h3 id="Admin客户端"><a href="#Admin客户端" class="headerlink" title="Admin客户端"></a>Admin客户端</h3><p>在要被监控的客户端添加pom引用:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>de.codecentric<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-admin-starter-client<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.1<span class="tag"></<span class="name">version</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-actuator<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure>
<p>在配置文件中如下配置:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="attr">spring:</span></div><div class="line"><span class="attr"> boot:</span></div><div class="line"><span class="attr"> admin:</span></div><div class="line"><span class="attr"> client:</span></div><div class="line"><span class="attr"> url:</span> <span class="attr">http://localhost:9030</span></div></pre></td></tr></table></figure>
<h3 id="Admin服务端"><a href="#Admin服务端" class="headerlink" title="Admin服务端"></a>Admin服务端</h3><p>新建一个项目,引入:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>de.codecentric<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-admin-starter-server<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.1<span class="tag"></<span class="name">version</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure>
<p>然后添加注解<code>@EnableAdminServer</code></p>
<hr>
<ul>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91">从0开始用SpringCloud搭建微服务系统【一】</a></li>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/">从0开始用SpringCloud搭建微服务系统【二】</a></li>
<li><a href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/">从0开始用SpringCloud搭建微服务系统【三】</a></li>
<li><a href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/">从0开始用SpringCloud搭建微服务系统【四】</a></li>
<li><a href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/">从0开始用SpringCloud搭建微服务系统【五】</a></li>
</ul>
]]></content>
<summary type="html">
<h2 id="调用链监控"><a href="#调用链监控" class="headerlink" title="调用链监控"></a>调用链监控</h2><p>如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统
</summary>
<category term="Spring" scheme="https://webfuse.cn/categories/Spring/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/categories/Spring/Spring-Cloud/"/>
<category term="微服务" scheme="https://webfuse.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/tags/Spring-Cloud/"/>
</entry>
<entry>
<title>从0开始用SpringCloud搭建微服务系统【四】</title>
<link href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/"/>
<id>https://webfuse.cn/2020/04/27/从0开始用SpringCloud搭建微服务系统【四】/</id>
<published>2020-04-27T08:30:38.000Z</published>
<updated>2020-04-30T02:25:33.310Z</updated>
<content type="html"><![CDATA[<h2 id="容错"><a href="#容错" class="headerlink" title="容错"></a>容错</h2><p>基本的容错模式有:</p>
<ul>
<li>主动超时:</li>
<li>限流:限制最大并发数</li>
<li>熔断:错误数达到阈值时,类似保险丝熔断</li>
<li>隔离:隔离不同的依赖调用或者隔离不同的线程</li>
<li>降级:服务降低</li>
</ul>
<p>容错理念:</p>
<ul>
<li>凡是依赖都可能会失败</li>
<li>凡是资源都有限制<ul>
<li>CPU/Memory/Threads/Queue</li>
</ul>
</li>
<li>网络并不可靠</li>
<li>延迟是应用稳定性杀手</li>
</ul>
<h3 id="Netflix-Hystrix"><a href="#Netflix-Hystrix" class="headerlink" title="Netflix Hystrix"></a>Netflix Hystrix</h3><p>Hystrix实现了 超时机制和断路器模式。</p>
<p>Hystrix是Netflix开源的一个类库,用于隔离远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。主要有以下几点功能:</p>
<ul>
<li>为系统提供保护机制。在依赖的服务出现高延迟或失败时,为系统提供保护和控制。</li>
<li>防止雪崩。</li>
<li>包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中运行。</li>
<li>跳闸机制:当某服务失败率达到一定的阈值时,Hystrix可以自动跳闸,停止请求该服务一段时间。</li>
<li>资源隔离:Hystrix为每个请求都的依赖都维护了一个小型线程池,如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。防止级联失败。</li>
<li>快速失败:Fail Fast。同时能快速恢复。侧重点是:(不去真正的请求服务,发生异常再返回),而是直接失败。</li>
<li>监控:Hystrix可以实时监控运行指标和配置的变化,提供近实时的监控、报警、运维控制。</li>
<li>回退机制:fallback,当请求失败、超时、被拒绝,或当断路器被打开时,执行回退逻辑。回退逻辑我们自定义,提供优雅的服务降级。</li>
<li>自我修复:断路器打开一段时间后,会自动进入“半开”状态,可以进行打开,关闭,半开状态的转换。</li>
</ul>
<h3 id="Hystrix设计原理"><a href="#Hystrix设计原理" class="headerlink" title="Hystrix设计原理"></a>Hystrix设计原理</h3><p>Hystrix的工作原理可以可以参考官网的<a href="https://github.com/Netflix/Hystrix/wiki/How-it-Works" target="_blank" rel="external">How it Works</a>。如果要看中文版,可以参考:<a href="https://segmentfault.com/a/1190000012439580" target="_blank" rel="external">Hystrix工作原理</a>。</p>
<h4 id="信号量隔离与线程隔离"><a href="#信号量隔离与线程隔离" class="headerlink" title="信号量隔离与线程隔离"></a>信号量隔离与线程隔离</h4><p>默认情况下hystrix使用线程池控制请求隔离</p>
<p>线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。</p>
<p>信号量隔离主要维护的是Tomcat的线程,不需要内部线程池,更加轻量级。</p>
<table>
<thead>
<tr>
<th></th>
<th>优点</th>
<th>不足</th>
<th>适用</th>
</tr>
</thead>
<tbody>
<tr>
<td>信号量隔离</td>
<td>轻量,无额外开销</td>
<td>不支持任务排队和主动超时<br>不支持异步调用</td>
<td>受信客户<br>高扇出(网关)<br>高频高速调用(cache)</td>
</tr>
<tr>
<td>线程池隔离</td>
<td>支持排队和超时<br>支持异步调用</td>
<td>线程调用会产生额外的开销</td>
<td>不受信客户<br>有限扇出</td>
</tr>
</tbody>
</table>
<h3 id="使用Hystrix"><a href="#使用Hystrix" class="headerlink" title="使用Hystrix"></a>使用Hystrix</h3><h4 id="独立使用"><a href="#独立使用" class="headerlink" title="独立使用"></a>独立使用</h4><p>独立使用Hystrix可以参考<a href="https://github.com/Netflix/Hystrix/wiki/How-To-Use" target="_blank" rel="external">How To Use</a>。</p>
<p>如果想要中文,可以参考:<a href="https://juejin.im/post/5c009ff6f265da614b11b84d" target="_blank" rel="external">Hystrix都停更了,我为什么还要学?</a></p>
<h4 id="与Resttemplate整合"><a href="#与Resttemplate整合" class="headerlink" title="与Resttemplate整合"></a>与Resttemplate整合</h4><p>引入依赖<code>spring-cloud-starter-netflix-hystrix</code>。</p>
<p>在Service中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GreetingService</span> </span>{</div><div class="line"> <span class="meta">@HystrixCommand</span>(fallbackMethod = <span class="string">"defaultGreeting"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getGreeting</span><span class="params">(String username)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate()</div><div class="line"> .getForObject(<span class="string">"http://localhost:9090/greeting/{username}"</span>, </div><div class="line"> String.class, username);</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">private</span> String <span class="title">defaultGreeting</span><span class="params">(String username)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="string">"Hello User!"</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在Application中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@SpringBootApplication</span></div><div class="line"><span class="meta">@EnableCircuitBreaker</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RestConsumerApplication</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</div><div class="line"> SpringApplication.run(RestConsumerApplication.class, args);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>@EnableCircuitBreaker注释将扫描类路径以查找任何兼容的Circuit Breaker实现。</p>
<h4 id="与Feign整合"><a href="#与Feign整合" class="headerlink" title="与Feign整合"></a>与Feign整合</h4><p>在集成Feign的基础上修改如下:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@FeignClient</span>(name = <span class="string">"statistics-service"</span>, fallback = StatisticsServiceClientFallback.class)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">StatisticsServiceClient</span> </span>{</div><div class="line"> <span class="meta">@RequestMapping</span>(method = RequestMethod.PUT, value = <span class="string">"/statistics/{accountName}"</span>)</div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">updateStatistics</span><span class="params">(@PathVariable(<span class="string">"accountName"</span>)</span> String accountName, Account account)</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="meta">@Slf</span>4j</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StatisticsServiceClientFallback</span> <span class="keyword">implements</span> <span class="title">StatisticsServiceClient</span> </span>{</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateStatistics</span><span class="params">(String accountName, Account account)</span> </span>{</div><div class="line"> log.error(<span class="string">"Error during update statistics for account: {}"</span>, accountName);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>然后在配置文件中打开:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">feign.hystrix.enabled=true</div></pre></td></tr></table></figure>
<h4 id="使用fallbackFactory检查具体错误"><a href="#使用fallbackFactory检查具体错误" class="headerlink" title="使用fallbackFactory检查具体错误"></a>使用fallbackFactory检查具体错误</h4><p>在一些场景中,简单的触发在 FeignClient 加入 Fallback 属性即可,而另外有一些场景需要访问导致回退触发的原因,那么这个时候可以在 FeignClient 中加入 FallbackFactory 属性即可;</p>
<ol>
<li>定义一个类,实现FallbackFactory</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WebError</span> <span class="keyword">implements</span> <span class="title">FallbackFactory</span><<span class="title">ConsumerApi</span>> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> ConsumerApi <span class="title">create</span><span class="params">(Throwable cause)</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ConsumerApi() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getById</span><span class="params">(Integer id)</span> </span>{</div><div class="line"> <span class="comment">//针对不同异常返回响应</span></div><div class="line"> <span class="keyword">if</span>(cause <span class="keyword">instanceof</span> InternalServerError) {</div><div class="line"> System.out.println(<span class="string">"InternalServerError"</span>);</div><div class="line"> <span class="keyword">return</span> <span class="string">"远程服务报错"</span>;</div><div class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(cause <span class="keyword">instanceof</span> RuntimeException) {</div><div class="line"> <span class="keyword">return</span> <span class="string">"请求时异常:"</span> + cause;</div><div class="line"> }<span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> <span class="string">"都算不上"</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> };</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<ol>
<li>@Feign中的使用<code>@FeignClient(name = "statistics-service", fallbackFactory = WebError.class)</code></li>
</ol>
<h3 id="主要配置项"><a href="#主要配置项" class="headerlink" title="主要配置项"></a>主要配置项</h3><table>
<thead>
<tr>
<th>配置项(前缀hystrix.command.*.)</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>execution.isolation.strategy</td>
<td>线程“THREAD”或信号量“SEMAPHORE”隔离(Default: THREAD)</td>
</tr>
<tr>
<td>execution.isolation.thread.timeoutInMilliseconds</td>
<td>run()方法执行超时时间(Default: 1000)</td>
</tr>
<tr>
<td>execution.isolation.semaphore.maxConcurrentRequests</td>
<td>信号量隔离最大并发数(Default:10)</td>
</tr>
<tr>
<td>circuitBreaker.errorThresholdPercentage</td>
<td>熔断的错误百分比阀值(Default:50)</td>
</tr>
<tr>
<td>circuitBreaker.requestVolumeThreshold</td>
<td>断路器生效必须满足的流量阀值(Default:20)</td>
</tr>
<tr>
<td>circuitBreaker.sleepWindowInMilliseconds</td>
<td>熔断后重置断路器的时间间隔(Default:5000)</td>
</tr>
<tr>
<td>circuitBreaker.forceOpen</td>
<td>设true表示强制熔断器进入打开状态(Default: false)</td>
</tr>
<tr>
<td>circuitBreaker.forceClosed</td>
<td>设true表示强制熔断器进入关闭状态(Default: false)</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>配置项(前缀hystrix.threadpool.*.)</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>coreSize</td>
<td>使用线程池时的最大并发请求(Default: 10)</td>
</tr>
<tr>
<td>maxQueueSize</td>
<td>最大LinkedBlockingQueue大小,-1表示用SynchronousQueue(Default:-1)</td>
</tr>
<tr>
<td>default.queueSizeRejectionThreshold</td>
<td>队列大小阀值,超过则拒绝(Default:5)</td>
</tr>
</tbody>
</table>
<h3 id="Hystrix-Dashboard"><a href="#Hystrix-Dashboard" class="headerlink" title="Hystrix Dashboard"></a><strong>Hystrix Dashboard</strong></h3><p>Hystrix有一个不错的可选功能是能够在仪表板上监视其状态。 为了启用它,我们将<code>spring-cloud-starter-hystrix-dashboard</code>和<code>spring-boot-starter-actuator</code>放入项目的pom.xml中。</p>
<p>然后添加<code>@EnableHystrixDashboard</code>注解。</p>
<p>启动应用程序后,将浏览器指向<a href="http://localhost:8080/hystrix,输入“" target="_blank" rel="external">http://localhost:8080/hystrix,输入“</a> hystrix.stream”的指标URL并开始监视。</p>
<h3 id="参考:"><a href="#参考:" class="headerlink" title="参考:"></a>参考:</h3><ul>
<li><a href="https://www.baeldung.com/spring-cloud-netflix-hystrix" target="_blank" rel="external">A Guide to Spring Cloud Netflix – Hystrix</a></li>
</ul>
<hr>
<ul>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91">从0开始用SpringCloud搭建微服务系统【一】</a></li>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/">从0开始用SpringCloud搭建微服务系统【二】</a></li>
<li><a href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/">从0开始用SpringCloud搭建微服务系统【三】</a></li>
<li><a href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/">从0开始用SpringCloud搭建微服务系统【四】</a></li>
<li><a href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/">从0开始用SpringCloud搭建微服务系统【五】</a></li>
</ul>
]]></content>
<summary type="html">
<h2 id="容错"><a href="#容错" class="headerlink" title="容错"></a>容错</h2><p>基本的容错模式有:</p>
<ul>
<li>主动超时:</li>
<li>限流:限制最大并发数</li>
<li>熔断:错误数达到阈值时,
</summary>
<category term="Spring" scheme="https://webfuse.cn/categories/Spring/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/categories/Spring/Spring-Cloud/"/>
<category term="微服务" scheme="https://webfuse.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/tags/Spring-Cloud/"/>
</entry>
<entry>
<title>从0开始用SpringCloud搭建微服务系统【三】</title>
<link href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/"/>
<id>https://webfuse.cn/2020/04/25/从0开始用SpringCloud搭建微服务系统【三】/</id>
<published>2020-04-25T09:38:59.000Z</published>
<updated>2020-04-30T02:25:26.833Z</updated>
<content type="html"><![CDATA[<h2 id="服务间通信"><a href="#服务间通信" class="headerlink" title="服务间通信"></a>服务间通信</h2><p>微服务间可以使用 HTTP 协议,RESTful 规范进行通信。Spring Cloud 提供了 2 种 RESTful 调用方式:Ribbon 和 Feign 。</p>
<h3 id="Ribbon"><a href="#Ribbon" class="headerlink" title="Ribbon"></a>Ribbon</h3><p>客户端软负载组件,支持Eureka对接,支持多种可插拔LB策略。依赖 <code>spring-cloud-starter-netflix-eureka-client</code> 中已经默认加载了 Ribbon 的依赖。</p>
<p>Ribbon作为Spring Cloud的负载均衡机制的实现:</p>
<ol>
<li>Ribbon可以单独使用,作为一个独立的负载均衡组件。只是需要我们手动配置 服务地址列表。</li>
<li>Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表(DiscoveryClient),并基于负载均衡算法,请求其中一个服务提供者实例。</li>
<li>Ribbon与OpenFeign和RestTemplate进行无缝对接,让二者具有负载均衡的能力。OpenFeign默认集成了ribbon。</li>
</ol>
<p>Ribbon 的自定义配置以及一些高级使用可以参考官方文档:<a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.2.RELEASE/reference/html/#spring-cloud-ribbon" target="_blank" rel="external">Client Side Load Balancer: Ribbon</a></p>
<h4 id="Ribbon组成"><a href="#Ribbon组成" class="headerlink" title="Ribbon组成"></a>Ribbon组成</h4><p>官网首页:<a href="https://github.com/Netflix/ribbon" target="_blank" rel="external">https://github.com/Netflix/ribbon</a></p>
<p>ribbon-core: 核心的通用性代码。api一些配置。</p>
<p>ribbon-eureka:基于eureka封装的模块,能快速集成eureka。</p>
<p>ribbon-examples:学习示例。</p>
<p>ribbon-httpclient:基于apache httpClient封装的rest客户端,集成了负载均衡模块,可以直接在项目中使用。</p>
<p>ribbon-loadbalancer:负载均衡模块。</p>
<p>ribbon-transport:基于netty实现多协议的支持。比如http,tcp,udp等。</p>
<h4 id="调用方式"><a href="#调用方式" class="headerlink" title="调用方式"></a>调用方式</h4><h5 id="使用RestTemplate"><a href="#使用RestTemplate" class="headerlink" title="使用RestTemplate"></a>使用RestTemplate</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoadBalancerClientConfig</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"> * 负载均衡的RestTemplate。</div><div class="line"> */</div><div class="line"> <span class="meta">@LoadBalanced</span></div><div class="line"> <span class="meta">@Bean</span>(name = <span class="string">"loadBalanced"</span>)</div><div class="line"> <span class="function">RestTemplate <span class="title">loadBalanced</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"> * 常规的RestTemplate。</div><div class="line"> */</div><div class="line"> <span class="meta">@Primary</span></div><div class="line"> <span class="meta">@Bean</span>(name = <span class="string">"restTemplate"</span>)</div><div class="line"> <span class="function">RestTemplate <span class="title">restTemplate</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>以上代码中使用 <code>@LoadBalanced</code> 注解,这样就可以让 RestTemplate 在请求时拥有客户端负载均衡的能力。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="meta">@Autowired</span></div><div class="line"><span class="meta">@Qualifier</span>(<span class="string">"loadBalanced"</span>)</div><div class="line"><span class="keyword">private</span> RestTemplate loadBalanced;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getTime</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> loadBalanced.getForEntity(<span class="string">"http://service-producer/get_time"</span>, String.class).getBody();</div><div class="line">}</div></pre></td></tr></table></figure>
<h5 id="使用DiscoveryClient"><a href="#使用DiscoveryClient" class="headerlink" title="使用DiscoveryClient"></a>使用DiscoveryClient</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="meta">@Autowired</span></div><div class="line"><span class="keyword">private</span> DiscoveryClient discoveryClient;</div><div class="line"> </div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">public</span> List<String> <span class="title">getAllInstance</span><span class="params">(@RequestParam(<span class="string">"service_id"</span>)</span> String serviceId) </span>{</div><div class="line"></div><div class="line"> List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);</div><div class="line"> <span class="keyword">return</span> instances.stream()</div><div class="line"> .map(instance -> String.format(<span class="string">"http://%s:%s"</span>, instance.getHost(), instance.getPort()))</div><div class="line"> .collect(Collectors.toList());</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="负载均衡算法"><a href="#负载均衡算法" class="headerlink" title="负载均衡算法"></a>负载均衡算法</h4><p>Ribbon 提供了很多负载均衡的策略。详情可见:</p>
<table>
<thead>
<tr>
<th>策略名称</th>
<th>策略描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>BestAvailableRule(最低并发策略)</td>
<td>会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。</td>
</tr>
<tr>
<td>AvailabilityFilteringRule(可用过滤策略)</td>
<td>会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。</td>
</tr>
<tr>
<td>WeightedResponseTimeRule(响应时间加权策略)</td>
<td>据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。</td>
</tr>
<tr>
<td>RetryRule(重试策略)</td>
<td>先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。</td>
</tr>
<tr>
<td>RoundRobinRule(轮询策略)</td>
<td>以简单轮询选择一个服务器。按顺序循环选择一个server。</td>
</tr>
<tr>
<td>RandomRule(随机策略)</td>
<td>随机选择一个服务器。</td>
</tr>
<tr>
<td>ZoneAvoidanceRule(区域权衡策略)【默认实现】</td>
<td>复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。</td>
</tr>
</tbody>
</table>
<h4 id="切换负载均衡策略"><a href="#切换负载均衡策略" class="headerlink" title="切换负载均衡策略"></a>切换负载均衡策略</h4><h5 id="注解方式"><a href="#注解方式" class="headerlink" title="注解方式"></a>注解方式</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultRibbonConfig</span> </span>{</div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="function"><span class="keyword">public</span> IRule <span class="title">ribbonRule</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BestAvailableRule();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如上,配置IRule的新值,直接可以切换负载均衡策略</p>
<h5 id="配置文件方式"><a href="#配置文件方式" class="headerlink" title="配置文件方式"></a>配置文件方式</h5><p>给所有的服务指定负载均衡策略:</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="attr">ribbon:</span></div><div class="line"><span class="attr"> NIWSServerListClassName:</span> <span class="string">com.netflix.loadbalancer.ConfigurationBasedServerList</span></div><div class="line"><span class="attr"> NFLoadBalancerRuleClassName:</span> <span class="string">com.netflix.loadbalancer.WeightedResponseTimeRule</span></div></pre></td></tr></table></figure>
<p>给特定的服务指定负载均衡策略:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><服务名>.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule</div></pre></td></tr></table></figure>
<h4 id="Ribbon拦截"><a href="#Ribbon拦截" class="headerlink" title="Ribbon拦截"></a>Ribbon拦截</h4><p>在服务调用的时候,我们可能不仅仅是简单地进行调用,会涉及到一些接口的校验、权限的校验等。要实现这些,可以实现<code>ClientHttpRequestInterceptor</code>接口。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoggingClientHttpRequestInterceptor</span> <span class="keyword">implements</span> <span class="title">ClientHttpRequestInterceptor</span> </span>{</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> ClientHttpResponse <span class="title">intercept</span><span class="params">(HttpRequest httpRequest, <span class="keyword">byte</span>[] bytes, ClientHttpRequestExecution clientHttpRequestExecution)</span> <span class="keyword">throws</span> IOException </span>{</div><div class="line"> System.out.println(<span class="string">"拦截啦!!!"</span>);</div><div class="line"> System.out.println(httpRequest.getURI());</div><div class="line"></div><div class="line"> ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);</div><div class="line"></div><div class="line"> System.out.println(response.getHeaders());</div><div class="line"> <span class="keyword">return</span> response;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>添加到resttemplate中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@LoadBalanced</span></div><div class="line"><span class="meta">@Bean</span>(name = <span class="string">"loadBalanced"</span>)</div><div class="line"><span class="meta">@Primary</span></div><div class="line"><span class="function">RestTemplate <span class="title">loadBalanced</span><span class="params">()</span> </span>{</div><div class="line"> RestTemplate restTemplate = <span class="keyword">new</span> RestTemplate();</div><div class="line"> restTemplate.getInterceptors().add(<span class="keyword">new</span> LoggingClientHttpRequestInterceptor());</div><div class="line"> <span class="keyword">return</span> restTemplate;</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="超时"><a href="#超时" class="headerlink" title="超时"></a>超时</h4><p>Ribbon的配置如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">#连接超时时间(ms)</div><div class="line">ribbon.ConnectTimeout=1000</div><div class="line">#业务逻辑超时时间(ms)</div><div class="line">ribbon.ReadTimeout=6000</div></pre></td></tr></table></figure>
<h4 id="重试"><a href="#重试" class="headerlink" title="重试"></a>重试</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">#同一台实例最大重试次数,不包括首次调用</div><div class="line">ribbon.MaxAutoRetries=1</div><div class="line">#重试负载均衡其他的实例最大重试次数,不包括首次调用</div><div class="line">ribbon.MaxAutoRetriesNextServer=1</div><div class="line">#是否所有操作都重试</div><div class="line">ribbon.OkToRetryOnAllOperations=false</div></pre></td></tr></table></figure>
<p>使用ribbon重试机制,请求失败后,每个6秒会重新尝试</p>
<h3 id="Feign"><a href="#Feign" class="headerlink" title="Feign"></a>Feign</h3><p>Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,Feign 的使用非常简单,创建一个接口,在接口上加入一些注解,这样就完成了代码开发。</p>
<p>Feign 是一个 Http 请求调用的轻量级框架,可以以 Java 接口注解的方式调用 Http 请求,而不用像 Java 中通过封装 HTTP 请求报文的方式直接调用。通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign 封装 了HTTP 调用流程,面向接口编程</p>
<h4 id="Spring-Cloud-OpenFeign"><a href="#Spring-Cloud-OpenFeign" class="headerlink" title="Spring Cloud OpenFeign"></a>Spring Cloud OpenFeign</h4><p>Spring Cloud OpenFeign 通过自动配置并绑定到Spring Environment和其他Spring编程模型习惯用法,为Spring Boot应用程序提供OpenFeign集成。</p>
<p>官方文档:<a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.2.RELEASE/reference/html/" target="_blank" rel="external">Spring Cloud OpenFeign</a></p>
<h4 id="服务调用"><a href="#服务调用" class="headerlink" title="服务调用"></a>服务调用</h4><p>使用 Feign 必须引入 <code>spring-cloud-starter-openfeign</code>。在启动类 <code>Application</code> 上 <code>@EnableFeignClients</code> 注解。</p>
<h5 id="服务提供者"><a href="#服务提供者" class="headerlink" title="服务提供者"></a>服务提供者</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@RequestMapping</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloApi</span> </span>{</div><div class="line"> <span class="meta">@GetMapping</span>(value = <span class="string">"/v0.1/greeting"</span>)</div><div class="line"> <span class="function">String <span class="title">greeting</span><span class="params">()</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@RestController</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> <span class="keyword">implements</span> <span class="title">HelloApi</span> </span>{</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">greeting</span><span class="params">()</span></span>{</div><div class="line"> <span class="keyword">return</span> <span class="string">"hello world"</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h5 id="服务消费者:简单使用"><a href="#服务消费者:简单使用" class="headerlink" title="服务消费者:简单使用"></a>服务消费者:简单使用</h5><p>简单使用Feign不需要代码耦合,但是需要硬编码接口的信息。如下:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"producer-service"</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloClient</span></span>{</div><div class="line"> <span class="meta">@GetMapping</span>(value = <span class="string">"/v0.1/greeting"</span>)</div><div class="line"> <span class="function">String <span class="title">greeting</span><span class="params">()</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestService</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> HelloClient helloClient;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</div><div class="line"> helloClient.greeting();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>@FeignClient</code>也可以脱离Eureka使用,如:<code>@FeignClient(name = "xxx",url="")</code> 这个url就是接口的地址。</p>
<h5 id="服务消费者:接口继承方式"><a href="#服务消费者:接口继承方式" class="headerlink" title="服务消费者:接口继承方式"></a>服务消费者:接口继承方式</h5><p>此方法需要引入<strong>服务提供者</strong>提供的接口jar包。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"producer-service"</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloService</span> <span class="keyword">extends</span> <span class="title">HelloApi</span> </span>{</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestService</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> HelloService helloService;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</div><div class="line"> helloService.greeting();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>以上代码中,<code>@FeignClient(value = "producer-service")</code> 指定了使用哪一个服务。</p>
<h4 id="高级用法"><a href="#高级用法" class="headerlink" title="高级用法"></a>高级用法</h4><h5 id="自定义配置"><a href="#自定义配置" class="headerlink" title="自定义配置"></a>自定义配置</h5><p>feign的默认配置类是:org.springframework.cloud.openfeign.FeignClientsConfiguration。默认定义了feign使用的编码器,解码器等。</p>
<p>允许使用@FeignClient的configuration的属性自定义Feign配置。自定义的配置优先级高于上面的FeignClientsConfiguration。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="meta">@Slf</span>4j</div><div class="line"><span class="meta">@EnableConfigurationProperties</span>(FeignSecurityProperties.class)</div><div class="line"><span class="meta">@ConditionalOnClass</span>(value = {RequestInterceptor.class, Decoder.class, Encoder.class})</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeignAutoConfig</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> FeignSecurityProperties feignSecurityProperties;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"> * 内部微服务请求,加上timestamp,并且按字典序进行签名,附上sig</div><div class="line"> *</div><div class="line"> * <span class="doctag">@return</span> RequestInterceptor 请求拦截器</div><div class="line"> */</div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="meta">@ConditionalOnMissingBean</span>(RequestInterceptor.class)</div><div class="line"> <span class="function"><span class="keyword">public</span> RequestInterceptor <span class="title">requestTokenBearerInterceptor</span><span class="params">()</span> </span>{</div><div class="line"> String apiSecretKey = feignSecurityProperties.getApiSignature().getInternalApiKey();</div><div class="line"> <span class="keyword">return</span> requestTemplate -> ApiSigUtil.sig(apiSecretKey, requestTemplate);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Decoder <span class="title">feignDecoder</span><span class="params">()</span> </span>{</div><div class="line"> HttpMessageConverter jacksonConverter = <span class="keyword">new</span> MappingJackson2HttpMessageConverter(customObjectMapper());</div><div class="line"> ObjectFactory<HttpMessageConverters> objectFactory = () -> <span class="keyword">new</span> HttpMessageConverters(jacksonConverter);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntityDecoder(<span class="keyword">new</span> SpringDecoder(objectFactory));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Encoder <span class="title">feignEncoder</span><span class="params">()</span> </span>{</div><div class="line"> HttpMessageConverter jacksonConverter = <span class="keyword">new</span> MappingJackson2HttpMessageConverter(customObjectMapper());</div><div class="line"> ObjectFactory<HttpMessageConverters> objectFactory = () -> <span class="keyword">new</span> HttpMessageConverters(jacksonConverter);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> SpringEncoder(objectFactory);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> ObjectMapper <span class="title">customObjectMapper</span><span class="params">()</span> </span>{</div><div class="line"> ObjectMapper objectMapper = <span class="keyword">new</span> ObjectMapper();</div><div class="line"> objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, <span class="keyword">true</span>);</div><div class="line"> <span class="keyword">return</span> objectMapper;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="meta">@ConditionalOnMissingBean</span>(Retryer.class)</div><div class="line"> <span class="function"><span class="keyword">public</span> Retryer <span class="title">feignRetryer</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> Retryer.NEVER_RETRY;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="meta">@ConditionalOnMissingBean</span>(Request.Options.class)</div><div class="line"> Request.<span class="function">Options <span class="title">feignOptions</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Request.Options(<span class="number">30</span> * <span class="number">1000</span>, <span class="number">30</span> * <span class="number">1000</span>);</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<p>如上面所示,<strong>在配置类上加上@Configuration注解,且该类在@ComponentScan所扫描的包中,那么该类中的配置信息就会被所有的@FeignClient共享。</strong>如果想要对某些的@FeignClient添加指定的配置,则:不指定@Configuration注解(或者指定configuration,用注解忽略),而是手动使用:<code>@FeignClient(name = "service-valuation",configuration = FeignAuthConfiguration.class)</code></p>
<h5 id="拦截器"><a href="#拦截器" class="headerlink" title="拦截器"></a>拦截器</h5><p>上面代码中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Bean</span></div><div class="line"> <span class="meta">@ConditionalOnMissingBean</span>(RequestInterceptor.class)</div><div class="line"> <span class="function"><span class="keyword">public</span> RequestInterceptor <span class="title">requestTokenBearerInterceptor</span><span class="params">()</span> </span>{</div><div class="line"> String apiSecretKey = feignSecurityProperties.getApiSignature().getInternalApiKey();</div><div class="line"> <span class="keyword">return</span> requestTemplate -> ApiSigUtil.sig(apiSecretKey, requestTemplate);</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><code>RequestInterceptor</code>是一个请求拦截器,我们可以继承它做很多事情,如:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> feign.RequestInterceptor;</div><div class="line"><span class="keyword">import</span> feign.RequestTemplate;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyBasicAuthRequestInterceptor</span> <span class="keyword">implements</span> <span class="title">RequestInterceptor</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">apply</span><span class="params">(RequestTemplate template)</span> </span>{</div><div class="line"> template.header(<span class="string">"Authorization"</span>, <span class="string">"Basic cm9vdDpyb290"</span>);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<blockquote>
<p>如果是BasicAuth认证,可以重写BasicAuthRequestInterceptor。</p>
</blockquote>
<p>然后,在配置文件中添加:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="attr">feign:</span></div><div class="line"><span class="attr"> client:</span> </div><div class="line"><span class="attr"> config:</span> </div><div class="line"><span class="attr"> service-valuation:</span> </div><div class="line"><span class="attr"> request-interceptors:</span></div><div class="line"><span class="bullet"> -</span> <span class="string">cn.webfuse.passenger.feign.interceptor.MyBasicAuthRequestInterceptor</span></div></pre></td></tr></table></figure>
<h5 id="配置文件扩展"><a href="#配置文件扩展" class="headerlink" title="配置文件扩展"></a>配置文件扩展</h5><p>指定服务名配置:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="attr">feign:</span></div><div class="line"><span class="attr"> client:</span> </div><div class="line"><span class="attr"> config:</span> </div><div class="line"><span class="attr"> service-valuation:</span> </div><div class="line"><span class="attr"> connect-timeout:</span> <span class="number">5000</span></div><div class="line"><span class="attr"> read-timeout:</span> <span class="number">5000</span></div><div class="line"><span class="attr"> logger-level:</span> <span class="string">full</span></div></pre></td></tr></table></figure>
<p>通用配置:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="attr">feign:</span></div><div class="line"><span class="attr"> client:</span> </div><div class="line"><span class="attr"> config:</span> </div><div class="line"><span class="attr"> default:</span> </div><div class="line"><span class="attr"> connect-timeout:</span> <span class="number">5000</span></div><div class="line"><span class="attr"> read-timeout:</span> <span class="number">5000</span></div><div class="line"><span class="attr"> logger-level:</span> <span class="string">full</span></div></pre></td></tr></table></figure>
<p>属性配置比Java代码优先级高。也可通过配置设置java代码优先级高:</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="attr">feign:</span></div><div class="line"> <span class="attr">client:</span> </div><div class="line"> <span class="attr">default-to-properties:</span> <span class="literal">false</span></div></pre></td></tr></table></figure>
<h5 id="压缩"><a href="#压缩" class="headerlink" title="压缩"></a>压缩</h5><figure class="highlight yaml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="attr">feign:</span></div><div class="line"><span class="attr"> compression:</span> <span class="comment"># 开启请求与响应的GZIP压缩</span></div><div class="line"><span class="attr"> request:</span></div><div class="line"><span class="attr"> enabled:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> min-request-size:</span> <span class="number">10000</span> <span class="comment"># 单位是B</span></div><div class="line"><span class="attr"> response:</span></div><div class="line"><span class="attr"> enabled:</span> <span class="literal">true</span></div></pre></td></tr></table></figure>
<h5 id="超时-1"><a href="#超时-1" class="headerlink" title="超时"></a>超时</h5><p>Feign默认支持Ribbon;Ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,使用Ribbon的重试机制。</p>
<h5 id="保留原始异常信息"><a href="#保留原始异常信息" class="headerlink" title="保留原始异常信息"></a>保留原始异常信息</h5><p>当调用服务时,如果服务返回的状态码不是200,就会进入到<code>Feign</code>的<code>ErrorDecoder</code>中,因此如果我们要解析异常信息,就要重写<code>ErrorDecoder</code></p>
<h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><ol>
<li>主程序入口添加@EnableFeignClients注解开启对Feign Client扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClient注解。</li>
<li>当程序启动时,会进行包扫描,扫描所有@FeignClient注解的类,并将这些信息注入Spring IoC容器中。当定义的Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装了HTTP请求需要的全部信息,如请求参数名、请求方法等信息都在这个过程中确定。</li>
<li>然后由RequestTemplate生成Request,然后把这个Request交给client处理,这里指的Client可以是JDK原生的URLConnection、Apache的Http Client,也可以是Okhttp。最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。</li>
</ol>
<h3 id="参考资源"><a href="#参考资源" class="headerlink" title="参考资源"></a>参考资源</h3><ul>
<li><a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/single/spring-cloud-netflix.html#_service_discovery_eureka_clients" target="_blank" rel="external">Service Discovery: Eureka Clients</a></li>
<li><a href="https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/single/spring-cloud-netflix.html#spring-cloud-eureka-server" target="_blank" rel="external">Service Discovery: Eureka Server</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/24829766" target="_blank" rel="external">深度剖析服务发现组件Netflix Eureka</a></li>
<li><a href="https://blog.csdn.net/forezp/article/details/73017664" target="_blank" rel="external">深入理解Eureka之源码解析</a></li>
<li><a href="https://blog.csdn.net/forezp/article/details/74820899" target="_blank" rel="external">深入理解Ribbon之源码解析</a></li>
<li><a href="https://blog.csdn.net/forezp/article/details/73480304" target="_blank" rel="external">深入理解Feign之源码解析</a></li>
</ul>
<hr>
<ul>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91">从0开始用SpringCloud搭建微服务系统【一】</a></li>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/">从0开始用SpringCloud搭建微服务系统【二】</a></li>
<li><a href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/">从0开始用SpringCloud搭建微服务系统【三】</a></li>
<li><a href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/">从0开始用SpringCloud搭建微服务系统【四】</a></li>
<li><a href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/">从0开始用SpringCloud搭建微服务系统【五】</a></li>
</ul>
]]></content>
<summary type="html">
<h2 id="服务间通信"><a href="#服务间通信" class="headerlink" title="服务间通信"></a>服务间通信</h2><p>微服务间可以使用 HTTP 协议,RESTful 规范进行通信。Spring Cloud 提供了 2 种 RESTf
</summary>
<category term="Spring" scheme="https://webfuse.cn/categories/Spring/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/categories/Spring/Spring-Cloud/"/>
<category term="微服务" scheme="https://webfuse.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/tags/Spring-Cloud/"/>
</entry>
<entry>
<title>从0开始用SpringCloud搭建微服务系统【二】</title>
<link href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/"/>
<id>https://webfuse.cn/2020/04/22/从0开始用SpringCloud搭建微服务系统【二】/</id>
<published>2020-04-22T12:22:35.000Z</published>
<updated>2020-04-30T02:25:15.091Z</updated>
<content type="html"><![CDATA[<h2 id="服务注册与服务发现"><a href="#服务注册与服务发现" class="headerlink" title="服务注册与服务发现"></a>服务注册与服务发现</h2><ol>
<li><p>背景:在传统应用中,组件之间的调用,通过有规范的约束的接口来实现,从而实现不同模块间良好的协作。但是被拆分成微服务后,每个微服务实例的网络地址都可能动态变化,数量也会变化,使得原来硬编码的地址失去了作用。需要一个中心化的组件来进行服务的登记和管理。</p>
</li>
<li><p>概念:实现服务治理,即管理所有的服务信息和状态。</p>
</li>
<li><p>注册中心好处:不用关心有多少提供方。</p>
</li>
<li><p>注册中心有哪些: Eureka,Nacos,Consul,Zookeeper等。</p>
</li>
<li><p>服务注册与发现包括两部分,一个是服务器端,另一个是客户端。</p>
<ol>
<li>服务端(Server)是一个公共服务,为Client提供服务注册和发现的功能,维护注册到自身的Client的相关信息,同时提供接口给Client获取注册表中其他服务的信息,使得动态变化的Client能够进行服务间的相互调用。</li>
<li>客户端(Client) 将自己的服务信息通过一定的方式登记到 Server上,并在正常范围内维护自己信息一致性,方便其他服务发现自己,同时可以通过Server获取到自己依赖的其他服务信息,完成服务调用,还内置了负载均衡器,用来进行基本的负载均衡。</li>
</ol>
</li>
</ol>
<h3 id="服务端和客户端功能"><a href="#服务端和客户端功能" class="headerlink" title="服务端和客户端功能"></a>服务端和客户端功能</h3><h4 id="server注册中心功能"><a href="#server注册中心功能" class="headerlink" title="server注册中心功能"></a>server注册中心功能</h4><ol>
<li>服务注册表:记录各个微服务信息,例如服务名称,ip,端口等。注册表提供查询 API(查询可用的微服务实例)和管理 API(用于服务的注册和注销)。</li>
<li>服务注册与发现:<ol>
<li>注册:将微服务信息注册到注册中心。</li>
<li>发现:查询可用微服务列表及其网络地址。</li>
</ol>
</li>
<li>服务检查:定时检测已注册的服务,如发现某实例长时间无法访问,就从注册表中移除。</li>
</ol>
<h4 id="client功能"><a href="#client功能" class="headerlink" title="client功能"></a>client功能</h4><ol>
<li>注册:每个微服务启动时,将自己的网络地址等信息注册到注册中心,注册中心会存储(内存中)这些信息。</li>
<li>获取服务注册表:服务消费者从注册中心,查询服务提供者的网络地址,并使用该地址调用服务提供者,为了避免每次都查注册表信息,所以 client 会定时去 serve r拉取注册表信息到缓存到 client 本地。</li>
<li>心跳:各个微服务与注册中心通过某种机制(心跳)通信,若注册中心长时间和服务间没有通信,就会注销该实例。</li>
<li>调用:实际的服务调用,通过注册表,解析服务名和具体地址的对应关系,找到具体服务的地址,进行实际调用。</li>
</ol>
<h3 id="Eureka"><a href="#Eureka" class="headerlink" title="Eureka"></a>Eureka</h3><ul>
<li>Eureka 是 Netflix 开源的一个 Restful 服务,主要用于服务的注册发现。它由两个组件组成:Eureka 服务器 和Eureka 客户端。</li>
<li>Eureka 服务器用作服务注册服务器。各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息。</li>
<li>Eureka 客户端是一个 Java 客户端,用来简化与服务器的交互,作为轮询负载均衡器,发现相关的服务,并提供服务的故障切换。</li>
<li><strong>Eureka 在设计时就优先保证可用性。</strong>即在 CAP 理论中,Eureka 满足 AP。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。</li>
<li>Eureka 的几个时间点:<ul>
<li>在应用启动后,将会向 Eureka Server 发送心跳,默认周期为 30 秒。</li>
<li>如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认90秒)。</li>
<li>Eureka Client 对已经获取到的注册信息做了 30s 缓存。即服务通过 Eureka 客户端第一次查询到可用服务地址后会将结果缓存,下次再调用时就不会真正向 Eureka 发起 HTTP 请求了。</li>
<li>负载均衡组件 Ribbon 也有 30s 缓存。Ribbon 会从上面提到的 Eureka Client 获取服务列表,然后将结果缓存 30s。</li>
<li><strong>最大可能出现2分钟的延迟</strong>:注册延迟30s + Eureka服务器响应延迟30s + Eureka客户端更新延迟30s + Ribbon服务列表更新延迟30s。</li>
</ul>
</li>
<li><p>Eureka 启动保护机制:</p>
<ul>
<li><p>如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那 么Eureka 就认为客户端与注册中心出现了网络故障。</p>
</li>
<li><p>自我保护机制的触发条件: </p>
<p> 条件:当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.server.enable-self-preservation = true ) 时,触发自我保护机制,不再自动过期续约。</p>
<p> 其中:</p>
<ol>
<li><p>numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 续租百分比( eureka.server.renewalPercentThreshold, 默认0.85 ) </p>
</li>
<li><p>expectedNumberOfRenewsPerMin = 当前注册的应用实例数 x 2 。为什么乘以 2: 默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。</p>
<p>解释:服务实例数10个,期望每分钟续约数10 <em> 2=20,期望阈值20</em>0.85=17,自我保护少于17时 触发。】</p>
</li>
</ol>
</li>
<li><p>Eureka 启动保护机制会出现以下情况:</p>
<ul>
<li>Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。</li>
<li>Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)。</li>
<li>当网络稳定时,当前实例新的注册信息会被同步到其它节点中。</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="架构原理图"><a href="#架构原理图" class="headerlink" title="架构原理图"></a>架构原理图</h4><img src="/2020/04/22/从0开始用SpringCloud搭建微服务系统【二】/eureka架构图.png" alt="eureka架构图.png" title="">
<h4 id="Eureka-Server"><a href="#Eureka-Server" class="headerlink" title="Eureka Server"></a>Eureka Server</h4><p>引入依赖 <code>spring-cloud-starter-netflix-eureka-server</code>, 在启动类 <code>Application</code> 上,添加 <code>@EnableEurekaServer</code> 注解。</p>
<p>如果是单机服务,可以在 <code>application.yml</code> 中使用以下配置:</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="attr">eureka:</span></div><div class="line"><span class="attr"> environment:</span> <span class="string">dev</span> <span class="comment"># 设置环境,可选</span></div><div class="line"><span class="attr"> server:</span></div><div class="line"><span class="attr"> enable-self-preservation:</span> <span class="literal">false</span> <span class="comment"># 中小规模下,自我保护模式坑比好处多,所以关闭它</span></div><div class="line"><span class="attr"> renewal-threshold-update-interval-ms:</span> <span class="number">120000</span> <span class="comment"># 心跳阈值计算周期,如果开启自我保护模式,可以改一下这个配置</span></div><div class="line"><span class="attr"> eviction-interval-timer-in-ms:</span> <span class="number">5000</span> <span class="comment"># 主动失效检测间隔,配置成5秒</span></div><div class="line"><span class="attr"> use-read-only-response-cache:</span> <span class="literal">false</span> <span class="comment"># 禁用readOnlyCacheMap</span></div><div class="line"><span class="attr"> wait-time-in-ms-when-sync-empty:</span> <span class="number">0</span> <span class="comment">#在Eureka服务器获取不到集群里对等服务器上的实例时,需要等待的时间,单机模式设置为0</span></div><div class="line"><span class="attr"> client:</span></div><div class="line"><span class="attr"> healthcheck:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> service-url:</span></div><div class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://${webfuse-security.user.name}:${webfuse-security.user.password}@localhost:2000/eureka/</span></div><div class="line"><span class="attr"> registry-fetch-interval-seconds:</span> <span class="number">5</span> <span class="comment"># 定时刷新本地缓存时间</span></div><div class="line"><span class="attr"> register-with-eureka:</span> <span class="literal">false</span> <span class="comment">#表示是否将自己注册到Eureka Server,默认为true。由于当前这个应用就是Eureka Server,故而设为false。</span></div><div class="line"><span class="attr"> fetch-registry:</span> <span class="literal">false</span> <span class="comment">#表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设为false。</span></div><div class="line"><span class="attr"> instance:</span></div><div class="line"><span class="attr"> hostname:</span> <span class="string">${hostname:localhost}</span></div><div class="line"><span class="attr"> instance-id:</span> <span class="string">${spring.application.name}@${spring.cloud.client.ip-address}:${server.port}</span> <span class="comment"># 自定义实例ID</span></div><div class="line"><span class="attr"> prefer-ip-address:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> lease-expiration-duration-in-seconds:</span> <span class="number">10</span> <span class="comment"># 没有心跳的淘汰时间,10秒</span></div><div class="line"><span class="attr"> lease-renewal-interval-in-seconds:</span> <span class="number">5</span> <span class="comment"># 心跳间隔,5秒</span></div></pre></td></tr></table></figure>
<p>启动服务,可在 <code>http://localhost:2000</code> 查看项目页面。</p>
<h4 id="Eureka-Client"><a href="#Eureka-Client" class="headerlink" title="Eureka Client"></a>Eureka Client</h4><p>EurekaClient 可以在客户端获取eureka服务器上的注册者信息。</p>
<p>引入依赖 <code>spring-cloud-starter-netflix-eureka-client</code>, 在启动类 <code>Application</code> 上,添加 <code>@EnableDiscoveryClient</code> 注解。</p>
<p>在 <code>application.yml</code> 中使用以下配置:</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="attr">eureka:</span></div><div class="line"><span class="attr"> environment:</span> <span class="string">dev</span></div><div class="line"><span class="attr"> client:</span></div><div class="line"><span class="attr"> healthcheck:</span></div><div class="line"><span class="attr"> enabled:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> service-url:</span></div><div class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://user:[email protected]:8010/eureka/</span></div><div class="line"><span class="attr"> registry-fetch-interval-seconds:</span> <span class="number">5</span> <span class="comment"># 定时刷新本地缓存时间</span></div><div class="line"><span class="attr"> instance:</span></div><div class="line"><span class="attr"> hostname:</span> <span class="string">${hostname:localhost}</span></div><div class="line"><span class="attr"> instance-id:</span> <span class="string">${spring.application.name}@${spring.cloud.client.ip-address}:${server.port}</span> <span class="comment"># 自定义实例ID</span></div><div class="line"><span class="attr"> prefer-ip-address:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> lease-expiration-duration-in-seconds:</span> <span class="number">10</span> <span class="comment"># 没有心跳的淘汰时间,10秒</span></div><div class="line"><span class="attr"> lease-renewal-interval-in-seconds:</span> <span class="number">5</span> <span class="comment"># 心跳间隔,5秒</span></div></pre></td></tr></table></figure>
<p>启动项目即可。可在 <code>http://localhost:2000</code> 中看到注册的状态。</p>
<h4 id="Eureka-高可用"><a href="#Eureka-高可用" class="headerlink" title="Eureka 高可用"></a>Eureka 高可用</h4><p>Eureka Server 之间是可以互相注册的。</p>
<p>举个例子,我们有 3 个 Eureka 注册中心,端口分别为 2001 、 2002 和 2003 。那么端口为 2001 的最基本的配置如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">eureka:</div><div class="line"> client:</div><div class="line"> service-url:</div><div class="line"> defaultZone: http://localhost:2002/eureka/,http://localhost:2003/eureka/</div></pre></td></tr></table></figure>
<p>端口 2002 和 2003 服务可以根据以上规则配置。</p>
<h4 id="多网卡选择"><a href="#多网卡选择" class="headerlink" title="多网卡选择"></a>多网卡选择</h4><p>服务器有多个网卡,eh0,eh1,eh2,只有eh0可以让外部其他服务访问进来,而Eureka client将eh1和eh2注册到Eureka server上,这样其他服务就无法访问该微服务了。有两种方式:</p>
<ul>
<li><p>指定IP注册</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="attr">eureka:</span></div><div class="line"><span class="attr"> instance:</span></div><div class="line"><span class="attr"> prefer-ip-address:</span> <span class="literal">true</span></div><div class="line"><span class="attr"> ip-address:</span> <span class="string">实际能访问到的IP</span></div></pre></td></tr></table></figure>
</li>
<li><p>使用<strong>spring.cloud.inetutils</strong>配置网卡选择</p>
</li>
</ul>
<h4 id="Eureka健康检查"><a href="#Eureka健康检查" class="headerlink" title="Eureka健康检查"></a>Eureka健康检查</h4><p>由于server和client通过心跳保持 服务状态,而只有状态为UP的服务才能被访问。看eureka界面中的status。</p>
<p>比如心跳一直正常,服务一直UP,但是此服务DB连不上了,无法正常提供服务。</p>
<p>此时,我们需要将 微服务的健康状态也同步到server。只需要启动eureka的健康检查就行。这样微服务就会将自己的健康状态同步到eureka。配置如下即可。</p>
<p>在client端加入Actuator,并配置<code>eureka.client.healthcheck.enabled=true</code>,将自己真正的健康状态传播到server。</p>
<p>通过代码来改动服务的状态:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="meta">@Data</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HealthStatusHandler</span> <span class="keyword">implements</span> <span class="title">HealthIndicator</span> </span>{</div><div class="line"> <span class="keyword">private</span> Boolean status = <span class="keyword">true</span>;</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Health <span class="title">health</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (status) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Health.Builder().up().build();</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Health.Builder().down().build();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>应用场景:比如说短信业务,欠费了等情况,可以暂时下线服务。</p>
<h4 id="Eureka遇到的坑"><a href="#Eureka遇到的坑" class="headerlink" title="Eureka遇到的坑"></a>Eureka遇到的坑</h4><h5 id="available-replicas为空的问题"><a href="#available-replicas为空的问题" class="headerlink" title="available-replicas为空的问题"></a>available-replicas为空的问题</h5><p>如果使用了<code>eureka.instance.prefer-ip-address: true</code>,然后<code>eureka.client.service-url.defaultZone</code>配置的IP与实例IP不一致,会出现available-replicas为空的问题。</p>
<p>解决方法:在<code>eureka.instance.ip-address</code>中强制设置IP,然后在<code>eureka.client.service-url.defaultZone</code>配置对应的IP。</p>
<h4 id="Eureka-最佳实践"><a href="#Eureka-最佳实践" class="headerlink" title="Eureka 最佳实践"></a>Eureka 最佳实践</h4><p>本人所在的公司由于体量小,<strong>生产环境直接使用 Eureka 的默认配置进行高可用性运行</strong>,目前也没有出现太大的问题。</p>
<p>以下是一些实践参考文章(注意:文章中的版本号不是最新的,可能在配置上会有调整):</p>
<ul>
<li><a href="http://www.itmuch.com/spring-cloud-sum-eureka/" target="_blank" rel="external">Spring Cloud中,Eureka常见问题总结</a></li>
<li><a href="https://github.com/spring-cloud/spring-cloud-netflix/issues/203" target="_blank" rel="external">Eureka Clustering documentation and best practices</a></li>
<li><a href="https://github.com/spring-cloud/spring-cloud-netflix/issues/373" target="_blank" rel="external">Documentation: changing Eureka renewal frequency <em>WILL</em> break the self-preservation feature of the server</a></li>
</ul>
<hr>
<ul>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91">从0开始用SpringCloud搭建微服务系统【一】</a></li>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/">从0开始用SpringCloud搭建微服务系统【二】</a></li>
<li><a href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/">从0开始用SpringCloud搭建微服务系统【三】</a></li>
<li><a href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/">从0开始用SpringCloud搭建微服务系统【四】</a></li>
<li><a href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/">从0开始用SpringCloud搭建微服务系统【五】</a></li>
</ul>
]]></content>
<summary type="html">
<h2 id="服务注册与服务发现"><a href="#服务注册与服务发现" class="headerlink" title="服务注册与服务发现"></a>服务注册与服务发现</h2><ol>
<li><p>背景:在传统应用中,组件之间的调用,通过有规范的约束的接口来实现,
</summary>
<category term="Spring" scheme="https://webfuse.cn/categories/Spring/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/categories/Spring/Spring-Cloud/"/>
<category term="微服务" scheme="https://webfuse.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/tags/Spring-Cloud/"/>
</entry>
<entry>
<title>从0开始用SpringCloud搭建微服务系统【一】</title>
<link href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91/"/>
<id>https://webfuse.cn/2020/04/22/从0开始用SpringCloud搭建微服务系统【一】/</id>
<published>2020-04-22T07:58:34.000Z</published>
<updated>2020-04-30T02:24:56.445Z</updated>
<content type="html"><![CDATA[<h2 id="微服务与-Spring-Cloud"><a href="#微服务与-Spring-Cloud" class="headerlink" title="微服务与 Spring Cloud"></a>微服务与 Spring Cloud</h2><h3 id="微服务"><a href="#微服务" class="headerlink" title="微服务"></a>微服务</h3><h4 id="从单体到微服务"><a href="#从单体到微服务" class="headerlink" title="从单体到微服务"></a>从单体到微服务</h4><h5 id="单体服务"><a href="#单体服务" class="headerlink" title="单体服务"></a>单体服务</h5><ul>
<li><p>概念:所有功能全部打包在一起,也就是全部的功能都在一个应用包中。应用大部分是一个 war 包或 jar 包。</p>
</li>
<li><p>优点:容易开发、测试、部署,适合项目初期试错。</p>
</li>
<li><p>缺点:</p>
<p> 随着项目越来越复杂,团队不断扩大。坏处就显现出来了。</p>
<ul>
<li>复杂性高:代码多,十万行,百万行级别。加一个小功能,会带来其他功能的隐患,因为它们在一起。</li>
<li>技术债务:人员流动,不坏不修,因为不敢修。</li>
<li>持续部署困难:由于是全量应用,改一个小功能,全部部署,会导致无关的功能暂停使用。编译部署上线耗时长,不敢随便部署,导致部署频率低,进而又导致两次部署之间 功能修改多,越不敢部署,恶性循环。</li>
<li>可靠性差:某个小问题,比如小功能出现 OOM,会导致整个应用崩溃。</li>
<li>扩展受限:只能整体扩展,无法按照需要进行扩展, 不能根据计算密集型(派单系统)和IO密集型(文件服务) 进行合适的区分。</li>
<li>阻碍创新:单体应用是以一种技术解决所有问题,不容易引入新技术。但在高速的互联网发展过程中,适应的潮流是:用合适的语言做合适的事情。比如在单体应用中,一个项目用 spring MVC,想换成s pring boot,切换成本很高,因为有可能10万,百万行代码都要改。</li>
</ul>
</li>
</ul>
<h5 id="微服务-1"><a href="#微服务-1" class="headerlink" title="微服务"></a>微服务</h5><ul>
<li><p><a href="https://martinfowler.com/" target="_blank" rel="external">Martin Fowler</a> 在 2014年提出 <a href="https://www.martinfowler.com/articles/microservices.html" target="_blank" rel="external">Microservices架构</a></p>
</li>
<li><p>微服务是一种架构风格,将单体应用划分为小型的服务单元。</p>
</li>
<li><p>微服务架构是一种使用一系列粒度较小的服务来开发单个应用的方式。</p>
<ul>
<li>每个服务运行在自己的进程中</li>
<li>服务间采用轻量级的方式进行通信(通常是HTTP API)</li>
<li>这些服务是基于业务逻辑和范围,通过自动化部署的机制来独立部署的,并且服务的集中管理应该是最低限度的,即每个服务可以采用不同的编程语言编写,使用不同的数据存储技术。</li>
</ul>
</li>
<li><p>优点:</p>
<ul>
<li>独立部署。不依赖其他服务,耦合性低,不用管其他服务的部署对自己的影响。</li>
<li>易于开发和维护。关注特定业务,所以业务清晰,代码量少,模块变的易开发、易理解、易维护。</li>
<li>启动快。功能少,代码少,所以启动快,有需要停机维护的服务,不会长时间暂停服务。</li>
<li>局部修改容易。只需要部署 相应的服务即可,适合敏捷开发。</li>
<li>技术栈不受限。java,node.js,go 等</li>
<li>按需伸缩.某个服务受限,可以按需增加内存,cpu 等。</li>
<li>职责专一。专门团队负责专门业务,有利于团队分工。</li>
<li>代码复用。不需要重复写。底层实现通过接口方式提供。</li>
<li>便于团队协作:每个团队只需要提供 API 就行,定义好 API 后,可以并行开发。</li>
</ul>
</li>
<li>缺点:<ul>
<li>分布式固有的复杂性。容错,网络延时,调用关系、分布式事务等,都会带来复杂。</li>
<li>分布式事务的挑战。每个服务有自己的数据库,有点在于不同服务可以选择适合自身业务的数据库。订单用MySQL,评论用Mongodb等。目前最理想解决方案是:柔性事务的最终一致性。</li>
<li>接口调整成本高。改一个接口,调用方都要改。</li>
<li>测试难度提升。一个接口改变,所有调用方都得测。自动化测试就变得重要了,API文档的管理也尤为重要。</li>
<li>运维要求高。需要维护几十上百个服务,监控变的复杂,并且还要关注多个集群。</li>
<li>重复工作。比如 java 的工具类可以在共享 common.ja r中,但在多语言下行不通,C++ 无法直接用 java的 jar 包。</li>
</ul>
</li>
</ul>
<h4 id="微服务组件"><a href="#微服务组件" class="headerlink" title="微服务组件"></a>微服务组件</h4><p>基于微服务的特性,微服务的组件不局限于技术的实现。主要的组件有:</p>
<ul>
<li>服务注册与发现:服务提供方将己方调用地址注册到服务注册中心,让服务调用方能够方便地找到自己;服务调用方从服务注册中心找到自己需要调用的服务的地址。</li>
<li>负载均衡:服务提供方一般以多实例的形式提供服务,负载均衡功能能够让服务调用方连接到合适的服务节点。并且,服务节点选择的过程对服务调用方来说是透明的。</li>
<li>服务网关:服务网关是服务调用的唯一入口,可以在这个组件中实现用户鉴权、动态路由、灰度发布、A/B测试、负载限流等功能。</li>
<li>配置中心:将本地化的配置信息(Properties、XML、YAML等形式)注册到配置中心,实现程序包在开发、测试、生产环境中的无差别性,方便程序包的迁移,也是无状态特性。</li>
<li>集成框架:微服务组件都以职责单一的程序包对外提供服务,集成框架以配置的形式将所有微服务组件(特别是管理端组件)集成到统一的界面框架下,让用户能够在统一的界面中使用系统。Spring Cloud 就是一个集成框架。</li>
<li>调用链监控:记录完成一次请求的先后衔接和调用关系,并将这种串行或并行的调用关系展示出来。在系统出错时,可以方便地找到出错点。</li>
<li>支撑平台:系统微服务化后,各个业务模块经过拆分变得更加细化,系统的部署、运维、监控等都比单体应用架构更加复杂,这就需要将大部分的工作自动化。现在,Docker 等工具可以给微服务架构的部署带来较多的便利,例如持续集成、蓝绿发布、健康检查、性能监控等等。如果没有合适的支撑平台或工具,微服务架构就无法发挥它最大的功效。</li>
</ul>
<h5 id="常见的架构图"><a href="#常见的架构图" class="headerlink" title="常见的架构图"></a>常见的架构图</h5><img src="/2020/04/22/从0开始用SpringCloud搭建微服务系统【一】/基础微服务架构图.png" alt="基础微服务架构图.png" title="">
<h4 id="Spring-Cloud"><a href="#Spring-Cloud" class="headerlink" title="Spring Cloud"></a>Spring Cloud</h4><p>Spring Cloud 是实现微服务架构的一系列框架的有机集合。是在 Spring Boot 基础上构建的,用于简化分布式系统构建的工具集。是拥有众多子项目的项目集合。利用 Spring Boot 的开发便利性,巧妙地简化了分布式系统基础设施(服务注册与发现、熔断机制、网关路由、配置中心、消息总线、负载均衡、链路追踪等)的开发。</p>
<h6 id="Spring-Cloud的基础组件"><a href="#Spring-Cloud的基础组件" class="headerlink" title="Spring Cloud的基础组件"></a>Spring Cloud的基础组件</h6><ul>
<li><p>Eureka:服务注册与发现,用于服务管理。</p>
</li>
<li><p>Feign: web调用客户端,能够简化HTTP接口的调用。</p>
</li>
<li><p>Ribbon:基于客户端的负载均衡。</p>
</li>
<li><p>Hystrix:熔断降级,防止服务雪崩。</p>
</li>
<li><p>Zuul:网关路由,提供路由转发、请求过滤、限流降级等功能。</p>
</li>
<li><p>Config:配置中心,分布式配置管理。</p>
</li>
<li><p>Sleuth:服务链路追踪</p>
</li>
<li><p>Admin:健康管理</p>
</li>
</ul>
<hr>
<ul>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%80%E3%80%91">从0开始用SpringCloud搭建微服务系统【一】</a></li>
<li><a href="https://webfuse.cn/2020/04/22/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%8C%E3%80%91/">从0开始用SpringCloud搭建微服务系统【二】</a></li>
<li><a href="https://webfuse.cn/2020/04/25/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%B8%89%E3%80%91/">从0开始用SpringCloud搭建微服务系统【三】</a></li>
<li><a href="https://webfuse.cn/2020/04/27/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E5%9B%9B%E3%80%91/">从0开始用SpringCloud搭建微服务系统【四】</a></li>
<li><a href="https://webfuse.cn/2020/04/30/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E7%94%A8SpringCloud%E6%90%AD%E5%BB%BA%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E3%80%90%E4%BA%94%E3%80%91/">从0开始用SpringCloud搭建微服务系统【五】</a></li>
</ul>
]]></content>
<summary type="html">
<h2 id="微服务与-Spring-Cloud"><a href="#微服务与-Spring-Cloud" class="headerlink" title="微服务与 Spring Cloud"></a>微服务与 Spring Cloud</h2><h3 id="微服务">
</summary>
<category term="Spring" scheme="https://webfuse.cn/categories/Spring/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/categories/Spring/Spring-Cloud/"/>
<category term="微服务" scheme="https://webfuse.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="https://webfuse.cn/tags/Spring-Cloud/"/>
</entry>
<entry>
<title>Golang简单入门笔记</title>
<link href="https://webfuse.cn/2020/04/06/Golang%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8%E7%AC%94%E8%AE%B0/"/>
<id>https://webfuse.cn/2020/04/06/Golang简单入门笔记/</id>
<published>2020-04-06T07:56:40.000Z</published>
<updated>2020-04-06T07:58:03.468Z</updated>
<content type="html"><![CDATA[<h2 id="基础"><a href="#基础" class="headerlink" title="基础"></a>基础</h2><h3 id="关键字"><a href="#关键字" class="headerlink" title="关键字"></a>关键字</h3><p>关键字有25个。关键字不能用于自定义名字,只能在特定语法结构中使用。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">break default func interface select</div><div class="line">case defer go map struct</div><div class="line">chan else goto package switch</div><div class="line">const fallthrough if range type</div><div class="line">continue for import return var</div></pre></td></tr></table></figure>
<h3 id="预定义名字"><a href="#预定义名字" class="headerlink" title="预定义名字"></a>预定义名字</h3><p>大约有30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">内建常量: true false iota nil</div><div class="line"></div><div class="line">内建类型: int int8 int16 int32 int64</div><div class="line"> uint uint8 uint16 uint32 uint64 uintptr</div><div class="line"> float32 float64 complex128 complex64</div><div class="line"> bool byte rune string error</div><div class="line"></div><div class="line">内建函数: make len cap new append copy close delete</div><div class="line"> complex real imag</div><div class="line"> panic recover</div></pre></td></tr></table></figure>
<p>其中:</p>
<ul>
<li>byte 是 uint8 类型的别名,存储 raw data</li>
<li>rune 是 int32 类型的别名,存储一个 Unicode code point 字符</li>
</ul>
<p>这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。</p>
<h3 id="命名规则"><a href="#命名规则" class="headerlink" title="命名规则"></a>命名规则</h3><p>Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:<strong>一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。</strong></p>
<p>如果一个名字是在函数内部定义,那么它的就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它将是导出的,也就是说可以被外部的包访问。</p>
<p>命名的建议:</p>
<ul>
<li>推荐使用 <strong>驼峰式</strong> 命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔。</li>
<li>包本身的名字一般总是用小写字母。</li>
<li>名字的长度没有逻辑限制,但是Go语言的风格是尽量使用短小的名字,对于局部变量尤其是这样。</li>
<li>如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义。</li>
</ul>
<h3 id="声明和变量"><a href="#声明和变量" class="headerlink" title="声明和变量"></a>声明和变量</h3><ul>
<li><strong>var</strong> 用于声明变量</li>
<li><strong>const</strong> 用于声明常量</li>
<li><strong>type</strong> 用于声明一个新的类型</li>
<li><strong>func</strong> 用于声明一个函数</li>
</ul>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> foo <span class="keyword">int</span> <span class="comment">// 无初值的声明</span></div><div class="line"><span class="keyword">var</span> foo <span class="keyword">int</span> = <span class="number">42</span> <span class="comment">// 带初值的声明</span></div><div class="line"><span class="keyword">var</span> foo, bar <span class="keyword">int</span> = <span class="number">42</span>, <span class="number">1302</span> <span class="comment">// 一次性声明并初始化多个变量</span></div><div class="line"><span class="keyword">var</span> foo = <span class="number">42</span> <span class="comment">// 类型推断,由使用的上下文决定</span></div><div class="line">foo := <span class="number">42</span> <span class="comment">// 简短模式,自动推断类型,并且必须在函数体内部</span></div><div class="line"><span class="keyword">const</span> constant = <span class="string">"This is a constant"</span> <span class="comment">//声明一个常量</span></div><div class="line"></div><div class="line"><span class="keyword">type</span> MyInt <span class="keyword">struct</span>{ <span class="comment">//声明一个类型</span></div><div class="line"> </div><div class="line">} </div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">fooFuc</span><span class="params">()</span></span>{ <span class="comment">//声明一个函数</span></div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。</li>
<li>简短模式有以下限制:<ul>
<li>定义变量,同时显式初始化。</li>
<li>不能提供数据类型。</li>
<li>只能用在函数内部。</li>
<li>由于使用了:=,而不是赋值的=,因此推导声明写法的左值变量必须是没有定义过的变量。若定义过,将会发生编译错误。</li>
<li>在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错。</li>
</ul>
</li>
<li><strong>当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。<font color="red">所有的内存在 Go 中都是经过初始化的。</font></strong></li>
</ul>
<h4 id="匿名变量"><a href="#匿名变量" class="headerlink" title="匿名变量"></a>匿名变量</h4><p>匿名变量的特点是一个下画线“<em>”,“</em>”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetData</span><span class="params">()</span> <span class="params">(<span class="keyword">int</span>, <span class="keyword">int</span>)</span></span> {</div><div class="line"> <span class="keyword">return</span> <span class="number">100</span>, <span class="number">200</span></div><div class="line">}</div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>{</div><div class="line"> a, _ := GetData()</div><div class="line"> _, b := GetData()</div><div class="line"> fmt.Println(a, b)</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="变量的生命周期"><a href="#变量的生命周期" class="headerlink" title="变量的生命周期"></a>变量的生命周期</h4><ul>
<li>对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。</li>
<li>局部变量的声明周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。</li>
<li>函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。</li>
</ul>
<h4 id="常量"><a href="#常量" class="headerlink" title="常量"></a>常量</h4><p>常量表达式的值在编译期计算,而不是在运行期。</p>
<h5 id="iota-常量生成器"><a href="#iota-常量生成器" class="headerlink" title="iota 常量生成器"></a>iota 常量生成器</h5><p>常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> Weekday <span class="keyword">int</span></div><div class="line"></div><div class="line"><span class="keyword">const</span> (</div><div class="line"> Sunday Weekday = <span class="literal">iota</span></div><div class="line"> Monday</div><div class="line"> Tuesday</div><div class="line"> Wednesday</div><div class="line"> Thursday</div><div class="line"> Friday</div><div class="line"> Saturday</div><div class="line">)</div><div class="line"></div><div class="line"><span class="keyword">const</span> (</div><div class="line"> _ = <span class="number">1</span> << (<span class="number">10</span> * <span class="literal">iota</span>)</div><div class="line"> KiB <span class="comment">// 1024</span></div><div class="line"> MiB <span class="comment">// 1048576</span></div><div class="line"> GiB <span class="comment">// 1073741824</span></div><div class="line"> TiB <span class="comment">// 1099511627776 (exceeds 1 << 32)</span></div><div class="line"> PiB <span class="comment">// 1125899906842624</span></div><div class="line"> EiB <span class="comment">// 1152921504606846976</span></div><div class="line"> ZiB <span class="comment">// 1180591620717411303424 (exceeds 1 << 64)</span></div><div class="line"> YiB <span class="comment">// 1208925819614629174706176</span></div><div class="line">)</div></pre></td></tr></table></figure>
<h3 id="new-和-make"><a href="#new-和-make" class="headerlink" title="new 和 make"></a>new 和 make</h3><p>new 和 make 是两个内置函数,主要用来创建并分配类型的内存。new 只分配内存,而 make 只能用于 slice、map 和 channel 的初始化。</p>
<ul>
<li>new 函数只接受一个参数,这个参数是一个类型,并且<strong>返回一个指向该类型内存地址的指针</strong>。同时 new 函数会把分配的内存置为零,也就是类型的零值。</li>
</ul>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> sum *<span class="keyword">int</span></div><div class="line">sum = <span class="built_in">new</span>(<span class="keyword">int</span>) <span class="comment">//分配空间</span></div><div class="line">*sum = <span class="number">98</span></div><div class="line">fmt.Println(*sum)</div><div class="line"></div><div class="line"><span class="keyword">type</span> Student <span class="keyword">struct</span> {</div><div class="line"> name <span class="keyword">string</span></div><div class="line"> age <span class="keyword">int</span></div><div class="line">}</div><div class="line"><span class="keyword">var</span> s *Student</div><div class="line">s = <span class="built_in">new</span>(Student) <span class="comment">//分配空间。如果我们不使用 new 函数为自定义类型分配空间,就会报错</span></div><div class="line">s.name =<span class="string">"dequan"</span></div><div class="line">fmt.Println(s)</div></pre></td></tr></table></figure>
<ul>
<li>make 也是用于内存分配的,但是和 new 不同,<strong>它只用于 chan、map 以及 slice 的内存创建</strong>,<strong>而且它返回的类型就是这三个类型本身,而不是他们的指针类型</strong>,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。</li>
</ul>
<h3 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h3><table>
<thead>
<tr>
<th style="text-align:center">运算符</th>
<th style="text-align:center">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><code>+</code></td>
<td style="text-align:center">加</td>
</tr>
<tr>
<td style="text-align:center"><code>-</code></td>
<td style="text-align:center">减</td>
</tr>
<tr>
<td style="text-align:center"><code>*</code></td>
<td style="text-align:center">乘</td>
</tr>
<tr>
<td style="text-align:center"><code>/</code></td>
<td style="text-align:center">除</td>
</tr>
<tr>
<td style="text-align:center"><code>%</code></td>
<td style="text-align:center">取余</td>
</tr>
<tr>
<td style="text-align:center"><code>&</code></td>
<td style="text-align:center">按位与</td>
</tr>
<tr>
<td style="text-align:center">`</td>
<td style="text-align:center">`</td>
<td>按位或</td>
</tr>
<tr>
<td style="text-align:center"><code>^</code></td>
<td style="text-align:center">按位异或</td>
</tr>
<tr>
<td style="text-align:center"><code>&^</code></td>
<td style="text-align:center">按位清除(AND NOT)<code>&^</code> 即 <code>AND NOT(x, y) = AND(x, NOT(Y))</code></td>
</tr>
<tr>
<td style="text-align:center"><code><<</code></td>
<td style="text-align:center">左移</td>
</tr>
<tr>
<td style="text-align:center"><code>>></code></td>
<td style="text-align:center">右移</td>
</tr>
<tr>
<td style="text-align:center"><code>==</code></td>
<td style="text-align:center">相等</td>
</tr>
<tr>
<td style="text-align:center"><code>!=</code></td>
<td style="text-align:center">不等</td>
</tr>
<tr>
<td style="text-align:center"><code><</code></td>
<td style="text-align:center">小于</td>
</tr>
<tr>
<td style="text-align:center"><code><=</code></td>
<td style="text-align:center">小于等于</td>
</tr>
<tr>
<td style="text-align:center"><code>></code></td>
<td style="text-align:center">大于</td>
</tr>
<tr>
<td style="text-align:center"><code>>=</code></td>
<td style="text-align:center">大于等于</td>
</tr>
<tr>
<td style="text-align:center"><code>&&</code></td>
<td style="text-align:center">逻辑与</td>
</tr>
<tr>
<td style="text-align:center">`</td>
<td style="text-align:center"></td>
<td>`</td>
<td>逻辑或</td>
</tr>
<tr>
<td style="text-align:center"><code>!</code></td>
<td style="text-align:center">取反</td>
</tr>
<tr>
<td style="text-align:center"><code>&</code></td>
<td style="text-align:center">寻址(生成指针)</td>
</tr>
<tr>
<td style="text-align:center"><code>*</code></td>
<td style="text-align:center">获取指针指向的数据</td>
</tr>
<tr>
<td style="text-align:center"><code><-</code></td>
<td style="text-align:center">向 channel 中发送 / 接收数据</td>
</tr>
</tbody>
</table>
<h3 id="包(-package-)"><a href="#包(-package-)" class="headerlink" title="包( package )"></a>包( package )</h3><ul>
<li>main package 才是可执行文件</li>
<li>package 名字与 import 路径的最后一个单词一致(如导入 math/rand 则 package 叫 rand)</li>
<li>写开头的标识符(变量名、函数名…),对其他 package 是可访问的</li>
<li>小写开头的标识符,对其他 package 是不可见的</li>
</ul>
<h4 id="包的初始化"><a href="#包的初始化" class="headerlink" title="包的初始化"></a>包的初始化</h4><ul>
<li>包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化。</li>
<li>如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。</li>
<li>每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。</li>
<li>初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依赖的包都已经完成初始化工作了。</li>
</ul>
<p>init初始化函数</p>
<p>对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> popcount</div><div class="line"></div><div class="line"><span class="comment">// pc[i] is the population count of i.</span></div><div class="line"><span class="keyword">var</span> pc [<span class="number">256</span>]<span class="keyword">byte</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">for</span> i := <span class="keyword">range</span> pc {</div><div class="line"> pc[i] = pc[i/<span class="number">2</span>] + <span class="keyword">byte</span>(i&<span class="number">1</span>)</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// PopCount returns the population count (number of set bits) of x.</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">PopCount</span><span class="params">(x <span class="keyword">uint64</span>)</span> <span class="title">int</span></span> {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">int</span>(pc[<span class="keyword">byte</span>(x>>(<span class="number">0</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">1</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">2</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">3</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">4</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">5</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">6</span>*<span class="number">8</span>))] +</div><div class="line"> pc[<span class="keyword">byte</span>(x>>(<span class="number">7</span>*<span class="number">8</span>))])</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p>数据类型分为:基础类型、复合类型、引用类型和接口类型。</p>
<ul>
<li>基础类型,包括:数字、字符串和布尔型等</li>
<li>复合数据类型:数组和结构体等</li>
<li>引用类型包括:指针、切片、字典、函数、通道等</li>
</ul>
<h3 id="基础类型"><a href="#基础类型" class="headerlink" title="基础类型"></a>基础类型</h3><ul>
<li><strong>uint</strong> 32 位或 64 位</li>
<li><strong>uint8</strong> 无符号 8 位整型 (0 到 255)</li>
<li><strong>uint16</strong> 无符号 16 位整型 (0 到 65535)</li>
<li><strong>uint32</strong> 无符号 32 位整型 (0 到 4294967295)</li>
<li><strong>uint64</strong> 无符号 64 位整型 (0 到 18446744073709551615)</li>
<li><strong>int</strong> 32 位或 64 位</li>
<li><strong>int8</strong> 有符号 8 位整型 (-128 到 127)</li>
<li><strong>int16</strong> 有符号 16 位整型 (-32768 到 32767)</li>
<li><strong>int32</strong> 有符号 32 位整型 (-2147483648 到 2147483647)</li>
<li><strong>int64</strong> 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)</li>
<li><strong>byte</strong> uint8 的别名(type byte = uint8)</li>
<li><strong>rune</strong> int32 的别名(type rune = int32),表示一个 unicode 码</li>
<li><strong>uintptr</strong> “无符号整型,用于存放一个指针是一种无符号的整数类型,没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程是才需要,特别是Go 语言和 C 语言函数库或操作系统接口相交互的地方。”</li>
<li><strong>float32</strong> IEEE-754 32 位浮点型数</li>
<li><strong>float64</strong> IEEE-754 64 位浮点型数</li>
<li><strong>complex64</strong> 32 位实数和虚数</li>
<li><strong>complex128</strong> 64 位实数和虚数</li>
<li><strong>string</strong> 字符串,默认值是空字符串,而非 NULL</li>
</ul>
<h3 id="复合数据类型"><a href="#复合数据类型" class="headerlink" title="复合数据类型"></a>复合数据类型</h3><p>复合数据类型类型包括数组、结构体、切片、字典等。</p>
<h4 id="struct"><a href="#struct" class="headerlink" title="struct"></a>struct</h4><p>Go 语言中没有 class 类的概念,取而代之的是 struct,struct 的方法对应到类的成员函数。</p>
<p>结构类型可以用来描述一组数据值,这组值的本质既可以是原始的,也可以是非原始的。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// struct 是一种类型,也是字段成员的集合体</span></div><div class="line"></div><div class="line"><span class="comment">// 声明 struct</span></div><div class="line"><span class="keyword">type</span> Vertex <span class="keyword">struct</span> {</div><div class="line"> X, Y <span class="keyword">int</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存,因此必须在定义结构体并实例化后才能使用结构体的字段。</span></div><div class="line"><span class="comment">// 初始化 struct</span></div><div class="line"><span class="keyword">var</span> v = Vertex{<span class="number">1</span>, <span class="number">2</span>} <span class="comment">// 字段名有序对应值</span></div><div class="line"><span class="keyword">var</span> v = Vertex{X: <span class="number">1</span>, Y: <span class="number">2</span>} <span class="comment">// 字段名对应值</span></div><div class="line"><span class="keyword">var</span> v = []Vertex{{<span class="number">1</span>,<span class="number">2</span>},{<span class="number">5</span>,<span class="number">2</span>},{<span class="number">5</span>,<span class="number">5</span>}} <span class="comment">// 初始化多个 struct 组成的 slice</span></div><div class="line"></div><div class="line"><span class="comment">//用键值对填充结构体的例子</span></div><div class="line"><span class="keyword">type</span> People <span class="keyword">struct</span> {</div><div class="line"> name <span class="keyword">string</span></div><div class="line"> child *People</div><div class="line">}</div><div class="line">relation := &People{</div><div class="line"> name: <span class="string">"爷爷"</span>,</div><div class="line"> child: &People{</div><div class="line"> name: <span class="string">"爸爸"</span>,</div><div class="line"> child: &People{</div><div class="line"> name: <span class="string">"我"</span>,</div><div class="line"> },</div><div class="line"> },</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 创建指针类型的结构体。实例化以后,实例的类型为 *T,属于指针。</span></div><div class="line">vertex := <span class="built_in">new</span>(Vertex)</div><div class="line"></div><div class="line"><span class="comment">// 取结构体的地址实例化。对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作。</span></div><div class="line"><span class="comment">// 实例化以后,实例的类型为 *T,属于指针。</span></div><div class="line">v := &Vertex{}</div><div class="line"></div><div class="line"><span class="comment">// 访问成员</span></div><div class="line">v.X = <span class="number">4</span></div><div class="line"></div><div class="line"><span class="comment">// 在 func 关键字和函数名之间,声明接收者是 struct</span></div><div class="line"><span class="comment">// 在方法内部,struct 实例被复制,传值引用</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Vertex)</span> <span class="title">Abs</span><span class="params">()</span> <span class="title">float64</span></span> {</div><div class="line"> <span class="keyword">return</span> math.Sqrt(v.X*v.X + v.Y*v.Y)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 调用方法(有接收者的函数)</span></div><div class="line">v.Abs()</div><div class="line"></div><div class="line"><span class="comment">// 有的方法接收者是指向 struct 的指针</span></div><div class="line"><span class="comment">// 此时在方法内调用实例,将是传址引用</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v *Vertex)</span> <span class="title">add</span><span class="params">(n <span class="keyword">float64</span>)</span></span> {</div><div class="line"> v.X += n</div><div class="line"> v.Y += n</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">//取地址实例化是最广泛的一种结构体实例化方式,可以使用函数封装上面的初始化过程</span></div><div class="line"><span class="keyword">type</span> Command <span class="keyword">struct</span> {</div><div class="line"> Name <span class="keyword">string</span> <span class="comment">// 指令名称</span></div><div class="line"> Var *<span class="keyword">int</span> <span class="comment">// 指令绑定的变量</span></div><div class="line"> Comment <span class="keyword">string</span> <span class="comment">// 指令的注释</span></div><div class="line">}</div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">newCommand</span><span class="params">(name <span class="keyword">string</span>, varref *<span class="keyword">int</span>, comment <span class="keyword">string</span>)</span> *<span class="title">Command</span></span> {</div><div class="line"> <span class="keyword">return</span> &Command{</div><div class="line"> Name: name,</div><div class="line"> Var: varref,</div><div class="line"> Comment: comment,</div><div class="line"> }</div><div class="line">}</div><div class="line">cmd = newCommand(</div><div class="line"> <span class="string">"version"</span>,</div><div class="line"> &version,</div><div class="line"> <span class="string">"show version"</span>,</div><div class="line">)</div></pre></td></tr></table></figure>
<p>匿名结构体</p>
<p>使用 <code>map[string]interface{}</code> 开销更小且更为安全。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">point := <span class="keyword">struct</span> {</div><div class="line"> X, Y <span class="keyword">int</span></div><div class="line">}{<span class="number">1</span>, <span class="number">2</span>}</div></pre></td></tr></table></figure>
<h4 id="array"><a href="#array" class="headerlink" title="array"></a>array</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a [<span class="number">10</span>]<span class="keyword">int</span> <span class="comment">// 声明长度为 10 的 int 型数组,注意数组类型 = (元素类型 int,元素个数 10)</span></div><div class="line">a[<span class="number">3</span>] = <span class="number">42</span> <span class="comment">// 设置元素值</span></div><div class="line">i := a[<span class="number">3</span>] <span class="comment">// 读取元素值</span></div><div class="line"></div><div class="line"><span class="comment">// 声明并初始化数组</span></div><div class="line"><span class="keyword">var</span> a = [<span class="number">2</span>]<span class="keyword">int</span>{<span class="number">1</span>, <span class="number">2</span>}</div><div class="line">a := [<span class="number">2</span>]<span class="keyword">int</span>{<span class="number">1</span>, <span class="number">2</span>} <span class="comment">// 简短声明</span></div><div class="line">a := [...]<span class="keyword">int</span>{<span class="number">1</span>, <span class="number">2</span>} <span class="comment">// 数组长度使用 ... 代替,编译器会自动计算元素个数</span></div><div class="line"></div><div class="line"><span class="comment">//遍历数组</span></div><div class="line"><span class="keyword">for</span> k, v := <span class="keyword">range</span> a {</div><div class="line"> fmt.Println(k, v)</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="list"><a href="#list" class="headerlink" title="list"></a>list</h4><p>在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。</p>
<p>初始化:</p>
<ul>
<li>变量名 := list.New()</li>
<li>var 变量名 list.List</li>
</ul>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">l := list.New()</div><div class="line">l.PushBack(<span class="string">"fist"</span>) <span class="comment">//在尾部插入</span></div><div class="line">l.PushFront(<span class="number">67</span>) <span class="comment">//在头部插入</span></div><div class="line"></div><div class="line"> </div><div class="line">l.Remove(element) <span class="comment">// 移除 element 变量对应的元素。</span></div><div class="line"></div><div class="line"><span class="comment">//遍历</span></div><div class="line"><span class="keyword">for</span> i := l.Front(); i != <span class="literal">nil</span>; i = i.Next() {</div><div class="line"> fmt.Println(i.Value)</div><div class="line">}</div></pre></td></tr></table></figure>
<h4 id="slice"><a href="#slice" class="headerlink" title="slice"></a>slice</h4><p>切片是有三个字段的数据结构,<strong>指向底层数组的指针,切片访问元素的个数(长度),切片允许增长到的元素的个数(容量)</strong>,这种数据结构便于使用和管理数据集。切片围绕动态数组的概念构建的,可以按需自动增长和缩小。切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法,切片的底层内存是在连续块中分配的。</p>
<p>语法:</p>
<p>slice [开始位置 : 结束位置]</p>
<ul>
<li>slice:表示目标切片对象;</li>
<li>开始位置:对应目标切片对象的索引;</li>
<li>结束位置:对应目标切片的结束索引。</li>
</ul>
<p>从数组或切片生成新的切片拥有如下特性:</p>
<ul>
<li>取出的元素数量为:结束位置 - 开始位置;</li>
<li>取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;</li>
<li>当缺省开始位置时,表示从连续区域开头到结束位置;</li>
<li>当缺省结束位置时,表示从开始位置到整个连续区域末尾;</li>
<li>两者同时缺省时,与切片本身等效;</li>
<li>两者同时为 0 时,等效于空切片,一般用于切片复位。</li>
</ul>
<p>根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写 len(slice) 但不会报错。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> a []<span class="keyword">int</span> <span class="comment">// 声明 slice,相当于声明未指定长度的数组</span></div><div class="line"><span class="keyword">var</span> a = []<span class="keyword">int</span> {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>} <span class="comment">// 声明并初始化 slice (基于 {} 中给出的底层数组)</span></div><div class="line">a := []<span class="keyword">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>} <span class="comment">// 简短声明</span></div><div class="line">chars := []<span class="keyword">string</span>{<span class="number">0</span>:<span class="string">"a"</span>, <span class="number">2</span>:<span class="string">"c"</span>, <span class="number">1</span>: <span class="string">"b"</span>} <span class="comment">// ["a", "b", "c"]</span></div><div class="line"></div><div class="line"><span class="keyword">var</span> b = a[lo:hi] <span class="comment">// 创建从 lo 到 hi-1 的 slice </span></div><div class="line"><span class="keyword">var</span> b = a[<span class="number">1</span>:<span class="number">4</span>] <span class="comment">// 创建从 1 到 3 的 slice</span></div><div class="line"><span class="keyword">var</span> b = a[:<span class="number">3</span>] <span class="comment">// 缺省 start index 则默认为 0 </span></div><div class="line"><span class="keyword">var</span> b = a[<span class="number">3</span>:] <span class="comment">// 缺省 end index 则默认为 len(a)</span></div><div class="line">a = <span class="built_in">append</span>(a,<span class="number">17</span>,<span class="number">3</span>) <span class="comment">// 向 slice a 中追加 17 和 3</span></div><div class="line">c := <span class="built_in">append</span>(a,b...) <span class="comment">// 合并两个 slice</span></div><div class="line"></div><div class="line"><span class="comment">// 使用 make 创建 slice</span></div><div class="line">a = <span class="built_in">make</span>([]<span class="keyword">byte</span>, <span class="number">5</span>, <span class="number">5</span>) <span class="comment">// 第一个参数是长度,第二个参数是容量</span></div><div class="line">a = <span class="built_in">make</span>([]<span class="keyword">byte</span>, <span class="number">5</span>) <span class="comment">// 容量参数是可选的</span></div><div class="line"></div><div class="line"><span class="comment">// 从数组创建 slice</span></div><div class="line">x := [<span class="number">3</span>]<span class="keyword">string</span>{<span class="string">"Лайка"</span>, <span class="string">"Белка"</span>, <span class="string">"Стрелка"</span>}</div><div class="line">s := x[:] <span class="comment">// slice s 指向底层数组 x</span></div><div class="line"></div><div class="line"><span class="comment">// 内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。</span></div><div class="line">slice1 := []<span class="keyword">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</div><div class="line">slice2 := []<span class="keyword">int</span>{<span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>}</div><div class="line"><span class="built_in">copy</span>(slice2, slice1) <span class="comment">// 只会复制slice1的前3个元素到slice2中</span></div><div class="line"><span class="built_in">copy</span>(slice1, slice2) <span class="comment">// 只会复制slice2的3个元素到slice1的前3个位置</span></div></pre></td></tr></table></figure>
<h4 id="map"><a href="#map" class="headerlink" title="map"></a>map</h4><p>map (映射)是一种数据结构,用于存储一系列无序的键值对。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> m <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">int</span></div><div class="line">m = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">int</span>)</div><div class="line">m[<span class="string">"key"</span>] = <span class="number">42</span></div><div class="line">fmt.Println(m[<span class="string">"key"</span>])</div><div class="line"></div><div class="line"><span class="built_in">delete</span>(m, <span class="string">"key"</span>)</div><div class="line"></div><div class="line">elem, ok := m[<span class="string">"key"</span>] <span class="comment">// 检查 m 中是否键为 key 的元素,如果有 ok 才为 true</span></div><div class="line"></div><div class="line"><span class="comment">// 使用键值对的形式来初始化 map</span></div><div class="line"><span class="keyword">var</span> m = <span class="keyword">map</span>[<span class="keyword">string</span>]Vertex{</div><div class="line"> <span class="string">"Bell Labs"</span>: {<span class="number">40.68433</span>, <span class="number">-74.39967</span>},</div><div class="line"> <span class="string">"Google"</span>: {<span class="number">37.42202</span>, <span class="number">-122.08408</span>},</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 从映射获取值并判断键是否存在</span></div><div class="line">value, exits := colors[<span class="string">"blue"</span>]</div><div class="line"><span class="keyword">if</span> exits {</div><div class="line"> <span class="comment">// blue</span></div><div class="line"> fmt.Println(value)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 使用range迭代映射</span></div><div class="line"><span class="keyword">for</span> key, value := <span class="keyword">range</span> colors {</div><div class="line"> <span class="comment">// key:red value:red</span></div><div class="line"> fmt.Printf(<span class="string">"key:%s value:%s\n"</span>, key, value)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。1.9 版本中提供了一种效率较高的并发安全的 sync.Map。</p>
<h3 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h3><p>一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。</p>
<p>当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。</p>
<p>每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加 &操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:</p>
<p><code>ptr := &v // v 的类型为 T</code></p>
<p>==其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为*T,称做 T 的指针类型,*代表指针。==</p>
<blockquote>
<p><strong>变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。</strong></p>
</blockquote>
<p><strong>从指针获取指针指向的值:</strong> 当使用<code>&</code>操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用<code>*</code>操作符,也就是指针取值。</p>
<font color="red"><strong>取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。</strong></font>
<p>变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:</p>
<ul>
<li>对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。</li>
<li>指针变量的值是指针地址。</li>
<li>对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。</li>
</ul>
<p>*操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。</p>
<p>new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。</p>
<h3 id="通道"><a href="#通道" class="headerlink" title="通道"></a>通道</h3><p>如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制。一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。</p>
<p>通道的声明如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">ch := make(chan 类型)</div></pre></td></tr></table></figure>
<p>如: <code>ch := make(chan int) // ch has type 'chan int'</code></p>
<p>两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相同的对象,那么比较的结果为真。一个channel也可以和nil进行比较。</p>
<p>channel的发送和接受操作:</p>
<ul>
<li>一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine</li>
<li>发送和接收两个操作都使用<code><-</code>运算符</li>
<li>在发送语句中,<-运算符分割channel和要发送的值</li>
<li>在接收语句中,<-运算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的</li>
</ul>
<p>示例如下:</p>