-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1516 lines (1516 loc) · 257 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>起点</title>
<url>/2020/05/25/%E8%B5%B7%E7%82%B9/</url>
<content><![CDATA[<h3 id="有的没的">有的没的</h3>
<p>搭建个人博客的念头也是早就有了,就是一直没有这个时间或是契机吧,各种个人笔记和刷题笔记都散落在不同的应用里,感觉有些杂乱而且浪费。所以趁这段时间将这个博客搭了起来,之前的各种笔记都会慢慢搬到这里,即便没人看,也权当是自己的仓库,记录自己的技术成长吧。暂时用LearningGp作为博客名,这个名字最早是游戏角色的名字LearningGroup但是因为那个游戏有长度限制,就变成了现在这样。游戏倒是没在玩了,名字倒是留下了。希望能把这个小地方一直做下去吧。</p>
<h3 id="规划">规划</h3>
<p>目前已经想好分类如下</p>
<ul>
<li>刷题记录——主要记录算法题题解</li>
<li>知识图谱——主要记录学过科目的知识图谱</li>
<li>踩坑记录——各种踩过的解决的没解决的坑</li>
<li>随笔——一些有的没的</li>
</ul>
]]></content>
<categories>
<category>随笔</category>
</categories>
</entry>
<entry>
<title>Hyperledger Caliper测试Demo</title>
<url>/2022/03/28/Hyperledger-Caliper%E6%B5%8B%E8%AF%95Demo/</url>
<content><![CDATA[<h1 id="有的没的">有的没的</h1>
<p>前不久在做一个有关Hyperledger Fabric的项目,不可避免地要使用Hyperledger Caliper来做性能测试,发现网上的教程不多,版本大都也比较低,花了我不少时间。所以将过程中遇到的坑和参考资料记录下来。</p>
<h1 id="正文">正文</h1>
<p>首先我的搭建步骤基本依照这篇文章<a href="https://blog.csdn.net/bean_business/article/details/108937601" target="_blank" rel="noopener">在现有Fabric 2.2.0 网络上设置和运行Caliper性能测试 实战</a>,搭建测试网络部分依照<a href="https://hyperledger-fabric.readthedocs.io/en/release-2.2/tutorials.html" target="_blank" rel="noopener">官方文档</a>。接下来把遇到的问题按照博客中的顺序一一列出。</p>
<h2 id="环境准备">环境准备</h2>
<p>整体来说需要准备<a href="https://blog.csdn.net/bean_business/article/details/108792976" target="_blank" rel="noopener">docker</a>、<a href="https://blog.csdn.net/qq_39361915/article/details/115324217" target="_blank" rel="noopener">docker-compose</a>、Node.js、<a href="https://www.jianshu.com/p/e6ecd86397fb" target="_blank" rel="noopener">Git</a>、<a href="https://blog.csdn.net/qq_41316955/article/details/120546932" target="_blank" rel="noopener">cURL</a>、<a href="https://www.jianshu.com/p/45e379131a31" target="_blank" rel="noopener">pip3</a>(这个忘记哪里会用到,不过确实在我的清单里)。这些都是常见的工具,如果有问题,给出了对应的链接。接下来会给出一些问题的解决方案。PS:本文的环境是CentOS7。</p>
<h3 id="启动docker时ssh掉线镜像获取速度慢">启动docker时ssh掉线&镜像获取速度慢</h3>
<p>docker0可能会占用接口,某些环境下会导致这个问题,需要我们编辑/etc/docker/daemon.json文件(文件不存在可以自己新建一个)。其中registry-mirrors,是<a href="https://developer.aliyun.com/article/29941" target="_blank" rel="noopener">阿里云镜像加速地址</a>,可以自行获取。 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> "registry-mirrors": [""],</span><br><span class="line"> "debug" : true,</span><br><span class="line"> "default-address-pools" : [</span><br><span class="line"> {</span><br><span class="line"> "base" : "10.50.0.0/16",</span><br><span class="line"> "size" : 24</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<h3 id="git卡在卡在clone-into">git卡在卡在clone into</h3>
<p>报错RPC failed; result=35, HTTP code = 0,可能是因为缓存设置的不够大。可以尝试以下命令(<a href="https://blog.csdn.net/qq_41025299/article/details/114980935" target="_blank" rel="noopener">参考链接</a>)。 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">git config --global http.postBuffer 50M</span><br></pre></td></tr></table></figure></p>
<h3 id="编译出错">编译出错</h3>
<p>使用gcc编译时可能会报错<code>warning: ‘deprecated’ attribute directive ignored [-Wattributes]</code>可以通过升级gcc解决(<a href="https://www.cnblogs.com/jixiaohua/p/11732225.html" target="_blank" rel="noopener">参考链接</a>)。</p>
<h3 id="node.js安装">Node.js安装</h3>
<p>Node.js安装不推荐使用博客中的方式,推荐使用nvm管理版本,因为Caliper以及链码安装可能会对npm和node有版本要求,<a href="https://cloud.tencent.com/developer/article/1812323" target="_blank" rel="noopener">安装nvm参考链接</a>。</p>
<h2 id="部署测试网络">部署测试网络</h2>
<p>这部分主要参考<a href="https://hyperledger-fabric.readthedocs.io/en/release-2.2/tutorials.html" target="_blank" rel="noopener">官方文档</a>。同样接下来会给出一些问题的解决方案。</p>
<h3 id="fabric版本">Fabric版本</h3>
<p>由于Caliper支持版本问题(官方说支持最新,不过好像有些问题),推荐在安装Fabric使用指定版本(比如Fabric v2.2.0 和 Fabric CA v1.4.7 )。</p>
<h3 id="无法获取官方脚本">无法获取官方脚本</h3>
<p>如果<code>curl -sSL https://bit.ly/2ysbOFE | bash -s</code>命令报错,基本是网络原因。如果无法科学上网,可以访问<a href="https://raw.githubusercontent.com/hyperledger/fabric/master/scripts/bootstrap.sh" target="_blank" rel="noopener">未缩写的 URL</a>并且将其复制下来,命名为bootstrap.sh文件并通过bash命令执行。</p>
<h3 id="bootstrap.sh脚本运行错误">bootstrap.sh脚本运行错误</h3>
<p>有可能是在获取二进制文件时出错,同样一般是因为网络问题。我的解决方案是在可以科学上网的设备上将二进制文件下载下来后复制到fabric-sample目录的子目录 bin中,然后将脚本中的pullBinaries方法删除。至于下载地址,可以查看脚本中的pullBinaries方法,也可以在删除pullBinaries方法前直接运行脚本观察输出即可找到地址(因为它会先输出地址然后卡在那一步)。</p>
<h3 id="运行测试网络错误链码安装失败">运行测试网络错误(链码安装失败)</h3>
<p>由于我们指定了Fabric的版本,但是在测试网络的配置中拉取的镜像是最新的,因此会造成一些问题需要手动调整。将/root/fabric-samples/test-network/docker路径下的文件中的镜像版本改成你所指定的版本。例如: <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">orderer.example.com:</span><br><span class="line"> container_name: orderer.example.com</span><br><span class="line"> image: hyperledger/fabric-orderer:2.2.0</span><br><span class="line"> environment:</span><br></pre></td></tr></table></figure></p>
<h2 id="运行calpier">运行Calpier</h2>
<p>这一部分依照<a href="https://blog.csdn.net/bean_business/article/details/108937601" target="_blank" rel="noopener">在现有Fabric 2.2.0 网络上设置和运行Caliper性能测试 实战</a>。同样接下来会给出一些问题的解决方案。</p>
<h3 id="npm权限不足">npm权限不足</h3>
<p>这好像是一种保护机制,可以在运行命令时在结尾加上<code>--unsafe-perm</code>,暂时绕过保护。</p>
<h3 id="链码执行超时">链码执行超时</h3>
<p>timeout expired while executing transaction 。可以修改fabric-samples/config/core.yaml 中的 peer.keepalive.client.timeout 来修改超时时间(<a href="https://charlielin.top/2020/03/26/%E5%9C%A8-fabric-%E4%B8%8A%E6%89%A7%E8%A1%8C-chaincode-%E7%9A%84%E6%A2%B3%E7%90%86/" target="_blank" rel="noopener">参考链接</a>)。PS:大部分Fabric的配置都可以在这里修改,配置含义可以参考这篇<a href="https://blog.csdn.net/QuietSugar/article/details/91541778" target="_blank" rel="noopener">文章</a>。</p>
<h3 id="超过并发限制">超过并发限制</h3>
<p>新版本的Fabric网关限制了并发数,但在性能测试中我们会需要测试高并发场景。可以修改fabric-samples/config/core.yaml中的这一部分。 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># Concurrency limits the number of concurrently running requests to a service on each peer.</span><br><span class="line"># Currently this option is only applied to endorser service and deliver service.</span><br><span class="line"># When the property is missing or the value is 0, the concurrency limit is disabled for the service.</span><br><span class="line">concurrency:</span><br><span class="line"># endorserService limits concurrent requests to endorser service that handles chaincode deployment, query and invocation,</span><br><span class="line"># including both user chaincodes and system chaincodes.</span><br><span class="line">endorserService: 2500</span><br><span class="line"># deliverService limits concurrent event listeners registered to deliver service for blocks and transaction events.</span><br><span class="line">deliverService: 2500</span><br><span class="line"># gatewayService limits concurrent requests to gateway service that handles the submission and evaluation of transactions.</span><br><span class="line">gatewayService: 500</span><br></pre></td></tr></table></figure></p>
<h3 id="caliper配置">Caliper配置</h3>
<p>最后附上Caliper的<a href="https://hyperledger.github.io/caliper/v0.4.2/getting-started/" target="_blank" rel="noopener">官方文档</a>其中的<a href="https://hyperledger.github.io/caliper/v0.4.2/bench-config/" target="_blank" rel="noopener">Benchmark Configuration</a>以及<a href="https://hyperledger.github.io/caliper/v0.4.2/rate-controllers/" target="_blank" rel="noopener">Rate Controllers</a>部分对前期测试比较重要。前者描述了测试相关的配置,比如轮数、持续时间等。后者介绍了速率控制器。</p>
]]></content>
<categories>
<category>踩坑记录</category>
</categories>
<tags>
<tag>Hyperledger Fabric</tag>
<tag>Hyperledger Caliper</tag>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title>Sentinel学习笔记:sentinel-cluster</title>
<url>/2021/07/10/Sentinel%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-sentinel-cluster/</url>
<content><![CDATA[<h1 id="简介"><strong>简介</strong></h1>
<p>由于流量存在不均匀的情况,单机限流无法实现准确的集群限流,往往在总流量没有到达集群阈值的情况下,集群中的某些机器就开始限流。例如一个集群限流阈值为200qps,由两个节点组成,那么每个节点限流配置100qps,在这种配置下即便集群流量未到达200qps,单个节点也有可能到达100qps而开始限流。而集群限流就是为这种场景服务的,目的就是实现精确地控制整个集群的 QPS。</p>
<h1 id="模块简介"><strong>模块简介</strong></h1>
<p><strong>sentinel-cluster-common-default:</strong>公共模块,定义通信协议,包括编码器和解码器接口、请求和响应实体(数据包),与底层使用哪种通信框架无关</p>
<p><strong>sentinel-cluster-client-default:</strong>集群限流客户端模块,实现公共模块定义的接口,使用 Netty 进行通信,实现自动连接与掉线重连、提供连接配置 API</p>
<p><strong>sentinel-cluster-server-default:</strong>集群限流服务端模块,实现公共模块定义的接口,使用 Netty 进行通信,同时提供扩展接口对接规则判断的具体实现(TokenService)</p>
<p><strong>sentinel-cluster-server-envoy-rls:</strong>提供了Envoy 集群流量控制,使sentinel具有在Service Mesh下的流控能力</p>
<h1 id="核心原理"><strong>核心原理</strong></h1>
<h2 id="单机限流流程"><strong>单机限流流程</strong></h2>
<ol type="1">
<li>FlowSlot 作为切入点,在 entry 方法中调用 FlowRuleChecker#checkFlow 方法判断是否限流;</li>
<li>FlowRuleChecker 根据资源名称从规则管理器获取配置的限流规则,遍历限流规则;</li>
<li>根据限流规则的 clusterMode 决定走本地限流逻辑还是走集群限流逻辑;</li>
<li>如果是本地限流,则调用流量效果控制器判断是否拒绝当前请求。</li>
</ol>
<p>由于网络延迟的存在,Sentinel 集群限流并未实现匀速排队流量效果控制,也没有支持冷启动,而只支持直接拒绝请求的流控效果。(具有优先级的流量在一定情况下可以尝试抢占之后的时间窗口的指标,见core部分DefaultController)</p>
<h2 id="集群限流流程"><strong>集群限流流程</strong></h2>
<p>在上述第(3)步中如判断为集群限流,则通过远程调用向集群限流服务端(TokenService)发起调用,由TokenService判断是否拒绝请求。结合令牌桶的思想,TokenService类似于令牌发放员,负责生产令牌,客户端向服务端申请令牌。</p>
<h1 id="服务模式"><strong>服务模式</strong></h1>
<p>Sentinel 集群限流客户端与集群限流服务端通信只保持一个长连接,底层通信基于 Netty 框架实现,自定义通信协议,并且数据包较小,网络 I/O 性能方面影响不大。</p>
<p>Sentinel 集群限流对限流服务端的可用性要求不高,当限流服务端挂掉时,可回退为本地限流。</p>
<h2 id="嵌入模式"><strong>嵌入模式</strong></h2>
<p>简单理解为,TokenService作为应用的内置服务同应用一起启动,可动态挑选一个节点作为TokenService(不具备类似主从自动切换的功能)</p>
<h2 id="独立模式"><strong>独立模式</strong></h2>
<p>单独部署的TokenService,可以为多个服务提供集群限流支持</p>
<h2 id="整体扩展架构"><strong>整体扩展架构</strong></h2>
<figure>
<img src="/2021/07/10/Sentinel学习笔记-sentinel-cluste/image.png" alt="" /><figcaption>来源见文末</figcaption>
</figure>
<h1 id="核心类及流程"><strong>核心类及流程</strong></h1>
<figure>
<img src="/2021/07/10/Sentinel学习笔记-sentinel-cluste/imag2e.png" alt="" /><figcaption>来源见文末</figcaption>
</figure>
<h2 id="sentinel-core-模块的-cluster-包"><strong>sentinel-core 模块的 cluster 包</strong></h2>
<p>定义了实现集群限流功能的相关接口</p>
<ul>
<li>TokenService:定义客户端向服务端申请 token 的接口,由 FlowRuleChecker 调用</li>
<li>ClusterTokenClient:集群限流客户端需要实现的接口,继承 TokenService</li>
<li>ClusterTokenServer:集群限流服务端需要实现的接口</li>
<li>EmbeddedClusterTokenServer:支持嵌入模式的集群限流服务端需要实现的接口,继承 TokenService、ClusterTokenServer</li>
</ul>
<p>在上图中DefaultClusterTokenClient是 sentinel-cluster-client-default 模块中的 ClusterTokenClient 接口实现类,DefaultTokenService 与 DefaultEmbeddedTokenServer 分别是 sentinel-cluster-server-default 模块中的 ClusterTokenServer 接口与 EmbeddedClusterTokenServer 接口的实现类。</p>
<p>当使用嵌入模式启用集群限流服务端时,使用的是 EmbeddedClusterTokenServer,否则使用 ClusterTokenServer,通过 Java SPI 实现</p>
<h2 id="集群限流客户端"><strong>集群限流客户端</strong></h2>
<p>与单机限流的不同之处从FlowRuleChecker#canPassCheck开始,在该方法中若判断为集群限流则调用FlowRuleChecker#passClusterCheck方法</p>
<h3 id="flowrulecheckerpassclustercheck"><strong>FlowRuleChecker#passClusterCheck(……)</strong></h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">passClusterCheck</span><span class="params">(FlowRule rule, Context context, DefaultNode node, <span class="keyword">int</span> acquireCount,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//获取TokenService实例</span></span><br><span class="line"> TokenService clusterService = pickClusterService();</span><br><span class="line"> <span class="keyword">if</span> (clusterService == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//获取集群限流规则的唯一全局id</span></span><br><span class="line"> <span class="keyword">long</span> flowId = rule.getClusterConfig().getFlowId();</span><br><span class="line"> <span class="comment">//申请令牌</span></span><br><span class="line"> TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);</span><br><span class="line"> <span class="comment">//根据结果判断是否拒绝请求</span></span><br><span class="line"> <span class="keyword">return</span> applyTokenResult(result, rule, context, node, acquireCount, prioritized);</span><br><span class="line"> <span class="comment">// If client is absent, then fallback to local mode.</span></span><br><span class="line"> } <span class="keyword">catch</span> (Throwable ex) {</span><br><span class="line"> RecordLog.warn(<span class="string">"[FlowRuleChecker] Request cluster token unexpected failed"</span>, ex);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Fallback to local flow control when token client or server for this rule is not available.</span></span><br><span class="line"> <span class="comment">// If fallback is not enabled, then directly pass.</span></span><br><span class="line"> <span class="keyword">return</span> fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="pickclusterservice"><strong>pickClusterService()</strong></h3>
<p>如果当前节点是客户端角色,获取ClusterTokenClient实例<strong>;</strong>如果当前节点是服务端角色(嵌入式),获取EmbeddedClusterTokenServer实例</p>
<h3 id="requesttokenflowid-acquirecount-prioritized"><strong>requestToken(flowId, acquireCount, prioritized)</strong></h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> TokenResult <span class="title">requestToken</span><span class="params">(Long flowId, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="comment">//检查输入有效性</span></span><br><span class="line"> <span class="keyword">if</span> (notValidRequest(flowId, acquireCount)) {</span><br><span class="line"> <span class="keyword">return</span> badRequest();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//构造请求体</span></span><br><span class="line"> FlowRequestData data = <span class="keyword">new</span> FlowRequestData().setCount(acquireCount)</span><br><span class="line"> .setFlowId(flowId).setPriority(prioritized);</span><br><span class="line"> <span class="comment">//构造请求</span></span><br><span class="line"> ClusterRequest<FlowRequestData> request = <span class="keyword">new</span> ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//发送请求,通过netty建立的连接发送</span></span><br><span class="line"> TokenResult result = sendTokenRequest(request);</span><br><span class="line"> logForResult(result);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> ClusterClientStatLogUtil.log(ex.getMessage());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.FAIL);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="applytokenresultresult-rule-context-node-acquirecount-prioritized"><strong>applyTokenResult(result, rule, context, node, acquireCount, prioritized)</strong></h3>
<ul>
<li>当响应状态码为 OK 时放行请求;</li>
<li>当响应状态码为 SHOULD_WAIT 时,休眠指定时间再放行请求;</li>
<li>当响应状态码为 BLOCKED,直接拒绝请求;</li>
<li>其它状态码均代表调用失败,根据规则配置的 fallbackToLocalWhenFail 是否为 true,决定是否回退为本地限流,如果需要回退为本地限流模式,则调用 passLocalCheck 方法重新判断。</li>
</ul>
<h2 id="集群限流服务端"><strong>集群限流服务端</strong></h2>
<p>从客户端发来的requestToken请求最后交由com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService#requestToken方法处理</p>
<h3 id="defaulttokenservicerequesttokenlong-ruleid-int-acquirecount-boolean-prioritized"><strong>DefaultTokenService#requestToken(Long ruleId, int acquireCount, boolean prioritized)</strong></h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> TokenResult <span class="title">requestToken</span><span class="params">(Long ruleId, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="comment">//验证规则有效性</span></span><br><span class="line"> <span class="keyword">if</span> (notValidRequest(ruleId, acquireCount)) {</span><br><span class="line"> <span class="keyword">return</span> badRequest();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// The rule should be valid.</span></span><br><span class="line"> <span class="comment">//获取对应规则</span></span><br><span class="line"> FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);</span><br><span class="line"> <span class="keyword">if</span> (rule == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.NO_RULE_EXISTS);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//获取令牌</span></span><br><span class="line"> <span class="keyword">return</span> ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="clusterparamflowcheckeracquireclustertoken"><strong>ClusterParamFlowChecker#acquireClusterToken</strong></h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> TokenResult <span class="title">acquireClusterToken</span><span class="params">(<span class="comment">/*@Valid*/</span> FlowRule rule, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> Long id = rule.getClusterConfig().getFlowId();</span><br><span class="line"> <span class="comment">//命名空间纬度的全局阈值</span></span><br><span class="line"> <span class="keyword">if</span> (!allowProceed(id)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.TOO_MANY_REQUEST);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//获取数据统计滑动窗口</span></span><br><span class="line"> ClusterMetric metric = ClusterMetricStatistics.getMetric(id);</span><br><span class="line"> <span class="keyword">if</span> (metric == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.FAIL);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//获取已通过数</span></span><br><span class="line"> <span class="keyword">double</span> latestQps = metric.getAvg(ClusterFlowEvent.PASS);</span><br><span class="line"> <span class="comment">//计算阈值</span></span><br><span class="line"> <span class="keyword">double</span> globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount();</span><br><span class="line"> <span class="comment">//剩余通过数=阈值-已通过数-请求数</span></span><br><span class="line"> <span class="keyword">double</span> nextRemaining = globalThreshold - latestQps - acquireCount;</span><br><span class="line"> <span class="comment">//剩余通过数>0直接放行</span></span><br><span class="line"> <span class="keyword">if</span> (nextRemaining >= <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> checking logic and metric operation should be separated.</span></span><br><span class="line"> metric.add(ClusterFlowEvent.PASS, acquireCount);</span><br><span class="line"> metric.add(ClusterFlowEvent.PASS_REQUEST, <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">if</span> (prioritized) {</span><br><span class="line"> <span class="comment">// Add prioritized pass.</span></span><br><span class="line"> metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Remaining count is cut down to a smaller integer.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.OK)</span><br><span class="line"> .setRemaining((<span class="keyword">int</span>) nextRemaining)</span><br><span class="line"> .setWaitInMs(<span class="number">0</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//剩余通过数不足,但是是优先流量,允许占用之后指标,类似core部分DefaultControlle</span></span><br><span class="line"> <span class="keyword">if</span> (prioritized) {</span><br><span class="line"> <span class="comment">// Try to occupy incoming buckets.</span></span><br><span class="line"> <span class="comment">//获取已经占用数</span></span><br><span class="line"> <span class="keyword">double</span> occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);</span><br><span class="line"> <span class="comment">//已经占用数小于允许占用的最大值(比例*阈值)</span></span><br><span class="line"> <span class="keyword">if</span> (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) {</span><br><span class="line"> <span class="comment">//计算占用后等待时间</span></span><br><span class="line"> <span class="keyword">int</span> waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);</span><br><span class="line"> <span class="comment">// waitInMs > 0 indicates pre-occupy incoming buckets successfully.</span></span><br><span class="line"> <span class="keyword">if</span> (waitInMs > <span class="number">0</span>) {</span><br><span class="line"> ClusterServerStatLogUtil.log(<span class="string">"flow|waiting|"</span> + id);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TokenResult(TokenResultStatus.SHOULD_WAIT)</span><br><span class="line"> .setRemaining(<span class="number">0</span>)</span><br><span class="line"> .setWaitInMs(waitInMs);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Or else occupy failed, should be blocked.</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Blocked.</span></span><br><span class="line"> metric.add(ClusterFlowEvent.BLOCK, acquireCount);</span><br><span class="line"> metric.add(ClusterFlowEvent.BLOCK_REQUEST, <span class="number">1</span>);</span><br><span class="line"> ClusterServerStatLogUtil.log(<span class="string">"flow|block|"</span> + id, acquireCount);</span><br><span class="line"> ClusterServerStatLogUtil.log(<span class="string">"flow|block_request|"</span> + id, <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">if</span> (prioritized) {</span><br><span class="line"> <span class="comment">// Add prioritized block.</span></span><br><span class="line"> metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount);</span><br><span class="line"> ClusterServerStatLogUtil.log(<span class="string">"flow|occupied_block|"</span> + id, <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> blockedResult();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="clusterparamflowcheckercalcglobalthreshold"><strong>ClusterParamFlowChecker#calcGlobalThreshold</strong></h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">double</span> <span class="title">calcGlobalThreshold</span><span class="params">(ParamFlowRule rule, Object value)</span> </span>{</span><br><span class="line"> <span class="comment">//获取阈值</span></span><br><span class="line"> <span class="keyword">double</span> count = getRawThreshold(rule, value);</span><br><span class="line"> <span class="comment">//当阈值类型为集群总 QPS 时,直接使用限流规则的阈值(count);</span></span><br><span class="line"> <span class="comment">//当阈值类型为单机均摊时,根据规则 ID 获取当前连接的客户端总数,将当前连接的客户端总数乘以限流规则的阈值(count)作为集群总 QPS 阈值。</span></span><br><span class="line"> <span class="keyword">switch</span> (rule.getClusterConfig().getThresholdType()) {</span><br><span class="line"> <span class="keyword">case</span> ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> <span class="keyword">case</span> ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">int</span> connectedCount = ClusterParamFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());</span><br><span class="line"> <span class="keyword">return</span> count * connectedCount;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="clustermetrictryoccupynext"><strong>ClusterMetric#tryOccupyNext</strong></h3>
<p>简化版的DefaultControlle,只尝试占用下一个样本窗口的指标</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">tryOccupyNext</span><span class="params">(ClusterFlowEvent event, <span class="keyword">int</span> acquireCount, <span class="keyword">double</span> threshold)</span> </span>{</span><br><span class="line"> <span class="comment">//获取当前窗口通过数</span></span><br><span class="line"> <span class="keyword">double</span> latestQps = getAvg(ClusterFlowEvent.PASS);</span><br><span class="line"> <span class="comment">//判断能否占用之后窗口</span></span><br><span class="line"> <span class="keyword">if</span> (!canOccupy(event, acquireCount, latestQps, threshold)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> metric.addOccupyPass(acquireCount);</span><br><span class="line"> add(ClusterFlowEvent.WAITING, acquireCount);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1000</span> / metric.getSampleCount();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">canOccupy</span><span class="params">(ClusterFlowEvent event, <span class="keyword">int</span> acquireCount, <span class="keyword">double</span> latestQps, <span class="keyword">double</span> threshold)</span> </span>{</span><br><span class="line"> <span class="comment">//获取未来窗口的通过数</span></span><br><span class="line"> <span class="keyword">long</span> headPass = metric.getFirstCountOfWindow(event);</span><br><span class="line"> <span class="comment">//获取未来窗口占用数</span></span><br><span class="line"> <span class="keyword">long</span> occupiedCount = metric.getOccupiedCount(event);</span><br><span class="line"> <span class="comment">// bucket to occupy (= incoming bucket)</span></span><br><span class="line"> <span class="comment">// ↓</span></span><br><span class="line"> <span class="comment">// | head bucket | | | | current bucket |</span></span><br><span class="line"> <span class="comment">// +-------------+----+----+----+----------- ----+</span></span><br><span class="line"> <span class="comment">// (headPass)</span></span><br><span class="line"> <span class="comment">//判断当前窗口已通过数+(请求数+未来窗口被占用数)-未来窗口通过数(重复计算所以减去)是否小于阈值</span></span><br><span class="line"> <span class="keyword">return</span> latestQps + (acquireCount + occupiedCount) - headPass <= threshold;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="滑动窗口"><strong>滑动窗口</strong></h3>
<p>集群限流使用的滑动窗口并非 sentinel-core 模块下实现的滑动窗口,而是 sentinel-cluster-server-default 模块自己实现的滑动窗口</p>
<p>实现集群限流需要收集的指标数据有以下几种:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> ClusterFlowEvent {</span><br><span class="line"> PASS,</span><br><span class="line"> BLOCK,</span><br><span class="line"> PASS_REQUEST,</span><br><span class="line"> BLOCK_REQUEST,</span><br><span class="line"> OCCUPIED_PASS,</span><br><span class="line"> OCCUPIED_BLOCK,</span><br><span class="line"> WAITING</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>PASS:已经发放的令牌总数</li>
<li>BLOCK:令牌申请被驳回的总数</li>
<li>PASS_REQUEST:被放行的请求总数</li>
<li>BLOCK_REQUEST:被拒绝的请求总数</li>
<li>OCCUPIED_PASS:预占用,已经发放的令牌总数</li>
<li>OCCUPIED_BLOCK:预占用,令牌申请被驳回的总数</li>
<li>WAITING:当前等待下一个时间窗口到来的请求总数</li>
</ul>
<p>除统计的指标项与 sentinel-core 包下实现的滑动窗口统计的指标项有些区别外,实现方式都一致。</p>
<h1 id="总结"><strong>总结</strong></h1>
<p>集群限流并非解决请求倾斜问题,在请求倾斜严重的情况下,集群限流可能会导致某些节点的流量过高,导致系统的负载过高,这时就需要使用系统自适应限流、熔断降级作为兜底解决方案。</p>
<h1 id="参考资料"><strong>参考资料</strong></h1>
<p><a href="http://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Sentinel%EF%BC%88%E5%AE%8C%EF%BC%89/18%20Sentinel%20%E9%9B%86%E7%BE%A4%E9%99%90%E6%B5%81%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%88%E4%B8%8A%EF%BC%89.md" target="_blank" rel="noopener">http://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Sentinel%EF%BC%88%E5%AE%8C%EF%BC%89/18%20Sentinel%20%E9%9B%86%E7%BE%A4%E9%99%90%E6%B5%81%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%88%E4%B8%8A%EF%BC%89.md</a></p>
<p><a href="https://github.com/alibaba/Sentinel/wiki/%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7" target="_blank" rel="noopener">https://github.com/alibaba/Sentinel/wiki/%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7</a></p>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>源码分析</tag>
<tag>Sentinel</tag>
</tags>
</entry>
<entry>
<title>Sentinel学习笔记:sentinel-core</title>
<url>/2021/07/04/Sentinel%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-sentinel-core/</url>
<content><![CDATA[<h1 id="有的没的">有的没的</h1>
<p>有蛮长时间没更新了,笔记一直有在做,不过暂时都放在本地了,因为有些还不太完整(选定的每篇范围太大也是一个原因)。加上最近在实习,大部分笔记放在内部的笔记软件中。不过因为Sentinel有很大一部分是开源的,所以将笔记中有关开源的分析部分重新整理出来公开了,这段时间应该陆续会有笔记被放到这边。 文中没标注的部分资料(图片)来源于文末参考链接。</p>
<h1 id="经典限流算法">经典限流算法</h1>
<h2 id="计数器算法">计数器算法</h2>
<p>Sentinel 中默认实现的 QPS 限流算法和 THREADS 限流算法都属于计数器算法。QPS 限流的默认算法是通过判断当前时间窗口(1 秒)的 pass(被放行的请求数量)指标数据判断,如果 pass 总数已经大于等于限流的 QPS 阈值,则直接拒绝当前请求,每通过一个请求当前时间窗口的 pass 指标计数加 1。THREADS 限流的实现是通过判断当前资源并行占用的线程数是否已经达到阈值,是则直接拒绝当前请求,每通过一个请求 THREADS 计数加 1,每完成一个请求 THREADS 计数减 1。</p>
<h2 id="漏桶算法leaky-bucket">漏桶算法(Leaky Bucket)</h2>
<p>漏桶就像在一个桶的底部开一个洞,不控制水放入桶的速度,而通过底部漏洞的大小控制水流失的速度,当水放入桶的速率小于或等于水通过底部漏洞流出的速率时,桶中没有剩余的水,而当水放入桶的速率大于漏洞流出的速率时,水就会逐渐在桶中积累,当桶装满水时,若再向桶中放入水,则放入的水就会溢出。我们把水换成请求,往桶里放入请求的速率就是接收请求的速率,而水流失就是请求通过,水溢出就是请求被拒绝。</p>
<h2 id="令牌桶算法token-bucket">令牌桶算法(Token Bucket)</h2>
<p>令牌桶不存放请求,而是存放为请求生成的令牌(Token),只有拿到令牌的请求才能通过。原理就是以固定速率往桶里放入令牌,每当有请求过来时,都尝试从桶中获取令牌,如果能拿到令牌请求就能通过。当桶放满令牌时,多余的令牌就会被丢弃,而当桶中的令牌被用完时,请求拿不到令牌就无法通过。</p>
<h1 id="数据模型">数据模型</h1>
<h2 id="核心类">核心类</h2>
<h3 id="arraymetric">ArrayMetric</h3>
<p>Metric的实现类,数据节点的addRtAndSuccess最后会落到该类上,该类也是sentinel记录数据模型的最外层包装</p>
<p>内部持有一个<code>LeapArray<MetricBucket> data</code>数据结构作为对窗口的包装,所有操作都会落到该数据结构上</p>
<p>构造方法通过传入参数的差异,为data赋值不同子类的引用(秒级窗口OccupiableBucketLeapArray,分钟级窗口BucketLeapArray)</p>
<h3 id="leaparray">LeapArray</h3>
<p>内部持有一个<code>AtomicReferenceArray<WindowWrap<T>></code>的数据结构作为实际容纳窗口的容器,各种操作会在该类中获取对应的<code>WindowWrap<T></code>再进行相关操作</p>
<p><strong>成员变量</strong></p>
<p>protected int windowLengthInMs;</p>
<p>每个窗口长度(Ms)</p>
<p>protected int sampleCount;</p>
<p>滑动窗口内样本个数</p>
<p>protected int intervalInMs;</p>
<p>LeapArray总时间(Ms)</p>
<p>private double intervalInSecond;</p>
<p>LeapArray总时间(s)</p>
<p>protected final AtomicReferenceArray<WindowWrap<T>> array;</p>
<p>private final ReentrantLock updateLock = new ReentrantLock();</p>
<p>更新<code>WindowWrap<T></code>的锁</p>
<h3 id="windowwrap">WindowWrap</h3>
<p>窗口包装类,主要用于包装MetricBucket,记录窗口元信息</p>
<p><strong>成员变量</strong></p>
<p>private final long windowLengthInMs;</p>
<p>窗口长度</p>
<p>private long windowStart;</p>
<p>窗口开始时间</p>
<p>private T value;</p>
<p>被包装的类</p>
<h3 id="metricbucket">MetricBucket</h3>
<p>实际数据存放类</p>
<p>维护了一个LongAdder数组counters,用于记录各种数据,包括以下几种</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> MetricEvent {</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Normal pass.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> PASS,</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Normal block.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> BLOCK,</span><br><span class="line"> EXCEPTION,</span><br><span class="line"> SUCCESS,</span><br><span class="line"> RT,</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Passed in future quota (pre-occupied, since 1.5.0).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> OCCUPIED_PASS</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="核心方法">核心方法</h2>
<h3 id="获取数据以success为例">获取数据(以success为例)</h3>
<p>com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric#success</p>
<p>获取窗口内的success个数</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">success</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//方法本身是用来获取当前时间所在窗口,在这边被用于更新当前时间所在窗口</span></span><br><span class="line"> data.currentWindow();</span><br><span class="line"> <span class="keyword">long</span> success = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取整个窗口内的所有MetricBucket</span></span><br><span class="line"> List<MetricBucket> list = data.values();</span><br><span class="line"> <span class="keyword">for</span> (MetricBucket window : list) {</span><br><span class="line"> <span class="comment">//累加计算总和</span></span><br><span class="line"> success += window.success();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> success;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>com.alibaba.csp.sentinel.slots.statistic.base.LeapArray#currentWindow(long)</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> WindowWrap<T> <span class="title">currentWindow</span><span class="params">(<span class="keyword">long</span> timeMillis)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (timeMillis < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> idx = calculateTimeIdx(timeMillis);</span><br><span class="line"> <span class="comment">// Calculate current bucket start time.</span></span><br><span class="line"> <span class="keyword">long</span> windowStart = calculateWindowStart(timeMillis);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Get bucket item at given time from the array.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.</span></span><br><span class="line"><span class="comment"> * (2) Bucket is up-to-date, then just return the bucket.</span></span><br><span class="line"><span class="comment"> * (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> WindowWrap<T> old = array.get(idx);</span><br><span class="line"> <span class="keyword">if</span> (old == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * B0 B1 B2 NULL B4</span></span><br><span class="line"><span class="comment"> * ||_______|_______|_______|_______|_______||___</span></span><br><span class="line"><span class="comment"> * 200 400 600 800 1000 1200 timestamp</span></span><br><span class="line"><span class="comment"> * ^</span></span><br><span class="line"><span class="comment"> * time=888</span></span><br><span class="line"><span class="comment"> * bucket is empty, so create new and update</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * If the old bucket is absent, then we create a new bucket at {@code windowStart},</span></span><br><span class="line"><span class="comment"> * then try to update circular array via a CAS operation. Only one thread can</span></span><br><span class="line"><span class="comment"> * succeed to update, while other threads yield its time slice.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> WindowWrap<T> window = <span class="keyword">new</span> WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));</span><br><span class="line"> <span class="keyword">if</span> (array.compareAndSet(idx, <span class="keyword">null</span>, window)) {</span><br><span class="line"> <span class="comment">// Successfully updated, return the created bucket.</span></span><br><span class="line"> <span class="keyword">return</span> window;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Contention failed, the thread will yield its time slice to wait for bucket available.</span></span><br><span class="line"> Thread.yield();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (windowStart == old.windowStart()) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * B0 B1 B2 B3 B4</span></span><br><span class="line"><span class="comment"> * ||_______|_______|_______|_______|_______||___</span></span><br><span class="line"><span class="comment"> * 200 400 600 800 1000 1200 timestamp</span></span><br><span class="line"><span class="comment"> * ^</span></span><br><span class="line"><span class="comment"> * time=888</span></span><br><span class="line"><span class="comment"> * startTime of Bucket 3: 800, so it's up-to-date</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * If current {@code windowStart} is equal to the start timestamp of old bucket,</span></span><br><span class="line"><span class="comment"> * that means the time is within the bucket, so directly return the bucket.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">return</span> old;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (windowStart > old.windowStart()) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * (old)</span></span><br><span class="line"><span class="comment"> * B0 B1 B2 NULL B4</span></span><br><span class="line"><span class="comment"> * |_______||_______|_______|_______|_______|_______||___</span></span><br><span class="line"><span class="comment"> * ... 1200 1400 1600 1800 2000 2200 timestamp</span></span><br><span class="line"><span class="comment"> * ^</span></span><br><span class="line"><span class="comment"> * time=1676</span></span><br><span class="line"><span class="comment"> * startTime of Bucket 2: 400, deprecated, should be reset</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * If the start timestamp of old bucket is behind provided time, that means</span></span><br><span class="line"><span class="comment"> * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.</span></span><br><span class="line"><span class="comment"> * Note that the reset and clean-up operations are hard to be atomic,</span></span><br><span class="line"><span class="comment"> * so we need a update lock to guarantee the correctness of bucket update.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * The update lock is conditional (tiny scope) and will take effect only when</span></span><br><span class="line"><span class="comment"> * bucket is deprecated, so in most cases it won't lead to performance loss.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (updateLock.tryLock()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// Successfully get the update lock, now we reset the bucket.</span></span><br><span class="line"> <span class="keyword">return</span> resetWindowTo(old, windowStart);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> updateLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Contention failed, the thread will yield its time slice to wait for bucket available.</span></span><br><span class="line"> Thread.yield();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (windowStart < old.windowStart()) {</span><br><span class="line"> <span class="comment">// Should not go through here, as the provided time is already behind.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>注释非常详细,需要注意的是resetWindowTo方法,不同子类有不同的实现</p>
<ul>
<li>BucketLeapArray:重置窗口开始时间,将各种数据置为0</li>
<li>OccupiableBucketLeapArray:重置窗口开始时间,将borrowArray中对应时间的MetricBucket的数据作为窗口的数据,而不是直接归零(具体作用在流控中解释)</li>
</ul>
<p>com.alibaba.csp.sentinel.slots.statistic.base.LeapArray#values()</p>
<p>com.alibaba.csp.sentinel.slots.statistic.base.LeapArray#values(long)</p>
<p>获取窗口内的所有有效的(非过期)MetricBucket,组成list返回</p>
<h3 id="增加数据以addsuccess为例">增加数据(以addSuccess为例)</h3>
<p>com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric#addSuccess</p>
<p>调用currentWindow获取当前窗口后调用value()获取MetricBucket再调用addSuccess(count)方法</p>
<h2 id="滑动窗口类型">滑动窗口类型</h2>
<h3 id="分钟级滑动窗口">分钟级滑动窗口</h3>
<p>窗口总长度60秒,每个样本窗口1秒,总共60个样本窗口</p>
<p>private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);</p>
<h3 id="秒级滑动窗口">秒级滑动窗口</h3>
<p>窗口总长度1秒,每个样本窗口500ms,总共2个样本窗口</p>
<p>private transient volatile Metric rollingCounterInSecond =<br />
new ArrayMetric(SampleCountProperty.<em>SAMPLE_COUNT</em>,IntervalProperty.<em>INTERVAL</em>);</p>
<p>PS:秒级窗口不精确,监控中的秒级数据来自于分钟级窗口,只有需要最近1秒的数据时才会使用秒级滑动窗口的值(详情可以看文首第二个链接)</p>
<h1 id="切入流程">切入流程</h1>
<p>(以SpringBoot版本为例)</p>
<h2 id="通过aop机制织入">通过AOP机制织入</h2>
<p>com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SentinelResourceAspect</span> <span class="keyword">extends</span> <span class="title">AbstractSentinelAspectSupport</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Pointcut</span>(<span class="string">"@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sentinelResourceAnnotationPointcut</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Around</span>(<span class="string">"sentinelResourceAnnotationPointcut()"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invokeResourceWithSentinel</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//获取方法</span></span><br><span class="line"> Method originMethod = resolveMethod(pjp);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取注解</span></span><br><span class="line"> SentinelResource annotation = originMethod.getAnnotation(SentinelResource<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> <span class="keyword">if</span> (annotation == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// Should not go through here.</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Wrong state for SentinelResource annotation"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//获取资源名称</span></span><br><span class="line"> String resourceName = getResourceName(annotation.value(), originMethod);</span><br><span class="line"> <span class="comment">//获取类型(OUT or IN)</span></span><br><span class="line"> EntryType entryType = annotation.entryType();</span><br><span class="line"> Entry entry = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//核心部分,入口</span></span><br><span class="line"> entry = SphU.entry(resourceName, entryType, <span class="number">1</span>, pjp.getArgs());</span><br><span class="line"> <span class="comment">//通过,允许调用</span></span><br><span class="line"> Object result = pjp.proceed();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException ex) {</span><br><span class="line"> <span class="comment">//不通过,不允许调用</span></span><br><span class="line"> <span class="keyword">return</span> handleBlockException(pjp, annotation, ex);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable ex) {</span><br><span class="line"> Tracer.trace(ex);</span><br><span class="line"> <span class="keyword">throw</span> ex;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (entry != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//退出入口</span></span><br><span class="line"> entry.exit();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="entry">Entry</h2>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/image2.png" alt="" /><figcaption>LearningGp</figcaption>
</figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Entry <span class="title">entryWithPriority</span><span class="params">(ResourceWrapper resourceWrapper, <span class="keyword">int</span> count, <span class="keyword">boolean</span> prioritized, Object... args)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> BlockException </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//获取ThreadLocal中已经存在的context</span></span><br><span class="line"> Context context = ContextUtil.getContext();</span><br><span class="line"> <span class="keyword">if</span> (context <span class="keyword">instanceof</span> NullContext) {</span><br><span class="line"> <span class="comment">// The {@link NullContext} indicates that the amount of context has exceeded the threshold,</span></span><br><span class="line"> <span class="comment">// so here init the entry only. No rule checking will be done.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> CtEntry(resourceWrapper, <span class="keyword">null</span>, context);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (context == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// Using default context.</span></span><br><span class="line"> context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Global switch is close, no rule checking will do.</span></span><br><span class="line"> <span class="keyword">if</span> (!Constants.ON) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> CtEntry(resourceWrapper, <span class="keyword">null</span>, context);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//通过SPI机制加载处理链</span></span><br><span class="line"> ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},</span></span><br><span class="line"><span class="comment"> * so no rule checking will be done.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (chain == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> CtEntry(resourceWrapper, <span class="keyword">null</span>, context);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//构造Entry实例</span></span><br><span class="line"> Entry e = <span class="keyword">new</span> CtEntry(resourceWrapper, chain, context);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> /进入处理链</span><br><span class="line"> chain.entry(context, resourceWrapper, <span class="keyword">null</span>, count, prioritized, args);</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException e1) {</span><br><span class="line"> e.exit(count, args);</span><br><span class="line"> <span class="keyword">throw</span> e1;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e1) {</span><br><span class="line"> <span class="comment">// This should not happen, unless there are errors existing in Sentinel internal.</span></span><br><span class="line"> RecordLog.info(<span class="string">"Sentinel unexpected exception"</span>, e1);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>之后在处理链指针不为空的情况下按序调用进入方法并返回</p>
<h1 id="核心类-1">核心类</h1>
<h2 id="context">Context</h2>
<p>Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context 名称即为调用链路入口名称。</p>
<p>Context 通过 ThreadLocal 传递,只在调用链路的入口处创建。</p>
<p>Context 的字段说明:</p>
<ul>
<li>name:Context 的名称。</li>
<li>entranceNode:当前调用树的入口节点,类型为 EntranceNode。同一个入口的资源,每个资源对应一个 DefaultNode,entranceNode#childList 用于存储这些资源的 DefaultNode。</li>
<li>curEntry:当前 Entry(CtEntry)。</li>
<li>origin:调用来源的名称,即服务消费者的名称或者服务消费者的来源 IP,取决于服务消费者是否使用 Sentinel,由 Sentinel 适配层传递过来。例如:服务提供者是 Spring MVC 应用,且服务提供者使用 Sentinel 的 Web MVC 适配,那么 Sentinel 会尝试从请求头获取"S-user",如果服务消费者有在请求头传递这个参数,那么就能够获取到</li>
</ul>
<h2 id="entry-1">Entry</h2>
<p>在调用 Context#getCurNode 方法获取调用链路上当前访问到的资源的 DefaultNode 时,实际是从 Context#curEntry 获取的,Entry 维护了当前资源的 DefaultNode,以及调用来源的 StatisticNode。</p>
<p>CtEntry 是 Entry 的直接子类</p>
<p>CtEntry 用于维护父子 Entry,每一次调用 SphU#entry 方法都会创建一个 CtEntry。如果服务 B 在处理一个请求的路径上会多次调用 SphU#entry,那么这些 CtEntry 会构成一个双向链表。在每次创建 CtEntry,都会将 Context.curEntry 设置为这个新的 CtEntry,双向链表的作用就是在调用 CtEntry#exit 方法时,能够将 Context.curEntry 还原为上一个资源的 CtEntry。</p>
<h2 id="node">Node</h2>
<p>用于持有实时统计的指标数据</p>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/image1.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<p>Node实际上是数据节点,主要用于统计各种规则所需要的数据,如QPS,线程数等等。各个Node因为记录数据的不同有以下四个实现</p>
<h3 id="statisticnode">StatisticNode</h3>
<p>数据统计节点,核心数据是以下三个</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Holds statistics of the recent {<span class="doctag">@code </span>INTERVAL} seconds. The {<span class="doctag">@code </span>INTERVAL} is divided into time spans</span></span><br><span class="line"><span class="comment"> * by given {<span class="doctag">@code </span>sampleCount}.</span></span><br><span class="line"><span class="comment"> * 秒级滑动窗口,用于统计实时的 QPS,2 个时间窗口大小为 500 毫秒的 Bucket</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">private transient volatile Metric rollingCounterInSecond = <span class="keyword">new</span> ArrayMetric(SampleCountProperty.SAMPLE_COUNT,</span><br><span class="line"> IntervalProperty.INTERVAL);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,</span></span><br><span class="line"><span class="comment"> * meaning each bucket per second, in this way we can get accurate statistics of each second.</span></span><br><span class="line"><span class="comment"> * 分级滑动窗口,用于保存最近一分钟内的历史指标数据,60 个 Bucket 数组,每个 Bucket 统计的时间窗口大小为 1 秒</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">private transient Metric rollingCounterInMinute = <span class="keyword">new</span> ArrayMetric(<span class="number">60</span>, <span class="number">60</span> * <span class="number">1000</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The counter for thread count.</span></span><br><span class="line"><span class="comment"> * 并发线程数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">private LongAdder curThreadNum = <span class="keyword">new</span> LongAdder();</span><br></pre></td></tr></table></figure>
<p>滑动窗口使用场景</p>
<ul>
<li>获取前一秒被 Sentinel 拒绝的请求总数从分钟级滑动窗口获取</li>
<li>获取当前一秒内已经被 Sentinel 拒绝的请求总数则从秒级滑动窗口获取</li>
<li>获取最小耗时也是从秒级的滑动窗口获取</li>
</ul>
<p>StatisticNode 还负责统计并行占用的线程数,用于实现信号量隔离,按资源所能并发占用的最大线程数实现限流。通过控制并发线程数实现信号量隔离的好处就是不让一个接口同时使用完线程池所有线程</p>
<p>数据统计部分采用改进的滑动窗口的方式,时间窗口+Bucket,通过循环复用 Bucket 以减少 Bucket 的创建和销毁。在统计指标数据时,利用当前时间戳定位 Bucket,使用 LongAdder 统计时间窗口内的请求成功数、失败数、总耗时等指标数据优化了并发锁。Sentinel 通过定时任务递增时间戳以获取当前时间戳,避免了每次获取时间戳都使用 System 获取的性能消耗。//todo</p>
<h3 id="defaultnode">DefaultNode</h3>
<p>resource * context纬度数据统计节点,存在每个 NodeSelectorSlot 的map里</p>
<p>DefaultNode 字段说明:</p>
<ul>
<li>id:资源 ID,ResourceWrapper 对象。</li>
<li>childList:childList 是一个 Node(DefaultNode)集合,用于存放子节点。</li>
<li>clusterNode:clusterNode 字段是一个 ClusterNode,ClusterNode 也是 StatisticNode 的子类。</li>
</ul>
<h3 id="entrancenode">EntranceNode</h3>
<p>入口节点,名称相同的 Context 都使用同一个 EntranceNode</p>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/image3.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<h3 id="clusternode">ClusterNode</h3>
<p>统计每个资源全局的指标数据,以及统计该资源按调用来源区分的指标数据。全局数据指的是不区分调用链路,一个资源 ID 只对应一个 ClusterNode。</p>
<p>ClusterNode 字段说明:</p>
<ul>
<li>name:资源名称</li>
<li>resourceType:资源类型</li>
<li>originCountMap:维护每个调用来源的指标数据统计数据(StatisticNode)</li>
</ul>
<h2 id="root与调用树">Root与调用树</h2>
<p>Constants 常量类用于声明全局静态常量,Constants 有一个 ROOT 静态字段,类型为 EntranceNode。</p>
<p>在调用 ContextUtil#enter 方法时,如果还没有为当前入口创建 EntranceNode,则会为当前入口创建 EntranceNode,将其赋值给 Context.entranceNode,同时也会将这个 EntranceNode 添加到 Constants.ROOT 的子节点(childList)。资源对应的 DefaultNode 则是在 NodeSelectorSlot 中创建,并赋值给 Context.curEntry.curNode。</p>
<p>Constants.ROOT、Context.entranceNode 与 Entry.curNode 三者关系如下图所示。</p>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/imag6e.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<h2 id="slot">Slot</h2>
<p>处理链上的插槽,核心逻辑所在</p>
<p>整体处理基于责任链模式,通过spi机制按顺序加载处理链,所有ProcessorSlot都继承AbstractLinkedProcessorSlot 类,从而组成单向链表,调用 fireEntry 方法逐个前进或者 fireExit 方法逐个后退</p>
<p>主要分成两类,一类数据统计(NodeSelectorSlot->ClusterBuilderSlot->StatisticSlot(logslot辅助用,暂时放在这类)),一类实现降级以及block</p>
<h3 id="数据统计slot">数据统计Slot</h3>
<h4 id="nodeselectorslot">NodeSelectorSlot</h4>
<p>这个 slot 主要负责收集资源的路径,并将这些资源的调用路径以树状结构存储起来,用于根据调用路径进行流量控制。</p>
<ul>
<li>为当前资源创建 DefaultNode,并且将 DefaultNode 赋值给 Context.curEntry.curNode</li>
<li>如果当前调用链路上只出现过一次 SphU#entry 的情况,将该 DefaultNode 添加到的 Context.entranceNode 的子节点,否则添加到 Context.curEntry.parent 的子节点(通过com.alibaba.csp.sentinel.context.Context#getLastNode实现)</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">firstLevel</span><span class="params">()</span></span>{</span><br><span class="line"> ContextUtil.enter(<span class="string">"level-test"</span>,<span class="string">"application"</span>);</span><br><span class="line"> <span class="keyword">try</span> (Entry entry = SphU.entry(<span class="string">"firstLevel"</span>)){</span><br><span class="line"> System.out.println(<span class="string">"firstLevel"</span>);</span><br><span class="line"> secondLevel();</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"firstLevel"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">secondLevel</span><span class="params">()</span></span>{</span><br><span class="line"> ContextUtil.enter(<span class="string">"level-test"</span>,<span class="string">"application"</span>);</span><br><span class="line"> <span class="keyword">try</span> (Entry entry = SphU.entry(<span class="string">"secondLevel"</span>)){</span><br><span class="line"> System.out.println(<span class="string">"secondLevel"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"secondLevel"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//此时调用firstLevel方法会形成如下调用树</span></span><br><span class="line"></span><br><span class="line"> ROOT (machine-root)</span><br><span class="line"> /</span><br><span class="line"> EntranceNode (context name: level-test)</span><br><span class="line"> / </span><br><span class="line"> DefaultNode (resource name:firstLevel) </span><br><span class="line"> / </span><br><span class="line">DefaultNode (resource name:firstLevel)</span><br></pre></td></tr></table></figure>
<h4 id="clusterbuilderslot">ClusterBuilderSlot</h4>
<p>这一环的主要作用是构建 ClusterNode,为资源纬度的统计节点</p>
<ul>
<li>如果当前资源未创建 ClusterNode,则为资源创建 ClusterNode;</li>
<li>将 ClusterNode 赋值给当前资源的 DefaultNode.clusterNode;</li>
<li>如果调用来源(origin)不为空,则为调用来源创建 StatisticNode,用于实现按调用来源统计资源的指标数据,ClusterNode 持有每个调用来源的 StatisticNode。</li>
</ul>
<p>ps:ClusterBuilderSlot持有非静态<code>private volatile ClusterNode clusterNode = null;</code>,因为一个资源只会创建一个 ProcessorSlotChain,意味着 ClusterBuilderSlot 也只会创建一个,那么让 ClusterBuilderSlot 持有该资源的 ClusterNode 就可以省去每次都从 Map 中获取的步骤</p>
<h4 id="logslot">LogSlot</h4>
<p>进入时直接fire,在后续节点抛出BlockException后在这里将相关日志记录</p>
<h4 id="statisticslot">StatisticSlot</h4>
<p>负责进行数据统计,也是先fire,在后续节点处理完之后,在这里对统计的数据进行记录</p>
<h5 id="entry-方法">entry 方法</h5>
<h6 id="请求正常通过">请求正常通过</h6>
<ul>
<li>当前资源并行占用的线程数增加 1、当前时间窗口被放行的请求总数加 1</li>
<li>如果调用来源不为空,也将调用来源的 StatisticNode 的当前并行占用线程数加 1、当前时间窗口被放行的请求数加 1</li>
<li>如果流量类型为 IN,则将资源全局唯一的 ClusterNode 的并行占用线程数、当前时间窗口被放行的请求数都增加 1</li>
<li>回调所有 ProcessorSlotEntryCallback#onPass 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">node.increaseThreadNum();</span><br><span class="line">node.addPassRequest(count);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (context.getCurEntry().getOriginNode() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// Add count for origin node.</span></span><br><span class="line"> context.getCurEntry().getOriginNode().increaseThreadNum();</span><br><span class="line"> context.getCurEntry().getOriginNode().addPassRequest(count);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (resourceWrapper.getEntryType() == EntryType.IN) {</span><br><span class="line"> <span class="comment">// Add count for global inbound entry node for global statistics.</span></span><br><span class="line"> Constants.ENTRY_NODE.increaseThreadNum();</span><br><span class="line"> Constants.ENTRY_NODE.addPassRequest(count);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Handle pass event with registered entry callback handlers.</span></span><br><span class="line"><span class="keyword">for</span> (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {</span><br><span class="line"> handler.onPass(context, resourceWrapper, node, count, args);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="捕获prioritywaitexception异常">捕获PriorityWaitException异常</h6>
<p>(特殊情况)</p>
<p>当捕获到 PriorityWaitException 异常时,说明当前请求已经被休眠了一会了,但请求还是允许通过的</p>
<ul>
<li>不需要为 DefaultNode 记录这个请求的指标数据</li>
<li>自增当前资源并行占用的线程数</li>
<li>为 ClusterNode 自增并行占用的线程数</li>
<li>回调所有 ProcessorSlotEntryCallback#onPass 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">node.increaseThreadNum();</span><br><span class="line"><span class="keyword">if</span> (context.getCurEntry().getOriginNode() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// Add count for origin node.</span></span><br><span class="line"> context.getCurEntry().getOriginNode().increaseThreadNum();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (resourceWrapper.getEntryType() == EntryType.IN) {</span><br><span class="line"> <span class="comment">// Add count for global inbound entry node for global statistics.</span></span><br><span class="line"> Constants.ENTRY_NODE.increaseThreadNum();</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Handle pass event with registered entry callback handlers.</span></span><br><span class="line"><span class="keyword">for</span> (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {</span><br><span class="line"> handler.onPass(context, resourceWrapper, node, count, args);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="捕获到-blockexception-异常">捕获到 BlockException 异常</h6>
<ul>
<li>将异常记录到调用链路上下文的当前 Entry(StatisticSlot 的 exit 方法会用到)</li>
<li>调用 DefaultNode#increaseBlockQps 方法记录当前请求被拒绝</li>
<li>将当前时间窗口的 block qps 这项指标数据的值加 1</li>
<li>如果调用来源不为空,让调用来源的 StatisticsNode 也记录当前请求被拒绝</li>
<li>如果流量类型为 IN,则让用于统计所有资源指标数据的 ClusterNode 也记录当前请求被拒绝</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Blocked, set block exception to current entry.</span></span><br><span class="line">context.getCurEntry().setBlockError(e);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add block count.</span></span><br><span class="line">node.increaseBlockQps(count);</span><br><span class="line"><span class="keyword">if</span> (context.getCurEntry().getOriginNode() != <span class="keyword">null</span>) {</span><br><span class="line"> context.getCurEntry().getOriginNode().increaseBlockQps(count);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (resourceWrapper.getEntryType() == EntryType.IN) {</span><br><span class="line"> <span class="comment">// Add count for global inbound entry node for global statistics.</span></span><br><span class="line"> Constants.ENTRY_NODE.increaseBlockQps(count);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Handle block event with registered entry callback handlers.</span></span><br><span class="line"><span class="keyword">for</span> (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {</span><br><span class="line"> handler.onBlocked(e, context, resourceWrapper, node, count, args);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">throw</span> e;</span><br></pre></td></tr></table></figure>
<h6 id="捕获到其他异常">捕获到其他异常</h6>
<ul>
<li>让 DefaultNode 记录当前请求异常</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Unexpected internal error, set error to current entry.</span></span><br><span class="line">context.getCurEntry().setError(e);</span><br><span class="line"></span><br><span class="line"><span class="keyword">throw</span> e;</span><br></pre></td></tr></table></figure>
<h5 id="exit-方法">exit 方法</h5>
<p>由于StatisticSlot 在捕获异常时将异常记录到当前 Entry,exit 方法中通过 Context 可获取到当前 CtEntry,从当前 CtEntry 可获取 entry 方法中写入的异常,从而得知请求的具体状况,完成相应操作</p>
<ul>
<li>计算耗时</li>
<li>记录执行耗时与成功总数</li>
<li>自减当前资源占用线程数</li>
<li>来源不为空,减少来源的线程数</li>
<li>流量为In,让用于统计所有资源指标数据的 ClusterNode 也记录相关信息</li>
<li>调用回调方法</li>
</ul>
<p>PS:在 DefaultNode 的相关指标数据收集方法被调用时,ClusterNode 的对应方法也会被调用</p>
<h3 id="限流降级以及流控slot">限流降级以及流控Slot</h3>
<h4 id="authorityslot">AuthoritySlot</h4>
<p>权限控制,根据 <strong>origin</strong> 做黑白名单的控制</p>
<h4 id="systemslot">SystemSlot</h4>
<p>实现自适应限流(针对全局入口流量)<br />
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem</p>
<p>主要比较参数有successQps,curThreadNum,avgRt,<em>highestSystemLoad,highestCpuUsage</em></p>
<p>通过起一个后台线程(SystemMetricCollectorTask),每秒查询一次系统负载和CPU使用负载</p>
<p>第一版基于TCP BBR算法(结合自动化控制理论优化中PID controller)</p>
<p>第二版将系统指标(load/CPU usage)作为一个启动条件</p>
<p>com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">checkSystem</span><span class="params">(ResourceWrapper resourceWrapper)</span> <span class="keyword">throws</span> BlockException </span>{</span><br><span class="line"> <span class="keyword">if</span> (resourceWrapper == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Ensure the checking switch is on.</span></span><br><span class="line"> <span class="keyword">if</span> (!checkSystemStatus.get()) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// for inbound traffic only</span></span><br><span class="line"> <span class="keyword">if</span> (resourceWrapper.getEntryType() != EntryType.IN) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// total qps</span></span><br><span class="line"> <span class="keyword">double</span> currentQps = Constants.ENTRY_NODE == <span class="keyword">null</span> ? <span class="number">0.0</span> : Constants.ENTRY_NODE.successQps();</span><br><span class="line"> <span class="keyword">if</span> (currentQps > qps) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemBlockException(resourceWrapper.getName(), <span class="string">"qps"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// total thread</span></span><br><span class="line"> <span class="keyword">int</span> currentThread = Constants.ENTRY_NODE == <span class="keyword">null</span> ? <span class="number">0</span> : Constants.ENTRY_NODE.curThreadNum();</span><br><span class="line"> <span class="keyword">if</span> (currentThread > maxThread) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemBlockException(resourceWrapper.getName(), <span class="string">"thread"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">double</span> rt = Constants.ENTRY_NODE == <span class="keyword">null</span> ? <span class="number">0</span> : Constants.ENTRY_NODE.avgRt();</span><br><span class="line"> <span class="keyword">if</span> (rt > maxRt) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemBlockException(resourceWrapper.getName(), <span class="string">"rt"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// load. BBR algorithm.</span></span><br><span class="line"> <span class="keyword">if</span> (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {</span><br><span class="line"> <span class="keyword">if</span> (!checkBbr(currentThread)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemBlockException(resourceWrapper.getName(), <span class="string">"load"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// cpu usage</span></span><br><span class="line"> <span class="keyword">if</span> (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemBlockException(resourceWrapper.getName(), <span class="string">"cpu"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="flowslot">FlowSlot</h4>
<p>负责流控功能的实现</p>
<p>由 ProcessorSlot、Checker、Rule、RuleManager 组合完成,ProcessorSlot作为入口,并持有对应的Checker,Checker根据Rule进行检查,RuleManager管理Rule</p>
<p>主要步骤如下</p>
<ol type="1">
<li>在 ProcessorSlot#entry 方法中调用 Checker#check 方法,并将 DefaultNode 传递给 Checker。</li>
<li>Checker 根据资源名称从 RuleManager 获取为该资源配置的规则。</li>
<li>Checker 从 传入context,以及node中根据rule和策略获取需要的node
<ol start="4" type="1">
<li>如果当前限流规则的 limitApp 不为 default,该限流规则只针对指定调用来源限流。当调用来源与当前限流规则的 limitApp 相等时:
<ol start="5" type="1">
<li>strategy 为 STRATEGY_DIRECT,则使用调用来源的 StatisticNode,实现针对调用来源限流。</li>
<li>strategy 为 STRATEGY_RELATE:根据限流规则配置的 refResource 获取引用资源的 ClusterNode,即使用引用资源的指标数据限流。通俗点说就是使用其它资源的指标数据限流,你的并发量高我就限流,让你多处理一点请求,等你并发量降低了,我就不限流了;</li>
<li>strategy 为 STRATEGY_CHAIN:使用当前资源的 DefauleNode,实现按调用链路的资源指标数据限流。</li>
</ol></li>
<li>当 limitApp 为 default 时,针对所有来源限流。
<ol start="9" type="1">
<li>strategy 为 STRATEGY_DIRECT,则使用当前资源的 ClusterNode。</li>
<li>strategy 为 STRATEGY_RELATE:使用引用资源的 ClusterNode;</li>
<li>strategy 为 STRATEGY_CHAIN:使用当前资源的 DefauleNode。</li>
</ol></li>
<li>如果 limitApp 为 other,且该资源的所有限流规则都没有针对当前的调用来源限流。
<ol start="12" type="1">
<li>如果 strategy 为 STRATEGY_DIRECT,则使用 origin 的 StatisticNode。</li>
</ol></li>
<li>strategy 为 STRATEGY_RELATE:使用引用资源的 ClusterNode</li>
<li>strategy 为 STRATEGY_CHAIN:使用当前资源的 DefauleNode</li>
</ol></li>
<li>根据配置调用对应的controller的canPass方法</li>
<li>从node中获取当前时间窗口的某项指标数据(QPS、avgRt 等)与规则的阈值对比,如果达到规则的阈值则抛出 Block 异常。</li>
</ol>
<h5 id="trafficshapingcontroller">TrafficShapingController</h5>
<p>实现流量塑形的controller主要有以下几种</p>
<h6 id="defaultcontroller">DefaultController</h6>
<p>实现快速失败</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">canPass</span><span class="params">(Node node, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="comment">//如果当前规则的限流阈值类型为 QPS,则返回node当前时间窗口统计的QPS;</span></span><br><span class="line"> <span class="comment">//如果当前规则的限流阈值类型为 THREADS,则返回node统计的当前并行占用的线程数。</span></span><br><span class="line"> <span class="keyword">int</span> curCount = avgUsedTokens(node);</span><br><span class="line"> <span class="comment">//判断加上acquireCount是否超过阈值,超过阈值则拒绝(prioritized除外)</span></span><br><span class="line"> <span class="keyword">if</span> (curCount + acquireCount > count) {</span><br><span class="line"> <span class="comment">//如果限流阈值类型为 QPS且为优先流量,可以占用之后的流量(并非直接执行,需要等待对应时间)</span></span><br><span class="line"> <span class="keyword">if</span> (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {</span><br><span class="line"> <span class="keyword">long</span> currentTime;</span><br><span class="line"> <span class="keyword">long</span> waitInMs;</span><br><span class="line"> currentTime = TimeUtil.currentTimeMillis();</span><br><span class="line"> <span class="comment">//计算占用之后流量后需要等待的时间(只允许占用1秒)</span></span><br><span class="line"> waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);</span><br><span class="line"> <span class="comment">//判断是否超过等待最长时间</span></span><br><span class="line"> <span class="keyword">if</span> (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {</span><br><span class="line"> <span class="comment">//允许占用</span></span><br><span class="line"> node.addWaitingRequest(currentTime + waitInMs, acquireCount);</span><br><span class="line"> node.addOccupiedPass(acquireCount);</span><br><span class="line"> sleep(waitInMs);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> PriorityWaitException(waitInMs);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">tryOccupyNext</span><span class="params">(<span class="keyword">long</span> currentTime, <span class="keyword">int</span> acquireCount, <span class="keyword">double</span> threshold)</span> </span>{</span><br><span class="line"> <span class="comment">//最多可占用令牌数(1s产生的令牌)</span></span><br><span class="line"> <span class="keyword">double</span> maxCount = threshold * IntervalProperty.INTERVAL / <span class="number">1000</span>;</span><br><span class="line"> <span class="comment">//当前秒级窗口已经被占用令牌数</span></span><br><span class="line"> <span class="keyword">long</span> currentBorrow = rollingCounterInSecond.waiting();</span><br><span class="line"> <span class="keyword">if</span> (currentBorrow >= maxCount) {</span><br><span class="line"> <span class="keyword">return</span> OccupyTimeoutProperty.getOccupyTimeout();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//样本窗口长度</span></span><br><span class="line"> <span class="keyword">int</span> windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT;</span><br><span class="line"> <span class="comment">//currentTime - currentTime % windowLength 当前时间所在样本窗口开始时间</span></span><br><span class="line"> <span class="comment">//当前窗口开始时间,也是要占用的第一个样本窗口的开始时间</span></span><br><span class="line"> <span class="keyword">long</span> earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> idx = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Note: here {@code currentPass} may be less than it really is NOW, because time difference</span></span><br><span class="line"><span class="comment"> * since call rollingCounterInSecond.pass(). So in high concurrency, the following code may</span></span><br><span class="line"><span class="comment"> * lead more tokens be borrowed.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="comment">//当前秒级窗口通过的请求数</span></span><br><span class="line"> <span class="keyword">long</span> currentPass = rollingCounterInSecond.pass();</span><br><span class="line"> <span class="keyword">while</span> (earliestTime < currentTime) {</span><br><span class="line"> <span class="comment">//计算等待时间,等待到当前窗口结束(因为一个样本窗口可以存在于多个未来窗口中,当idx为0时,当前样本窗口为当前未来窗口的最后一个样本窗口,为1时当前样本窗口为当前未来窗口的倒数第二个样本窗口)</span></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> *|--1--|--2--|--3--|</span></span><br><span class="line"><span class="comment"> *idx=0 当前样本窗口为3,等待到结束</span></span><br><span class="line"><span class="comment"> *idx=1 当前样本窗口为2,同样等待到结束,比idx=1时多一个样本窗口时间长度</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">long</span> waitInMs = idx * windowLength + windowLength - currentTime % windowLength;</span><br><span class="line"> <span class="comment">//超时退出</span></span><br><span class="line"> <span class="keyword">if</span> (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//目前所在未来窗口的通过数</span></span><br><span class="line"> <span class="keyword">long</span> windowPass = rollingCounterInSecond.getWindowPass(earliestTime);</span><br><span class="line"> <span class="comment">//当前窗口通过数+当前窗口被占用数+需要的令牌数-目前所在未来窗口的通过数(重复计算所以减去)<=最多可占用令牌数</span></span><br><span class="line"> <span class="keyword">if</span> (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) {</span><br><span class="line"> <span class="keyword">return</span> waitInMs;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//占用窗口后移</span></span><br><span class="line"> earliestTime += windowLength;</span><br><span class="line"> currentPass -= windowPass;</span><br><span class="line"> idx++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> OccupyTimeoutProperty.getOccupyTimeout();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="ratelimitercontroller">RateLimiterController</h6>
<p>实现排队等待</p>
<blockquote>
<p>设置 QPS 为 10,那么每 100 毫秒允许通过一个,通过计算当前时间是否已经过了上一个请求的通过时间 latestPassedTime 之后的 100 毫秒,来判断是否可以通过。假设才过了 50ms,那么需要当前线程再 sleep 50ms,然后才可以通过。如果同时有另一个请求呢?那需要 sleep 150ms 才行。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">canPass</span><span class="params">(Node node, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="comment">// Pass when acquire count is less or equal than 0.</span></span><br><span class="line"> <span class="keyword">if</span> (acquireCount <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Reject when count is less or equal than 0.</span></span><br><span class="line"> <span class="comment">// Otherwise,the costTime will be max of long and waitTime will overflow in some cases.</span></span><br><span class="line"> <span class="keyword">if</span> (count <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">long</span> currentTime = TimeUtil.currentTimeMillis();</span><br><span class="line"> <span class="comment">// Calculate the interval between every two requests.</span></span><br><span class="line"> <span class="keyword">long</span> costTime = Math.round(<span class="number">1.0</span> * (acquireCount) / count * <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Expected pass time of this request.</span></span><br><span class="line"> <span class="keyword">long</span> expectedTime = costTime + latestPassedTime.get();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (expectedTime <= currentTime) {</span><br><span class="line"> <span class="comment">// Contention may exist here, but it's okay.</span></span><br><span class="line"> latestPassedTime.set(currentTime);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Calculate the time to wait.</span></span><br><span class="line"> <span class="keyword">long</span> waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();</span><br><span class="line"> <span class="keyword">if</span> (waitTime > maxQueueingTimeMs) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">long</span> oldTime = latestPassedTime.addAndGet(costTime);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> waitTime = oldTime - TimeUtil.currentTimeMillis();</span><br><span class="line"> <span class="keyword">if</span> (waitTime > maxQueueingTimeMs) {</span><br><span class="line"> latestPassedTime.addAndGet(-costTime);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// in race condition waitTime may <= 0</span></span><br><span class="line"> <span class="keyword">if</span> (waitTime > <span class="number">0</span>) {</span><br><span class="line"> Thread.sleep(waitTime);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h6 id="warmupcontroller">WarmUpController</h6>
<p>实现Warm Up</p>
<p>Warm Up,冷启动。在应用升级重启时或长时间低压力之后,应用自身需要一个预热的过程,预热之后才能到达一个稳定的性能状态,比如说,接口预热阶段完成 JIT 即时编译、完成一些单例对象的创建、线程池的创建、各种连接池的初始化、或者执行首次需要加锁执行的代码块。核心算法借鉴了Guava中SmoothWarmingUp的实现,详细分析见<br />
<a href="https://www.javadoop.com/post/rate-limiter" target="_blank" rel="noopener">https://www.javadoop.com/post/rate-limiter</a><br />
<a href="https://www.jianshu.com/p/280bf2dbd6f0" target="_blank" rel="noopener">https://www.jianshu.com/p/280bf2dbd6f0</a>(推荐这篇)</p>
<p>在构造函数中通过double count, int warmUpPeriodInSec, int coldFactor三个参数,计算出warningToken,maxToken,slope</p>
<p>但是由于关注的qps所以对一些变量进行了替换</p>
<ul>
<li>warmupPeriod=warmUpPeriodInSec</li>
<li>stableInterval=1/count</li>
<li>maxPermits=maxToken</li>
<li>thresholdPermits=warningToken</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">construct</span><span class="params">(<span class="keyword">double</span> count, <span class="keyword">int</span> warmUpPeriodInSec, <span class="keyword">int</span> coldFactor)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (coldFactor <= <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Cold factor should be larger than 1"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.count = count;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.coldFactor = coldFactor;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// thresholdPermits = 0.5 * warmupPeriod / stableInterval.</span></span><br><span class="line"> <span class="comment">// warningToken = 100;</span></span><br><span class="line"> warningToken = (<span class="keyword">int</span>)(warmUpPeriodInSec * count) / (coldFactor - <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// / maxPermits = thresholdPermits + 2 * warmupPeriod /</span></span><br><span class="line"> <span class="comment">// (stableInterval + coldInterval)</span></span><br><span class="line"> <span class="comment">// maxToken = 200</span></span><br><span class="line"> maxToken = warningToken + (<span class="keyword">int</span>)(<span class="number">2</span> * warmUpPeriodInSec * count / (<span class="number">1.0</span> + coldFactor));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// slope</span></span><br><span class="line"> <span class="comment">// slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits</span></span><br><span class="line"> <span class="comment">// - thresholdPermits);</span></span><br><span class="line"> slope = (coldFactor - <span class="number">1.0</span>) / count / (maxToken - warningToken);</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>通过判断计算该点对应的qps阈值,进行判断</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">canPass</span><span class="params">(Node node, <span class="keyword">int</span> acquireCount, <span class="keyword">boolean</span> prioritized)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> passQps = (<span class="keyword">long</span>) node.passQps();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">long</span> previousQps = (<span class="keyword">long</span>) node.previousPassQps();</span><br><span class="line"> <span class="comment">//设置 storedTokens 和 lastFilledTime 到正确的值,重新装填令牌桶</span></span><br><span class="line"> syncToken(previousQps);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 开始计算它的斜率</span></span><br><span class="line"> <span class="comment">// 如果进入了警戒线,开始调整他的qps</span></span><br><span class="line"> <span class="keyword">long</span> restToken = storedTokens.get();</span><br><span class="line"> <span class="keyword">if</span> (restToken >= warningToken) {</span><br><span class="line"> <span class="keyword">long</span> aboveToken = restToken - warningToken;</span><br><span class="line"> <span class="comment">// 消耗的速度要比warning快</span></span><br><span class="line"> <span class="comment">// current interval = restToken*slope+1/count</span></span><br><span class="line"> <span class="keyword">double</span> warningQps = Math.nextUp(<span class="number">1.0</span> / (aboveToken * slope + <span class="number">1.0</span> / count));</span><br><span class="line"> <span class="keyword">if</span> (passQps + acquireCount <= warningQps) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (passQps + acquireCount <= count) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">syncToken</span><span class="params">(<span class="keyword">long</span> passQps)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> currentTime = TimeUtil.currentTimeMillis();</span><br><span class="line"> currentTime = currentTime - currentTime % <span class="number">1000</span>;</span><br><span class="line"> <span class="keyword">long</span> oldLastFillTime = lastFilledTime.get();</span><br><span class="line"> <span class="keyword">if</span> (currentTime <= oldLastFillTime) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">long</span> oldValue = storedTokens.get();</span><br><span class="line"> <span class="comment">//计算新的令牌数量</span></span><br><span class="line"> <span class="keyword">long</span> newValue = coolDownTokens(currentTime, passQps);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (storedTokens.compareAndSet(oldValue, newValue)) {</span><br><span class="line"> <span class="keyword">long</span> currentValue = storedTokens.addAndGet(<span class="number">0</span> - passQps);</span><br><span class="line"> <span class="keyword">if</span> (currentValue < <span class="number">0</span>) {</span><br><span class="line"> storedTokens.set(<span class="number">0L</span>);</span><br><span class="line"> }</span><br><span class="line"> lastFilledTime.set(currentTime);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">long</span> <span class="title">coolDownTokens</span><span class="params">(<span class="keyword">long</span> currentTime, <span class="keyword">long</span> passQps)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> oldValue = storedTokens.get();</span><br><span class="line"> <span class="keyword">long</span> newValue = oldValue;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加令牌的判断前提条件:</span></span><br><span class="line"> <span class="comment">// 当令牌的消耗程度远远低于警戒线的时候</span></span><br><span class="line"> <span class="keyword">if</span> (oldValue < warningToken) {</span><br><span class="line"> <span class="comment">//按照每秒count个的速度掉落</span></span><br><span class="line"> newValue = (<span class="keyword">long</span>)(oldValue + (currentTime - lastFilledTime.get()) * count / <span class="number">1000</span>);</span><br><span class="line"> <span class="comment">//令牌消耗速率大于警戒线</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (oldValue > warningToken) {</span><br><span class="line"> <span class="comment">//前一个bucket通过的qps小于阈值/ coldFactor,说明系统消耗令牌的速度,小于冷却速度,需要添加令牌</span></span><br><span class="line"> <span class="keyword">if</span> (passQps < (<span class="keyword">int</span>)count / coldFactor) {</span><br><span class="line"> newValue = (<span class="keyword">long</span>)(oldValue + (currentTime - lastFilledTime.get()) * count / <span class="number">1000</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Math.min(newValue, maxToken);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="warmupratelimitercontroller">WarmUpRateLimiterController</h6>
<p>RateLimiterController与WarmUpController的结合</p>
<p>通过WarmUpController中一样的算法计算出当前的qps阈值,再用该阈值去计算等待时间</p>
<h4 id="degradeslot">DegradeSlot</h4>
<p>实现熔断降级的切入点,功能由ProcessorSlot、CircuitBreaker、DegradeRule、DegradeRuleManager组合完成。且经过一次升级,通过状态间的自动转换避免了原版本使用定时器的缺点。</p>
<h5 id="熔断策略">熔断策略</h5>
<ul>
<li>SLOW_REQUEST_RATIO:按慢请求比率</li>
<li>ERROR_RATIO:按失败比率</li>
<li>ERROR_COUNT:按失败次数</li>
</ul>
<p>ExceptionCircuitBreaker实现根据异常比例熔断</p>
<p>ResponseTimeCircuitBreaker实现根据RT时间熔断</p>
<p>slot中通过调用两者的onRequestComplete方法统计并且判断是否熔断的逻辑</p>
<h5 id="熔断器状态">熔断器状态</h5>
<ul>
<li>当熔断器状态为半开启状态时,直接拒绝请求;</li>
<li>当熔断器为关闭状态时,请求被允许通过;</li>
<li>当熔断器状态为开启状态时,根据 timeWindow 尝试将开关状态改为半闭合,如何修改成功,则允许当前请求通过。</li>
</ul>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/ima8ge.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<h5 id="详细分析">详细分析</h5>
<p>这一块的实现,内部版本与开源版本略有不同,这边的分析以开源版本为例。</p>
<p>核心方法如下</p>
<p><strong>AbstractCircuitBreaker#tryPass</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">tryPass</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> <span class="comment">// Template implementation.</span></span><br><span class="line"> <span class="keyword">if</span> (currentState.get() == State.CLOSED) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (currentState.get() == State.OPEN) {</span><br><span class="line"> <span class="comment">// For half-open state we allow a request for probing.</span></span><br><span class="line"> <span class="comment">//熔断器开启状态,判断是否到达可以重试时间并尝试将状态改到半开启</span></span><br><span class="line"> <span class="keyword">return</span> retryTimeoutArrived() && fromOpenToHalfOpen(context);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>AbstractCircuitBreaker#状态转换</strong></p>
<p>基本都是使用CAS机制更改状态并且调用监听</p>
<p>下面展示的是解决一个bug的临时方案</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">fromOpenToHalfOpen</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {</span><br><span class="line"> notifyObservers(State.OPEN, State.HALF_OPEN, <span class="keyword">null</span>);</span><br><span class="line"> Entry entry = context.getCurEntry();</span><br><span class="line"> entry.whenTerminate(<span class="keyword">new</span> BiConsumer<Context, Entry>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">accept</span><span class="params">(Context context, Entry entry)</span> </span>{</span><br><span class="line"> <span class="comment">// Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638</span></span><br><span class="line"> <span class="comment">// Without the hook, the circuit breaker won't recover from half-open state in some circumstances</span></span><br><span class="line"> <span class="comment">// when the request is actually blocked by upcoming rules (not only degrade rules).</span></span><br><span class="line"> <span class="comment">//为了解决注释中提到的问题,在发生异常后将状态改回open,以防止永久处于half-open状态的bug</span></span><br><span class="line"> <span class="keyword">if</span> (entry.getBlockError() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// Fallback to OPEN due to detecting request is blocked</span></span><br><span class="line"> currentState.compareAndSet(State.HALF_OPEN, State.OPEN);</span><br><span class="line"> notifyObservers(State.HALF_OPEN, State.OPEN, <span class="number">1.0</span>d);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>ExceptionCircuitBreaker#onRequestComplete</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onRequestComplete</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> Entry entry = context.getCurEntry();</span><br><span class="line"> <span class="keyword">if</span> (entry == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> Throwable error = entry.getError();</span><br><span class="line"> SimpleErrorCounter counter = stat.currentWindow().value();</span><br><span class="line"> <span class="keyword">if</span> (error != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//出现异常,增加异常计数器</span></span><br><span class="line"> counter.getErrorCount().add(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//增加总计数器</span></span><br><span class="line"> counter.getTotalCount().add(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> handleStateChangeWhenThresholdExceeded(error);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">handleStateChangeWhenThresholdExceeded</span><span class="params">(Throwable error)</span> </span>{</span><br><span class="line"> <span class="comment">//熔断器打开直接返回</span></span><br><span class="line"> <span class="keyword">if</span> (currentState.get() == State.OPEN) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//熔断器半开,本次请求如果无异常,关闭熔断器,否则重新打开熔断器</span></span><br><span class="line"> <span class="keyword">if</span> (currentState.get() == State.HALF_OPEN) {</span><br><span class="line"> <span class="comment">// In detecting request</span></span><br><span class="line"> <span class="keyword">if</span> (error == <span class="keyword">null</span>) {</span><br><span class="line"> fromHalfOpenToClose();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fromHalfOpenToOpen(<span class="number">1.0</span>d);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//熔断器关闭状态</span></span><br><span class="line"> List<SimpleErrorCounter> counters = stat.values();</span><br><span class="line"> <span class="keyword">long</span> errCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">long</span> totalCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (SimpleErrorCounter counter : counters) {</span><br><span class="line"> errCount += counter.errorCount.sum();</span><br><span class="line"> totalCount += counter.totalCount.sum();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//未达到最小请求数,返回</span></span><br><span class="line"> <span class="keyword">if</span> (totalCount < minRequestAmount) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">double</span> curCount = errCount;</span><br><span class="line"> <span class="comment">//计算异常比例</span></span><br><span class="line"> <span class="keyword">if</span> (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {</span><br><span class="line"> <span class="comment">// Use errorRatio</span></span><br><span class="line"> curCount = errCount * <span class="number">1.0</span>d / totalCount;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//异常比例大于阈值,熔断器打开</span></span><br><span class="line"> <span class="keyword">if</span> (curCount > threshold) {</span><br><span class="line"> transformToOpen(curCount);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>ResponseTimeCircuitBreaker#onRequestComplete</strong></p>
<p>整体类似上一个方法,不再赘述</p>
<h1 id="一些对应关系">一些对应关系</h1>
<p>一个context(context name为唯一标识,context跟着线程走,切换线程需要手动切换)</p>
<ul>
<li>对应 一个调用链路 (默认值为sentinel_default_context)</li>
<li>对应一个 entrancenode(在Context.enter时创建)</li>
</ul>
<p>一个resource (resource name为唯一标识)</p>
<ul>
<li>对应一个责任链实例(共享同一个 NodeSelectorSlot 实例以及 ClusterBuilderSlot实例)</li>
<li>对应一个ClusterNode(不同origin会在该ClusterNode下挂不同的数据节点)</li>
</ul>
<p>一个DefaultNode(维持调用树状结构)</p>
<ul>
<li>对应一个context下的一个resource,即它的纬度是context*resource,存在NodeSelectorSlot的map中(key为context name)</li>
</ul>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/imag9e.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<figure>
<img src="/2021/07/04/Sentinel学习笔记-sentinel-cor/image9.png" alt="" /><figcaption>来源见参考链接</figcaption>
</figure>
<h1 id="举例">举例</h1>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//规则</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">initFlowRules</span><span class="params">()</span></span>{</span><br><span class="line"> List<FlowRule> rules = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"></span><br><span class="line"> FlowRule rule = <span class="keyword">new</span> FlowRule();</span><br><span class="line"> rule.setResource(<span class="string">"helloAnother"</span>);</span><br><span class="line"> rule.setGrade(RuleConstant.FLOW_GRADE_QPS);</span><br><span class="line"> <span class="comment">// Set limit QPS to 20.</span></span><br><span class="line"> rule.setCount(<span class="number">20</span>);</span><br><span class="line"></span><br><span class="line"> FlowRule rule2 = <span class="keyword">new</span> FlowRule();</span><br><span class="line"> rule2.setResource(<span class="string">"hello"</span>);</span><br><span class="line"> rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);</span><br><span class="line"> <span class="comment">// Set limit QPS to 2.</span></span><br><span class="line"> rule2.setCount(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> rules.add(rule);</span><br><span class="line"> rules.add(rule2);</span><br><span class="line"> FlowRuleManager.loadRules(rules);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//资源hello</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@SentinelResource</span>(value = <span class="string">"hello"</span>, fallback = <span class="string">"helloFallback"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(<span class="keyword">long</span> s)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (s < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"invalid arg"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> String.format(<span class="string">"Hello at %d"</span>, s);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//资源helloAnother</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@SentinelResource</span>(value = <span class="string">"helloAnother"</span>, defaultFallback = <span class="string">"defaultFallback"</span>,</span><br><span class="line"> exceptionsToIgnore = {IllegalStateException<span class="class">.<span class="keyword">class</span>})</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">String</span> <span class="title">helloAnother</span>(<span class="title">String</span> <span class="title">name</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (name == <span class="keyword">null</span> || <span class="string">"bad"</span>.equals(name)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"oops"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"foo"</span>.equals(name)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"oops"</span>);</span><br><span class="line"> }</span><br><span class="line"> String temp=testService.hello(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello, "</span> + name + <span class="string">" "</span> + temp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span> </span></span><br><span class="line"><span class="class"></span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> count=<span class="number">0</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> interval=<span class="number">0</span>;</span><br><span class="line"></span><br><span 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></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span> (args.length==<span class="number">0</span>){</span><br><span class="line"> interval=<span class="number">100</span>;</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> Float qps=Float.parseFloat(args[<span class="number">0</span>]);</span><br><span class="line"> interval=Math.round(<span class="number">1000</span>/qps);</span><br><span class="line"> }</span><br><span class="line"> doTest(interval);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">doTest</span><span class="params">(<span class="keyword">int</span> interval)</span></span>{</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>){</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(interval);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> firstLevel();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(e.getMessage() + <span class="string">" block"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">firstLevel</span><span class="params">()</span></span>{</span><br><span class="line"> ContextUtil.enter(<span class="string">"level-test"</span>,<span class="string">"application"</span>);</span><br><span class="line"> <span class="keyword">try</span> (Entry entry = SphU.entry(<span class="string">"firstLevel"</span>, EntryType.IN)){</span><br><span class="line"> <span class="keyword">if</span> (count%(interval*<span class="number">5</span>)==<span class="number">0</span>){</span><br><span class="line"> System.out.println(<span class="string">"firstLevel"</span>);</span><br><span class="line"> }</span><br><span class="line"> secondLevel();</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"firstLevel"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">secondLevel</span><span class="params">()</span></span>{</span><br><span class="line"> ContextUtil.enter(<span class="string">"level-test"</span>,<span class="string">"application"</span>);</span><br><span class="line"> <span class="keyword">try</span> (Entry entry = SphU.entry(<span class="string">"secondLevel"</span>,EntryType.OUT)){</span><br><span class="line"> <span class="keyword">if</span> (count%(interval*<span class="number">5</span>)==<span class="number">0</span>){</span><br><span class="line"> System.out.println(<span class="string">"secondLevel"</span>);</span><br><span class="line"> count=<span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> count++;</span><br><span class="line"> } <span class="keyword">catch</span> (BlockException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"secondLevel"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="参考链接">参考链接</h1>
<p><a href="https://github.com/sentinel-group/sentinel-awesome" target="_blank" rel="noopener">https://github.com/sentinel-group/sentinel-awesome</a></p>
<p><a href="https://www.javadoop.com/post/sentinel" target="_blank" rel="noopener">https://www.javadoop.com/post/sentinel</a></p>
<p><a href="http://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Sentinel%EF%BC%88%E5%AE%8C%EF%BC%89/01%20%E5%BC%80%E7%AF%87%E8%AF%8D%EF%BC%9A%E4%B8%80%E6%AC%A1%E6%9C%8D%E5%8A%A1%E9%9B%AA%E5%B4%A9%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E7%BB%8F%E5%8E%86.md" target="_blank" rel="noopener">http://learn.lianglianglee.com/</a></p>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>源码分析</tag>
<tag>Sentinel</tag>
</tags>
</entry>
<entry>
<title>并查集</title>
<url>/2021/01/13/%E5%B9%B6%E6%9F%A5%E9%9B%86/</url>
<content><![CDATA[<h1 id="概念">概念</h1>
<p>并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的 <strong>合并</strong> 及 <strong>查询</strong> 问题。 它支持两种操作:</p>
<ul>
<li>查找(Find):确定某个元素处于哪个子集;</li>
<li>合并(Union):将两个子集合并成一个集合。</li>
</ul>
<h1 id="结构">结构</h1>
<p>在理解上并查集是一个森林,但是在实际实现上位了方便将其简化成数组。</p>
<h2 id="理解上的结构">理解上的结构</h2>
<h3 id="结构-1">结构</h3>
<p>每棵树代表一个子集,根节点用来标记这个子集</p>
<h3 id="操作">操作</h3>
<ul>
<li>find:递归查找父节点,最终得到标记子集的根节点(祖先)</li>
<li>union:将两棵树合并(不必在意细节,因为实现时使用数组)</li>
</ul>
<p><img src="/2021/01/13/并查/1.png" /></p>
<h2 id="实际的实现">实际的实现</h2>
<h3 id="结构-2">结构</h3>
<p>我们使用一个parent数组来存储对应索引的父亲的索引</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//初始化代码,一开始每个节点一个子集,自己的父亲为自己</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">this</span>.parent[i] = i;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="操作-1">操作</h3>
<ul>
<li>find操作,递归查找父节点,最终得到标记子集的根节点(祖先)</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> x)</span> </span>{</span><br><span class="line"> <span class="comment">// 寻找x的祖先</span></span><br><span class="line"> <span class="keyword">if</span> (parent[x] == x) { <span class="comment">// 如果x是祖先则返回</span></span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> find(parent[x]); <span class="comment">// 如果不是则x的爸爸问x的爷爷</span></span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>union操作,合并子集</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//使两个集合拥有相同的祖先</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">union</span><span class="params">(<span class="keyword">int</span> x,<span class="keyword">int</span> y)</span></span>{</span><br><span class="line"> parent[find(x)]=find(y);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="优化">优化</h1>
<h2 id="路径压缩">路径压缩</h2>
<h3 id="概念-1">概念</h3>
<p>把每个节点都直接连接到根上 ,这就是路径压缩(把递归查找祖先的路径压缩)</p>
<p><img src="/2021/01/13/并查/2.png" /></p>
<h3 id="实现">实现</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> x)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (x != parent[x]) {</span><br><span class="line"> parent[x] = find(parent[x]); <span class="comment">//把寻找祖先路上经过的节点都压缩</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> parent[x];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="启发式合并按秩合并">启发式合并(按秩合并)</h2>
<h3 id="概念-2">概念</h3>
<p>简单来说在合并时将深度(或者节点个数)较小的合并子集到较大的子集中去</p>
<p>需要一个rank数组来粗略的记录深度(主要是为了方便,没必要精确记录深度)</p>
<h3 id="实现-1">实现</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//初始化rank数组</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">this</span>.rank[i] = <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">union</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> rootX = find(x);</span><br><span class="line"> <span class="keyword">int</span> rootY = find(y);</span><br><span class="line"> <span class="keyword">if</span> (rootX == rootY) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (rank[rootX] == rank[rootY]) {</span><br><span class="line"> parent[rootX] = rootY;</span><br><span class="line"> <span class="comment">// 此时以 rootY 为根结点的树的高度仅加了 1</span></span><br><span class="line"> rank[rootY]++;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (rank[rootX] < rank[rootY]) {</span><br><span class="line"> parent[rootX] = rootY;</span><br><span class="line"> <span class="comment">// 此时以 rootY 为根结点的树的高度不变</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 同理,此时以 rootX 为根结点的树的高度不变</span></span><br><span class="line"> parent[rootY] = rootX;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="总体实现">总体实现</h1>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">UnionFind</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] parent;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 以 i 为根结点的子树的高度(引入了路径压缩以后该定义并不准确)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] rank;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnionFind</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.parent = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> <span class="keyword">this</span>.rank = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">this</span>.parent[i] = i;</span><br><span class="line"> <span class="keyword">this</span>.rank[i] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">union</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> rootX = find(x);</span><br><span class="line"> <span class="keyword">int</span> rootY = find(y);</span><br><span class="line"> <span class="keyword">if</span> (rootX == rootY) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (rank[rootX] == rank[rootY]) {</span><br><span class="line"> parent[rootX] = rootY;</span><br><span class="line"> <span class="comment">// 此时以 rootY 为根结点的树的高度仅加了 1</span></span><br><span class="line"> rank[rootY]++;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (rank[rootX] < rank[rootY]) {</span><br><span class="line"> parent[rootX] = rootY;</span><br><span class="line"> <span class="comment">// 此时以 rootY 为根结点的树的高度不变</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 同理,此时以 rootX 为根结点的树的高度不变</span></span><br><span class="line"> parent[rootY] = rootX;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> x)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (x != parent[x]) {</span><br><span class="line"> parent[x] = find(parent[x]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> parent[x];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="复杂度">复杂度</h1>
<p><a href="https://oi-wiki.org/ds/dsu/" target="_blank" rel="noopener">oi-wiki-并查集</a></p>
<h1 id="references">References</h1>
<p><a href="https://oi-wiki.org/ds/dsu/" target="_blank" rel="noopener">oi-wiki-并查集</a><br />
<a href="https://leetcode-cn.com/problems/smallest-string-with-swaps/solution/1202-jiao-huan-zi-fu-chuan-zhong-de-yuan-wgab/" target="_blank" rel="noopener">leetcode-1202题解</a></p>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>并查集</tag>
</tags>
</entry>
<entry>
<title>Java访问权限之Protected</title>
<url>/2020/12/20/JavaProtected%E6%9D%83%E9%99%90/</url>
<content><![CDATA[<h1 id="java访问权限之protected">Java访问权限之Protected</h1>
<h2 id="有的没的">有的没的</h2>
<p>示例来自于<a href="https://blog.csdn.net/justloveyou_/article/details/61672133" target="_blank" rel="noopener">Java 访问权限控制_CSDN</a>,然后个人感觉原文很详细但是解释的不太自洽,自行解释了一下。</p>
<h2 id="可见性规则">可见性规则</h2>
<ul>
<li>基类的protected成员是包内可见的,并且对子类可见;
<ul>
<li>(自己实现的类,仅包内以及子类可见)</li>
</ul></li>
<li>若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。
<ul>
<li>(不同包时继承得到的方法只能由自己和自己的父类可见)</li>
<li>(不同包时继承得到的方法,自己只能看见自己的方法)</li>
</ul></li>
</ul>
<h2 id="示例分析">示例分析</h2>
<h3 id="示例一">示例一</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例一</span></span><br><span class="line"><span class="keyword">package</span> p1;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Father1</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span> </span>{} <span class="comment">// 父类Father1中的protected方法</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p1;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Son1</span> <span class="keyword">extends</span> <span class="title">Father1</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p11;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Son11</span> <span class="keyword">extends</span> <span class="title">Father1</span></span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p1;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test1</span> </span>{</span><br><span 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>{</span><br><span class="line"> Son1 son1 = <span class="keyword">new</span> Son1();</span><br><span class="line"> son1.f(); <span class="comment">// Compile OK ----(1)</span></span><br><span class="line"> son1.clone(); <span class="comment">// Compile Error ----(2)</span></span><br><span class="line"></span><br><span class="line"> Son11 son = <span class="keyword">new</span> Son11(); </span><br><span class="line"> son11.f(); <span class="comment">// Compile OK ----(3)</span></span><br><span class="line"> son11.clone(); <span class="comment">// Compile Error ----(4)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1)(3):f()从父类Father1处继承,Test1与Father1同包→Compile OK</p>
<p>(2)(4):clone()从Object类(间接)继承而来,Son1的clone()只有自己以及自己的父类(Father1)可见,Test1不可见→Compile Error</p>
<h3 id="示例二">示例二</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例二</span></span><br><span class="line"><span class="keyword">package</span> p2;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject2</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Object <span class="title">clone</span><span class="params">()</span> <span class="keyword">throws</span> CloneNotSupportedException</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.clone();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p22;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test2</span> <span class="keyword">extends</span> <span class="title">MyObject2</span> </span>{</span><br><span 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>{</span><br><span class="line"> MyObject2 obj = <span class="keyword">new</span> MyObject2();</span><br><span class="line"> obj.clone(); <span class="comment">// Compile Error ----(1)</span></span><br><span class="line"></span><br><span class="line"> Test2 tobj = <span class="keyword">new</span> Test2();</span><br><span class="line"> tobj.clone(); <span class="comment">// Complie OK ----(2)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()由MyObject2 自己实现,自己以及子类(Test2)可见→Compile Error</p>
<p>(2):clone()从父类MyObject2继承, 只有自己以及自己的父类(MyObject2)可见→Compile OK</p>
<h3 id="示例三">示例三</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例三</span></span><br><span class="line"><span class="keyword">package</span> p3;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject3</span> <span class="keyword">extends</span> <span class="title">Test3</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p33;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test3</span> </span>{</span><br><span 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>{</span><br><span class="line"> MyObject3 obj = <span class="keyword">new</span> MyObject3();</span><br><span class="line"> obj.clone(); <span class="comment">// Compile OK ------(1)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()从Object类(间接)继承而来,只有自己以及自己的父类(Test3)可见→Compile OK</p>
<h3 id="示例四">示例四</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例四</span></span><br><span class="line"><span class="keyword">package</span> p4;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject4</span> <span class="keyword">extends</span> <span class="title">Test4</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Object <span class="title">clone</span><span class="params">()</span> <span class="keyword">throws</span> CloneNotSupportedException </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.clone();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p44;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test4</span> </span>{</span><br><span 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>{</span><br><span class="line"> MyObject4 obj = <span class="keyword">new</span> MyObject4();</span><br><span class="line"> obj.clone(); <span class="comment">// Compile Error -----(1)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()由MyObject4自己实现,只有自己、包内以及自己子类可见,Test4是它的父类→Compile Error</p>
<h3 id="示例五">示例五</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例五</span></span><br><span class="line"><span class="keyword">package</span> p5;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject5</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Object <span class="title">clone</span><span class="params">()</span> <span class="keyword">throws</span> CloneNotSupportedException</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.clone();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test5</span> </span>{</span><br><span 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 class="keyword">throws</span> CloneNotSupportedException </span>{</span><br><span class="line"> MyObject5 obj = <span class="keyword">new</span> MyObject5();</span><br><span class="line"> obj.clone(); <span class="comment">// Compile OK ----(1)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()由MyObject4自己实现,只有自己、包内以及自己子类可见,Test5属于p5→Compile OK</p>
<h3 id="实例六">实例六</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例六</span></span><br><span class="line"><span class="keyword">package</span> p6;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject6</span> <span class="keyword">extends</span> <span class="title">Test6</span></span>{}</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test6</span> </span>{</span><br><span 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>{</span><br><span class="line"> MyObject6 obj = <span class="keyword">new</span> MyObject6();</span><br><span class="line"> obj.clone(); <span class="comment">// Compile OK -------(1)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()从Object(间接)继承而来,只有自己以及自己的父类(Test6)可见→Compile OK</p>
<h3 id="示例七">示例七</h3>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//示例七</span></span><br><span class="line"><span class="keyword">package</span> p7;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyObject7</span> <span class="keyword">extends</span> <span class="title">Test7</span> </span>{</span><br><span 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>{</span><br><span class="line"> Test7 test = <span class="keyword">new</span> Test7();</span><br><span class="line"> test.clone(); <span class="comment">// Compile Error ----- (1)</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test7</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>(1):clone()从Object继承而来,只有自己以及自己的父类可见→Compile Error</p>
]]></content>
<categories>
<category>踩坑记录</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>网络爬虫笔记</title>
<url>/2020/10/21/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AC%94%E8%AE%B0/</url>
<content><![CDATA[<h1 id="爬虫笔记">爬虫笔记</h1>
<h2 id="有的没的">有的没的</h2>
<p>这个笔记是一个目录性质的笔记,主要是方便自己日后回忆,如果有有缘的大兄弟也学习了这套课程,欢迎交流。这套课程感觉还是挺实用的,在学习过程中也试着解决了一些正好遇到的需求。课程在B站上有(路飞学城)。<br />
自己学习过程中写的相关代码都push到了自己的<a href="https://github.com/LearningGp/WebCrawler" target="_blank" rel="noopener">GitHub</a>上,可以自由查阅。之后可能也会就一些部分写详细的学习笔记。</p>
<h2 id="http">http</h2>
<h3 id="常用请求头信息">常用请求头信息</h3>
<ul>
<li>User-Agent:请求载体的身份标识</li>
<li>Connection:请求完毕后,是否保持连接</li>
</ul>
<h3 id="常用响应头信息">常用响应头信息</h3>
<ul>
<li>Content-Type:服务器响应回客户端的数据类型</li>
</ul>
<h2 id="https">https</h2>
<p>安全的超文本传输协议</p>
<h3 id="加密方式">加密方式</h3>
<ul>
<li>对称密钥加密</li>
<li>非对称密钥加密</li>
<li>证书密钥加密(https)</li>
</ul>
<h2 id="requests模块">requests模块</h2>
<p>python中原生的基于网络请求的模块,用于模拟浏览器发请求。</p>
<h3 id="流程">流程</h3>
<ul>
<li>指定url</li>
<li>发起请求</li>
<li>获取响应数据</li>
<li>持久化存储</li>
</ul>
<h3 id="实例">实例</h3>
<ul>
<li><p>例1 爬取百度首页</p></li>
<li><p>例2 爬取百度查询页面</p></li>
<li><p>例3 爬取百度翻译结果</p></li>
<li><p>例4 爬取豆瓣电影排名</p></li>
<li><p>练习 爬取KFC餐厅地址</p></li>
<li><p>例5 爬取药监局网站信息</p></li>
</ul>
<h2 id="聚焦爬虫">聚焦爬虫</h2>
<h3 id="流程-1">流程</h3>
<ul>
<li>指定url</li>
<li>发起请求</li>
<li>获取响应数据</li>
<li>数据解析</li>
<li>持久化存储</li>
</ul>
<h3 id="实现">实现</h3>
<h4 id="正则">正则</h4>
<p>利用正则表达式从爬取的数据中匹配出我们所需要的部分</p>
<h5 id="常用表达式">常用表达式</h5>
<figure>
<img src="/2020/10/21/网络爬虫笔/zhengze.png" alt="" /><figcaption>LearningGp</figcaption>
</figure>
<h5 id="实例-爬取糗事百科图片">实例 爬取糗事百科图片</h5>
<h4 id="bs4">bs4</h4>
<h5 id="步骤简介">步骤简介</h5>
<ol type="1">
<li>实例化BeautifulSoup对象</li>
<li>将页面源码数据加载到该对象中</li>
<li>通过BeautifulSoup对象的属性或方法定位标签以及数据提取</li>
</ol>
<h5 id="数据解析相关方法和属性">数据解析相关方法和属性</h5>
<ul>
<li>tagName:返回html中第一次出现的tagName标签</li>
<li>find():
<ul>
<li>find('tagName'):返回html中第一次出现的tagName标签</li>
<li>find('tagName',class_/id/attr='xxx' ):属性定位</li>
</ul></li>
<li>find_all():同find,但是返回所有符合条件项(列表)</li>
<li>select():
<ul>
<li>select('某种选择器(id,class,标签)'):返回列表</li>
<li>select('用>以及空格连接的多个选择器组成层级选择器'):返回列表(>表示一个层级,空格表示多个层级)</li>
</ul></li>
<li>获取标签之间的文本数据
<ul>
<li>text/string/get_text()
<ul>
<li>text/get_text():获取所定位标签下所有文本</li>
<li>string:只获取所定位标签下直系文本</li>
</ul></li>
</ul></li>
<li>获取标签中的属性值
<ul>
<li>['标签名']</li>
</ul></li>
</ul>
<h5 id="实例-爬取三国演义所以章节标题和内容">实例 爬取三国演义所以章节标题和内容</h5>
<h4 id="xpath">xpath</h4>
<p>最常用且最便捷高效的解析方式,同时具有较好的通用性</p>
<h5 id="步骤简介-1">步骤简介</h5>
<ol type="1">
<li>实例化etree的对象
<ul>
<li>加载本地文档:etree.parse(filePath)</li>
<li>加载网络上获取的源码数据:etree.HTML('page_text')</li>
</ul></li>
<li>将页面源码数据加载到该对象中</li>
<li>调用rtree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获</li>
</ol>
<h4 id="xpath表达式">xpath表达式</h4>
<p>返回基本为列表,定位阶段列表内为对象(tips:谷歌浏览器F12下,Elemens窗口下选中需要的标签右键->copy->copy Xpath 可以复制对应Xpath,按需修改) - 标签定位:'/html/head/title'(层级定位) - /:表示一个层级 - //:表示多个层级,或表示从任意位置开始定位 - 属性定位:'//div<span class="citation" data-cites="class">[@class="song"]</span>' - 索引定位:'//div<span class="citation" data-cites="class">[@class="song"]</span>/p[3]'(该div下第三个p标签,从1开始) - 取文本:'/html/head/title/text()' - 取属性:'//div<span class="citation" data-cites="class">[@class="song"]</span>/img/<span class="citation" data-cites="src">@src</span>'</p>
<h4 id="实例-1">实例</h4>
<ul>
<li><p>实例1 爬取58同城二手房标题</p></li>
<li><p>实例2 爬取图片</p></li>
<li><p>实例3 爬取城市名称</p></li>
<li><p>练习1 爬取简历模板</p></li>
</ul>
<h2 id="验证码识别">验证码识别</h2>
<h3 id="简介">简介</h3>
<p>这边采用将验证码图片下载下来后调用第三方识别服务进行识别,本文采用超级鹰的服务,需要提前前往其网站注册账号并充值。</p>
<h3 id="实例-2">实例</h3>
<ul>
<li><p>实例1 超级鹰服务测试</p></li>
<li><p>实例2 古诗文网站验证码识别</p></li>
</ul>
<h2 id="代理爬虫">代理爬虫</h2>
<h3 id="简介-1">简介</h3>
<p>主要作用有</p>
<ol type="1">
<li>突破自身IP访问的限制</li>
<li>隐藏自身真实IP</li>
</ol>
<p>代理相关工具网站</p>
<ul>
<li>快代理</li>
<li>西祠代理</li>
<li>www.goubanjia.com</li>
</ul>
<h3 id="实现-1">实现</h3>
<h4 id="实例-代理爬虫查询ip">实例 代理爬虫查询IP</h4>
<h2 id="异步爬虫">异步爬虫</h2>
<h3 id="简介-2">简介</h3>
<p>单线程串行的方式爬取效率较低,需要我们通过一些方法实现异步爬虫。</p>
<h3 id="实现方式">实现方式</h3>
<ul>
<li>多线程,多进程(不推荐)
<ul>
<li>好处 :为相关的阻塞操作开启线程或进程,阻塞操作就可以异步执行</li>
<li>弊端:无法无限制开启多线程或多进程</li>
</ul></li>
<li>线程池、进程池(适当使用)
<ul>
<li>好处:降低系统对进程或者线程创建和销毁的频率,降低系统开销</li>
<li>弊端:池中线程或进程的数量有上限</li>
</ul></li>
<li>单线程+异步协程(推荐)
<ul>
<li>使用asyncio以及aiohttp实现</li>
</ul></li>
</ul>
<h2 id="selenium模块">selenium模块</h2>
<h3 id="简介-3">简介</h3>
<p>是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器</p>
<h3 id="实例-3">实例</h3>
<ul>
<li>例1 人人网模拟登录</li>
</ul>
<h2 id="scrapy框架">scrapy框架</h2>
<h3 id="简介-4">简介</h3>
<p>一个为了爬取网站数据,提取结构性数据而编写的应用框架。其内部已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)。</p>
<h3 id="流程-2">流程</h3>
<ul>
<li>创建工程:
<ul>
<li>scrapy startproject ProName</li>
</ul></li>
<li>进入工程目录:
<ul>
<li>cd ProName</li>
</ul></li>
<li>创建爬虫文件:
<ul>
<li>scrapy genspider spiderName www.xxx.com</li>
</ul></li>
<li>编写相关操作代码</li>
<li>执行工程:
<ul>
<li>scrapy crawl spiderName</li>
</ul></li>
</ul>
<h3 id="功能">功能</h3>
<h4 id="基于管道的持久化存储">基于管道的持久化存储</h4>
<ul>
<li>修改item文件</li>
<li>修改pipelines文件</li>
<li>修改配置文件</li>
</ul>
<h4 id="便捷的全站爬取">便捷的全站爬取</h4>
<h4 id="支持分布式集群">支持分布式集群</h4>
<h3 id="实例-4">实例</h3>
<ul>
<li><p>例1 爬取糗百(持久化)</p></li>
<li><p>例2 爬取boss直聘(中间件)</p></li>
<li><p>例3 爬取图片</p></li>
<li><p>例4 爬取网易(手动全站)</p></li>
<li><p>例5 爬取阳光政务(自动全站)</p></li>
</ul>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>爬虫</tag>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title>ECS 7天实践训练营@day06</title>
<url>/2020/08/11/ECS-7%E5%A4%A9%E5%AE%9E%E8%B7%B5%E8%AE%AD%E7%BB%83%E8%90%A5-day06/</url>
<content><![CDATA[<h1 id="ecs-7天实践训练营day06">ECS 7天实践训练营@day06</h1>
<h2 id="有的没的">有的没的</h2>
<p>今天的课程是搭建离线下载服务器,主要用于帮助我们离线下载一些网络资源,方便我们直接从服务器取用,在服务器网络带宽够大的情况下,能帮助我们下载比较大的文件。流程依旧讲解的很清楚,跟着做基本没有问题。今天是训练营最后一天了,总体下来收获还是挺多的,之后的几天会好好考虑怎么更好的利用云服务器来方便生活学习。</p>
<h2 id="打造离线下载服务器">打造离线下载服务器</h2>
<h3 id="创建资源连接云服务器">创建资源&连接云服务器</h3>
<p>这两个步骤与之前没有差别,就不再赘述。(安全组处需要开启6080端口)</p>
<h3 id="下载安装ccaa方案包">下载安装CCAA方案包</h3>
<ol type="1">
<li><p>输入命令下载(完成后自动开始安装) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">#国内服务器</span><br><span class="line">bash <(curl -Lsk</span><br><span class="line">https://raw.githubusercontent.com/helloxz/ccaa/master/ccaa.sh) cdn</span><br><span class="line">#海外服务器</span><br><span class="line">bash <(curl -Lsk</span><br><span class="line">https://raw.githubusercontent.com/helloxz/ccaa/master/ccaa.sh)</span><br></pre></td></tr></table></figure></p></li>
<li><p>输入1安装CCAA <img src="/2020/08/11/ECS-7天实践训练营-day0/2.png" alt="LearningGp" /></p></li>
<li><p>设置下载路径,可以直接回车 <img src="/2020/08/11/ECS-7天实践训练营-day0/3.png" alt="LearningGp" /></p></li>
<li><p>输⼊ Aria2 的 RPC 通信密钥,输⼊不含特殊字符的内容即可 <img src="/2020/08/11/ECS-7天实践训练营-day0/4.png" alt="LearningGp" /></p></li>
<li><p>安装成功 <img src="/2020/08/11/ECS-7天实践训练营-day0/5.png" alt="LearningGp" /></p></li>
<li><p>设置 浏览器打开上一步显示的url,进入设置,填写密钥后点击重新载入即可 <img src="/2020/08/11/ECS-7天实践训练营-day0/6e.png" alt="overwrote existing file" /></p></li>
</ol>
<h3 id="使用离线下载服务器">使用离线下载服务器</h3>
<h4 id="常用命令">常用命令</h4>
<ul>
<li>ccaa:进⼊CCAA操作界⾯</li>
<li>ccaa status:查看CCAA运⾏状态</li>
<li>ccaa stop:停⽌CCAA</li>
<li>ccaa start:启动CCAA</li>
<li>ccaa restart:重启CCAA</li>
<li>ccaa -v:查看CCAA版本(2.0开始⽀持)</li>
</ul>
<h4 id="离线下载">离线下载</h4>
<p>界面其实比较容易理解,点击新建后输入下载链接,分行可输入多个链接后点击立即下载即可,也可以上传种子文件。</p>
<h4 id="下载至本地">下载至本地</h4>
<p>在文件管理中选中所需要的文件,点击下载即可(推荐按量付费的云服务器,可以保证速度)</p>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>ECS</tag>
<tag>CCAA</tag>
</tags>
</entry>
<entry>
<title>ECS 7天实践训练营@day05</title>
<url>/2020/08/10/ECS-7%E5%A4%A9%E5%AE%9E%E8%B7%B5%E8%AE%AD%E7%BB%83%E8%90%A5-day05/</url>
<content><![CDATA[<h1 id="ecs-7天实践训练营day05">ECS 7天实践训练营@day05</h1>
<h2 id="有的没的">有的没的</h2>
<p>昨天的课程是在线编程比赛,成绩不太满意感觉退步蛮多,最近也确实没有好好刷算法,还是得多花些时间在算法上。今天的课程是搭建Java Web开发环境。因为之前做过Java Web的项目,所以都还算熟悉。课程讲的很详细,也是跟着做就ok。<a href="https://developer.aliyun.com/adc/scenario/bbad6f5e0cba4c0ba5c904f6cf06a8d0" target="_blank" rel="noopener">体验链接</a></p>
<h2 id="搭建java-web开发环境">搭建Java Web开发环境</h2>
<p>Java Web开发环境最基础的就是JDK+MySQL+TomCat,基本流程就是按顺序安装即可。</p>
<h3 id="创建资源连接云服务器">创建资源&连接云服务器</h3>
<p>这两个步骤与之前没有差别,就不再赘述。</p>
<h3 id="安装jdk">安装JDK</h3>
<ol type="1">
<li><p>执行以下命令,查看yum源中JDK版本 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">yum list java*</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,使用yum安装JDK1.8 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">yum -y install java-1.8.0-openjdk*</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,查看是否安装成功 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">java -version</span><br></pre></td></tr></table></figure> <img src="/2020/08/10/ECS-7天实践训练营-day0/java.png" alt="LearningGp" /></p></li>
</ol>
<h3 id="安装mysql数据库">安装MySQL数据库</h3>
<ol type="1">
<li><p>依次执行下方命令,下载并安装MySQL官方的Yum Repository <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm</span><br><span class="line">yum -y install mysql57-community-release-el7-10.noarch.rpm</span><br><span class="line">yum -y install mysql-community-server</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,启动 MySQL 数据库 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">systemctl start mysqld.service</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,查看MySQL初始密码(最后的即为密码) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">grep "password" /var/log/mysqld.log</span><br></pre></td></tr></table></figure> <img src="/2020/08/10/ECS-7天实践训练营-day0/mysql.png" alt="LearningGp" /></p></li>
<li><p>执行以下命令,输入前面获取的密码,登录数据库(出现mysql>即为登录成功) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">mysql -uroot -p</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,修改MySQL默认密码(也可以不输入第一行,第二行将密码改为符合原密码安全策略的复杂密码) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">set global validate_password_policy=0; #修改密码安全策略为低(只校验密码长度,至少8位)。</span><br><span class="line">ALTER USER 'root'@'localhost' IDENTIFIED BY '12345678';</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,授予root用户远程管理权限(熟悉mysql的可以为项目建立对应用户,授予相应用户相应的远程权限) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '12345678';</span><br></pre></td></tr></table></figure></p></li>
<li><p>输入exit退出数据库</p></li>
</ol>
<h3 id="安装tomcat">安装Tomcat</h3>
<ol type="1">
<li><p>执行以下命令,下载Tomcat压缩包 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">wget https://mirror.bit.edu.cn/apache/tomcat/tomcat-8/v8.5.57/bin/apache-tomcat-8.5.57.tar.gz</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,解压刚刚下载Tomcat包 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">tar -zxvf apache-tomcat-8.5.57.tar.gz</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,修改Tomcat名字(方便操作) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">mv apache-tomcat-8.5.57 /usr/local/Tomcat8.5</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,为Tomcat授权 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">chmod +x /usr/local/Tomcat8.5/bin/*.sh</span><br></pre></td></tr></table></figure></p></li>
<li><p>执行以下命令,修改Tomcat默认端口号为80(主要是为了能够输入地址直接访问而不用输入端口号) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sed -i 's/Connector port="8080"/Connector port="80"/' /usr/local/Tomcat8.5/conf/server.xml</span><br></pre></td></tr></table></figure></p></li>
<li><p>启动Tomcat(为了方便也可以将其添加为服务) <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/usr/local/Tomcat8.5/bin/./startup.sh</span><br></pre></td></tr></table></figure> ### 访问Tomcat 打开浏览器,在地址栏中输入ECS公网地址,如显示下图界面,则搭建完成 <img src="/2020/08/10/ECS-7天实践训练营-day0/tomcat.png" alt="LearningGp" /></p></li>
</ol>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>ECS</tag>
<tag>Java Web</tag>
</tags>
</entry>
<entry>
<title>ECS 7天实践训练营@day03</title>
<url>/2020/08/08/ECS-7%E5%A4%A9%E5%AE%9E%E8%B7%B5%E8%AE%AD%E7%BB%83%E8%90%A5-day03/</url>
<content><![CDATA[<h1 id="ecs-7天实践训练营day03">ECS 7天实践训练营@day03</h1>
<h2 id="有的没的">有的没的</h2>
<p>今天的课程总体和昨天的差不大多,不过由于个人暂时没有这方面的需求,所以也只是先做到了安装部署的步骤。前两天的笔记由于GitHub域名解析问题临时放到了csdn,所以今天将博客同时部署到GitHub以及coding,配置了域名的解析,国内的访问会解析到coding的page。个人测试没有啥问题,希望真的没有啥问题。(后续:又出现了问题,coding实在太慢了,现在部署到了云服务器上)开通云服务器、安装运行环境以及上传文件的的操作与昨天的相同,可以点击<a href="https://arvinhe.club/2020/08/07/ECS-7%E5%A4%A9%E5%AE%9E%E8%B7%B5%E8%AE%AD%E7%BB%83%E8%90%A5-day02/#more" target="_blank" rel="noopener">链接</a>查看。</p>
<h2 id="基于云服务器构建微信公众号管理系统">基于云服务器构建微信公众号管理系统</h2>
<h3 id="开通云服务器">开通云服务器</h3>
<p>同样采用阿里云的ECS并在安全组中开放http80端口,https443端口,ssh22端口。</p>
<h3 id="安装运行环境">安装运行环境</h3>
<p>采用LAMP环境(Ubuntu 18.04 Apache PHP7.1)</p>
<h3 id="安装微擎">安装微擎</h3>
<h4 id="前置准备">前置准备</h4>
<p>微擎的install.php做了处理,所以要先去<a href="https://www.w7.cc/" target="_blank" rel="noopener">微擎官网</a>注册账号,然后再<a href="https://cdn.w7.cc/download/WeEngine-Laster-Online.zip" target="_blank" rel="noopener">下载</a>相关文件,并且解压上传到 <img src="/2020/08/08/ECS-7天实践训练营-day0/e.png" alt="LearningGp" /> 然后为目录赋予权限 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">chown -R www /data/wwwroot/default</span><br></pre></td></tr></table></figure></p>
<h4 id="创建数据库">创建数据库</h4>
<ol type="1">
<li><p>查询数据库初始密码 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">grep dbrootpwd /root/oneinstack/options.conf</span><br></pre></td></tr></table></figure> <img src="/2020/08/08/ECS-7天实践训练营-day0/mima.png" alt="LearningGp" /></p></li>
<li><p>浏览器打开http://ecs公网IP/old/phpmyadmin (这里我碰到一个小坑,会显示404,后来发现只要将old中phpMyaAdmin文件夹名称改为全小写字母即可)</p></li>
<li><p>输入 root 账户和密码,点击【执行】进行登录</p></li>
<li><p>在左侧列表点击【new】新建数据库,名称随意</p></li>
</ol>
<h4 id="安装微擎-1">安装微擎</h4>
<ol type="1">
<li><p>浏览器打开http://你的域名/install.php 登陆后进行安装(如果提示拒绝访问,没有权限之类可以再为目录赋予一下权限)</p></li>
<li><p>填写相关信息 - 数据库主机:localhost - 数据库用户:root - 数据库密码:之前查询出的密码 - 数据库名称:之前创建的数据库名称 - 表前缀:默认(ims_)</p></li>
</ol>
<h4 id="登录微擎">登录微擎</h4>
<p>访问自己的域名进入登录界面 <img src="/2020/08/08/ECS-7天实践训练营-day0/login.png" alt="LearningGp" /></p>
<p>登录后在左侧平台管理中可以添加平台 <img src="/2020/08/08/ECS-7天实践训练营-day0/pingtai.png" alt="LearningGp" /> 任选一个方式,相关信息可以在自己的公众号平台找到。因为个人暂时没这个需求就不深入了解啦。</p>
]]></content>
<categories>
<category>知识图谱</category>
</categories>
<tags>
<tag>ECS</tag>
<tag>微擎</tag>
</tags>
</entry>
<entry>
<title>ECS 7天实践训练营@day02</title>
<url>/2020/08/07/ECS-7%E5%A4%A9%E5%AE%9E%E8%B7%B5%E8%AE%AD%E7%BB%83%E8%90%A5-day02/</url>
<content><![CDATA[<h1 id="ecs-7天实践训练营day02">ECS 7天实践训练营@day02</h1>
<h2 id="有的没的">有的没的</h2>
<p>第二天的内容是搭建wiki知识库,就是搭建一个私人百科,之前也没有想到过这个想法,不过确实是有实用价值的。课程讲解的很清楚,基本上跟着走就可以完成。不过这个应用对于我个人暂时还没有应用的场景,暂且把搭建方法留存下来备用。</p>
<h2 id="搭建wiki知识库">搭建wiki知识库</h2>
<h3 id="开通云服务器">开通云服务器</h3>
<p>同样采用阿里云的ECS并在安全组中开放http80端口,https443端口,ssh22端口。</p>
<h3 id="安装运行环境">安装运行环境</h3>
<p>这里课程采用的方案是从镜像市场下载LAMP环境(Ubuntu16.04 Apache PHP7.1)镜像,实际操作时没搜到这个,所以用了 LAMP环境(Ubuntu 18.04 Apache PHP7.1)代替。<br />
在阿里云平台上部署镜像有三种方式:</p>
<ul>
<li>购买时直接部署镜像 <img src="/2020/08/07/ECS-7天实践训练营-day0/1.png" alt="LearningGp" /></li>
<li>停止实例后更换操作系统 <img src="/2020/08/07/ECS-7天实践训练营-day0/2.png" alt="LearningGp" /></li>
<li>在<a href="https://market.aliyun.com/products/53398003/cmjj016263.html?spm=5176.10731542.0.0.b6f1677fKwFqek" target="_blank" rel="noopener">镜像页面</a>购买 <img src="/2020/08/07/ECS-7天实践训练营-day0/3.png" alt="LearningGp" /></li>
</ul>
<p>前两种方式需要在后续弹出界面选择需要的镜像</p>
<h3 id="安装mediawiki">安装MediaWiki</h3>
<h4 id="安装远程终端控制和文件传输">安装远程终端控制和文件传输</h4>
<p>课程采用的是PuTTY+FileZiila的方案,这两款比较简洁,适用性高。但是由于个人习惯还是采用了Xshell以及配套的Xftp。</p>
<h4 id="下载mediawiki">下载MediaWiki</h4>
<p><a href="https://releases.wikimedia.org/mediawiki/1.29/mediawiki-1.29.1.tar.gz" target="_blank" rel="noopener">下载MediaWiki并解压缩</a></p>
<h4 id="上传程序文件">上传程序文件</h4>
<ol type="1">
<li>进入 /data/wwwroot/default/ 目录,创建一个old文件夹,将目录上的原始文件全部搬到old内</li>
<li>将解压缩后的MediaWiki程序文件上传至 /data/wwwroot/default/ 目录 <img src="/2020/08/07/ECS-7天实践训练营-day0/e.png" alt="LearningGp" /></li>
<li>使用 chown 命令为目录赋予权限 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">chown -R www /data/wwwroot/default</span><br></pre></td></tr></table></figure></li>
</ol>