-
Notifications
You must be signed in to change notification settings - Fork 683
/
Copy path异步网络模型.md.html
990 lines (893 loc) · 59.8 KB
/
异步网络模型.md.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
<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>异步网络模型.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(上).md.html">Java中9种常见的CMS GC问题分析与解决(上).md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(下).md.html">Java中9种常见的CMS GC问题分析与解决(下).md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类:Unsafe应用解析.md.html">Java魔法类:Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化:优化 select count().md.html">MySQL优化:优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC(多版本并发控制).md.html">MySQL的MVCC(多版本并发控制).md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始,聊微服务架构.md.html">从SpringCloud开始,聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时,我们在讨论些神马?.md.html">当我们在讨论CQRS时,我们在讨论些神马?.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html">服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶:Dockerfile 高阶使用指南及镜像优化.md.html">进阶:Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>异步网络模型</h1>
<p>异步网络模型在服务开发中应用非常广泛,相关资料和开源库也非常多。项目中,使用现成的轮子提高了开发效率,除了能使用轮子,还是有必要了解一下轮子的内部构造。</p>
<p>这篇文章从最基础的5种I/O模型切入,到I/O事件处理模型,再到并发模式,最后以Swoole开源库来做具体分析,逐步深入。文中提到的模型都是一些通用的模型,在《linux高性能服务器编程》中也都有涉及。文章不涉及模型的实现细节,最重要的是去理解各个模型的工作模式以及其优缺点。</p>
<p>文中涉及接口调用的部分,都是指Linux系统的接口调用。 共分为5部分:</p>
<p><strong>I/O模型</strong></p>
<p>从基础的系统调用方法出发,给大家从头回顾一下最基本的I/O模型,虽然简单,但是不可或缺的基础;</p>
<p><strong>事件处理模型</strong></p>
<p>这部分在同步I/O、异步I/O的基础上分别介绍Reactor模型以及Proactor模型,着重两种模型的构成以及事件处理流程。Reactor模型是我们常见的;不同平台对异步I/O系统接口的支持力度不同,这部分还介绍了一种使用同步I/O来模拟Proactor模型的方法。</p>
<p><strong>并发模式</strong></p>
<p>就是多线程、多进程的编程的模式。介绍了两种较为高效的并发模型,半同步/半异步(包括其演变模式)、Follower/Leader模式。</p>
<p><strong>Swoole异步网络模型分析</strong></p>
<p>这部分是结合已介绍的事件处理模型、并发模式对Swoole的异步模型进行分析; 从分析的过程来看,看似复杂的网络模型,可以拆分为简单的模型单元,只不过我们需要权衡利弊,选取合适业务需求的模型单元进行组合。 我们团队基于Swoole 1.8.5版本,做了很多修改,部分模块做了重构,计划在17年6月底将修改后版本开源出去,敬请期待。</p>
<p><strong>改善性能的方法</strong></p>
<p>最后一部分是在引入话题,介绍的是几种常用的方法。性能优化是没有终点的,希望大家能贡献一些想法和具体方法。</p>
<h2>I/O模型</h2>
<p>POSIX 规范中定义了同步I/O 和异步I/O的术语,
<strong>同步I/O</strong> : 需要进程去真正的去操作I/O;</p>
<p><strong>异步I/O</strong>:内核在I/O操作完成后再通知应用进程操作结果。</p>
<p>在《UNIX网络编程》中介绍了5中I/O模型:阻塞I/O、非阻塞I/O、I/O复用、SIGIO 、异步I/O;本节对这5种I/O模型进行说明和对比。</p>
<h3>I/O阻塞</h3>
<p>通常把阻塞的文件描述符(file descriptor,fd)称之为阻塞I/O。默认条件下,创建的socket fd是阻塞的,针对阻塞I/O调用系统接口,可能因为等待的事件没有到达而被系统挂起,直到等待的事件触发调用接口才返回,例如,tcp socket的connect调用会阻塞至第三次握手成功(不考虑socket 出错或系统中断),如图1所示。另外socket 的系统API ,如,accept、send、recv等都可能被阻塞。</p>
<p><img src="assets/connect_bloackxx-1.png" alt="img" /></p>
<blockquote>
<pre><code> 图1 I/O 阻塞模型示意图
</code></pre>
</blockquote>
<p>另外补充一点,网络编程中,通常把可能永远阻塞的系统API调用 称为慢系统调用,典型的如 accept、recv、select等。慢系统调用在阻塞期间可能被信号中断而返回错误,相应的errno 被设置为EINTR,我们需要处理这种错误,解决办法有:</p>
<p><strong>1. 重启系统调用</strong></p>
<p>直接上示例代码吧,以accept为例,被中断后重启accept 。有个例外,若connect 系统调用在阻塞时被中断,是不能直接重启的(与内核socket 的状态有关),有兴趣的同学可以深入研究一下connect 的内核实现。使用I/O复用等待连接完成,能避免connect不能重启的问题。</p>
<pre><code class="language-c">int client_fd = -1;
struct sockaddr_in client_addr;
socklen_t child_addrlen;
while (1) {
call_accept:
client_fd = accept(server_fd,NULL,NULL);
if (client_fd < 0) {
if (EINTR == errno) {
goto call_accept;
} else {
sw_sysError("accept fail");
break;
}
}
}
</code></pre>
<p><strong>2. 信号处理</strong></p>
<p>利用信号处理,可以选择忽略信号,或者在安装信号时设置SA_RESTART属性。设置属性SA_RESTART,信号处理函数返回后,被安装信号中断的系统调用将自动恢复,示例代码如下。需要知道的是,设置SA_RESTART属性方法并不完全适用,对某些系统调用可能无效,这里只是提供一种解决问题的思路,示例代码如下:</p>
<pre><code class="language-c">int client_fd = -1;
struct sigaction action,old_action;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_flags |= SA_RESTART;
/// 若信号已经被忽略,则不设置
sigaction(SIGALRM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGALRM, &action, NULL);
}
while (1) {
client_fd = accept(server_fd,NULL,NULL);
if (client_fd < 0) {
sw_sysError("accept fail");
break;
}
}
</code></pre>
<h3>I/O非阻塞</h3>
<p>把非阻塞的文件描述符称为非阻塞I/O。可以通过设置SOCK_NONBLOCK标记创建非阻塞的socket fd,或者使用fcntl将fd设置为非阻塞。</p>
<p>对非阻塞fd调用系统接口时,不需要等待事件发生而立即返回,事件没有发生,接口返回-1,此时需要通过errno的值来区分是否出错,有过网络编程的经验的应该都了解这点。不同的接口,立即返回时的errno值不尽相同,如,recv、send、accept errno通常被设置为EAGIN 或者EWOULDBLOCK,connect 则为EINPRO- GRESS 。</p>
<p>以recv操作非阻塞套接字为例,如图2所示。</p>
<p><img src="assets/recv_noblock-1.png" alt="img" /></p>
<blockquote>
<pre><code> 图2 非阻塞I/O模型示意图
</code></pre>
</blockquote>
<p>当我们需要读取,在有数据可读的事件触发时,再调用recv,避免应用层不断去轮询检查是否可读,提高程序的处理效率。通常非阻塞I/O与I/O事件处理机制结合使用。</p>
<h3>I/O复用</h3>
<p>最常用的I/O事件通知机制就是I/O复用(I/O multiplexing)。Linux 环境中使用select/poll/epoll 实现I/O复用,I/O复用接口本身是阻塞的,在应用程序中通过I/O复用接口向内核注册fd所关注的事件,当关注事件触发时,通过I/O复用接口的返回值通知到应用程序,如图3所示,以recv为例。I/O复用接口可以同时监听多个I/O事件以提高事件处理效率。</p>
<p><img src="assets/io---1.png" alt="img" /></p>
<blockquote>
<pre><code> 图 3 I/O复用模型示意图
</code></pre>
</blockquote>
<p>关于select/poll/epoll的对比,可以参考[],epoll使用比较多,但是在并发的模式下,需要关注惊群的影响。</p>
<h3>SIGIO</h3>
<p>除了I/O复用方式通知I/O事件,还可以通过SIGIO信号来通知I/O事件,如图4所示。两者不同的是,在等待数据达到期间,I/O复用是会阻塞应用程序,而SIGIO方式是不会阻塞应用程序的。</p>
<p><img src="assets/------1.png" alt="img" /></p>
<blockquote>
<pre><code> 图 4 信号驱动I/O模型示意图
</code></pre>
</blockquote>
<h3>异步I/O</h3>
<p>POSIX规范定义了一组异步操作I/O的接口,不用关心fd 是阻塞还是非阻塞,异步I/O是由内核接管应用层对fd的I/O操作。异步I/O向应用层通知I/O操作完成的事件,这与前面介绍的I/O 复用模型、SIGIO模型通知事件就绪的方式明显不同。以aio_read 实现异步读取IO数据为例,如图5所示,在等待I/O操作完成期间,不会阻塞应用程序。</p>
<p><img src="assets/--io-1.png" alt="img" /></p>
<blockquote>
<pre><code> 图 5 异步I/O 模型示意图
</code></pre>
</blockquote>
<h3>I/O模型对比</h3>
<p>前面介绍的5中I/O中,I/O 阻塞、I/O非阻塞、I/O复用、SIGIO 都会在不同程度上阻塞应用程序,而只有异步I/O模型在整个操作期间都不会阻塞应用程序。</p>
<p>如图6所示,列出了5种I/O模型的比较</p>
<p><img src="assets/-------1--1.png" alt="img" /></p>
<blockquote>
<pre><code> 图6 五种I/O 模型比较示意图
</code></pre>
</blockquote>
<h2>事件处理模型</h2>
<p>网络设计模式中,如何处理各种I/O事件是其非常重要的一部分,Reactor 和Proactor两种事件处理模型应运而生。上章节提到将I/O分为同步I/O 和 异步I/O,可以使用同步I/O实现Reactor模型,使用异步I/O实现Proactor模型。</p>
<p>本章节将介绍Reactor和Proactor两种模型,最后将介绍一种使用同步I/O模拟Proactor事件处理模型。</p>
<h3><strong>Reactor事件处理模型</strong></h3>
<p>Reactor模型是同步I/O事件处理的一种常见模型,关于Reactor模型结构的资料非常多,一个典型的Reactor模型类图结构如图7所示,</p>
<p><img src="assets/import5.png" alt="img" /></p>
<blockquote>
<pre><code> 图 7 Reactor 模型类结构图
</code></pre>
</blockquote>
<p><strong>Reactor的核心思想</strong>:将关注的I/O事件注册到多路复用器上,一旦有I/O事件触发,将事件分发到事件处理器中,执行就绪I/O事件对应的处理函数中。模型中有三个重要的组件:</p>
<ul>
<li><strong>多路复用器</strong>:由操作系统提供接口,Linux提供的I/O复用接口有select、poll、epoll;</li>
<li><strong>事件分离器</strong>:将多路复用器返回的就绪事件分发到事件处理器中;</li>
<li><strong>事件处理器</strong>:处理就绪事件处理函数。</li>
</ul>
<p>图7所示,Reactor 类结构中包含有如下角色。</p>
<ul>
<li><strong>Handle</strong>:标示文件描述符;</li>
<li><strong>Event Demultiplexer</strong>:执行多路事件分解操作,对操作系统内核实现I/O复用接口的封装;用于阻塞等待发生在句柄集合上的一个或多个事件(如select/poll/epoll);</li>
<li><strong>Event Handler</strong>:事件处理接口;</li>
<li><strong>Event Handler A(B)</strong>:实现应用程序所提供的特定事件处理逻辑;</li>
<li><strong>Reactor</strong>:反应器,定义一个接口,实现以下功能:</li>
</ul>
<pre><code> a)供应用程序注册和删除关注的事件句柄;
b)运行事件处理循环;
c)等待的就绪事件触发,分发事件到之前注册的回调函数上处理.
</code></pre>
<p>接下来介绍Reactor的工作流程,如图8所示,为Reactor模型工作的简化流程。</p>
<p><img src="assets/-------4-.png" alt="img" /></p>
<blockquote>
<pre><code> 图8 Reactor模型简化流程示意图
</code></pre>
</blockquote>
<ol>
<li>注册I/O就绪事件处理器;</li>
<li>事件分离器等待I/O就绪事件;</li>
<li>I/O事件触发,激活事件分离器,分离器调度对应的事件处理器;</li>
<li>事件处理器完成I/O操作,处理数据.</li>
</ol>
<p>网络设计中,Reactor使用非常广,在开源社区有很许多非常成熟的、跨平台的、Reactor模型的网络库,比较典型如libevent。</p>
<h3>Proactor事件处理模型</h3>
<p>与Reactor不同的是,Proactor使用异步I/O系统接口将I/O操作托管给操作系统,Proactor模型中分发处理异步I/O完成事件,并调用相应的事件处理接口来处理业务逻辑。Proactor类结构如图9所示。</p>
<p><img src="assets/import8.png" alt="img" /></p>
<blockquote>
<pre><code> 图9 Proactor模型类结构图
</code></pre>
</blockquote>
<p>图9所示,Proactor类结构中包含有如下角色:</p>
<ul>
<li><strong>Handle</strong>: 用来标识socket连接或是打开文件;</li>
<li><strong>Async Operation Processor</strong>:异步操作处理器;负责执行异步操作,一般由操作系统内核实现;</li>
<li><strong>Async Operation</strong>:异步操作;</li>
<li><strong>Completion Event Queue</strong>:完成事件队列;异步操作完成的结果放到队列中等待后续使用;</li>
<li><strong>Proactor</strong>:主动器;为应用程序进程提供事件循环;从完成事件队列中取出异步操作的结果,分发调用相应的后续处理逻辑;</li>
<li><strong>Completion Handler</strong>:完成事件接口;一般是由回调函数组成的接口;</li>
<li><strong>Completion Handler A(B)</strong>:完成事件处理逻辑;实现接口定义特定的应用处理逻辑。</li>
</ul>
<p>Proactor模型的简化的工作流程,如图10所示。</p>
<p><img src="assets/-------3-.png" alt="img" /></p>
<blockquote>
<pre><code> 图10 Proactor模型简化工作流程示意图
</code></pre>
</blockquote>
<ol>
<li>发起I/O异步操作,注册I/O完成事件处理器;</li>
<li>事件分离器等待I/O操作完成事件;</li>
<li>内核并行执行实际的I/O操作,并将结果数据存入用户自定义缓 冲区;</li>
<li>内核完成I/O操作,通知事件分离器,事件分离器调度对应的事件处理器;</li>
<li>事件处理器处理用户自定义缓冲区中的数据。</li>
</ol>
<p>Proactor利用异步I/O并行能力,可给应用程序带来更高的效率,但是同时也增加了编程的复杂度。windows对异步I/O提供了非常好的支持,常用Proactor的模型实现服务器;而Linux对异步I/O操作(aio接口)的支持并不是特别理想,而且不能直接处理accept,因此Linux平台上还是以Reactor模型为主。</p>
<p>Boost asio采用的是Proactor模型,但是Linux上,采用I/O复用的方式来模拟Proactor,另启用线程来完成读写操作和调度。</p>
<h3>同步I/O模拟Proactor</h3>
<p>下面一种使用同步I/O模拟Proactor的方案,原理是:</p>
<p><strong>主线程执行数据读写操作,读写操作完成后,主线程向工作线程通知I/O操作“完成事件”;</strong></p>
<p>工作流程如图 11所示。</p>
<p><img src="assets/11.png" alt="img" /></p>
<blockquote>
<pre><code> 图11 同步I/O模拟Proactor模型
</code></pre>
</blockquote>
<p>简单的描述一下图11 的执行流程:</p>
<ol>
<li>主线程往系统I/O复用中注册文件描述符fd上的读就绪事件;</li>
<li>主线程调用调用系统I/O复用接口等待文件描述符fd上有数据可读;</li>
<li>当fd上有数据可读时,通知主线程。主线程循环读取fd上的数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。</li>
<li>睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后向I/O复用中注册fd上的写就绪事件。主线程进入事件等待循环,等待fd可写。</li>
</ol>
<h2>并发模式</h2>
<p>在I/O密集型的程序,采用并发方式可以提高CPU的使用率,可采用多进程和多线程两种方式实现并发。当前有高效的两种并发模式,半同步/半异步模式、Follower/Leader模式。</p>
<h3>半同步/半异步模式</h3>
<p>首先区分一个概念,并发模式中的“同步”、“异步”与 I/O模型中的“同步”、“异步”是两个不同的概念:</p>
<p><strong>并发模式中</strong>,“同步”指程序按照代码顺序执行,“异步”指程序依赖事件驱动,如图12 所示并发模式的“同步”执行和“异步”执行的读操作;</p>
<p><strong>I/O模型中</strong>,“同步”、“异步”用来区分I/O操作的方式,是主动通过I/O操作拿到结果,还是由内核异步的返回操作结果。</p>
<p><img src="assets/12.png" alt="img" /></p>
<blockquote>
<pre><code> 图12(a) 同步读操作示意图
</code></pre>
</blockquote>
<p><img src="assets/d4293cab-d80d-4396-9790-425c1e414cc5.png" alt="img" /></p>
<blockquote>
<pre><code> 图12(b) 异步读操作示意图
</code></pre>
</blockquote>
<p>本节从最简单的半同步/半异步模式的工作流程出发,并结合事件处理模型介绍两种演变的模式。</p>
<h4><strong>半同步/半异步工作流程</strong></h4>
<p>半同步/半异步模式的工作流程如图13 所示。</p>
<p><img src="assets/13-1.png" alt="img" /></p>
<blockquote>
<pre><code> 图13 半同步/半异步模式的工作流程示意图
</code></pre>
</blockquote>
<p>其中异步线程处理I/O事件,同步线程处理请求对象,简单的来说:</p>
<ol>
<li>异步线程监听到事件后,将其封装为请求对象插入到请求队列中;</li>
<li>请求队列有新的请求对象,通知同步线程获取请求对象;</li>
<li>同步线程处理请求对象,实现业务逻辑。</li>
</ol>
<h4>半同步/半反应堆模式</h4>
<p>考虑将两种事件处理模型,即Reactor和Proactor,与几种I/O模型结合在一起,那么半同步/半异步模式就演变为半同步/半反应堆模式。先看看使用Reactor的方式,如图14 所示。</p>
<p><img src="assets/15.png" alt="img" /></p>
<blockquote>
<pre><code> 图14 半同步/半反应堆模式示意图
</code></pre>
</blockquote>
<p>其工作流程为:</p>
<ol>
<li>异步线程监听所有fd上的I/O事件,若监听socket接可读,接受新的连接;并监听该连接上的读写事件;</li>
<li>若连接socket上有读写事件发生,异步线程将该连接socket插入请求队列中;</li>
<li>同步线程被唤醒,并接管连接socket,从socket上读取请求和发送应答;</li>
</ol>
<p>若将Reactor替换为Proactor,那么其工作流程为:</p>
<ol>
<li>异步线程完成I/O操作,并I/O操作的结果封装为任务对象,插入请求队列中;</li>
<li>请求队列通知同步线程处理任务;</li>
<li>同步线程执行任务处理逻辑。</li>
</ol>
<h4>一种高效的演变模式</h4>
<p>半同步/半反应堆模式有明显的缺点:</p>
<ol>
<li>异步线程和同步线程共享队列,需要保护,存在资源竞争;</li>
<li>工作线程同一时间只能处理一个任务,任务处理量很大或者任务处理存在一定的阻塞时,任务队列将会堆积,任务的时效性也等不到保证;不能简单地考虑增加工作线程来处理该问题,线程数达到一定的程度,工作线程的切换也将白白消耗大量的CPU资源。</li>
</ol>
<p>下面介绍一种改进的方式,如图15 所示,每个工作线程都有自己的事件循环,能同时独立处理多个用户连接。</p>
<p><img src="assets/16.png" alt="img" /></p>
<blockquote>
<pre><code> 图 15 半同步/半反应堆模式的演变模式
</code></pre>
</blockquote>
<p>其工作流程为:</p>
<ol>
<li>主线程实现连接监听,只处理网络I/O连接事件;</li>
<li>新的连接socket分发至工作线程中,这个socket上的I/O事件都由该工作线程处理,工作线程都可以处理多个socket 的I/O事件;</li>
<li>工作线程独立维护自己的事件循环,监听不同连接socket的I/O事件。</li>
</ol>
<h3>Follower/Leader 模式</h3>
<p>Follower/Leader是多个工作线程轮流进行事件监听、事件分发、处理事件的模式。</p>
<p>在Follower/Leader模式工作的任何一个时间点,只有一个工作线程处理成为Leader ,负责I/O事件监听,而其他线程都是Follower,并等待成为Leader。</p>
<p>Follower/Leader模式的工作流概述如下:</p>
<ol>
<li>当前Leader Thread1监听到就绪事件后,从Follower 线程集中推选出 Thread 2成为新的Leader;</li>
<li>新的Leader Thread2 继续事件I/O监听;</li>
<li>Thread1继续处理I/O就绪事件,执行完后加入到Follower 线程集中,等待成为Leader。</li>
</ol>
<p>从上描述,Leader/Follower模式的工作线程存在三种状态,工作线程同一时间只能处于一种状态,这三种状态为:</p>
<ul>
<li>Leader:线程处于领导者状态,负责监听I/O事件;</li>
<li>Processing:线程处理就绪I/O事件;</li>
<li>Follower:等待成为新的领导者或者可能被当前Leader指定处理就绪事件。</li>
</ul>
<p>Leader监听到I/O就绪事件后,有两种处理方式:</p>
<ol>
<li>推选出新的Leader后,并转移到Processing处理该I/O就绪事件;</li>
<li>指定其他Follower 线程处理该I/O就绪事件,此时保持Leader状态不变;</li>
</ol>
<p>如图16所示为上面描述的三种状态的转移关系。</p>
<p><img src="assets/17.png" alt="img" /></p>
<blockquote>
<pre><code> 图16 Follower/Leader模式状态转移示意图
</code></pre>
</blockquote>
<p>如图16所示,处于Processing状态的线程处理完I/O事件后,若当前不存在Leader,就自动提升为Leader,否则转变Follower。</p>
<p>从以上描述中可知,Follower/Leader模式中不需要在线程间传递数据,线程间也不存在共享资源。但很明显Follower/Leader 仅支持一个事件处理源集,无法做到图15所示的每个工作线程独立监听I/O事件。</p>
<h2>Swoole 网络模型分析</h2>
<p>Swoole为PHP提供I/O扩展功能,支持异步I/O、同步I/O、并发通信,并且为PHP多进程模式提供了并发数据结构和IPC通信机制;Swoole 既可以充当网络I/O服务器,也支持I/O客户端,较大程度为用户简化了网络I/O、多进程/多线程并发编程的工作。</p>
<p>Swoole作为server时,支持3种运行模式,分别是多进程模式、多线程模式、多进程+多线程模式;多进程+多线程模式是其中最为复杂的方式,其他两种方式可以认为是其特例。</p>
<p>本节结合之前介绍几种事件处理模型、并发模式来分析Swoole server的多进程+多线程模型,如图17。<img src="assets/2.png" alt="img" /></p>
<blockquote>
<pre><code> 图17 swoole server多进程+多线程模型结构示意图
</code></pre>
</blockquote>
<p>图17所示,整体上可以分为Master Process、Manger Process、Work Process Pool三部分。这三部分的主要功能:</p>
<ol>
<li>**Master Process:**监听服务端口,接收用户连接,收发连接数据,依靠reactor模型驱动;</li>
<li>**Manager Process:**Master Process的子进程,负责fork WorkProcess,并监控Work Process的运行状态;</li>
<li>**Work Process Pool:**工作进程池,与PHP业务层交互,将客户端数据或者事件(如连接关闭)回调给业务层,并将业务层的响应数据或者操作(如主动关闭连接)交给Master Process处理;工作进程依靠reactor模型驱动。</li>
</ol>
<p>Manager Process 监控Work Process进程,本节不做进一步讲解,主要关注Master和Work。</p>
<h4><strong>Master Process</strong></h4>
<p>Master Process 内部包括主线程(Main Thread)和工作线程池(Work Thread Pool),这两部分主要功能分别是:</p>
<p><strong>主线程:</strong> 监听服务端口,接收网络连接,将成功建立的连接分发到线程池中;依赖reactor模型驱动;</p>
<p><strong>工作线程池:</strong> 独立管理连接,收发网络数据;依赖Reactor事件处理驱动。</p>
<p>顾一下前面介绍的半同步/半异步并发模式,很明显,主进程的工作方式就是图15所示的方式。</p>
<h4>Work Process</h4>
<p>如上所描述,Work Process是Master Process和PHP层之间的媒介:</p>
<ol>
<li>Work Process接收来自Master Process的数据,包括网络数据和连接事件,回调至PHP业务层;</li>
<li>将来自PHP层的数据和连接控制信息发送给Master Process进程,Master Process来处理。</li>
</ol>
<p>Work Process同样是依赖Reactor事件模型驱动,其工作方式一个典型的Reactor模式。</p>
<p>Work Process作为Master Process和PHP层之间的媒介,将数据收发操作和数据处理分离开来,即使PHP层因消息处理将Work进程阻塞一段时间,也不会对其他连接有影响。</p>
<p>从整体层面来看,Master Process实现对连接socket上数据的I/O操作,这个过程对于Work Process是异步的,结合图11 所描述的同步I/O模拟Proactor模式,两种方式如出一辙,只不过这里使用的是多进程。</p>
<h4><strong>进程间通信</strong></h4>
<p>Work Process是Master Process和PHP层之间的媒介,那么需要看看Work Process 与Master Process之间的通信方式,并在Swoole server 的多进程+多线程模型进程中,整个过程还是有些复杂,下面说明一下该流程,如图18所示。
<img src="assets/-------12--2.png" alt="img" /></p>
<blockquote>
<pre><code> 图18 swoole server 多进程多线程通信示意图
</code></pre>
</blockquote>
<p>具体流程为:</p>
<ol>
<li>Master 进程主线程接收客户端连接,连接建立成功后,分发至工作线程,工作线程通过Unix Socket通知Work进程连接信息;</li>
<li>Work 进程将连接信息回调至PHP业务层;</li>
<li>Maser 进程中的工作线程接收客户端请求消息,并通过Unix Socket方式发送到Work进程;</li>
<li>Work 进程将请求消息回调至PHP业务层;</li>
<li>PHP业务层构造回复消息,通过Work进程发送,Work进程将回复消息拷贝至共享内存中,并通过Unix Socket通知发送至Master进程的工作线程有数据需要发送;</li>
<li>工作线程从共享内存中取出需发送的数据,并发送至客户端;</li>
<li>客户端断开连接,工作线程将连接断开的事件通过UnixSocket发送至Work进程;</li>
<li>Work进程将连接断开事件回调至PHP业务层.</li>
</ol>
<p>需要注意在步骤5中,Work进程通知Master进程有数据需要发送,不是将数据直接发送给Master进程,而是将数据地址(在共享内存中)发送给Master进程。</p>
<h2>改善性能的方法</h2>
<p>性能对于服务器而言是非常敏感和重要的,当前,硬件的发展虽然不是服务器性能的瓶颈,作为软件开发人员还是应该考虑在软件层面来上改善服务性能。好的网络模块,除了稳定性,还有非常多的细节、技巧处理来提升服务性能,感兴趣的同学可以深入了解Ngnix源码的细节,以及陈硕的《Linux多线程服务器编程》。</p>
<h3>数据复制</h3>
<p>如果应用程序不关心数据的内容,就没有必要将数据拷贝到应用缓冲区,可以借助内核接口直接将数据拷贝到内核缓冲区处理,如在提供文件下载服务时,不需要将文件内容先读到应用缓冲区,在调用send接口发送出去,可以直接使用sendfile (零拷贝)接口直接发送出去。</p>
<p>应用程序的工作模块之间也应该避免数据拷贝,如:</p>
<ol>
<li>当两个工作进程之间需要传递数据,可以考虑使用共享内存的方式实现数据共享;</li>
<li>在流媒体的应用中,对帧数据的非必要拷贝会对程序性能的影响,特备是在嵌入式环境中影响非常明显。通常采用的办法是,给每帧数据分配内存(下面统称为buffer),当需要使用该buffer时,会增加该buffer的引用计数,buffer的引用计数为0时才会释放对应的内存。这种方式适合在进程内数据无拷贝传递,并且不会给释放buffer带来困扰。</li>
</ol>
<h3>资源池</h3>
<p>在服务运行期间,需要使用系统调用为用户分配资源,通常系统资源的分配都是比较耗时的,如动态创建进程/线程。可以考虑在服务启动时预先分配资源,即创建资源池,当需要资源,从资源池中获取即可,若资源池不够用时,再动态的分配,使用完成后交还到资源池中。这实际上是用空间换取时间,在服务运行期间可以节省非必要的资源创建过程。需要注意的是,使用资源池还需要根据业务和硬件环境对资源池的大小进行限制。</p>
<p>资源池是一个抽象的概念,常见的包括进程池、线程池、 内存池、连接池;这些资源池的相关资料非常多,这里就不一一介绍了。</p>
<h3>锁/上下文切换</h3>
<p>1.<strong>关于锁</strong>
对共享资源的操作是并发程序中经常被提起的一个话题,都知道在业务逻辑上无法保证同步操作共享资源时,需要对共享资源加锁保护,但是锁不仅不能处理任何业务逻辑,而且还存在一定的系统开销。并且对锁的不恰当使用,可能成为服务期性能的瓶颈。</p>
<p>针对锁的使用有如下建议:</p>
<ol>
<li>如果能够在设计层面避免共享资源竞争,就可以避免锁,如图15描述的模式;</li>
<li>若无法避免对共享资源的竞争,优先考虑使用无锁队列的方式实现共享资源;</li>
<li>使用锁时,优先考虑使用读写锁;此外,锁的范围也要考虑,尽量较少锁的颗粒度,避免其他线程无谓的等待。</li>
</ol>
<p>2.<strong>上下文切换</strong>
并发程序需要考虑上下文切换的问题,内核调度线程(进程)执行是存在系统开销的,若线程(进程)调度占用CPU的时间比重过大,那处理业务逻辑占用的CPU时间就会不足。在项目中,线程(进程)数量越多,上下文切换会很频繁,因此是不建议为每个用户连接创建一个线程,如图15所示的并发模式,一个线程可同时处理多个用户连接,是比较合理的解决方案。</p>
<p>多核的机器上,并发程序的不同线程可以运行在不同的CPU上,只要线程数量不大于CPU数目,上下文切换不会有什么问题,在实际的并发网络模块中,线程(进程)的个数也是根据CPU数目来确定的。在多核机器上,可以设置CPU亲和性,将进程/线程与CPU绑定,提高CPU cache的命中率,建好内存访问损耗。</p>
<h3>有限状态机器</h3>
<p>有限状态机是一种高效的逻辑处理方式,在网络协议处理中应用非常广泛,最典型的是内核协议栈中TCP状态转移。有限状态机中每种类型对应执行逻辑单元的状态,对逻辑事务的处理非常有效。 有限状态机包括两种,一种是每个状态都是相互独立的,状态间不存在转移;另一种就是状态间存在转移。有限状态机比较容易理解,下面给出两种有限状态机的示例代码。</p>
<p><strong>不存在状态转移</strong></p>
<pre><code class="language-c">typedef enum _tag_state_enum{
A_STATE,
B_STATE,
C_STATE,
D_STATE
}state_enum;
void STATE_MACHINE_HANDLER(state_enum cur_state) {
switch (cur_state){
case A_STATE:
process_A_STATE();
break;
case B_STATE:
process_B_STATE();
break;
case C_STATE:
process_C_STATE();
break;
default:
break;
}
return ;
}
</code></pre>
<p><strong>存在状态转移</strong></p>
<pre><code class="language-c">void TRANS_STATE_MACHINE_HANDLER(state_enum cur_state) {
while (C_STATE != cur_state) {
switch (cur_state) {
case A_STATE:
process_A_STATE();
cur_state = B_STATE;
break;
case B_STATE:
process_B_STATE();
cur_state = C_STATE;
break;
case C_STATE:
process_C_STATE();
cur_state = D_STATE;
break;
default:
return ;
}
}
return ;
}
</code></pre>
<h3>时间轮</h3>
<p>经常会面临一些业务定时超时的需求,用例子来说明吧。</p>
<p><strong>功能需求</strong>:服务器需要维护来自大量客户端的TCP连接(假设单机服务器需要支持的最大TCP连接数在10W级别),如果某连接上60s内没有数据到达,就认为相应的客户端下线。</p>
<p>先介绍一下两种容易想到的解决方案,</p>
<p><strong>方案a</strong> <strong>轮询扫描</strong></p>
<p>处理过程为:</p>
<ol>
<li>维护一个map<client_id, last_update_time > 记录客户端最近一次的请求时间;</li>
<li>当client_id对应连接有数据到达时,更新last_update_time;</li>
<li>启动一个定时器,轮询扫描map 中client_id 对应的last_update_time,若超过 60s,则认为对应的客户端下线。</li>
</ol>
<p>轮询扫描,只启动一个定时器,但轮询效率低,特别是服务器维护的连接数很大时,部分连接超时事件得不到及时处理。</p>
<p><strong>方案b</strong> <strong>多定时器触发</strong></p>
<p>处理过程为:</p>
<ol>
<li>维护一个map<client_id, last_update_time > 记录客户端最近一次的请求时间;</li>
<li>当某client_id 对应连接有数据到达时,更新last_update_time,同时为client_id启用一个定时器,60s后触发;</li>
<li>当client_id对应的定时器触发后,查看map中client_id对应的last_update_time是否超过60s,若超时则认为对应客户端下线。</li>
</ol>
<p>多定时器触发,每次请求都要启动一个定时器,可以想象,消息请求非常频繁是,定时器的数量将会很庞大,消耗大量的系统资源。</p>
<p><strong>方案c 时间轮方案</strong></p>
<p>下面介绍一下利用时间轮的方式实现的一种高效、能批量的处理方案,先说一下需要的数据结构:</p>
<ol>
<li>创建0~60的数据,构成环形队列time_wheel,current_index维护环形队列的当前游标,如图19所示;</li>
<li>数组元素是slot 结构,slot是一个set<client_id>,构成任务集;</li>
<li>维护一个map<client_id,index>,记录client_id 落在哪个slot上。</li>
</ol>
<p><img src="assets/1.png" alt="img" /></p>
<blockquote>
<pre><code> 图19 时间轮环形队列示意图
</code></pre>
</blockquote>
<p>执行过程为:</p>
<ol>
<li>启用一个定时器,运行间隔1s,更新current_index,指向环形队列下一个元素,0->1->2->3...->58->59->60...0;</li>
<li>连接上数据到达时,从map中获取client_id所在的slot,在slot的set中删除该client_id;</li>
<li>将client_id加入到current_index - 1锁标记的slot中;</li>
<li>更新map中client_id 为current_id-1 。</li>
</ol>
<p><strong>与a、b两种方案相比,方案c具有如下优势</strong>:</p>
<ol>
<li>只需要一个定时器,运行间隔1s,CPU消耗非常少;</li>
<li>current_index 所标记的slot中的set不为空时,set中的所有client_id对应的客户端均认为下线,即批量超时。</li>
</ol>
<p>上面描述的时间轮处理方式会存在1s以内的误差,若考虑实时性,可以提高定时器的运行间隔,另外该方案可以根据实际业务需求扩展到应用中。我们对Swoole的修改中,包括对定时器进行了重构,其中超时定时器采用的就是如上所描述的时间轮方案,并且精度可控。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/当我们在讨论CQRS时,我们在讨论些神马?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709980565bdb8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>