forked from zsr/zsr.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1361 lines (767 loc) · 142 KB
/
index.html
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
<!doctype html>
<html class="theme-next pisces use-motion">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/vendors/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
<link href="/vendors/font-awesome/css/font-awesome.min.css?v=4.4.0" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.0.1" rel="stylesheet" type="text/css" />
<meta name="keywords" content="Hexo, NexT" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.0.1" />
<meta name="description" content="Develop Notes">
<meta property="og:type" content="website">
<meta property="og:title" content="Hello Coder">
<meta property="og:url" content="http://zsr.github.io/index.html">
<meta property="og:site_name" content="Hello Coder">
<meta property="og:description" content="Develop Notes">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Hello Coder">
<meta name="twitter:description" content="Develop Notes">
<script type="text/javascript" id="hexo.configuration">
var NexT = window.NexT || {};
var CONFIG = {
scheme: 'Pisces',
sidebar: {"position":"left","display":"post"},
fancybox: true,
motion: true,
duoshuo: {
userId: 0,
author: '博主'
}
};
</script>
<link rel="canonical" href="http://zsr.github.io/"/>
<title> Hello Coder </title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-81477846-1', 'auto');
ga('send', 'pageview');
</script>
<div class="container one-collumn sidebar-position-left
page-home
">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">Hello Coder</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle"></p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags" rel="section">
<i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
标签
</a>
</li>
<li class="menu-item menu-item-search">
<a href="javascript:;" class="popup-trigger">
<i class="menu-item-icon fa fa-search fa-fw"></i> <br />
搜索
</a>
</li>
</ul>
<div class="site-search">
<div class="popup">
<span class="search-icon fa fa-search"></span>
<input type="text" id="local-search-input">
<div id="local-search-result"></div>
<span class="popup-btn-close">close</span>
</div>
</div>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/03/29/CMS收集过程和日志分析/" itemprop="url">
CMS收集过程和日志分析
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2019-03-29T14:36:59+08:00" content="2019-03-29">
2019-03-29
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>本文根据线上<code>jvm</code>环境(<code>Java 8</code>)以及<code>gc.log</code>主要梳理<code>ParNew GC</code>和<code>CMS GC</code>执行过程</p>
<h3 id="JVM参数"><a href="#JVM参数" class="headerlink" title="JVM参数"></a><code>JVM</code>参数</h3><p>主要贴出相关一些参数:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">-XX:MaxTenuringThreshold=8, </div><div class="line">-XX:ParallelGCThreads=8, </div><div class="line">-XX:+UseConcMarkSweepGC, </div><div class="line">-XX:+UseParNewGC, </div><div class="line">-XX:+DisableExplicitGC, </div><div class="line">-XX:+CMSParallelRemarkEnabled, </div><div class="line">-XX:+CMSClassUnloadingEnabled, </div><div class="line">-XX:CMSInitiatingOccupancyFraction=70, </div><div class="line">-XX:CMSFullGCsBeforeCompaction=5, </div><div class="line">-XX:+UseCMSCompactAtFullCollection, </div><div class="line">-XX:+CMSScavengeBeforeRemark,</div></pre></td></tr></table></figure>
<h4 id="ParNew-and-CMS"><a href="#ParNew-and-CMS" class="headerlink" title="ParNew and CMS"></a><code>ParNew and CMS</code></h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">-XX:+UseParNewGC -XX:+UseConcMarkSweepGC</div></pre></td></tr></table></figure>
<p><code>ParNew</code>收集器是<code>Serial</code>收集器的多线程版本,作用于年轻代,采用多线程进行收集,但一样要STW;</p>
<p> 345<code>CMS</code>的全称是<code>Concurrent Mark and Sweep</code>,作用于老年代,目标是最短停顿时间,我司绝大部分<code>web</code>服务采用<code>ParNew + CMS</code>(还有一些<code>G1</code>)</p>
<p><code>ParNew</code>:采用 <a href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/copy" target="_blank" rel="external">标记-复制</a> 算法;</p>
<p><code>CMS</code>:采用<a href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/sweep" target="_blank" rel="external">标记-清除</a> 算法;</p>
<h3 id="GC-日志"><a href="#GC-日志" class="headerlink" title="GC`日志"></a>GC`日志</h3><p>截取线上一段<code>gc.log</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">// </div><div class="line">2019-03-21T11:19:41.609+0800: 1086345.976: [GC (Allocation Failure) 2019-03-21T11:19:41.609+0800: 1086345.976: [ParNew: 873168K->36622K(943744K), 0.0312849 secs] 3074543K->2238776K(4089472K), 0.0316707 secs] [Times: user=0.14 sys=0.00, real=0.04 secs] </div><div class="line"></div><div class="line">2019-03-21T11:19:41.643+0800: 1086346.010: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2202154K(3145728K)] 2238847K(4089472K), 0.0057407 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] </div><div class="line">2019-03-21T11:19:41.649+0800: 1086346.016: [CMS-concurrent-mark-start]</div><div class="line">2019-03-21T11:19:41.915+0800: 1086346.282: [CMS-concurrent-mark: 0.266/0.266 secs] [Times: user=0.64 sys=0.01, real=0.27 secs] </div><div class="line">2019-03-21T11:19:41.915+0800: 1086346.282: [CMS-concurrent-preclean-start]</div><div class="line">2019-03-21T11:19:41.968+0800: 1086346.335: [CMS-concurrent-preclean: 0.049/0.053 secs] [Times: user=0.06 sys=0.00, real=0.05 secs] </div><div class="line">2019-03-21T11:19:41.968+0800: 1086346.335: [CMS-concurrent-abortable-preclean-start]</div><div class="line">2019-03-21T11:19:47.067+0800: 1086351.434: [CMS-concurrent-abortable-preclean: 4.876/5.099 secs] [Times: user=6.58 sys=0.02, real=5.10 secs] </div><div class="line">2019-03-21T11:19:47.069+0800: 1086351.436: [GC (CMS Final Remark) [YG occupancy: 51879 K (943744 K)]2019-03-21T11:19:47.069+0800: 1086351.436: [GC (CMS Final Remark) 2019-03-21T11:19:47.069+0800: 1086351.436: [ParNew: 51879K->23393K(943744K), 0.0156467 secs] 2254034K->2226423K(4089472K), 0.0159200 secs] [Times: user=0.12 sys=0.00, real=0.02 secs] </div><div class="line">2019-03-21T11:19:47.085+0800: 1086351.452: [Rescan (parallel) , 0.0106023 secs]2019-03-21T11:19:47.096+0800: 1086351.462: [weak refs processing, 0.0000353 secs]2019-03-21T11:19:47.096+0800: 1086351.462: [class unloading, 0.0421021 secs]2019-03-21T11:19:47.138+0800: 1086351.505: [scrub symbol table, 0.0157111 secs]2019-03-21T11:19:47.153+0800: 1086351.520: [scrub string table, 0.0014866 secs][1 CMS-remark: 2203030K(3145728K)] 2226423K(4089472K), 0.0887818 secs] [Times: user=0.26 sys=0.01, real=0.09 secs] </div><div class="line">2019-03-21T11:19:47.158+0800: 1086351.525: [CMS-concurrent-sweep-start]</div><div class="line">2019-03-21T11:19:47.350+0800: 1086351.717: [CMS-concurrent-sweep: 0.192/0.192 secs] [Times: user=0.31 sys=0.00, real=0.20 secs] </div><div class="line">2019-03-21T11:19:47.351+0800: 1086351.717: [CMS-concurrent-reset-start]</div><div class="line">2019-03-21T11:19:47.356+0800: 1086351.723: [CMS-concurrent-reset: 0.005/0.005 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]</div></pre></td></tr></table></figure>
<h3 id="ParNew-GC"><a href="#ParNew-GC" class="headerlink" title="ParNew GC"></a><code>ParNew GC</code></h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">2019-03-21T11:19:41.609+0800: 1086345.976: [GC (Allocation Failure) 2019-03-21T11:19:41.609+0800: 1086345.976: [ParNew: 873168K->36622K(943744K), 0.0312849 secs] 3074543K->2238776K(4089472K), 0.0316707 secs] [Times: user=0.14 sys=0.00, real=0.04 secs]</div></pre></td></tr></table></figure>
<ol>
<li><code>GC</code>:区别<code>MinorGC</code>和<code>FullGC</code>的标识,这里代表的是<code>MinorGC</code>;</li>
<li><code>Allocation Failure</code>:<code>MinorGC</code>的原因,在这里由于年轻代不满足申请的空间,因此触发了<code>MinorGC</code>(年轻代<code>GC</code>);</li>
<li><code>ParNew</code>:收集器的类型,它预示了年轻代使用一个并行的<code>mark-copy</code>垃圾收集器;</li>
<li><code>873168K->36622K</code>:收集前后年轻代的使用情况;</li>
<li><code>943744K</code>: 整个年轻代的容量;</li>
<li><code>3074543K->2238776K</code>:收集前后整个堆的使用情况;</li>
<li><code>4089472K</code>:整个堆的容量;</li>
<li><code>0.0316707 secs</code>:<code>ParNew</code>收集器标记和复制年轻代活着的对象所花费的时间(包括和老年代通信的开销、对象晋升到老年代时间、垃圾收集周期结束最后的清理对象等的花销);</li>
<li><code>[Times: user=0.14 sys=0.00, real=0.04 secs]</code>:<code>GC</code>事件在不同维度的耗时:<ul>
<li><code>user</code>:<code>GC</code>线程在垃圾收集期间所使用的<code>CPU</code>总时间;</li>
<li><code>sys</code>:系统调用或者等待系统事件花费的时间;</li>
<li><code>real</code>:应用被暂停的时钟时间,由于<code>GC</code>线程是多线程的,导致了<code>real</code>小于<code>user+real</code>,如果是<code>GC</code>线程是单线程的话,<code>real</code>是接近于<code>user+real</code>时间。</li>
</ul>
</li>
</ol>
<h3 id="CMS-GC"><a href="#CMS-GC" class="headerlink" title="CMS GC"></a><code>CMS GC</code></h3><p><code>CMS GC</code>是基于<strong>标记-清除</strong>算法实现的,整个过程分几步:</p>
<p><img src="https://tva1.sinaimg.cn/large/006tKfTcgy1g1n78irqnij31h207ugmn.jpg" alt=""></p>
<ol>
<li>初始标记(<code>initial-mark</code>):从<code>GC Root</code>开始,仅扫描与根节点直接关联的对象并标记,这个过程需要<code>STW</code>,但是GC <code>Root</code>数量有限,因此时间较短</li>
<li>并发标记(<code>concurrent-marking</code>):这个阶段在初始标记的基础上继续向下进行遍历标记。这个阶段与用户线程并发执行,因此不停顿</li>
<li>并发预清理(<code>concurrent-precleaning</code>):该阶段并发执行,应用不停顿;在并发标记阶段执行期间,会出现一些刚刚晋升老年代的对象或新分配的对象,该阶段通过重新扫描减少下一阶段的工作;<code>precleaning</code>是为了减少下一阶段“重新标记”的工作量,因为<code>remark</code>阶段会<code>STW</code></li>
<li>可终止的预清理(<code>Concurrent Abortable Preclean</code>):可中止预清理阶段,运行在并发预清理和重新标记之间,直到获得所期望的<code>eden</code>空间占用率。不会暂停应用,继续预清理,直到<code>eden</code>区占用量达到<code>CMSScheduleRemarkEdenPenetration</code>(默认50%)或达到5秒钟</li>
<li>重新标记(<code>remark</code>):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,以保证执行清理之前对象引用关系是正确的。这一阶段需要<code>STW</code>,时间也比较短暂</li>
<li>并发清理(<code>concurrent-sweeping</code>):清理垃圾对象,这个过程与用户线程并发执行,不停顿</li>
<li>并发重置(<code>reset</code>):重置<code>CMS</code>收集器的数据结构,做好下一次执行<code>GC</code>任务的准备工作</li>
</ol>
<p>整个过程中需要<code>STW</code>的阶段仅有<strong>初始标记</strong>和<strong>重新标记</strong>阶段,所以可以说它的停顿时间比较短</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep" target="_blank" rel="external">https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep</a></p>
<p><a href="https://www.cnblogs.com/zhangxiaoguang/p/5792468.html" target="_blank" rel="external">(推荐)GC之详解CMS收集过程和日志分析</a></p>
<p><a href="https://access.redhat.com/solutions/971613" target="_blank" rel="external">CMS: abort preclean due to time</a></p>
<p><a href="http://www.blogjava.net/DLevin/archive/2015/08/01/426418.html" target="_blank" rel="external">Java CMS GC 361s引发的血案</a></p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/03/22/线上频繁Full-GC分析/" itemprop="url">
线上频繁Full GC分析
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2019-03-22T14:48:02+08:00" content="2019-03-22">
2019-03-22
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>同事的项目<code>voice-analyzer-web</code>线上环境,监控系统频繁报警<strong>发生JVMFullGC卡顿次数频繁</strong>(4台服务器都报警),监控每10分钟统计一次,该时间段内<code>Full GC</code>超过10次就会报警。</p>
<h3 id="线上JVM参数-有省略"><a href="#线上JVM参数-有省略" class="headerlink" title="线上JVM参数(有省略)"></a>线上JVM参数(有省略)</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">XX:MaxMetaspaceSize=256M, -Xms4g, -Xmx4g, -Xmn1g, -Xss256k, -XX:SurvivorRatio=8, -XX:MaxTenuringThreshold=8, -XX:ParallelGCThreads=8, -XX:+UseConcMarkSweepGC, -XX:+UseParNewGC, -XX:+DisableExplicitGC, -XX:+CMSParallelRemarkEnabled, -XX:+CMSClassUnloadingEnabled, -XX:CMSInitiatingOccupancyFraction=70, -XX:CMSFullGCsBeforeCompaction=5, -XX:+UseCMSCompactAtFullCollection, -XX:+CMSScavengeBeforeRemark, -XX:+HeapDumpOnOutOfMemoryError, -Xloggc:/usr/local/webserver/voice-analyzer-web/logs/gc.log, -XX:+UseGCLogFileRotation, -XX:NumberOfGCLogFiles=10, -XX:GCLogFileSize=10M, -XX:+PrintGCDetails, -XX:+PrintGCDateStamps, -XX:+PrintGCApplicationStoppedTime, -XX:+PrintGCApplicationConcurrentTime, -, -XX:HeapDumpPath=/usr/local/webserver/voice-analyzer-web/logs/voice-analyzer-web.hprof</div></pre></td></tr></table></figure>
<p>分配了4GB堆内存,年轻代1GB,老年代3GB,<code>eden</code>区800M,每个<code>Survivor</code>区100M,老年代占用率达到70%(2.1G左右)执行<code>CMS GC</code></p>
<h3 id="日志分析"><a href="#日志分析" class="headerlink" title="日志分析"></a>日志分析</h3><h4 id="初步分析gc-log"><a href="#初步分析gc-log" class="headerlink" title="初步分析gc.log"></a>初步分析<code>gc.log</code></h4><p>找一台线上机器下载<code>gc.log</code>(5M大小)到本地,推荐在线图像化分析<code>gc</code>地址<a href="[https://www.gceasy.io](https://www.gceasy.io/">geceasy</a>)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line">2019-03-21T11:19:41.609+0800: 1086345.976: [GC (Allocation Failure) 2019-03-21T11:19:41.609+0800: 1086345.976: [ParNew: 873168K->36622K(943744K), 0.0312849 secs] 3074543K->2238776K(4089472K), 0.0316707 secs] [Times: user=0.14 sys=0.00, real=0.04 secs] </div><div class="line">2019-03-21T11:19:41.641+0800: 1086346.008: Total time for which application threads were stopped: 0.0339016 seconds, Stopping threads took: 0.0002451 seconds</div><div class="line">2019-03-21T11:19:41.641+0800: 1086346.008: Application time: 0.0000710 seconds</div><div class="line">2019-03-21T11:19:41.643+0800: 1086346.010: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2202154K(3145728K)] 2238847K(4089472K), 0.0057407 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] </div><div class="line">2019-03-21T11:19:41.649+0800: 1086346.015: Total time for which application threads were stopped: 0.0076999 seconds, Stopping threads took: 0.0002899 seconds</div><div class="line">2019-03-21T11:19:41.649+0800: 1086346.016: [CMS-concurrent-mark-start]</div><div class="line">2019-03-21T11:19:41.915+0800: 1086346.282: [CMS-concurrent-mark: 0.266/0.266 secs] [Times: user=0.64 sys=0.01, real=0.27 secs] </div><div class="line">2019-03-21T11:19:41.915+0800: 1086346.282: [CMS-concurrent-preclean-start]</div><div class="line">2019-03-21T11:19:41.968+0800: 1086346.335: [CMS-concurrent-preclean: 0.049/0.053 secs] [Times: user=0.06 sys=0.00, real=0.05 secs] </div><div class="line">2019-03-21T11:19:41.968+0800: 1086346.335: [CMS-concurrent-abortable-preclean-start]</div><div class="line">2019-03-21T11:19:45.263+0800: 1086349.630: Application time: 3.6144054 seconds</div><div class="line">2019-03-21T11:19:45.268+0800: 1086349.635: Total time for which application threads were stopped: 0.0049627 seconds, Stopping threads took: 0.0002311 seconds</div><div class="line">2019-03-21T11:19:45.268+0800: 1086349.635: Application time: 0.0000508 seconds</div><div class="line">2019-03-21T11:19:45.269+0800: 1086349.636: Total time for which application threads were stopped: 0.0010917 seconds, Stopping threads took: 0.0001300 seconds</div><div class="line"> CMS: abort preclean due to time 2019-03-21T11:19:47.067+0800: 1086351.434: [CMS-concurrent-abortable-preclean: 4.876/5.099 secs] [Times: user=6.58 sys=0.02, real=5.10 secs] </div><div class="line">2019-03-21T11:19:47.067+0800: 1086351.434: Application time: 1.7978130 seconds</div><div class="line">2019-03-21T11:19:47.069+0800: 1086351.436: [GC (CMS Final Remark) [YG occupancy: 51879 K (943744 K)]2019-03-21T11:19:47.069+0800: 1086351.436: [GC (CMS Final Remark) 2019-03-21T11:19:47.069+0800: 1086351.436: [ParNew: 51879K->23393K(943744K), 0.0156467 secs] 2254034K->2226423K(4089472K), 0.0159200 secs] [Times: user=0.12 sys=0.00, real=0.02 secs] </div><div class="line">2019-03-21T11:19:47.085+0800: 1086351.452: [Rescan (parallel) , 0.0106023 secs]2019-03-21T11:19:47.096+0800: 1086351.462: [weak refs processing, 0.0000353 secs]2019-03-21T11:19:47.096+0800: 1086351.462: [class unloading, 0.0421021 secs]2019-03-21T11:19:47.138+0800: 1086351.505: [scrub symbol table, 0.0157111 secs]2019-03-21T11:19:47.153+0800: 1086351.520: [scrub string table, 0.0014866 secs][1 CMS-remark: 2203030K(3145728K)] 2226423K(4089472K), 0.0887818 secs] [Times: user=0.26 sys=0.01, real=0.09 secs] </div><div class="line">2019-03-21T11:19:47.158+0800: 1086351.525: Total time for which application threads were stopped: 0.0908702 seconds, Stopping threads took: 0.0002256 seconds</div><div class="line">2019-03-21T11:19:47.158+0800: 1086351.525: [CMS-concurrent-sweep-start]</div><div class="line">2019-03-21T11:19:47.350+0800: 1086351.717: [CMS-concurrent-sweep: 0.192/0.192 secs] [Times: user=0.31 sys=0.00, real=0.20 secs] </div><div class="line">2019-03-21T11:19:47.351+0800: 1086351.717: [CMS-concurrent-reset-start]</div><div class="line">2019-03-21T11:19:47.356+0800: 1086351.723: [CMS-concurrent-reset: 0.005/0.005 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] </div><div class="line">2019-03-21T11:19:48.158+0800: 1086352.525: Application time: 1.0000965 seconds</div><div class="line">2019-03-21T11:19:48.160+0800: 1086352.527: Total time for which application threads were stopped: 0.0019235 seconds, Stopping threads took: 0.0002317 seconds</div><div class="line">2019-03-21T11:19:49.356+0800: 1086353.723: Application time: 1.1961818 seconds</div><div class="line">2019-03-21T11:19:49.358+0800: 1086353.725: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2202654K(3145728K)] 2234684K(4089472K), 0.0023390 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] </div><div class="line">2019-03-21T11:19:49.360+0800: 1086353.727: Total time for which application threads were stopped: 0.0043086 seconds, Stopping threads took: 0.0002062 seconds</div><div class="line">2019-03-21T11:19:49.360+0800: 1086353.727: [CMS-concurrent-mark-start]</div><div class="line">2019-03-21T11:19:49.623+0800: 1086353.990: [CMS-concurrent-mark: 0.262/0.262 secs] [Times: user=0.55 sys=0.00, real=0.27 secs] </div><div class="line">2019-03-21T11:19:49.623+0800: 1086353.990: [CMS-concurrent-preclean-start]</div><div class="line">2019-03-21T11:19:49.689+0800: 1086354.056: [CMS-concurrent-preclean: 0.062/0.066 secs] [Times: user=0.10 sys=0.00, real=0.06 secs]</div></pre></td></tr></table></figure>
<p>观察频繁<code>CMS GC</code>相邻间隔时间8秒左右,检查<code>CMS GC</code>回收前后老年代内存使用情况:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">2019-03-21T11:19:41.643+0800: 1086346.010: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2202154K(3145728K)] 2238847K(4089472K), 0.0057407 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] </div><div class="line">2019-03-21T11:19:49.358+0800: 1086353.725: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2202654K(3145728K)] 2234684K(4089472K), 0.0023390 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]</div></pre></td></tr></table></figure>
<p>老年代容量为3145728K,第一次在使用了2202154K时触发了<code>CMS GC</code>初始标记操作,第二次在使用了2202654K后触发<code>CMS GC</code>初始标记操作;2次<code>CMS GC</code>之间老年代反而增加了0.5M大小,初步怀疑方向:</p>
<ul>
<li><code>CMSInitiatingOccupancyFraction</code>比例大小,导致频繁触发</li>
<li>该段时间有大量内存转移到老年代</li>
<li>堆内存泄漏</li>
</ul>
<p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1g1ea5brnq0j317o0ke411.jpg" alt=""></p>
<p>这张图比较直观展示老年代内存一直维持在2.1G左右,<code>GC</code>前后并没有降低老年代大小,而且这段时间并没有大量并发请求,怀疑堆内存泄漏。</p>
<h4 id="分析heap-dump"><a href="#分析heap-dump" class="headerlink" title="分析heap dump"></a>分析<code>heap dump</code></h4><p>选择一台服务器,联系运维<code>dump heap</code>(2.4G),重启全部服务器(内存直线掉下来,老年代200M。。。)</p>
<p>使用工具<code>VisualVm</code>或者<code>Eclipse MAT</code>分析<code>dump</code>日志</p>
<h4 id="使用VisualVM"><a href="#使用VisualVM" class="headerlink" title="使用VisualVM"></a>使用<code>VisualVM</code></h4><ul>
<li>检查堆内存大对象:</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/006tKfTcgy1g1f0vdxwdqj31jc0hwwka.jpg" alt=""></p>
<p>可见共有377094个<code>double[]</code>数组占有内存2G左右,<code>retained size</code>是该对象被GC之后所能回收到内存的总和;怀疑<code>double[]</code>对象泄漏,没有被回收</p>
<ul>
<li>查看该对象的<code>GC Root</code>:</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/006tKfTcgy1g1f1ip6yf2j31gh0u0n4r.jpg" alt=""></p>
<p>上图可以看出该<code>double[]</code>被<code>DataBuffer</code>直接引用,最后被缓存在<code>Guava LocalCache</code>中;从<code>GC Root</code>可以看出,有<code>ScheudleTask</code>(<code>DataPublisher</code>中创建)引用了该对象</p>
<h3 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h3><ul>
<li>进入<code>DataPublisher</code>类中,检查<code>ScheudleTask</code>创建过程:</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (future == <span class="keyword">null</span>) {</div><div class="line"> Runnable task = <span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 主要做计算统计数据</span></div><div class="line"> accumulator.publish();</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> handleException(e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> };</div><div class="line"> <span class="comment">// 从上下文看,延迟1秒定期执行一次</span></div><div class="line"> future = getExecutor().scheduleWithFixedDelay(task,</div><div class="line"> delayMillis, delayMillis,</div><div class="line"> TimeUnit.MILLISECONDS);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">synchronized</span> ScheduledExecutorService <span class="title">getExecutor</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (sharedExecutor == <span class="keyword">null</span>) {</div><div class="line"> sharedExecutor = Executors.newScheduledThreadPool(<span class="number">1</span>, <span class="keyword">new</span> PublishThreadFactory());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> sharedExecutor;</div><div class="line"> }</div></pre></td></tr></table></figure>
<ul>
<li>逆向检查<code>start()</code>调用</li>
</ul>
<p>由<code>ServerStats</code>类<code>initialize</code>方法触发</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">initialize</span><span class="params">(Server server)</span> </span>{</div><div class="line"> serverFailureCounts = <span class="keyword">new</span> MeasuredRate(failureCountSlidingWindowInterval);</div><div class="line"> requestCountInWindow = <span class="keyword">new</span> MeasuredRate(<span class="number">300000L</span>);</div><div class="line"> <span class="keyword">if</span> (publisher == <span class="keyword">null</span>) {</div><div class="line"> <span class="comment">// DataBuffer实际存在于DataDistribution中,bufferSize=1000</span></div><div class="line"> dataDist = <span class="keyword">new</span> DataDistribution(getBufferSize(), PERCENTS);</div><div class="line"> <span class="comment">// DataPublisher间接引用了DataBuffer</span></div><div class="line"> publisher = <span class="keyword">new</span> DataPublisher(dataDist, getPublishIntervalMillis());</div><div class="line"> <span class="comment">// 触发调用</span></div><div class="line"> publisher.start();</div><div class="line"> }</div><div class="line"> <span class="keyword">this</span>.server = server;</div><div class="line"> }</div></pre></td></tr></table></figure>
<ul>
<li>逆向检查<code>initialize()</code>调用</li>
</ul>
<p>调用关系如下:</p>
<p><img src="https://tva1.sinaimg.cn/large/006tKfTcgy1g1f27thkyjj30vm042jsn.jpg" alt=""></p>
<p>由<code>LoadBalancerStats</code>类<code>createServerStats</code>方法触发</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> ServerStats <span class="title">createServerStats</span><span class="params">(Server server)</span> </span>{</div><div class="line"> <span class="comment">// 创建ServerStats并初始化</span></div><div class="line"> ServerStats ss = <span class="keyword">new</span> ServerStats(<span class="keyword">this</span>);</div><div class="line"> ss.setBufferSize(<span class="number">1000</span>);</div><div class="line"> ss.setPublishInterval(<span class="number">1000</span>); </div><div class="line"> ss.initialize(server);</div><div class="line"> <span class="keyword">return</span> ss; </div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">private</span> ServerStats <span class="title">getServerStats</span><span class="params">(Server server)</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 从localcache缓存中获取,如果没有</span></div><div class="line"> <span class="comment">// 则调用createServerStats创建ServerStats并缓存</span></div><div class="line"> <span class="keyword">return</span> serverStatsCache.get(server);</div><div class="line"> } <span class="keyword">catch</span> (ExecutionException e) {</div><div class="line"> ServerStats stats = createServerStats(server);</div><div class="line"> serverStatsCache.asMap().putIfAbsent(server, stats);</div><div class="line"> <span class="keyword">return</span> serverStatsCache.asMap().get(server);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// localcache在这里。。。,key是Server, value是ServerStats</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> LoadingCache<Server, ServerStats> serverStatsCache = </div><div class="line"> CacheBuilder.newBuilder()</div><div class="line"> .expireAfterAccess(SERVERSTATS_EXPIRE_MINUTES.get(), TimeUnit.MINUTES)</div><div class="line"> .removalListener(<span class="keyword">new</span> RemovalListener<Server, ServerStats>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onRemoval</span><span class="params">(RemovalNotification<Server, ServerStats> notification)</span> </span>{</div><div class="line"> notification.getValue().close();</div><div class="line"> }</div><div class="line"> })</div><div class="line"> .build(</div><div class="line"> <span class="keyword">new</span> CacheLoader<Server, ServerStats>() {</div><div class="line"> <span class="comment">// 缓存没有则创建ServerStats</span></div><div class="line"> <span class="function"><span class="keyword">public</span> ServerStats <span class="title">load</span><span class="params">(Server server)</span> </span>{</div><div class="line"> <span class="keyword">return</span> createServerStats(server);</div><div class="line"> }</div><div class="line"> });</div></pre></td></tr></table></figure>
<p>可以看出<code>bufferSize=1000</code>,<code>Heap dump</code>中每个<code>double[]</code>元素也是1000;而且,定时任务时间间隔为1秒</p>
<p><strong>从缓存<code>localcache</code>的角度来看,如果元素一直增加,说明一直有新的<code>Server</code>被创建并添加到缓存中;但是据同事了解我们的<code>Server</code>只有10来个,缓存正常不会一直增长;怀疑是否代码问题导致不停创建新的<code>Server</code>?</strong></p>
<ul>
<li>检查<code>getServerStats</code>调用关系</li>
</ul>
<p>入口调用代码如下:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">call</span><span class="params">(<span class="keyword">final</span> String path, Map<String, Object> param, Integer uploadType)</span> </span>{</div><div class="line"> <span class="keyword">return</span> LoadBalancerCommand.<String> builder().withLoadBalancer(loadBalancer).build()</div><div class="line"> .submit(<span class="keyword">new</span> ServerOperation<String>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Observable<String> <span class="title">call</span><span class="params">(Server server)</span> </span>{</div><div class="line"> URL url;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> String s = HttpClientUtil.post(<span class="string">"http://"</span> + server.getHost() + <span class="string">":"</span> + server.getPort() + path, param);</div><div class="line"> <span class="keyword">return</span> Observable.just(s);</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> logger.info(<span class="string">"python identify voice failed! host {}"</span>, server.getHost());</div><div class="line"> <span class="keyword">return</span> Observable.error(e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }).toBlocking().first();</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>重点观察<code>LoadBalancerCommand.submit()</code>方法:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"> <span class="function"><span class="keyword">public</span> Observable<T> <span class="title">submit</span><span class="params">(<span class="keyword">final</span> ServerOperation<T> operation)</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">// Use the load balancer</span></div><div class="line"> Observable<T> o = </div><div class="line"> (server == <span class="keyword">null</span> ? selectServer() : Observable.just(server))</div><div class="line"> .concatMap(<span class="keyword">new</span> Func1<Server, Observable<T>>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="comment">// Called for each server being selected</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Observable<T> <span class="title">call</span><span class="params">(Server server)</span> </span>{</div><div class="line"> context.setServer(server);</div><div class="line"> <span class="comment">// getServerStats的入口在这里。。。</span></div><div class="line"> <span class="keyword">final</span> ServerStats stats = loadBalancerContext.getServerStats(server);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>观察<code>LoadBalancerContext.getServerStats()</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ServerStats <span class="title">getServerStats</span><span class="params">(Server server)</span> </span>{</div><div class="line"> ServerStats serverStats = <span class="keyword">null</span>;</div><div class="line"> <span class="comment">// LoadBalancerContext持有LoadBalancer对象</span></div><div class="line"> ILoadBalancer lb = <span class="keyword">this</span>.getLoadBalancer();</div><div class="line"> <span class="keyword">if</span> (lb <span class="keyword">instanceof</span> AbstractLoadBalancer){</div><div class="line"> <span class="comment">// LoadBalancer持有LoadBalancerStats对象</span></div><div class="line"> LoadBalancerStats lbStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats();</div><div class="line"> serverStats = lbStats.getSingleServerStat(server);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> serverStats;</div><div class="line"> }</div></pre></td></tr></table></figure>
<ul>
<li>检查<code>LoadBalancer</code>创建过程</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> initLoadbalance();</div><div class="line"> <span class="comment">// 每隔3分钟重新创建Loadbalance</span></div><div class="line"> service.scheduleAtFixedRate(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> initLoadbalance();</div><div class="line"> }</div><div class="line"> }, <span class="number">3</span>, <span class="number">3</span>, TimeUnit.MINUTES);</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * 初始化普通机器负载均衡策略</div><div class="line"> */</div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initLoadbalance</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> List<Server> serverList = <span class="keyword">new</span> ArrayList<>();</div><div class="line"> String machine = footballConfigFamilyService.getConfigMap().get(<span class="string">"voice-analyze"</span>);</div><div class="line"> List<Weight> list = JSON.parseObject(machine, <span class="keyword">new</span> TypeReference<List<Weight>>() {</div><div class="line"> });</div><div class="line"> <span class="comment">// serverList只有10来个</span></div><div class="line"> <span class="keyword">for</span> (Weight weight : list) {</div><div class="line"> serverList.add(<span class="keyword">new</span> WeightServer(weight.getHost(), weight.getPort(), weight.getWeight()));</div><div class="line"> }</div><div class="line"> <span class="comment">// 创建loadBalancer</span></div><div class="line"> loadBalancer = LoadBalancerBuilder.newBuilder().withRule(<span class="keyword">new</span> WeightedRule())</div><div class="line"> .buildFixedServerListLoadBalancer(serverList);</div><div class="line"> </div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>观察<code>LoadBalancerBuilder.buildFixedServerListLoadBalancer()</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"> <span class="function"><span class="keyword">public</span> BaseLoadBalancer <span class="title">buildFixedServerListLoadBalancer</span><span class="params">(List<T> servers)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (rule == <span class="keyword">null</span>) {</div><div class="line"> rule = createRuleFromConfig(config);</div><div class="line"> }</div><div class="line"> <span class="comment">// 创建BaseLoadBalancer</span></div><div class="line"> BaseLoadBalancer lb = <span class="keyword">new</span> BaseLoadBalancer(config, rule, ping);</div><div class="line"> <span class="comment">// 设置负载均衡服务器列表</span></div><div class="line"> lb.setServersList(servers);</div><div class="line"> <span class="keyword">return</span> lb;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BaseLoadBalancer</span><span class="params">(IClientConfig config, IRule rule, IPing ping)</span> </span>{</div><div class="line"> initWithConfig(config, rule, ping);</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">initWithConfig</span><span class="params">(IClientConfig clientConfig, IRule rule, IPing ping)</span> </span>{</div><div class="line"> setLoadBalancerStats(<span class="keyword">new</span> LoadBalancerStats(clientName));</div><div class="line"> rule.setLoadBalancer(<span class="keyword">this</span>);</div><div class="line"> <span class="keyword">if</span> (ping <span class="keyword">instanceof</span> AbstractLoadBalancerPing) {</div><div class="line"> ((AbstractLoadBalancerPing) ping).setLoadBalancer(<span class="keyword">this</span>);</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>从上面代码看,每隔3分钟就会重新创建新的<code>LoadBalancer</code>,每创建一个<code>LoadBalancer</code>,都会创建<code>LoadBalancerStats</code>作为<code>LoadBalancer</code>属性</p>
<p>难道每隔3分钟时间创建的<code>Server</code>对<code>Guava LocalCache</code>来说都是不同的吗?窃喜,感觉问题已经找到😄</p>
<p>现在需要确定2个<code>WeightServer</code>对象,具有相同的<code>ip</code>和<code>port</code>, 在<code>LocalCache</code>中是一个吗?</p>
<ul>
<li>检查<code>WeightServer</code></li>
</ul>
<p>具体的<code>guava cache</code> 源码不再叙述,整体逻辑设计参考<code>CurrentHashMap</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Server</span><span class="params">(String host, <span class="keyword">int</span> port)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.host = host;</div><div class="line"> <span class="keyword">this</span>.port = port;</div><div class="line"> <span class="keyword">this</span>.id = host + <span class="string">":"</span> + port;</div><div class="line"> isAliveFlag = <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object obj)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">this</span> == obj)</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keyword">if</span> (!(obj <span class="keyword">instanceof</span> Server))</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> Server svc = (Server) obj;</div><div class="line"> <span class="keyword">return</span> svc.getId().equals(<span class="keyword">this</span>.getId());</div><div class="line"></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">int</span> hash = <span class="number">7</span>;</div><div class="line"> hash = <span class="number">31</span> * hash + (<span class="keyword">null</span> == <span class="keyword">this</span>.getId() ? <span class="number">0</span> : <span class="keyword">this</span>.getId().hashCode());</div><div class="line"> <span class="keyword">return</span> hash;</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>从上面看,2个<code>WeightServer</code>如果<code>ip</code>和<code>port</code>相同,经测试<code>cache</code>元素只有一个,<code>guava cache</code>认为是同一个元素。尴尬,猜错了😅。。。</p>
<h4 id="重新检查代码"><a href="#重新检查代码" class="headerlink" title="重新检查代码"></a>重新检查代码</h4><p>既然<code>heap dump</code>显示<code>cache</code>中的<code>Server</code>在不停增加,实际情况却是一个<code>cache</code>中只会有10来个<code>Server</code>;突然想起来,<strong>每个<code>LoadBalancerStats</code>对象都有一个<code>cache</code>对象</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">BaseLoadBalancer</span><span class="params">(IClientConfig config, IRule rule, IPing ping)</span> </span>{</div><div class="line"> initWithConfig(config, rule, ping);</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">initWithConfig</span><span class="params">(IClientConfig clientConfig, IRule rule, IPing ping)</span> </span>{</div><div class="line"> setLoadBalancerStats(<span class="keyword">new</span> LoadBalancerStats(clientName));</div><div class="line"> rule.setLoadBalancer(<span class="keyword">this</span>);</div><div class="line"> <span class="keyword">if</span> (ping <span class="keyword">instanceof</span> AbstractLoadBalancerPing) {</div><div class="line"> ((AbstractLoadBalancerPing) ping).setLoadBalancer(<span class="keyword">this</span>);</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><strong>原因:定时任务执行<code>initLoadbalance()</code>,导致不停创建新的<code>LoadBalancer</code>,即<code>LoadBalancerStats</code>一直增加,全部<code>cache</code>缓存的<code>Server</code>也会增加</strong></p>
<p>这里有一个问题,旧的<code>cache</code>为什么不被<code>GC</code>回收呢?回过头来看<code>DataPublisher.start()</code>方法:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (future == <span class="keyword">null</span>) {</div><div class="line"> Runnable task = <span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> accumulator.publish();</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> handleException(e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> };</div><div class="line"> future = getExecutor().scheduleWithFixedDelay(task,</div><div class="line"> delayMillis, delayMillis,</div><div class="line"> TimeUnit.MILLISECONDS);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">synchronized</span> ScheduledExecutorService <span class="title">getExecutor</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (sharedExecutor == <span class="keyword">null</span>) {</div><div class="line"> sharedExecutor = Executors.newScheduledThreadPool(<span class="number">1</span>, <span class="keyword">new</span> PublishThreadFactory());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> sharedExecutor;</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>从上面的代码看出,<strong>每个<code>Server</code>都关联了一个线程池执行定时任务,导致<code>cache</code>中<code>Server</code>对象一直被引用,<code>GC</code>不会回收这类对象。</strong></p>
<h4 id="比较有意思的地方:"><a href="#比较有意思的地方:" class="headerlink" title="比较有意思的地方:"></a>比较有意思的地方:</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 过期时间默认30分钟</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> LoadingCache<Server, ServerStats> serverStatsCache = </div><div class="line"> CacheBuilder.newBuilder()</div><div class="line"> .expireAfterAccess(SERVERSTATS_EXPIRE_MINUTES.get(), TimeUnit.MINUTES)</div><div class="line"> .removalListener(<span class="keyword">new</span> RemovalListener<Server, ServerStats>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onRemoval</span><span class="params">(RemovalNotification<Server, ServerStats> notification)</span> </span>{</div><div class="line"> notification.getValue().close();</div><div class="line"> }</div><div class="line"> })</div><div class="line"> .build(</div><div class="line"> <span class="keyword">new</span> CacheLoader<Server, ServerStats>() {</div><div class="line"> <span class="comment">// 缓存没有则创建ServerStats</span></div><div class="line"> <span class="function"><span class="keyword">public</span> ServerStats <span class="title">load</span><span class="params">(Server server)</span> </span>{</div><div class="line"> <span class="keyword">return</span> createServerStats(server);</div><div class="line"> }</div><div class="line"> });</div></pre></td></tr></table></figure>
<p>这里的<code>guava cache</code>使用了<code>expireAfterAccess</code>和<code>removalListener</code>, 我猜<a href="[https://github.com/Netflix/ribbon](https://github.com/Netflix/ribbon"><code>robbin</code>框架</a>)作者本意是使用过期函数以及监听器在<code>Server</code>失效后关闭线程池运行,防止线程一直运行;但是<code>guava cache</code>的过期删除是被动的,就是说如果元素过期后再次被访问,会触发删除并重新加载</p>
<p><strong>以该项目代码执行来看,只会一直添加新的<code>cache</code>,旧的<code>cache</code>不能被访问,导致缓存对象不能释放</strong></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><ul>
<li>避免<code>LoadBalancer</code>不停的创建,覆盖<code>ServerList</code>即可</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initLoadbalance</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> List<Server> serverList = <span class="keyword">new</span> ArrayList<>();</div><div class="line"> String machine = footballConfigFamilyService.getConfigMap().get(<span class="string">"voice-analyze"</span>);</div><div class="line"> List<Weight> list = JSON.parseObject(machine, <span class="keyword">new</span> TypeReference<List<Weight>>() {</div><div class="line"> });</div><div class="line"> <span class="comment">// serverList只有10来个</span></div><div class="line"> <span class="keyword">for</span> (Weight weight : list) {</div><div class="line"> serverList.add(<span class="keyword">new</span> WeightServer(weight.getHost(), weight.getPort(), weight.getWeight()));</div><div class="line"> }</div><div class="line"> <span class="comment">// **********代码修复处************</span></div><div class="line"> <span class="keyword">if</span>(loadBalancer == <span class="keyword">null</span>){</div><div class="line"> loadBalancer = LoadBalancerBuilder.newBuilder().withRule(<span class="keyword">new</span> WeightedRule())</div><div class="line"> .buildFixedServerListLoadBalancer(serverList);</div><div class="line"> }<span class="keyword">else</span>{</div><div class="line"> <span class="comment">// 覆盖旧的server list</span></div><div class="line"> loadBalancer.setServersList(serverList);</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>使用第三方框架,一定要了解底层运行机制。😄</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/02/13/Netty-服务端启动/" itemprop="url">
Netty 服务端启动
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2019-02-13T14:17:51+08:00" content="2019-02-13">
2019-02-13
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="服务端启动代码"><a href="#服务端启动代码" class="headerlink" title="服务端启动代码"></a>服务端启动代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">EventLoopGroup bossGroup = <span class="keyword">new</span> NioEventLoopGroup(<span class="number">1</span>);</div><div class="line">EventLoopGroup workerGroup = <span class="keyword">new</span> NioEventLoopGroup();</div><div class="line">ServerBootstrap bootstrap = <span class="keyword">new</span> ServerBootstrap();</div><div class="line">bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)</div><div class="line"> .childHandler(<span class="keyword">new</span> ChannelInitializer<SocketChannel>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> }</div><div class="line"> }).option(ChannelOption.SO_BACKLOG, <span class="number">128</span>).childOption(ChannelOption.SO_KEEPALIVE, <span class="keyword">true</span>);</div><div class="line"></div><div class="line">ChannelFuture channelFuture = bootstrap.bind(<span class="keyword">new</span> InetSocketAddress(port)).sync();</div></pre></td></tr></table></figure>
<h3 id="启动过程分析"><a href="#启动过程分析" class="headerlink" title="启动过程分析"></a>启动过程分析</h3><p>跟踪<code>ServerBootstrap.bind()</code>方法,追到<code>AbstractBootstrap.doBind()</code>:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> ChannelFuture <span class="title">doBind</span><span class="params">(<span class="keyword">final</span> SocketAddress localAddress)</span> </span>{</div><div class="line"> <span class="comment">// 创建channel并且注册selector</span></div><div class="line"> <span class="keyword">final</span> ChannelFuture regFuture = initAndRegister();</div><div class="line"> <span class="keyword">final</span> Channel channel = regFuture.channel();</div><div class="line"> <span class="keyword">if</span> (regFuture.cause() != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">return</span> regFuture;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (regFuture.isDone()) {</div><div class="line"> <span class="comment">// At this point we know that the registration was complete and successful.</span></div><div class="line"> ChannelPromise promise = channel.newPromise();</div><div class="line"> <span class="comment">// 将ServerSocketChannel绑定到本地的端口</span></div><div class="line"> doBind0(regFuture, channel, localAddress, promise);</div><div class="line"> <span class="keyword">return</span> promise;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>跟踪<code>initAndRegister()</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">Channel channel = <span class="keyword">null</span>;</div><div class="line"><span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 创建channel</span></div><div class="line"> channel = channelFactory.newChannel();</div><div class="line"> <span class="comment">// 初始化channel</span></div><div class="line"> init(channel);</div><div class="line">} <span class="keyword">catch</span> (Throwable t) {</div><div class="line"> <span class="keyword">if</span> (channel != <span class="keyword">null</span>) {</div><div class="line"> channel.unsafe().closeForcibly();</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> DefaultChannelPromise(<span class="keyword">new</span> FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);</div><div class="line">}</div><div class="line"><span class="comment">// 注册channel到selector</span></div><div class="line">ChannelFuture regFuture = config().group().register(channel);</div></pre></td></tr></table></figure>
<p>这里先通过工厂创建了一个<code>Channel</code>(这里服务端指定类型<code>NioServerSocketChannel</code>),然后初始化这个<code>Channel</code>。最后把这个<code>Channel</code>注册到<code>EventLoopGroup</code>上(<code>config().group()</code>返回的是我们设置的<code>Boss Group</code>)</p>
<h4 id="创建channel"><a href="#创建channel" class="headerlink" title="创建channel"></a>创建<code>channel</code></h4><p>查看<code>NioServerSocketChannel</code>构造方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioServerSocketChannel</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">//调用下面这个方法</span></div><div class="line"> <span class="keyword">this</span>(newSocket(DEFAULT_SELECTOR_PROVIDER));</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioServerSocketChannel</span><span class="params">(ServerSocketChannel channel)</span> </span>{</div><div class="line"> <span class="comment">//注意,这里是初始化了一个对SelectionKey.OP_ACCEPT感兴趣的管道</span></div><div class="line"> <span class="comment">//Boss的Reactor线程轮询到都是ACCEPT事件</span></div><div class="line"> <span class="keyword">super</span>(<span class="keyword">null</span>, channel, SelectionKey.OP_ACCEPT);</div><div class="line"> config = <span class="keyword">new</span> NioServerSocketChannelConfig(<span class="keyword">this</span>, javaChannel().socket());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> ServerSocketChannel <span class="title">newSocket</span><span class="params">(SelectorProvider provider)</span> </span>{</div><div class="line"> <span class="keyword">return</span> provider.openServerSocketChannel();</div><div class="line">}</div></pre></td></tr></table></figure>
<p><code>newSocket</code>使用<code>SelectorProvider</code>的<code>openServerSocketChannel</code>打开服务器套接字通道。<code>SelectorProvider</code>主要工作是根据操作系统类型和版本选择合适的<code>Provider</code>:如果<code>Linux</code>内核版本>=2.6则,具体的<code>SelectorProvider</code>为<code>EPollSelectorProvider</code>,否则为默认的<code>PollSelectorProvider</code></p>
<p>继续追溯<code>super</code>方法到<code>AbstractChannel</code>,如下方法:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="title">AbstractChannel</span><span class="params">(Channel parent)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.parent = parent;</div><div class="line"> <span class="comment">// ChannelId是一个全局唯一的值</span></div><div class="line"> id = newId();</div><div class="line"> unsafe = newUnsafe();</div><div class="line"> pipeline = newChannelPipeline();</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里主要做了2件事:</p>
<ul>
<li><p>创建<code>NioMessageUnsafe</code>实例(客户端创建<code>NioByteUnsafe</code>),该类为<code>Channel</code>提供了用于完成网络通讯相关的底层操作,如<code>connect(),read(),register(),bind(),close()</code>等;</p>
</li>
<li><p>给该<code>Channel</code>创建<code>DefaultChannelPipeline</code>并初始化,这里用的是默认<code>Pipeline</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="title">DefaultChannelPipeline</span><span class="params">(Channel channel)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.channel = ObjectUtil.checkNotNull(channel, <span class="string">"channel"</span>);</div><div class="line"> succeededFuture = <span class="keyword">new</span> SucceededChannelFuture(channel, <span class="keyword">null</span>);</div><div class="line"> voidPromise = <span class="keyword">new</span> VoidChannelPromise(channel, <span class="keyword">true</span>);</div><div class="line"></div><div class="line"> <span class="comment">// 创建tail</span></div><div class="line"> tail = <span class="keyword">new</span> TailContext(<span class="keyword">this</span>);</div><div class="line"> <span class="comment">// 创建head </span></div><div class="line"> head = <span class="keyword">new</span> HeadContext(<span class="keyword">this</span>);</div><div class="line"></div><div class="line"> head.next = tail;</div><div class="line"> tail.prev = head;</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>具体的<code>ChannelPipeline</code>执行流程看<a href="https://zsr.github.io/2018/12/15/Netty-核心组件/">前篇文章</a></p>
</li>
</ul>
<p>到这里整个<code>Channel</code>创建就结束了。</p>
<p>总结一下:</p>
<ol>
<li>通过工厂创建一个<code>Channel</code>,这里的<code>Channel</code>由于是在服务端,使用的是<code>NioServerSocketChannel</code>,</li>
<li>这个管道的构造方法中,会初始化一个原生的<code>ServerSocketChannel</code>,最后初始化<code>pipeline</code>和<code>unsafe</code>。</li>
<li>初始化<code>pipeline</code>的时候,又会初始化<code>pipeline</code>中的<code>Head</code>和<code>Tail</code>。</li>
</ol>
<h4 id="初始化channel"><a href="#初始化channel" class="headerlink" title="初始化channel"></a>初始化<code>channel</code></h4><p>服务端<code>init()</code>实现方法在<code>ServerBootstrap</code>中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">init</span><span class="params">(Channel channel)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="comment">// 添加一个handle-ChannelInitializer</span></div><div class="line"> p.addLast(<span class="keyword">new</span> ChannelInitializer<Channel>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="comment">// 这里还并未执行initChannel方法,该方法是在执行回调方法handlerAdded后调用的</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(<span class="keyword">final</span> Channel ch)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">final</span> ChannelPipeline pipeline = ch.pipeline();</div><div class="line"> ChannelHandler handler = config.handler();</div><div class="line"> <span class="keyword">if</span> (handler != <span class="keyword">null</span>) {</div><div class="line"> pipeline.addLast(handler);</div><div class="line"> }</div><div class="line"></div><div class="line"> ch.eventLoop().execute(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> pipeline.addLast(<span class="keyword">new</span> ServerBootstrapAcceptor(</div><div class="line"> ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>服务端的<code>init()</code>分为2个步骤:</p>
<ul>
<li>将<code>bootstrap</code>配置的属性设置到<code>Channel</code>上面</li>
<li>给<code>NioServerSocketChannel</code>的<code>pipeline</code>里,添加了一个<code>ChannelHandle</code>-<code>ChannelInitializer</code>,供注册后使用</li>
</ul>
<p>初始化结束,此时<code>NioServerSocketChannel</code>的<code>pipeline</code>链表结构为:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Head <--> ChannelInitializer <--> Tail</div></pre></td></tr></table></figure>
<h4 id="注册channel"><a href="#注册channel" class="headerlink" title="注册channel"></a>注册<code>channel</code></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">ChannelFuture regFuture = config().group().register(channel);</div></pre></td></tr></table></figure>
<p>主要将创建并初始化后的<code>Channel</code>注册到<code>selector</code>上面:</p>
<ul>
<li>将<code>Channel</code>注册到<code>EventLoop</code>的<code>selector</code>上;</li>
<li>触发<code>Pipeline</code>上面<code>ChannelHandler</code>的<code>channelRegistered</code></li>
</ul>
<p>这里的<code>config().group()</code>结果是<code>NioEventLoopGroup</code>,父类是<code>MultithreadEventLoopGroup</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"> <span class="function"><span class="keyword">public</span> ChannelFuture <span class="title">register</span><span class="params">(Channel channel)</span> </span>{</div><div class="line"> <span class="keyword">return</span> next().register(channel);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里的<code>next()</code>方法最后执行到<code>chooser.next()</code>,<code>chooser</code>有两种:<code>PowerOfTwoEventExecutorChooser</code>和<code>GenericEventExecutorChooser</code>。如果该<code>EventLoopGroup</code>中<code>EventLoop</code>是2的倍数则选择<code>PowerOfTwoEventExecutorChooser</code>(2的倍数方便位操作);因为这里的<code>Group</code>是<code>NioEventLoopGroup</code>,所以<code>next()</code>返回的是<code>NioEventLoop</code></p>
<p>继续看<code>NioEventLoop.register()</code>,具体方法实现在<code>SingleThreadEventLoop</code>中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">promise.channel().unsafe().register(<span class="keyword">this</span>, promise);</div><div class="line"><span class="keyword">return</span> promise;</div></pre></td></tr></table></figure>
<p>最终使用<code>channel</code>中的<code>unsafe()</code>注册,具体方法实现在<code>AbstractUnsafe</code>中:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">register0</span><span class="params">(ChannelPromise promise)</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">boolean</span> firstRegistration = neverRegistered;<span class="comment">// 是否为首次注册</span></div><div class="line"> <span class="comment">// 实际的注册动作, 把Channel感兴趣的事件注册到Eventloop.selector上面</span></div><div class="line"> doRegister();</div><div class="line"> neverRegistered = <span class="keyword">false</span>;</div><div class="line"> registered = <span class="keyword">true</span>;</div><div class="line"> pipeline.invokeHandlerAddedIfNeeded();<span class="comment">// 将注册之前加入的handler加入进来</span></div><div class="line"> safeSetSuccess(promise); <span class="comment">// 注册成功,通知promise</span></div><div class="line"> pipeline.fireChannelRegistered();<span class="comment">// pipeline通知触发注册成功</span></div><div class="line"> </div><div class="line"> <span class="keyword">if</span> (isActive()) { <span class="comment">// 是否已经绑定 因为register和bind阶段是异步的</span></div><div class="line"> <span class="keyword">if</span> (firstRegistration) { </div><div class="line"> pipeline.fireChannelActive(); <span class="comment">// 首次注册,通知</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (config().isAutoRead()) {</div><div class="line"> beginRead();</div><div class="line"> }</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (Throwable t) {</div><div class="line"> closeForcibly();</div><div class="line"> closeFuture.setClosed();</div><div class="line"> safeSetFailure(promise, t);</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><code>invokeHandlerAddedIfNeeded</code>: 注册成功后,找到初始化阶段通过<code>pipeline.addLast()</code>加入的<code>ChannelInitializer</code>,执行其<code>ChannelInitializer</code>的<code>initChannel</code>方法(添加<code>ChannelHandle</code>-<code>ServerBootstrapAcceptor</code>),之后将<code>ChannelInitializer</code> 在<code>pipeline</code>中删除</p>
<p>注册结束,此时<code>NioServerSocketChannel</code>的<code>pipeline</code>链表结构为:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Head <--> ServerBootstrapAcceptor <--> Tail</div></pre></td></tr></table></figure>
<p><code>fireChannelRegistered</code>沿着<code>pipeline</code>的<code>head</code>到<code>tail</code>,调用<code>ChannelHandler.channelRegistered</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelPipeline <span class="title">fireChannelRegistered</span><span class="params">()</span> </span>{</div><div class="line"> AbstractChannelHandlerContext.invokeChannelRegistered(head);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">invokeChannelRegistered</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (invokeHandler()) { <span class="comment">// 状态是否正确</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> ((ChannelInboundHandler) handler()).channelRegistered(<span class="keyword">this</span>); <span class="comment">// 触发</span></div><div class="line"> } <span class="keyword">catch</span> (Throwable t) {</div><div class="line"> notifyHandlerException(t);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> fireChannelRegistered();<span class="comment">// 状态不正确,通知下一个Handler</span></div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><code>fireChannelActive</code> 由于注册阶段和绑定<code>bind</code>阶段都是异步的,如果此时注册完成时<code>bind</code>阶段已经绑定了本地端口,会沿着<code>pipeline</code>的<code>head</code>到<code>tail</code>,调用各个<code>ChannelHandler</code>的<code>channelActive</code>方法</p>
<h4 id="bind本地地址"><a href="#bind本地地址" class="headerlink" title="bind本地地址"></a><code>bind</code>本地地址</h4><p>将<code>NioServerSocketChannel</code>内部<code>ServerSocketChannel</code>绑定到本地的端口上面,然后调用<code>fireChannelActive</code>通知<code>pipeline</code>里的<code>ChannelHandle</code>,执行其<code>channelActive</code>方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">doBind0</span><span class="params">(</span></span></div><div class="line"> <span class="keyword">final</span> ChannelFuture regFuture, <span class="keyword">final</span> Channel channel,</div><div class="line"> <span class="keyword">final</span> SocketAddress localAddress, <span class="keyword">final</span> ChannelPromise promise) {</div><div class="line"></div><div class="line"> <span class="comment">// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up</span></div><div class="line"> <span class="comment">// the pipeline in its channelRegistered() implementation.</span></div><div class="line"> channel.eventLoop().execute(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (regFuture.isSuccess()) {</div><div class="line"> channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> promise.setFailure(regFuture.cause());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><code>AbstractBootstrap</code>的<code>doBind0()</code>,内部会调用<code>pipeline</code>中的<code>bind</code>方法,逻辑为从<code>tail</code>出发调用<code>outbound</code>的<code>ChannelHandler</code>的<code>bind</code>方法。当前<code>pipeline</code>链表结构中只有<code>Head</code>是继承了<code>outbound</code>接口</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelFuture <span class="title">bind</span><span class="params">(SocketAddress localAddress, ChannelPromise promise)</span> </span>{</div><div class="line"> <span class="keyword">return</span> tail.bind(localAddress, promise);</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> ChannelFuture <span class="title">bind</span><span class="params">(<span class="keyword">final</span> SocketAddress localAddress, <span class="keyword">final</span> ChannelPromise promise)</span> </span>{</div><div class="line"> <span class="comment">// 找出下一个outbound,这里找出来的是HeadContext</span></div><div class="line"> <span class="keyword">final</span> AbstractChannelHandlerContext next = findContextOutbound();</div><div class="line"> EventExecutor executor = next.executor();</div><div class="line"> <span class="keyword">if</span> (executor.inEventLoop()) {</div><div class="line"> next.invokeBind(localAddress, promise);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> safeExecute(executor, <span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> next.invokeBind(localAddress, promise);</div><div class="line"> }</div><div class="line"> }, promise, <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> promise;</div><div class="line"> }</div></pre></td></tr></table></figure>
<p><code>Head</code>的<code>bind</code>方法由<code>NioServerSocketChannel</code>创建过程中生成的<code>unsafe</code>的实例<code>NioMessageUnsafe</code>来执行,该实例的<code>bind</code>方法继承于<code>AbstractUnsafe.bind()</code>,然后触发<code>fireChannelActive</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// AbstractUnsafe.class</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">bind</span><span class="params">(<span class="keyword">final</span> SocketAddress localAddress, <span class="keyword">final</span> ChannelPromise promise)</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">boolean</span> wasActive = isActive();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 把channel bind到本地地址</span></div><div class="line"> doBind(localAddress);</div><div class="line"> } <span class="keyword">catch</span> (Throwable t) {</div><div class="line"> safeSetFailure(promise, t);</div><div class="line"> closeIfClosed();</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// bind之后,这里的isActive会返回true</span></div><div class="line"> <span class="keyword">if</span> (!wasActive && isActive()) {</div><div class="line"> invokeLater(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Overridem</span> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">//在这里触发的是NioServerSocketChannel对应的pipeline的channelActive</span></div><div class="line"> pipeline.fireChannelActive();</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"></div><div class="line"> safeSetSuccess(promise);</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>这里的<code>doBind(localAddress)</code>方法由创建的<code>NioServerSocketChannel</code>执行</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doBind</span><span class="params">(SocketAddress localAddress)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">if</span> (PlatformDependent.javaVersion() >= <span class="number">7</span>) {</div><div class="line"> javaChannel().bind(localAddress, config.getBacklog());</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> javaChannel().socket().bind(localAddress, config.getBacklog());</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>这里把<code>java</code>原生的<code>ServerSocketChannel</code>绑定到本地地址上</p>
<p>绑定到本地地址后会执行<code>pipeline.fireChannelActive()</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// DefaultChannelPipeline.class</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelPipeline <span class="title">fireChannelActive</span><span class="params">()</span> </span>{</div><div class="line"> AbstractChannelHandlerContext.invokeChannelActive(head);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">invokeChannelActive</span><span class="params">(<span class="keyword">final</span> AbstractChannelHandlerContext next)</span> </span>{</div><div class="line"> EventExecutor executor = next.executor();</div><div class="line"> <span class="keyword">if</span> (executor.inEventLoop()) {</div><div class="line"> next.invokeChannelActive();</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> executor.execute(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> next.invokeChannelActive();</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">invokeChannelActive</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (invokeHandler()) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 这里handler()返回HeadContext</span></div><div class="line"> ((ChannelInboundHandler) handler()).channelActive(<span class="keyword">this</span>);</div><div class="line"> } <span class="keyword">catch</span> (Throwable t) {</div><div class="line"> notifyHandlerException(t);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> fireChannelActive();</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>这里的<code>channelInactive</code>会调用<code>HeadContext</code>类的<code>channelInactive</code>方法:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> ctx.fireChannelActive();</div><div class="line"> readIfIsAutoRead();</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里首先执行<code>fireChannelActive</code>方法,沿着<code>pipeline</code>传播给后续<code>ChannelInboundHandler</code>,执行<code>hannelActive()</code>方法;接着执行<code>readIfIsAutoRead()</code>,可以发现这个方法作用在<code>pipeline</code>上,从<code>tail</code>到<code>head</code>遍历<code>ChannelOutboundHandler</code>执行<code>read()</code>方法,我们这里只有<code>head</code>是<code>outBound</code>,<code>read()</code>方法最后进入<code>AbstractNioChannel.doBeginRead()</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doBeginRead</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">final</span> SelectionKey selectionKey = <span class="keyword">this</span>.selectionKey;</div><div class="line"> <span class="keyword">if</span> (!selectionKey.isValid()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> readPending = <span class="keyword">true</span>;</div><div class="line"> <span class="keyword">final</span> <span class="keyword">int</span> interestOps = selectionKey.interestOps();</div><div class="line"> <span class="keyword">if</span> ((interestOps & readInterestOp) == <span class="number">0</span>) { </div><div class="line"> <span class="comment">// interestOps是0,按位与结果肯定是0</span></div><div class="line"> selectionKey.interestOps(interestOps | readInterestOp);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>可以发现在执行<code>doRegister()</code>注册这个<code>Channel</code>到<code>selector</code>的时候,<code>selectKey</code>赋予了0</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doRegister</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">boolean</span> selected = <span class="keyword">false</span>;</div><div class="line"> <span class="keyword">for</span> (;;) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// selectKey赋予了0</span></div><div class="line"> selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="number">0</span>, <span class="keyword">this</span>);</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> } <span class="keyword">catch</span> (CancelledKeyException e) { </div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>这里<code>readInterestOp</code>这个哪来的:在创建<code>NioServerSocketChannel</code>的时候</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioServerSocketChannel</span><span class="params">(ServerSocketChannel channel)</span> </span>{</div><div class="line"> <span class="keyword">super</span>(<span class="keyword">null</span>, channel, SelectionKey.OP_ACCEPT);</div><div class="line"> config = <span class="keyword">new</span> NioServerSocketChannelConfig(<span class="keyword">this</span>, javaChannel().socket());</div><div class="line"> }</div></pre></td></tr></table></figure>
<p>在这里重新注册了<code>accept</code>事件,也就是说从此开始,<code>selector</code>再轮询到新的连接接入事件,就可以交给这个<code>NioServerSocketChannel</code>来处理了</p>
<p>至此,<code>Netty</code>服务器段已经启动,<code>Channel</code>和<code>ChannelPipeline</code>已经建立,<code>EventLoop</code>也在不断的<code>select()</code>查找准备好的I/O。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ol>
<li>通过工厂生成<code>NioServerSocketChannel</code>。</li>
<li>设置<code>NioServerSocketChannel</code>对<code>accept</code>事件感兴趣。此时<code>Channel</code>已经绑定了<code>unsafe</code>和<code>pipeline</code></li>
<li>初始化<code>Channel</code>,这里主要是设置<code>Channel</code>的<code>pipeline</code>中的<code>handler</code>、<code>attr</code>和<code>option</code>。这里的<code>handler</code>是特殊类型<code>ChannelInitializer</code>,等待注册后回调。</li>
<li>注册到<code>EventLoopGroup</code>里,最终是注册到某一个<code>NioEventLoop</code>上面</li>
<li>在注册时实际上的<code>register0</code>操作是异步的,<code>register0</code>的主要作用是把原生<code>Channel</code>注册到原生<code>selector</code>上。</li>
<li>执行<code>doBind0</code>也是异步只想,这里其实是和<code>register0</code>一起在执行的。<code>doBind0</code>是把<code>channel</code>注册到本地端口上</li>
<li>执行<code>pipeline.read()</code>方法,最终传递到<code>AbstractNioChannel</code>,重新向<code>selector</code>注册<code>OP_ACCEPT</code>事件</li>
</ol>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/12/19/Netty-零拷贝/" itemprop="url">
Netty 零拷贝
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2018-12-19T21:00:32+08:00" content="2018-12-19">
2018-12-19
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Zero-Copy describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.</div></pre></td></tr></table></figure>
<h3 id="磁盘数据通过网络发送"><a href="#磁盘数据通过网络发送" class="headerlink" title="磁盘数据通过网络发送"></a>磁盘数据通过网络发送</h3><h4 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h4><ol>
<li>当应用程序发起<code>read</code>系统调用时,通过<code>DMA(Direct Memory Access)</code>将数据<code>copy</code>到内核空间<code>buffer</code></li>
<li>然后由<code>CPU</code>控制将内核空间数据<code>copy</code>到用户空间下的 <code>buffer</code>中</li>
<li><code>read</code>调用完成后,<code>write</code>调用首先将用户空间下 <code>buffer</code>中的数据<code>copy</code>到内核模式下的<code>socket buffer</code>中</li>
<li>最后通过<code>DMA</code>将内核模式下的<code>socket buffer</code>中的数据<code>copy</code>到网卡<code>buffer</code>中</li>
</ol>
<h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><p>数据从内核模式到用户模式走了一圈,浪费了两次<code>copy</code>,而这两次<code>copy</code>都是<code>CPU copy</code>(占用<code>CPU</code>资源)</p>
<h3 id="操作系统-零拷贝"><a href="#操作系统-零拷贝" class="headerlink" title="操作系统 零拷贝"></a>操作系统 零拷贝</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">OS-level zero copy involves avoiding copying memory blocks from one location to another (typically from user space to kernel space) before sending data to the hardware driver (network card or disk drive) or vice versa.</div></pre></td></tr></table></figure>
<p>典型场景:内核空间和用户空间的数据拷贝</p>
<h4 id="Linux-sendfile"><a href="#Linux-sendfile" class="headerlink" title="Linux sendfile"></a><code>Linux sendfile</code></h4><p><strong>os >= Linux 2.4内核</strong></p>
<h5 id="步骤-1"><a href="#步骤-1" class="headerlink" title="步骤"></a>步骤</h5><ol>
<li><code>DMA copy</code>将磁盘数据<code>copy</code>到<code>kernel buffer</code>中</li>
<li>向<code>socket buffer</code>中追加当前要发送的数据在<code>kernel buffer</code>中的位置和偏移量</li>
<li><code>DMA gather copy</code>(需要网卡支持数据收集模式)根据<code>socket buffer</code>中的位置和偏移量直接将<code>kernel buffer</code>中的数据<code>copy</code>到网卡上</li>
</ol>
<h5 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h5><p>程序只需发出一次系统调用<code>sendfile()</code>,数据只经过了2次<code>copy</code>就从磁盘传送出去了,没有<code>CPU copy</code></p>
<p><code>nginx</code>,<code>java FileChannel.transferTo()</code>等在<code>Linux</code>系统中都引用了<code>sendfile</code>机制</p>
<h3 id="FileChannel-transferTo-Java中的零拷贝"><a href="#FileChannel-transferTo-Java中的零拷贝" class="headerlink" title="FileChannel.transferTo(Java中的零拷贝)"></a><code>FileChannel.transferTo</code>(<code>Java中的零拷贝</code>)</h3><p><code>Java NIO</code>中<code>FileChannel.transferTo(long position, long count, WriteableByteChannel target)</code>方法将当前通道中的数据传送到目标通道中,在支持<code>Zero-Copy</code>的<code>linux</code>系统中,<code>transferTo()</code>的实现依赖于<code>sendfile()</code>调用</p>
<p><img src="https://tva1.sinaimg.cn/large/006tNbRwgy1fymbp5iat5j30uo0imjsz.jpg" alt=""></p>
<h3 id="Netty零拷贝"><a href="#Netty零拷贝" class="headerlink" title="Netty零拷贝"></a><code>Netty</code>零拷贝</h3><ol>
<li><code>Netty</code>的接收和发送<code>ByteBuffer</code>采用<code>DIRECT BUFFERS</code>,使用堆外内存进行<code>Socket</code>读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(<code>HEAP BUFFERS</code>)进行<code>Socket</code>读写,<code>JVM</code>会将堆内存<code>Buffer</code>拷贝一份到直接内存中,然后才写入<code>Socket</code>中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。</li>
<li><p><code>Netty</code>提供了<code>CompositeByteBuf</code>对象,可以聚合多个<code>ByteBuffer</code>对象,用户可以像操作一个<code>Buffer</code>那样方便的对组合<code>Buffer</code>进行操作,避免了传统通过内存拷贝的方式将几个小<code>Buffer</code>合并成一个大的<code>Buffer</code>。</p>
</li>
<li><p><code>Netty</code>的文件传输采用了<code>java nio transferTo</code>方法,它可以直接将文件缓冲区的数据发送到目标<code>Channel</code>(传统的做法: 拷贝文件内容到临时 <code>buffer</code>, 然后再将 <code>buffer</code> 写入<code>Channel</code>),使用操作系统级别的零拷贝,避免了传统通过循环<code>write</code>方式导致的内存拷贝问题</p>
</li>
</ol>
<h4 id="CompositeByteBuf"><a href="#CompositeByteBuf" class="headerlink" title="CompositeByteBuf"></a><code>CompositeByteBuf</code></h4><p><img src="https://tva1.sinaimg.cn/large/006tNbRwgy1fynh0n2p3wj30l40bwmxi.jpg" alt=""></p>
<p><strong>注意: <code>CompositeByteBuf</code> 是由多个 <code>ByteBuf</code> 组合而成的, 不过在 <code>CompositeByteBuf</code> 内部, 这些 <code>ByteBuf</code> 都是单独存在的, <code>CompositeByteBuf</code> 保存了它们的引用,只是在逻辑上是一个整体</strong></p>
<h4 id="java-ByteBuffer-vs-netty-ByteBuf"><a href="#java-ByteBuffer-vs-netty-ByteBuf" class="headerlink" title="java ByteBuffer vs netty ByteBuf"></a><code>java ByteBuffer</code> vs <code>netty ByteBuf</code></h4><p><code>java</code> 本身就有 <code>ByteBuffer</code>,为什么要额外设计一个 <code>ByteBuf</code>?</p>
<ul>
<li><code>ByteBuffer</code> 只用一个 <code>position</code> 变量表示当前位置,所以在进行读写切换的时候都需要调用<code>flip()</code>和<code>clear()</code>等方法,否则功能将出错</li>
<li><code>ByteBuf</code> 使用 <code>readerIndex</code> 和 <code>writerIndex</code> 分别表示读写位置,不需要调用函数切换,体验更好。</li>
</ul>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://segmentfault.com/a/1190000007692223" target="_blank" rel="external">磁盘及网络IO工作方式解析</a></p>
<p><a href="https://stackoverflow.com/questions/20727615/is-nettys-zero-copy-different-from-os-level-zero-copy" target="_blank" rel="external">Is Netty’s Zero Copy different from OS level Zero Copy?</a></p>
<p><a href="https://caorong.github.io/2017/01/16/head-first-netty-3/" target="_blank" rel="external">深入浅出Netty - ByteBuf 和 ByteBufPool</a></p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/12/15/Netty-核心组件/" itemprop="url">
Netty 核心组件
</a>
</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time itemprop="dateCreated" datetime="2018-12-15T18:47:35+08:00" content="2018-12-15">
2018-12-15
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="Netty工作架构图"><a href="#Netty工作架构图" class="headerlink" title="Netty工作架构图"></a><code>Netty</code>工作架构图</h3><p><img src="https://tva1.sinaimg.cn/large/006tNbRwgy1fycc2uem9sj313i0p643a.jpg" alt=""></p>
<h3 id="模块组件"><a href="#模块组件" class="headerlink" title="模块组件"></a>模块组件</h3><h4 id="Bootstrap"><a href="#Bootstrap" class="headerlink" title="Bootstrap"></a><code>Bootstrap</code></h4><p><code>Bootstrap</code>是启动引导类,一个 <code>Netty</code> 应用通常由一个 <code>Bootstrap</code> 开始,主要作用是配置整个 <code>Netty</code> 程序,串联各个组件</p>
<ul>
<li><code>Bootstrap</code> 类是客户端程序的启动引导类</li>
<li><code>ServerBootstrap</code> 类是服务端启动引导类</li>
</ul>
<h4 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a><code>Channel</code></h4><p><code>Channel</code>的本质是对操作系统产生的FD(文件描述符)的映射,并且提供绑定到<code>Selector</code>多路选择器上的能力</p>
<h4 id="ChannelFuture"><a href="#ChannelFuture" class="headerlink" title="ChannelFuture"></a><code>ChannelFuture</code></h4><p>在 <code>Netty</code> 中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理。 <code>Future</code> 对象可以看作是一个异步操作结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。</p>
<p><code>Netty</code> 提供了<code>ChannelFuture</code>,用于在执行异步操作的时候使用。每个<code>Netty</code>的出站IO操作都会返回一个<code>ChannelFuture</code>。<code>ChannelFuture</code>可以注册<code>ChannelFutureListener</code> 实例,其中的回调方法<code>operationComplete()</code>,将会在对应的操作完成时被调用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function">ChannelFuture <span class="title">write</span><span class="params">(Object msg, ChannelPromise promise)</span></span>;</div></pre></td></tr></table></figure>
<h4 id="ChannelHandler"><a href="#ChannelHandler" class="headerlink" title="ChannelHandler"></a><code>ChannelHandler</code></h4><p><code>ChannelHandler</code> 是一个接口,它充当了所有处理入站和出站数据的应用程序逻辑的容器,并将其转发到其 <code>ChannelPipeline</code>中的下一个<code>ChannelHandler</code>;该类是基于事件驱动的,它会响应相关的事件然后去调用其关联的回调函数,例如:当一个新的连接被建立时,<code>ChannelHandler</code>的<code>channelActive()</code>方法将会被调用</p>
<p><code>ChannelHandler</code>子类,例如:</p>
<ul>
<li><code>ChannelInboundHandler</code>:用于处理入站IO事件。</li>
<li><code>ChannelOutboundHandler</code>:用于处理出站IO操作。</li>
</ul>
<h4 id="ChannelPipline"><a href="#ChannelPipline" class="headerlink" title="ChannelPipline"></a><code>ChannelPipline</code></h4><p><code>ChannelPipeline</code> 是聚合了 <code>ChannelHandler</code> 的管道,本质上是一个职责链路(用于处理<code>Channel</code>的入站事件和出站操作),在<code>Worker</code>线程中会对每一个<code>Channel</code>执行其所对应的<code>pipeline</code>链,完成整个生命周期。</p>
<p><img src="https://tva1.sinaimg.cn/large/006tNbRwgy1fy8rq2caj7j30uw08k76d.jpg" alt=""></p>
<p><img src="https://tva1.sinaimg.cn/large/006tNbRwgy1fy8tkgaop2j31jo0mcac9.jpg" alt=""></p>
<p>蓝色表示 <code>ChannelInboundHandler</code> ,绿色表示 <code>ChannelOutboundHandler</code>,黄色则承担两个角色,而每个<code>Handler</code>又会使用 <code>ChannelHandlerContext</code> 封装起来,在 <code>ChannelPipeline</code> 中组装成双向链表。</p>
<p><strong>ChannelPipeline handler 执行顺序</strong>:</p>
<ul>
<li><strong>入站事件流从头到尾执行所有的<code>handler</code>(入站的时候也会经过<code>OutboundChannelHandler</code>,只不过略过了这个<code>ChannelHandler</code>)</strong></li>
<li><strong>出站事件流从尾到头执行所有的<code>handler</code>(出站的时候也会经过<code>InboundChannelHandler</code>,只不过略过了这个<code>ChannelHandler</code>)</strong></li>
</ul>
<p><strong>注意: 在具体业务处理类<code>ProcessingHandler</code>中将响应数据发出有2种方式</strong>:</p>
<ol>
<li><code>ChannelHandlerContext.writeAndFlush(msg)</code>:不用经过<code>TailConext</code>, 从该<code>handler</code>开始往前执行<code>OutboundChannelHandler</code>,性能更好一些</li>
<li><code>ChannelHandlerContext.pipeline().writeAndFlush(msg)</code>:从<code>TailConext</code>开始执行, 往前执行<code>OutboundChannelHandler</code></li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> AbstractChannelHandlerContext <span class="title">findContextOutbound</span><span class="params">()</span> </span>{</div><div class="line"> AbstractChannelHandlerContext ctx = <span class="keyword">this</span>;</div><div class="line"> do {</div><div class="line"> <span class="comment">// 注意,这里是往前找</span></div><div class="line"> ctx = ctx.prev;</div><div class="line"> } <span class="keyword">while</span> (!ctx.outbound);</div><div class="line"> <span class="keyword">return</span> ctx;</div><div class="line"> }</div></pre></td></tr></table></figure>
<h4 id="EventLoop"><a href="#EventLoop" class="headerlink" title="EventLoop"></a><code>EventLoop</code></h4><p><code>EventLoop</code> 是事件循环,对应<code>Reactor</code>线程,以 <code>NioEventLoop</code> 为例,实现原理是:维护了一个线程和任务队列,支持异步提交任务,线程启动时会调用<code>NioEventLoop</code> 的 <code>run</code> 方法,执行IO任务和非IO任务,由于 <code>EventLoop</code> 是单线程,因此在使用时要注意耗时操作,阻塞操作都不要放到 <code>EventLoop</code> 中,其适合处理耗时短并且简单的任务</p>
<ul>
<li><strong>IO任务</strong>:即 <code>selectionKey</code> 中事件,如 <code>accept</code>、<code>connect</code>、<code>read</code>、<code>write</code>等,由<code>processSelectedKeys</code>方法触发。</li>
<li><strong>非IO任务</strong>:添加到 <code>taskQueue</code> 中的任务,如延迟任务,由 <code>runAllTasks</code> 方法触发。</li>
</ul>
<p>两种任务的执行时间比由变量 <code>ioRatio</code> 控制,默认为50%,则表示允许<strong>非IO任务执行的时间与IO任务的执行时间相等</strong></p>
<h4 id="EventLoopGroup"><a href="#EventLoopGroup" class="headerlink" title="EventLoopGroup"></a><code>EventLoopGroup</code></h4><p><code>EventLoopGroup</code>包含一个或者多个<code>EventLoop</code>,主要管理<code>EventLoop</code>的生命周期,可以理解为一个线程池,内部维护了一组线程,<strong>每个线程(<code>EventLoop</code>)可以负责处理多个<code>Channel</code>上的事件,而一个<code>Channel</code>只能注册于一个<code>EventLoop</code>(防止线程并发问题)</strong></p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://www.lmlphp.com/user/95/article/item/12624/" target="_blank" rel="external">最透彻的Netty原理架构解析</a></p>
<p><a href="http://www.liuhaihua.cn/archives/530938.html" target="_blank" rel="external">Netty–Reactor模型的应用</a></p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>