-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdubbo.txt
1070 lines (853 loc) · 21.7 KB
/
dubbo.txt
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
第1章 如何设计一款重复的高性能分布式发号器1
1.1 可选方案及技术选型2
1.1.1 为什么不用UUID2
1.1.2 基于数据库的实现方案2
1.1.3 Snowflake开源项目3
1.1.4 小结4
1.2 分布式对发号器的基本需求4
1.3 架构设计与核心要点6
1.3.1 发布模式6
1.3.2 ID类型7
1.3.3 数据结构7
1.3.4 并发9
1.3.5 机器ID的分配9
1.3.6 时间同步
1.3.7 设计验证11
1.4 如何根据设计实现多场景的发号器11
1.4.1 项目结构12
1.4.2 服务接口的定义14
1.4.3 服务接口的实现15
1.4.4 ID元数据与长整型ID的互相转换22
1.4.5 时间操作25
1.4.6 机器ID的生成27
1.4.7 小结32
1.5 如何保证性能需求32
1.5.1 嵌入发布模式的压测结果33
1.5.2 中心服务器发布模式的压测结果33
1.5.3 REST发布模式(Netty实现)的压测结果33
1.5.4 REST发布模式(Spring Boot Tomcat实现)的压测结果34
1.5.5 性能测试总结34
1.6 如何让用户快速使用35
1.6.1 REST发布模式的使用指南35
1.6.2 服务化模式的使用指南38
1.6.3 嵌入发布模式的使用指南41
1.7 为用户提供API文档43
1.7.1 RESTful API文档44
1.7.2 Java API文档45
第2章 可灵活扩展的消息队列框架的设计与实现49
2.1 背景介绍50
2.2 项目目标50
2.2.1 简单易用50
2.2.2 高性能51
2.2.3 高稳定性51
2.3 架构难点51
2.3.1 线程模型51
2.3.2 异常处理53
2.3.3 优雅关机53
2.4 设计与实现54
2.4.1 项目结构54
2.4.2 项目的规划55
2.4.3 生产者的设计与实现57
2.4.4 消费者的设计与实现58
2.4.5 启动模块的设计与实现67
2.4.6 消息处理器的体系结构76
2.4.7 反射机制79
2.4.8 模板项目的设计80
2.5 使用指南82
2.5.1 安装步骤82
2.5.2 Java API83
2.5.3 与Spring环境集成84
2.5.4 对服务源码进行注解85
2.6 API简介87
2.6.1 Producer API87
2.6.2 Consumer API88
2.6.3 消息处理器88
2.6.4 消息处理器定义的注解90
2.7 消息处理机模板项目91
2.7.1 快速开发向导91
2.7.2 后台监控和管理92
第3章 轻量级的数据库分库分表架构与框架93
3.1 什么是分库分表94
3.1.1 使用数据库的三个阶段94
3.1.2 在什么情况下需要分库分表95
3.1.3 分库分表的典型实例96
3.2 三种分而治之的解决方案97
3.2.1 客户端分片97
3.2.2 代理分片0
3.2.3 支持事务的分布式数据库1
3.3 分库分表的架构设计2
3.3.1 整体的切分方式2
3.3.2 水平切分方式的路由过程和分片维度6
3.3.3 分片后的事务处理机制7
3.3.4 读写分离119
3.3.5 分库分表引起的问题119
3.4 流行代理分片框架Mycat的初体验123
3.4.1 安装Mycat123
3.4.2 配置Mycat124
3.4.3 配置数据库节点128
3.4.4 数据迁移129
3.4.5 Mycat支持的分片规则129
3.5 流行的客户端分片框架Sharding JDBC的初体验138
3.5.1 Sharding JDBC简介138
3.5.2 Sharding JDBC的功能139
3.5.3 Sharding JDBC的使用141
3.5.4 Sharding JDBC的使用限制152
3.6 自研客户端分片框架dbsplit的设计、实现与使用153
3.6.1 项目结构154
3.6.2 结构和执行流程155
3.6.3 切片下标命名策略159
3.6.4 SQL解析和组装167
3.6.5 SQL实用程序168
3.6.6 反射实用程序173
3.6.7 分片规则的配置177
3.6.8 支持分片的SplitJdbcTemplate和SimpleSplitJdbcTemplate接口API179
3.6.9 JdbcTemplate的扩展SimpleJdbcTemplate接口API184
3.6. 用于创建分库分表数据库的脚本工具187
3.6.11 使用dbsplit的一个简单示例192
3.6.12 使用dbsplit的线上真实示例展示199
第4章 缓存的本质和缓存使用的实践201
4.1 使用缓存的目的和问题202
4.2 自相似,CPU的缓存和架构的缓存203
4.2.1 CPU缓存的架构及性能205
4.2.2 CPU缓存的运行过程分析206
4.2.3 缓存行与伪共享208
4.2.4 从CPU的体系架构到分布式的缓存架构218
4.3 常用的分布式缓存解决方案221
4.3.1 常用的分布式缓存的对比221
4.3.2 Redis初体验225
4.4 分布式缓存的通用方法229
4.4.1 缓存编程的具体方法229
4.4.2 应用层访问缓存的模式233
4.4.3 分布式缓存分片的三种模式235
4.4.4 分布式缓存的迁移方案238
4.4.5 缓存穿透、缓存并发和缓存雪崩244
4.4.6 缓存对事务的支持246
4.5 分布式缓存的设计与案例248
4.5.1 缓存设计的核心要素248
4.5.2 缓存设计的实践250
4.5.3 关于常见的缓存线上问题的案例253
4.6 客户端缓存分片框架redic的设计与实现257
4.6.1 什么时候需要redic258
4.6.2 如何使用redic258
4.6.3 更多的配置258
4.6.4 项目结构260
4.6.5 结构261
4.6.6 设计与实现的过程261
第5章 大数据利器之Elasticsearch268
5.1 Lucene简介269
5.1.1 核心模块269
5.1.2 核心术语270
5.1.3 检索方式271
5.1.4 分段存储273
5.1.5 段合并策略275
5.1.6 Lucene相似度打分278
5.2 Elasticsearch简介286
5.2.1 核心概念286
5.2.2 3C和脑裂289
5.2.3 事务日志291
5.2.4 在集群中写索引294
5.2.5 集群中的查询流程295
5.3 Elasticsearch实战298
5.3.1 Elasticsearch的配置说明298
5.3.2 常用的接口300
5.4 性能调优305
5.4.1 写优化305
5.4.2 读优化308
5.4.3 堆大小的设置313
5.4.4 服务器配置的选择315
5.4.5 硬盘的选择和设置316
5.4.6 接入方式318
5.4.7 角色隔离和脑裂319
第7章 RPC服务的发展历程和对比分析377
7.1 什么是RPC服务378
7.2 RPC服务的原理379
7.2.1 Sokcet套接字379
7.2.2 RPC的调用过程380
7.3 在程序中使用RPC服务382
7.4 RPC服务的发展历程383
7.4.1 代RPC:以ONC RPC和DCE RPC为代表的函数式RPC384
7.4.2 第二代RPC:支持面对象的编程388
7.4.3 第三代RPC:SOA和微服务398
7.4.4 架构的演进402
7.5 主流的RPC框架403
7.5.1 Thrift403
7.5.2 ZeroC Ice4
7.5.3 gRPC418
7.5.4 Dubbo430
第8章 Dubbo实战及源码分析436
8.1 Dubbo的四种配置方式437
8.1.1 XML配置437
8.1.2 属性配置440
8.1.3 API配置441
8.1.4 注解配置443
8.2 服务的注册与发现446
8.2.1 注册中心446
8.2.2 服务暴露449
8.2.3 引用服务451
8.3 Dubbo通信协议及序列化探讨455
8.3.1 Dubbo支持的协议455
8.3.2 协议的配置方法456
8.3.3 多协议暴露服务457
8.3.4 Dubbo协议的使用注意事项458
8.3.5 Dubbo协议的约束459
8.4 Dubbo中高效的I/O线程模型459
8.4.1 对Dubbo中I/O模型的分析459
8.4.2 Dubbo中线程配置的相关参数460
8.4.3 在Dubbo线程方面踩过的坑461
8.4.4 对Dubbo中线程使用的建议462
8.5 集群的容错机制与负载均衡462
8.5.1 集群容错机制的原理462
8.5.2 集群容错模式的配置方法464
8.5.3 六种集群容错模式464
8.5.4 集群的负载均衡465
8.6 监控和运维实践467
8.6.1 日志适配467
8.6.2 监控管理后台467
8.6.3 服务降级473
8.6.4 优雅停机475
8.6.5 灰度发布475
8.7 Dubbo项目线上案例解析477
8.7.1 线上问题的通用解决方案477
8.7.2 耗时服务耗尽了线程池的案例480
8.7.3 容错重试机制引发服务雪崩的案例481
8.8 深入剖析Dubbo源码及其实现483
8.8.1 Dubbo的总体架构设计483
8.8.2 配置文件486
8.8.3 Dubbo的核心RPC488
8.8.4 Dubbo巧妙的URL总线设计491
8.8.5 Dubbo的扩展点加载SPI492
8.8.6 Dubbo服务暴露的过程493
8.8.7 服务引用502
8.8.8 集群容错和负载均衡503
8.8.9 集群容错504
8.8. 负载均衡509
第9章 高性能网络中间件512
9.1 TCP/UDP的核心原理及本质探索513
9.1.1 网络模型513
9.1.2 UDP、IP及其未解决的问题515
9.1.3 TCP详解519
9.1.4 是否可以用UDP代替TCP527
9.1.5 网络通信的不可靠性讨论529
9.2 网络测试实践530
9.2.1 网络测试的关键点530
9.2.2 那些必不可少的网络测试工具532
9.2.3 典型的测试报告539
9.3 高性能网络框架的设计与实现544
9.3.1 对代理功能的测试及分析545
9.3.2 网络中间件的使用介绍549
9.3.3 内存和缓存的优化551
9.3.4 快速解析流数据554
===========
用户文档
入门
背景
需求
架构
用法
快速启动
依赖
成熟度
配置
XML配置
API配置
注解配置
动态配置中心
配置加载流程
示例
启动时检查
集群容错
负载均衡
线程模型
直连提供者
只订阅
只注册
静态服务
多协议
多注册中心
服务分组
多版本
分组聚合
参数验证
结果缓存
泛化引用
泛化实现
回声测试
上下文信息
隐式参数
Consumer异步调用
Provider异步执行
本地调用
参数回调
事件通知
本地存根
本地伪装
延迟暴露
并发控制
连接控制
延迟连接
粘滞连接
令牌验证
路由规则
配置规则
服务降级
优雅停机
主机绑定
日志适配
访问日志
服务容器
Reference Config 缓存
分布式事务
线程栈自动dump
Netty4
Kryo和FST序列化
简化注册中心URL
API配置参考手册
schema配置参考手册
介绍
dubbo:service
dubbo:reference
dubbo:protocol
dubbo:registry
dubbo:monitor
dubbo:application
dubbo:module
dubbo:provider
dubbo:consumer
dubbo:method
dubbo:argument
dubbo:parameter
dubbo:config-center
协议参考手册
介绍
dubbo://
rmi://
hessian://
http://
webservice://
thrift://
memcached://
redis://
rest://
注册中心参考手册
介绍
Multicast 注册中心
Zookeeper 注册中心
Redis 注册中心
Simple 注册中心
元数据中心参考手册
介绍
Redis
Zookeeper
telnet命令参考手册
在线运维命令-QOS
maven插件参考手册
服务化最佳实践
推荐用法
容量规划
性能测试报告
测试覆盖率报告
版本与升级
2.7.x升级步骤及注意事项
开发者指南
源码构建
框架设计
扩展点加载
实现细节
SPI 扩展实现
协议扩展
调用拦截扩展
引用监听扩展
暴露监听扩展
集群扩展
路由扩展
负载均衡扩展
合并结果扩展
注册中心扩展
监控中心扩展
扩展点加载扩展
动态代理扩展
编译器扩展
消息派发扩展
线程池扩展
序列化扩展
网络传输扩展
信息交换扩展
组网扩展
Telnet 命令扩展
状态检查扩展
容器扩展
页面扩展
缓存扩展
验证扩展
日志适配扩展
公共契约
编码约定
设计原则
魔鬼在细节
一些设计上的基本常识
谈谈扩充式扩展与增量式扩展
配置设计
设计实现的健壮性
防痴呆设计
扩展点重构
版本管理
贡献
检查列表
坏味道
技术兼容性测试
源码导读
Dubbo SPI
自适应拓展机制
服务导出
服务引入
服务字典
服务路由
集群
负载均衡
服务调用过程
运维管理
控制台介绍
服务搜索
服务治理
服务测试
=================
Spring Boot Dubbo 应用启停源码分析
Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发。同时也整合了 Spring Boot 特性:
自动装配 (比如: 注解驱动, 自动装配等).
Production-Ready (比如: 安全, 健康检查, 外部化配置等).
DubboConsumer启动分析
你有没有想过一个问题? incubator-dubbo-spring-boot-project中的 DubboConsumerDemo应用就一行代码, main方法执行完之后,为什么不会直接退出呢?
@SpringBootApplication
(scanBasePackages =
"com.alibaba.boot.dubbo.demo.consumer.controller"
)
public
class
DubboConsumerDemo
{
public
static
void
main(
String
[] args) {
SpringApplication
.run(
DubboConsumerDemo
.
class
,args);
}
}
其实要回答这样一个问题,我们首先需要把这个问题进行一个抽象,即一个JVM进程,在什么情况下会退出?
以Java 8为例,通过查阅JVM语言规范[1],在12.8章节中有清晰的描述:
A program terminates all its activity and exits when one of two things happens:
All the threads that are not daemon threads terminate.
Some thread invokes the exit method of class Runtime or class System, and the exitoperation is not forbidden by the security manager.
也就是说,导致JVM的退出只有2种情况:
所有的非daemon进程完全终止
某个线程调用了 System.exit()或 Runtime.exit()
因此针对上面的情况,我们判断,一定是有某个非daemon线程没有退出导致。我们知道,通过jstack可以看到所有的线程信息,包括他们是否是daemon线程,可以通过jstack找出那些是非deamon的线程。
➜ jstack
57785
| grep tid | grep -v
"daemon"
"container-0"
#37 prio=5 os_prio=31 tid=0x00007fbe312f5800 nid=0x7103 waiting on condition [0x0000700010144000]
"container-1"
#49 prio=5 os_prio=31 tid=0x00007fbe3117f800 nid=0x7b03 waiting on condition [0x0000700010859000]
"DestroyJavaVM"
#83 prio=5 os_prio=31 tid=0x00007fbe30011000 nid=0x2703 waiting on condition [0x0000000000000000]
"VM Thread"
os_prio=
31
tid=
0x00007fbe3005e800
nid=
0x3703
runnable
"GC Thread#0"
os_prio=
31
tid=
0x00007fbe30013800
nid=
0x5403
runnable
"GC Thread#1"
os_prio=
31
tid=
0x00007fbe30021000
nid=
0x5303
runnable
"GC Thread#2"
os_prio=
31
tid=
0x00007fbe30021800
nid=
0x2d03
runnable
"GC Thread#3"
os_prio=
31
tid=
0x00007fbe30022000
nid=
0x2f03
runnable
"G1 Main Marker"
os_prio=
31
tid=
0x00007fbe30040800
nid=
0x5203
runnable
"G1 Conc#0"
os_prio=
31
tid=
0x00007fbe30041000
nid=
0x4f03
runnable
"G1 Refine#0"
os_prio=
31
tid=
0x00007fbe31044800
nid=
0x4e03
runnable
"G1 Refine#1"
os_prio=
31
tid=
0x00007fbe31045800
nid=
0x4d03
runnable
"G1 Refine#2"
os_prio=
31
tid=
0x00007fbe31046000
nid=
0x4c03
runnable
"G1 Refine#3"
os_prio=
31
tid=
0x00007fbe31047000
nid=
0x4b03
runnable
"G1 Young RemSet Sampling"
os_prio=
31
tid=
0x00007fbe31047800
nid=
0x3603
runnable
"VM Periodic Task Thread"
os_prio=
31
tid=
0x00007fbe31129000
nid=
0x6703
waiting on condition
此处通过grep tid 找出所有的线程摘要,通过grep -v找出不包含daemon关键字的行
通过上面的结果,我们发现了一些信息:
有两个线程 container-0, container-1非常可疑,他们是非daemon线程,处于wait状态
有一些GC相关的线程,和VM打头的线程,也是非daemon线程,但他们很有可能是JVM自己的线程,在此暂时忽略。
综上,我们可以推断,很可能是因为 container-0和 container-1导致JVM没有退出。现在我们通过源码,搜索一下到底是谁创建的这两个线程。
通过对spring-boot的源码分析,我们在 org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer的 startDaemonAwaitThread找到了如下代码
private
void
startDaemonAwaitThread() {
Thread
awaitThread =
new
Thread
(
"container-"
+ (containerCounter.
get
())) {
@Override
public
void
run() {
TomcatEmbeddedServletContainer
.
this
.tomcat.getServer().
await
();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(
false
);
awaitThread.start();
}
在这个方法加个断点,看下调用堆栈:
initialize:
115
,
TomcatEmbeddedServletContainer
(org.springframework.boot.context.embedded.tomcat)
<init>
:
84
,
TomcatEmbeddedServletContainer
(org.springframework.boot.context.embedded.tomcat)
getTomcatEmbeddedServletContainer:
554
,
TomcatEmbeddedServletContainerFactory
(org.springframework.boot.context.embedded.tomcat)
getEmbeddedServletContainer:
179
,
TomcatEmbeddedServletContainerFactory
(org.springframework.boot.context.embedded.tomcat)
createEmbeddedServletContainer:
164
,
EmbeddedWebApplicationContext
(org.springframework.boot.context.embedded)
onRefresh:
134
,
EmbeddedWebApplicationContext
(org.springframework.boot.context.embedded)
refresh:
537
,
AbstractApplicationContext
(org.springframework.context.support)
refresh:
122
,
EmbeddedWebApplicationContext
(org.springframework.boot.context.embedded)
refresh:
693
,
SpringApplication
(org.springframework.boot)
refreshContext:
360
,
SpringApplication
(org.springframework.boot)
run:
303
,
SpringApplication
(org.springframework.boot)
run:
1118
,
SpringApplication
(org.springframework.boot)
run:
1107
,
SpringApplication
(org.springframework.boot)
main:
35
,
DubboConsumerDemo
(com.alibaba.boot.dubbo.demo.consumer.bootstrap)
可以看到,spring-boot应用在启动的过程中,由于默认启动了Tomcat暴露HTTP服务,所以执行到了上述方法,而Tomcat启动的所有的线程,默认都是daemon线程,例如监听请求的Acceptor,工作线程池等等,如果这里不加控制的话,启动完成之后JVM也会退出。因此需要显式地启动一个线程,在某个条件下进行持续等待,从而避免线程退出。Spring Boot 2.x 启动全过程源码分析(全),这篇文章推荐大家看下。
下面我们在深挖一下,在Tomcat的 this.tomcat.getServer().await()这个方法中,线程是如何实现不退出的。这里为了阅读方便,去掉了不相关的代码。
public
void
await
() {
// ...
if
( port==-
1
) {
try
{
awaitThread =
Thread
.currentThread();
while
(!stopAwait) {
try
{
Thread
.sleep(
10000
);
}
catch
(
InterruptedException
ex ) {
// continue and check the flag
}
}
}
finally
{
awaitThread =
null
;
}
return
;
}
// ...
}
在await方法中,实际上当前线程在一个while循环中每10秒检查一次 stopAwait这个变量,它是一个 volatile 类型变量,用于确保被另一个线程修改后,当前线程能够立即看到这个变化。如果没有变化,就会一直处于while循环中。这就是该线程不退出的原因,也就是整个spring-boot应用不退出的原因。
因为Springboot应用同时启动了8080和8081(management port)两个端口,实际是启动了两个Tomcat,因此会有两个线程 container-0和 container-1。
接下来,我们再看看,这个Spring-boot应用又是如何退出的呢?
DubboConsumer退出分析
在前面的描述中提到,有一个线程持续的在检查 stopAwait这个变量,那么我们自然想到,在Stop的时候,应该会有一个线程去修改 stopAwait,打破这个while循环,那又是谁在修改这个变量呢?
通过对源码分析,可以看到只有一个方法修改了 stopAwait,即 org.apache.catalina.core.StandardServer#stopAwait,我们在此处加个断点,看看是谁在调用。
注意,当我们在Intellij IDEA的Debug模式,加上一个断点后,需要在命令行下使用 kill-s INT $PID或者 kill-s TERM $PID才能触发断点,点击IDE上的Stop按钮,不会触发断点。这是IDEA的bug。在 IDEA 中调试 Bug,真是太厉害了!这个推荐大家看下。
可以看到有一个名为 Thread-3的线程调用了该方法:
stopAwait:
390
,
StandardServer
(org.apache.catalina.core)
stopInternal:
819
,
StandardServer
(org.apache.catalina.core)
stop:
226
,
LifecycleBase
(org.apache.catalina.util)
stop:
377
,
Tomcat
(org.apache.catalina.startup)
stopTomcat:
241
,
TomcatEmbeddedServletContainer
(org.springframework.boot.context.embedded.tomcat)
stop:
295
,
TomcatEmbeddedServletContainer
(org.springframework.boot.context.embedded.tomcat)
stopAndReleaseEmbeddedServletContainer:
306
,
EmbeddedWebApplicationContext
(org.springframework.boot.context.embedded)
onClose:
155
,
EmbeddedWebApplicationContext
(org.springframework.boot.context.embedded)
doClose:
1014
,
AbstractApplicationContext
(org.springframework.context.support)
run:
929
,
AbstractApplicationContext$2
(org.springframework.context.support)
通过源码分析,原来是通过Spring注册的 ShutdownHook来执行的
@Override
public
void
registerShutdownHook() {
if
(
this
.shutdownHook ==
null
) {
// No shutdown hook registered yet.
this
.shutdownHook =
new
Thread
() {
@Override
public
void
run() {
synchronized
(startupShutdownMonitor) {
doClose();
}
}
};
Runtime
.getRuntime().addShutdownHook(
this
.shutdownHook);
}
}
通过查阅Java的API文档[2], 我们可以知道ShutdownHook将在下面两种情况下执行
The Java virtual machine shuts down in response to two kinds of events:
The program exits normally, when the last non-daemon thread exits or when the exit(equivalently, System.exit) method is invoked, or
The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.
调用了System.exit()方法