-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1766 lines (921 loc) · 85.6 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 gemini use-motion" lang="">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Hexo 3.9.0">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=7.3.0">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=7.3.0">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=7.3.0">
<link rel="mask-icon" href="/images/logo.svg?v=7.3.0" color="#222">
<link rel="stylesheet" href="/css/main.css?v=7.3.0">
<link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css?v=4.7.0">
<script id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Gemini',
version: '7.3.0',
sidebar: {"position":"left","display":"post","offset":12,"onmobile":false},
back2top: {"enable":true,"sidebar":false,"scrollpercent":false},
copycode: {"enable":false,"show_result":false,"style":null},
fancybox: false,
mediumzoom: false,
lazyload: false,
pangu: false,
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
},
localsearch: {"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},
search: {
root: '/',
path: ''
},
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
translation: {
copy_button: 'Copy',
copy_success: 'Copied',
copy_failure: 'Copy failed'
}
};
</script>
<meta property="og:type" content="website">
<meta property="og:title" content="姜太公的记事本">
<meta property="og:url" content="http://oditszapc.com/index.html">
<meta property="og:site_name" content="姜太公的记事本">
<meta property="og:locale" content="default">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="姜太公的记事本">
<link rel="canonical" href="http://oditszapc.com/">
<script id="page.configurations">
CONFIG.page = {
sidebar: "",
};
</script>
<title>姜太公的记事本</title>
<noscript>
<style>
.use-motion .motion-element,
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-title { opacity: initial; }
.use-motion .logo,
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="default">
<div class="container 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-brand-wrapper">
<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">姜太公的记事本</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
</div>
<div class="site-nav-toggle">
<button aria-label="Toggle navigation bar">
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home menu-item-active">
<a href="/" rel="section"><i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br>Home</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives" rel="section"><i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br>Archives</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags" rel="section"><i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br>Tags</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories" rel="section"><i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br>Categories</a>
</li>
</ul>
</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">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2019/09/20/high-performance-server/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2019/09/20/high-performance-server/" class="post-title-link" itemprop="url">Untitled</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2019-09-20 00:00:00" itemprop="dateCreated datePublished" datetime="2019-09-20T00:00:00+08:00">2019-09-20</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-10-22 12:45:04" itemprop="dateModified" datetime="2019-10-22T12:45:04+08:00">2019-10-22</time>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="Linux高性能服务器设计"><a href="#Linux高性能服务器设计" class="headerlink" title="Linux高性能服务器设计"></a>Linux高性能服务器设计</h1><h2 id="C10K和C10M"><a href="#C10K和C10M" class="headerlink" title="C10K和C10M"></a>C10K和C10M</h2><p>计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,Dan Kegel提出了著名的C10问题:一台服务器上同时处理10000个客户网络连接。10000个网络连接并不会发送请求到服务器,有些连接并不活跃,同一时刻,只有极少的部分连接发送请求。不同的服务类型,每个连接发送请求的频率也不相同,游戏服务器的连接会频繁的发送请求,而Web服务器的连接发送请求的频率就低很多。无论如何,根据经验法则,对于特定的服务类型,连接越多,同一时刻发送请求的连接也越多。</p>
<p>时至今日,C10K问题当然早已解决,不仅如此,一台机器能支撑的连接越来越多,后来提出了C10M问题,在一台机器上支撑1000万的连接,2015年,MigratoryData在单机承载12M的连接,解决了C10M问题。</p>
<p>本文先回顾C10问题的解决方案,再探讨如何构建支撑C10M的应用程序,聊聊其中涉及的各种技术。</p>
<h2 id="C10K问题的解决"><a href="#C10K问题的解决" class="headerlink" title="C10K问题的解决"></a>C10K问题的解决</h2><p>时间退回到1999年,当时要实现一个网络服务器,大概有这样几种模式</p>
<h3 id="简单进程-线程模型"><a href="#简单进程-线程模型" class="headerlink" title="简单进程/线程模型"></a>简单进程/线程模型</h3><p>这是一种非常简单的模式,服务器启动后监听端口,阻塞在accept上,当新网络连接建立后,accept返回新连接,服务器启动一个新的进程/线程专门负责这个连接。从性能和伸缩性来说,这种模式是非常糟糕的,原因在于</p>
<ul>
<li>进程/线程创建和销毁的时间,操作系统创建一个进程/线程显然需要时间,在一个繁忙的服务器上,如果每秒都有大量的连接建立和断开,采用每个进程/线程处理一个客户连接的模式,每个新连接都要创建创建一个进程/线程,当连接断开时,销毁对应的线程/进程。创建和销毁进程/线程的操作消耗了大量的CPU资源。使用进程池和线程池可以缓解这个问题。</li>
<li>内存占用。主要包含两方面,一个是内核数据结构所占用的内存空间,另外一个是Stack所占用的内存。有些应用的调用栈很深,比如Java应用,经常能看到几十上百层的调用栈。</li>
<li>上下文切换的开销。上下文切换时,操作系统的调度器中断当前线程,选择另外一个可运行的线程在CPU上继续运行。调度器需要保存当前线程的现场信息,然后选择一个可运行的线程,再将新线程的状态恢复到寄存器中。保存和恢复现场所需要的时间和CPU型号有关,选择一个可运行的线程则完全是软件操作,Linux 2.6才开始使用常量时间的调度算法。 以上是上下文切换的直接开销。除此之外还有一些间接开销,上下文切换导致相关的缓存失效,比如L1/L2 Cache,TLB等,这些也会影响程序的性能,但是间接开销很难衡量。</li>
</ul>
<p>有意思的是,这种模式虽然性能极差,但却依然是我们今天最常见到的模式,很多Web程序都是这样的方式在运行。</p>
<h3 id="select-poll"><a href="#select-poll" class="headerlink" title="select/poll"></a>select/poll</h3><p>另外一种方式是使用select/poll,在一个线程内处理多个客户连接。select和poll能够监控多个socket文件描述符,当某个文件描述符就绪,select/soll从阻塞状态返回,通知应用程序可以处理用户连接了。使用这种方式,我们只需要一个线程就可以处理大量的连接,避免了多进程/线程的开销。之所以把select和poll放在一起说,原因在于两者非常相似,性能上基本没有区别,唯一的区别在于poll突破了select 1024个文件描述符的限制,然而当文件描述符数量增加时,poll性能急剧下降,因此所谓突破1024个文件描述符实际上毫无意义。select/poll并不完美,依然存在很多问题:</p>
<ol>
<li>每次调用select/poll,都要把文件描述符的集合从用户地址空间复制到内核地址空间</li>
<li>select/poll返回后,调用方必须遍历所有的文件描述符,逐一判断文件描述符是否可读/可写。</li>
</ol>
<p>这两个限制让select/poll完全失去了伸缩性。连接数越多,文件描述符就越多,文件描述符越多,每次调用select/poll所带来的用户空间到内核空间的复制开销越大。最严重的是当报文达到,select/poll返回之后,必须遍历所有的文件描述符。假设现在有1万个连接,其中只一个连接发送了请求,但是select/poll就要把1万个连接全部检查一遍。</p>
<h3 id="epoll"><a href="#epoll" class="headerlink" title="epoll"></a>epoll</h3><p>FreeBSD 4.1引入了kqueue,此时是2000年7月,而在Linux上,还要等待2年后的2002年才开始引入kqueue的类似实现: epoll。epoll最初于 2.5.44进入Linux kernel mainline,此时已经是2002年,距离C10K问题提出已经过了3年。</p>
<p>epoll是如何提供一个高性能可伸缩的IO多路复用机制呢?首先,epoll引入了epoll instance这个概念,epoll instance在内核中关联了一组要监听的文件描述符配置:interest list,这样的好处在于,每次要增加一个要监听的文件描述符,不需要把所有的文件描述符都配置一次,然后从用户地址空间复制到内核地址空间,只需要把单个文件描述符复制到内核地址空间,复制开销从O(n)降到了O(1)。</p>
<p>注册完文件描述符后,调用epoll_wait开始等待文件描述符事件。epoll_wait可以只返回已经ready的文件描述符,因此,在epoll_wait返回之后,程序只需要处理真正需要处理的文件描述符,而不用把所有的文件描述符全部遍历一遍。假设在全部N个文件描述符中,只有一个文件描述符Ready,select/poll要执行N次循环,epoll只需要一次。</p>
<p>epoll出现之后,Linux上才真正有了一个可伸缩的IO多路复用机制。基于epoll,能够支撑的网络连接数取决于硬件资源的配置,而不再受限于内核的实现机制。CPU越强,内存越大,能支撑的连接数越多。</p>
<h2 id="编程模型"><a href="#编程模型" class="headerlink" title="编程模型"></a>编程模型</h2><h3 id="Reactor和proactor"><a href="#Reactor和proactor" class="headerlink" title="Reactor和proactor"></a>Reactor和proactor</h3><p>不同的操作系统上提供了不同的IO多路复用实现,Linux上有epoll,FreeBSD有kqueue,Windows有IOCP。对于需要跨平台的程序,必然需要一个抽象层,提供一个统一的IO多路复用接口,屏蔽各个系统接口的差异性。</p>
<p>Reactor是实现这个目标的一次尝试,最早出现在Douglas C. Schmidt的论文”The Reactor An Object-Oriented Wrapper for Event-Driven Port Monitoring and Service Demultiplexing”中。从论文的名字可以看出,Reactor是poll这种编程模式的一个面向对象包装。考虑到论文的时间,当时正是面向对象概念正火热的时候,什么东西都要蹭蹭面向对象的热度。论文中,DC Schmidt描述了为什么要做这样的一个Wrapper,给出了下面几个原因</p>
<ol>
<li>操作系统提供的接口太复杂,容易出错。select和poll都是通用接口,因为通用,增加了学习和正确使用的复杂度。</li>
<li>接口抽象层次太低,涉及太多底层的细节。</li>
<li>不能跨平台移植。</li>
<li>难以扩展。</li>
</ol>
<p>实际上除了第三条跨平台,其他几个理由实在难以站得住脚。select/poll这类接口复杂吗,使用起来容易出错吗,写出来的程序难以扩展吗?不过不这么说怎么体现Reactor的价值呢。正如论文名称所说的,Reactor本质是对操作系统IO多路复用机制的一个面向对象包装,为了证明Reactor的价值,DC Schmidt还用C++面向对象的特性实现了一个编程框架:ACE,实际上使用ACE比直接使用poll或者epoll复杂多了。</p>
<p>后来DC Schmidt写了一本书《面向模式的软件架构》,再次提到了Reactor,并重新命名为Reactor Pattern,现在网络上能找到的Reactor资料,基本上都是基于Reactor Pattern,而不是早期的面向Object-Orientend Wrapper。</p>
<p>《面向模式的软件》架构中还提到了另外一种叫做Proactor的模式,和Reactor非常类似,Reactor针对同步IO,Proactor则针对异步IO。</p>
<h3 id="Callback,Future和纤程"><a href="#Callback,Future和纤程" class="headerlink" title="Callback,Future和纤程"></a>Callback,Future和纤程</h3><p>Reactor看上去并不复杂,但是想编写一个完整的应用程序时候就会发现其实没那么简单。为了避免Reactor主逻辑阻塞,所有可能会导致阻塞的操作必须注册到epoll上,带来的问题就是处理逻辑的支离破碎,大量使用callback,产生的代码复杂难懂。如果应用程序中还有非网络IO的阻塞操作,问题更严重,比如在程序中读写文件。Linux中文件系统操作都是阻塞的,虽然也有Linux AIO,但是一直不够成熟,难堪大用。很多软件采用线程池来解决这个问题,不能通过epoll解决的阻塞操作,扔到一个线程池执行。这又产生了多线程内存开销和上下文切换的问题。</p>
<p>Future机制是对Callback的简单优化,本质上还是Callback,但是提供了一致的接口,代码相对来说简单一些,不过在实际使用中还是比较复杂的。Seastar是一个非常彻底的future风格的框架,从它的代码可以看到这种编程风格真的非常复杂,阻塞式编程中一个函数几行代码就能搞定的事情,在Seastar里需要上百行代码,几十个labmda (在Seastar里叫做continuation)。</p>
<p>纤程是一种用户态调度的线程,比如Go语言中的goroutine,有些人可能会把这种机制成为coroutine,不过我认为coroutine和纤程还是有很大区别的,coroutine是泛化的子进程,具有多个进入和退出点,用来一些一些相互协作的程序,典型的例子就是Python中的generator。纤程则是一种运行和调度机制。</p>
<p>纤程真正做到了高性能和易用,在Go语言中,使用goroutine实现的高性能服务器是一件轻松愉快的事情,完全不用考虑线程数、epoll、回调之类的复杂操作,和编写阻塞式程序完全一样。</p>
<h2 id="网络优化"><a href="#网络优化" class="headerlink" title="网络优化"></a>网络优化</h2><h3 id="Kernel-bypass"><a href="#Kernel-bypass" class="headerlink" title="Kernel bypass"></a>Kernel bypass</h3><p>网络子系统是Linux内核中一个非常庞大的组件,提供了各种通用的网络能力。通用通常意味在在某些场景下并不是最佳选择。实际上业界的共识是Linux内核网络不支持超大并发的网络能力。根据我过去的经验,Linux最大只能处理1MPPS,而现在的10Gbps网卡通常可以处理10MPPS。随着更高性能的25Gbps,40Gbps网卡出现,Linux内核网络能力越发捉襟见肘。</p>
<p>为什么Linux不能充分发挥网卡的处理能力?原因在于:</p>
<ul>
<li>大多数网卡收发使用中断方式,每次中断处理时间大约100us,另外要考虑cache miss带来的开销。部分网卡使用NAPI,轮询+中断结合的方式处理报文,当报文放进队列之后,依然要触发软中断。</li>
<li>数据从内核地址空间复制到用户地址空间。</li>
<li>收发包都有系统调用。</li>
<li>网卡到应用进程的链路太长,包含了很多不必要的操作。</li>
</ul>
<p>Linux高性能网络一个方向就是绕过内核的网络栈(kernel bypass),业界有不少尝试</p>
<ul>
<li>PF_RING 高效的数据包捕获技术,比libpcap性能更好。需要自己安装内核模块,启用ZC Driver,设置transparent_mode=2的情况下,报文直接投递到客户端程序,绕过内核网络栈。</li>
<li>Snabbswitch 一个Lua写的网络框架。完全接管网卡,使用UIO(Userspace IO)技术在用户态实现了网卡驱动。</li>
<li>Intel DPDK,直接在用户态处理报文。非常成熟,性能强大,限制是只能用在Intel的网卡上。根据DPDK的数据,3GHz的CPU Core上,平均每个报文的处理时间只要60ns(一次内存的访问时间)。</li>
<li>Netmap 一个高性能收发原始数据包的框架,包含了内核模块以及用户态库函数,需要网卡驱动程序配合,因此目前只支持特定的几种网卡类型,用户也可以自己修改网卡驱动。</li>
<li>XDP,使用Linux eBPF机制,将报文处理逻辑下放到网卡驱动程序中。一般用于报文过滤、转发的场景。</li>
</ul>
<p>kernel bypass技术最大的问题在于不支持POSIX接口,用户没办法不修改代码直接移植到一种kernel bypass技术上。对于大多数程序来说,还要要运行在标准的内核网络栈上,通过调整内核参数提升网络性能。</p>
<h3 id="网卡多队列"><a href="#网卡多队列" class="headerlink" title="网卡多队列"></a>网卡多队列</h3><p>报文到达网卡之后,在一个CPU上触发中断,CPU执行网卡驱动程序从网卡硬件缓冲区读取报文内容,解析后放到CPU接收队列上。这里所有的操作都在一个特定的CPU上完成,高性能场景下,单个CPU处理不了所有的报文。对于支持多队列的网卡,报文可以分散到多个队列上,每个队列对应一个CPU处理,解决了单个CPU处理瓶颈。</p>
<p>为了充分发挥多队列网卡的价值,我们还得做一些额外的设置:把每个队列的中断号绑定到特定CPU上。这样做的目的,一方面确保网卡中断的负载能分配到不同的CPU上,另外一方面可以将负责网卡中断的CPU和负责应用程序的CPU区分开,避免相互干扰。</p>
<p>在Linux中,/sys/class/net/${interface}/device/msi_irqs下保存了每个队列的中断号,有了中断号之后,我们就可以设置中断和CPU的对应关系了。网上有很多文章可以参考。</p>
<h3 id="网卡Offloading"><a href="#网卡Offloading" class="headerlink" title="网卡Offloading"></a>网卡Offloading</h3><p>回忆下TCP数据的发送过程:应用程序将数据写到套接字缓冲区,内核将缓冲区数据切分成不大于MSS的片段,附加上TCP Header和IP Header,计算Checksum,然后将数据推到网卡发送队列。这个过程中需要CPU全程参与, 随着网卡的速度越来越快,CPU逐渐成为瓶颈,CPU处理数据的速度已经赶不上网卡发送数据的速度。经验法则,发送或者接收1bit/s TCP数据,需要1Hz的CPU,1Gbps需要1GHz的CPU,10Gbps需要10GHz的CPU,已经远超单核CPU的能力,即使能完全使用多核,假设单个CPU Core是2.5GHz,依然需要4个CPU Core。</p>
<p>为了优化性能,现代网卡都在硬件层面集成了TCP分段、添加IP Header、计算Checksum等功能,这些操作不再需要CPU参与。这个功能叫做tcp segment offloading,简称tso。使用ethtool -k 可以检查网卡是否开启了tso</p>
<p>除了tso,还有其他几种offloading,比如支持udp分片的ufo,不依赖驱动的gso,优化接收链路的lro</p>
<h2 id="充分利用多核"><a href="#充分利用多核" class="headerlink" title="充分利用多核"></a>充分利用多核</h2><p>随着摩尔定律失效,CPU已经从追求高主频转向追求更多的核数,现在的服务器大都是96核甚至更高。构建一个支撑C10M的应用程序,必须充分利用所有的CPU,最重要的是程序要具备水平伸缩的能力:随着CPU数量的增多程序能够支撑更多的连接。</p>
<p>很多人都有一个误解,认为程序里使用了多线程就能利用多核,考虑下CPython程序,你可以创建多个线程,但是由于GIL的存在,程序最多只能使用单个CPU。实际上多线程和并行本身就是不同的概念,多线程表示程序内部多个任务并发执行,每个线程内的任务可以完全不一样,线程数和CPU核数没有直接关系,单核机器上可以跑几百个线程。并行则是为了充分利用计算资源,将一个大的任务拆解成小规模的任务,分配到每个CPU上运行。并行可以 通过多线程实现,系统上有几个CPU就启动几个线程,每个线程完成一部分任务。</p>
<p>并行编程的难点在于如何正确处理共享资源。并发访问共享资源,最简单的方式就加锁,然而使用锁又带来性能问题,获取锁和释放锁本身有性能开销,锁保护的临界区代码不能只能顺序执行,就像CPython的GIL,没能充分利用CPU。</p>
<h3 id="Thread-Local和Per-CPU变量"><a href="#Thread-Local和Per-CPU变量" class="headerlink" title="Thread Local和Per-CPU变量"></a>Thread Local和Per-CPU变量</h3><p>这两种方式的思路是一样的,都是创建变量的多个副本,使用变量时只访问本地副本,因此不需要任何同步。现代编程语言基本上都支持Thread Local,使用起来也很简单,C/C++里也可以使用__thread标记声明ThreadLocal变量。</p>
<p>Per-CPU则依赖操作系统,当我们提到Per-CPU的时候,通常是指Linux的Per-CPU机制。Linux内核代码中大量使用Per-CPU变量,但应用代码中并不常见,如果应用程序中工作线程数等于CPU数量,且每个线程Pin到一个CPU上,此时才可以使用。</p>
<h3 id="原子变量"><a href="#原子变量" class="headerlink" title="原子变量"></a>原子变量</h3><p>如果共享资源是int之类的简单类型,访问模式也比较简单,此时可以使用原子变量。相比使用锁,原子变量性能更好。在竞争不激烈的情况下,原子变量的操作性能基本上和加锁的性能一致,但是在并发比较激烈的时候,等待锁的线程要进入等待队列等待重新调度,这里的挂起和重新调度过程需要上下文切换,浪费了更多的时间。</p>
<p>大部分编程语言都提供了基本变量对应的原子类型,一般提供set, get, compareAndSet等操作。</p>
<h3 id="lock-free"><a href="#lock-free" class="headerlink" title="lock-free"></a>lock-free</h3><p>lock-free这个概念来自<java concurrency in practice></java></p>
<blockquote>
<p> An algorithm is called non‐blocking if failure or suspension of any thread cannot cause failure or suspension of another thread; an algorithm is called lock‐free if, at each step, some thread can make progress.</p>
</blockquote>
<p>non-blocking算法任何线程失败或者挂起,不会导致其他线程失败或者挂起,lock-free则进一步保证线程间无依赖。这个表述比较抽象,具体来说,non-blocking要求不存在互斥,存在互斥的情况下,线程必须先获取锁再进入临界区,如果当前持有锁的线程被挂起,等待锁的线程必然需要一直等待下去。对于活锁或者饥饿的场景,线程失败或者挂起的时候,其他线程完全不仅能正常运行,说不定还解决了活锁和饥饿的问题,因此活锁和饥饿符合non-blocking,但是不符合lock-free。</p>
<p>实现一个lock-free数据结构并不容易,好在已经有了几种常见数据结构的的lock-free实现:buffer, list, stack, queue, map, deque,我们直接拿来使用就行了。</p>
<h3 id="优化对锁的使用"><a href="#优化对锁的使用" class="headerlink" title="优化对锁的使用"></a>优化对锁的使用</h3><p>有时候没有条件使用lock-free,还是得用锁,对于这种情况,还是有一些优化手段的。首先使用尽量减少临界区的大小,使用细粒度的锁,锁粒度越细,并行执行的效果越好。其次选择适合的锁,比如考虑选择读写锁。</p>
<h3 id="CPU-affinity"><a href="#CPU-affinity" class="headerlink" title="CPU affinity"></a>CPU affinity</h3><p>使用CPU affinity机制合理规划线程和CPU的绑定关系。前面提到使用CPU affinity机制,将多队列网卡的中断处理分散到多个CPU上。不仅是中断处理,线程也可以绑定,绑定之后,线程只会运行在绑定的CPU上。为什么要将线程绑定到CPU上呢?绑定CPU有这样几个好处</p>
<ul>
<li>为线程保留CPU,确保线程有足够的资源运行</li>
<li>提高CPU cache的命中率,某些对cache敏感的线程必须绑定到CPU上才行。</li>
<li>更精细的资源控制。可以预先需要静态划分各个工作线程的资源,例如为每个请求处理线程分配一个CPU,其他后台线程共享一个CPU,工作线程和中断处理程序工作在不同的CPU上。</li>
<li>NUMA架构中,每个CPU有自己的内存控制器和内存插槽,CPU访问本地内存别访问远程内存快3倍左右。使用affinity将线程绑定在CPU上,相关的数据也分配到CPU对应的本地内存上。</li>
</ul>
<p>Linux上设置CPU affinity很简单,可以使用命令行工具taskset,也可以在程序内直接调用API <code>sched_getaffinity</code>和<code>sched_setaffinity</code></p>
<h2 id="其他优化技术"><a href="#其他优化技术" class="headerlink" title="其他优化技术"></a>其他优化技术</h2><h3 id="使用Hugepage"><a href="#使用Hugepage" class="headerlink" title="使用Hugepage"></a>使用Hugepage</h3><p>Linux中,程序内使用的内存地址是虚拟地址,并不是内存的物理地址。为了简化虚拟地址到物理地址的映射,虚拟地址到物理地址的映射最小单位是“Page”,默认情况下,每个页大小为4KB。CPU指令中出现的虚拟地址,为了读取内存中的数据,指令执行前要把虚拟地址转换成内存物理地址。Linux为每个进程维护了一张虚拟地址到物理地址的映射表,CPU先查表找到虚拟地址对应的物理地址,再执行指令。由于映射表维护在内存中,CPU查表就要访问内存。相对CPU的速度来说,内存其实是相当慢的,一般来说,CPU L1 Cache的访问速度在1ns左右,而一次内存访问需要60-100ns,比CPU执行一条指令要慢得多。如果每个指令都要访问内存,比如严重拖慢CPU速度,为了解决这个问题,CPU引入了TLB(translation lookaside buffer),一个高性能缓存,缓存映射表中一部分条目。转换地址时,先从TLB查找,没找到再读内存。</p>
<p>显然,最理想的情况是映射表能够完全缓存到TLB中,地址转换完全不需要访问内存。为了减少映射表大小,我们可以使用“HugePages”:大于4KB的内存页。默认HugePages是2MB,最大可以到1GB。</p>
<h3 id="避免动态分配内存"><a href="#避免动态分配内存" class="headerlink" title="避免动态分配内存"></a>避免动态分配内存</h3><p>内存分配是个复杂且耗时的操作,涉及空闲内存管理、分配策略的权衡(分配效率,碎片),尤其是在并发环境中,还要保证内存分配的线程安全。如果内存分配成为了应用瓶颈,可以尝试一些优化策略。比如内存复用i:不要重复分配内存,而是复用已经分配过的内存,在C++/Java里则考虑复用已有对象,这个技巧在Java里尤其重要,不仅能降低对象创建的开销,还避免了大量创建对象导致的GC开销。另外一个技巧是预先分配内存,实际上相当于在应用内实现了一套简单的内存管理,比如Memcached的Slab。</p>
<h3 id="Zero-Copy"><a href="#Zero-Copy" class="headerlink" title="Zero Copy"></a>Zero Copy</h3><p>对于一个Web服务器来说,响应一个静态文件请求需要先将文件从磁盘读取到内存中,再发送到客户端。如果自信分析这个过程,会发现数据首先从磁盘读取到内核的页缓冲区,再从页缓冲区复制到Web服务器缓冲区,接着从Web服务器缓冲区发送到TCP发送缓冲区,最后经网卡发送出去。这个过程中,数据先从内核复制到进程内,再从进程内回到内核,这两次复制完全是多余的。Zero Copy就是类似情况的优化方案,数据直接在内核中完成处理,不需要额外的复制。</p>
<p>Linux中提供了几种ZeroCopy相关的技术,包括<code>sendfile</code>,<code>splice</code>,<code>copy_file_range</code>,Web服务器中经常使用<code>sendfile</code>优化性能。</p>
<h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>千万牢记:不要过早优化。</p>
<p>优化之前,先考虑两个问题:</p>
<ol>
<li>现在的性能是否已经满足需求了</li>
<li>如果真的要优化,是不是已经定位了瓶颈</li>
</ol>
<p>在回答清楚这两个问题之前,不要盲目动手。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2013/07/02/java-unit-test/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2013/07/02/java-unit-test/" class="post-title-link" itemprop="url">关于单元测试</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2013-07-02 00:00:00" itemprop="dateCreated datePublished" datetime="2013-07-02T00:00:00+08:00">2013-07-02</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-08-03 17:06:41" itemprop="dateModified" datetime="2019-08-03T17:06:41+08:00">2019-08-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/development/" itemprop="url" rel="index"><span itemprop="name">软件开发</span></a></span>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>在公司里,关于单元测试的话题,每两个月就要来一次小的,每年就要来一次大的,虽然频率比月经低,但是讨厌的程度丝毫不亚于后者(虽然我是男的)。</p>
<p>一般的节奏是这样的:过程改进部门(忘了那个神奇的部门叫什么名字了)新来的一个家伙,所谓新官上任三把火,就算是为了绩效考核也得做点什么吧,最简单并且能出成绩的当然是搞单元测试,因为每个项目每个产品的单元测试都烂的一塌糊涂或者根本没有单元测试。于是这位很快的给出了单元测试代码覆盖率85%的指标,并且任务下发到各个小部门,苦逼的开发开始吭吃吭吃的开始补写单元测试,但是这些苦逼的开发平时的工作都要加班完成,哪还有闲情逸致慢慢写单元测试,只能想方设法的搞定覆盖率,完全不管是否真的起到了测试的作用(最坑爹的情况是单元测试里看不到一个assert),运行,绿色!</p>
<p>就这样,苦逼的开发补完了单元测试,达到了85%的覆盖率,负责过程改进的家伙完成了自己的绩效,皆大欢喜。一段时间之后,数据库的数据开始乱掉了,很多单元测试变红了,有些代码修改了,对应的单元测试却没有修改,渐渐的,又回到的最初的状态,直到下一个负责过程改进的家伙来了。</p>
<h2 id="为什么要写单元测试"><a href="#为什么要写单元测试" class="headerlink" title="为什么要写单元测试"></a>为什么要写单元测试</h2><p>在单元测试这个话题上,我只是一个菜鸟,是在不敢胡乱发表什么观点。Google上能搜到很多“为什么要写单元测试”的文章,我比较赞同的有下面两个观点</p>
<ol>
<li>单元测试可以尽早的发现bug<br>在开发过程中,越早发现问题,解决所要付出的代价越小。在单元测试期间发现bug,定位和修复都比较简单,修改后验证也很容易,不像集成测试要搭建复杂的环境,修复一个bug就要浪费很多时间。</li>
<li>作为重构的质量保证<br>如果有单元测试,对代码重构后只要简单的运行一遍单元测试,就能知道重构是否引入了bug,这对单元测试是有要求的,后面会提到。</li>
</ol>
<h2 id="问题在哪里"><a href="#问题在哪里" class="headerlink" title="问题在哪里"></a>问题在哪里</h2><p>开头的真实故事里,问题的根源在单元测试的质量,由于太多的外部依赖,只要稍有一点配置不对,单元测试就没法通过,运行成功单元测试本身就成了一件困难重重的任务,除了写单元测试的时候,没有人愿意运行单元测试,即使修改了代码也不愿意管它,单元测试成了摆设。代码修改,单元测试却从来不改,最终完全腐烂了。</p>
<h2 id="该怎么写单元测试"><a href="#该怎么写单元测试" class="headerlink" title="该怎么写单元测试"></a>该怎么写单元测试</h2><p>对于如何写单元测试,在网络上能看到的都是一些理论的东西,诸如“要Mock所有的依赖”、“AAA”,偶尔举个例子,也是对一个很简单场景下很简单的类写单元测试,完全没见过真正有用的。</p>
<p>对应”为什么要写单元测试”,个人认为写单元测试分为两个层面:</p>
<ol>
<li>验证代码正确性<br>这是常规意义上“测试”所要求的部分,要设计各种测试用例、考虑边界条件,然后写成单元测试即可,这个比较简单,也是基本要求。</li>
<li>作为重构的依据<br>这是对单元测试“可维护性”方面的要求,单元测试必需能很容易运行而不需要预先大量的配置,不能有太多的外部依赖,所有这样的依赖都应该MOCK。</li>
</ol>
<p>写单元测试的重点,就在于考虑其“可维护性”。在具体实践上会比较灵活,肯定不会像某些鼓吹单元测试的人所说MOCK全部依赖,即使有jmockit、easymock之类的工具,mock依然是件很繁琐事情,尤其是依赖比较多的时候,单元测试里将充满mock代码,真正的测试代码反而只占小部分。我个人的习惯是,外部的不可控的就mock,比如数据库、网络,完全自己实现的逻辑可以不用mock,比如一个纯算法的类。</p>
<h2 id="为什么一直说覆盖率"><a href="#为什么一直说覆盖率" class="headerlink" title="为什么一直说覆盖率"></a>为什么一直说覆盖率</h2><p>为什么单元测试的考核指标总是覆盖率?我个人的猜测是,对于负责过程改进的那个家伙和QA,覆盖率是他们唯一能看到并且可量化的指标,毕竟单元测试不像功能代码,没做相应的功能就体现不出来。但是,覆盖率显然不是一个评价单元测试质量的好指标。</p>
<p>如果真要找出一种保证单元测试质量的办法,我觉得Code Review还真不错,我们一直都用它保证我们功能代码。</p>
<h2 id="代价"><a href="#代价" class="headerlink" title="代价"></a>代价</h2><p>无论是写单元测试,还是维护单元测试代码,都是有成本的,所以做到什么程度,又是另外一个要权衡的问题了。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2013/04/25/bash-array-expansion/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2013/04/25/bash-array-expansion/" class="post-title-link" itemprop="url">Bash中数组的扩展</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2013-04-25 00:00:00" itemprop="dateCreated datePublished" datetime="2013-04-25T00:00:00+08:00">2013-04-25</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-08-03 17:08:59" itemprop="dateModified" datetime="2019-08-03T17:08:59+08:00">2019-08-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/" itemprop="url" rel="index"><span itemprop="name">编程语言</span></a></span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/Shell/" itemprop="url" rel="index"><span itemprop="name">Shell</span></a></span>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>在Bash里,数组的扩展很坑爹,虽然数组的出镜率很低(除了特殊变量<code>$@</code>和<code>$*</code>,我没有用过数组)。假设有以下脚本outer:</p>
<pre><code>#out.sh
./inner "a $@ b"</code></pre><p>调用<code>outer 1 2</code>, 请问inner收到几个参数,分别是什么?如果你完全不知道怎么回答,可以先看看我原来写的一篇文章<a href="http://jjz.iteye.com/blog/388946" target="_blank" rel="noopener">Shell命令中的扩展和替换</a>,如果你回答1个,那可以接着往下看。</p>
<p>Bash中当引用数组全部元素的时候,有两种方式:<code>${array[@]}</code>和<code>${array[*]}</code>,通常情况下,两种方式是一样的,但是……当数组出现在双引号内部时,就有所区别区别了:<code>$array{*}</code>会扩展成一个字符串,字符串的内容是以IFS首个字母(一般就是空格)为分隔把数组的元素连在一起。为了便于理解,我们先创建两个脚本文件outer.sh和inner.sh,内容如下</p>
<pre><code>#outer.sh
#!/bin/bash
./inner "$*"
#inner.sh
#!/bin/bash
for arg
do
echo "[$arg]"
done</code></pre><p>然后运行: <code>./outer.sh 1 2 3</code>,输出 <code>[1 2 3]</code>,显然,inner.sh只收到1个参数。</p>
<p><code>$array[@]</code>会扩展成多个字符串,字符串的数量就是数组的长度。 把outer.sh里的<code>$*</code>换成<code>$@</code>,再运行,输出</p>
<pre><code>[1]
[2]
[3]</code></pre><p>inner.sh收到三个参数。以上,稍微用过Bash的同学应该都知道,之所以写这么多完全是显着无聊凑字数。</p>
<p>下面说说真正坑爹的。当出现诸如<code>"prefix $array[@] suffix"</code>的情况时,也就是<code>$array[@]</code>出现在双引号里面并且前后还有字符串,会扩展成什么呢?答案是:会扩展成多个字符串,数量就是数组的长度,但是:** 第一个字符串是prefix加上数组的第一个元素,最后一个字符串是数组的最后一个元素加上suffix **,把outer.sh改成这样</p>
<pre><code>#!/bin/bash
./inner "prefix $@ suffix"</code></pre><p>运行<code>./outer.sh 1 2 3</code>,输出</p>
<pre><code>[prefix 1]
[2]
[3 suffix]</code></pre><p>这种行为完全不符合常理啊!</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2013/04/19/java-socket/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2013/04/19/java-socket/" class="post-title-link" itemprop="url">Java Socket的一些常见问题</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2013-04-19 00:00:00" itemprop="dateCreated datePublished" datetime="2013-04-19T00:00:00+08:00">2013-04-19</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-08-03 17:06:10" itemprop="dateModified" datetime="2019-08-03T17:06:10+08:00">2019-08-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/Network/" itemprop="url" rel="index"><span itemprop="name">Network</span></a></span>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>最后一篇博客到现在已经快两个月了,果然还是太懒,总是不愿意写点东西,有什么办法能治好吗? 好吧,不废话,进入正题。</p>
<p>用Java写网络程序,大部分时间都很好,所有的东西都封装了,简单,方便,无需处理大量的细节问题,看上去很完美。可惜一旦出现问题,就很难排查,封装的太厚了,而且所有的错误全部用IOException或者SocketException的方式抛出,加上一些怪异的信息,比如Connection reset,完全不知道到底错误原因是什么。网络通信本身并不复杂,无非就是发送数据包,接收数据包。以最常用的TCP为例,其核心也无非就是可靠传输加上拥塞控制,而可靠传输的精华就是确认和重传,很简单不是吗。</p>
<p>再看看linux的socket,比如connect,所有的错误都有明确的errorcode,一看就知道发生了什么错误。</p>
<h2 id="socket和数据包"><a href="#socket和数据包" class="headerlink" title="socket和数据包"></a>socket和数据包</h2><p>之所以要先说socket,因为这是基础,了解了这个才能真正了解Java Socket里那些提示信息,如果根本不懂tcp,不懂socket,直接处理Java网络编程,也只能是浅浅的留在表面,不得深入。</p>
<pre><code>socket s = socket()
s.connect(ip, port)
s.send('data')
s.recv()
s.close()</code></pre><p>这是一段简单的伪代码,描述客户端tcp socket建立连接和发送、接收数据,大部分语言都是类似的写法。那么,每一步可能会发生什么错误呢?我们一点点来分析。当然,忽略一些本地错误,比如没权限建socket之类的,只分析网络交互上的错误。</p>
<h3 id="connect"><a href="#connect" class="headerlink" title="connect"></a>connect</h3><p>在connect里,常见的错误有两个:Connection refused和Connection Timeout。众所周知,tcp建立连接需要三次握手,读者可以自己Google下“tcp 三次握手”的图片,一看就能明白这个过程。</p>
<p>在这个过程中,如果客户端发送SYN包,服务器返回了RST包,这表明在服务器上,没有服务监听客户端要访问的端口,在Unix上,connect系统调用直接返回ECONNREFUSED,也就是连接拒绝。(RST全称reset,三种情况下会产生RST:1. SYN达到某端口,但没有监听这个端口的服务;2. TCP想取消一个已有的连接;3. TCP收到一个根本不存在的连接上的数据包)。</p>
<p>另外,路由器可能会返回一个目的不可达的ICMP包,Unix上,connect系统调用直接返回ENETUNREACH,这种情况下connect会重试,重新发送SYN,重试一段时间依然不行才会返回ENETUNREACH.</p>
<p>最后一种情况是,过了一段时间,客户端根本没收到任何响应的数据包,connect系统调用返回ETIMEOUT,也就是连接超时。</p>
<p>使用Java的时候,第一种情况抛出java.net.ConnectException: Connection refused. 第二和第三种情况都会在等很久出现java.net.ConnectException: Connection timed out,但其实导致问题的原因不一样。</p>
<p>Java在调用connect时可以指定超时,这点比connect系统调用好,后者没法直接设置超时。</p>
<h3 id="send"><a href="#send" class="headerlink" title="send"></a>send</h3><p>在send里可能发生的错误有超时和Connection reset。先说第一种情况,了解tcp的都知道,tcp发送了数据后要等对方确认,如果对方一直没有发送确认消息,发送方会一直重试,当然,实际上采用的是“指数退避”算法,不过我们不关心这个,只要知道发送了没确认tcp会一直重试,直到达到一定次数为止。然后就超时了。</p>
<p>另一种情况是对方关闭了socket,主动close或者crash了,这边还在send,对方服务器返回RST包,在Unix上,send系统调用返回ECONNRESET,错误信息时Connection reset by peer。 用Java也差不多,错误是java.net.SocketException: Connection reset。</p>
<h3 id="recv"><a href="#recv" class="headerlink" title="recv"></a>recv</h3><p>读取对方的数据,最常见的错误是超时,及其偶然的情况下,出现Connection rest。我唯一能想到的出现Connection reset的情况是这样的:本机阻塞在read或者recv上,对方关闭了连接,但本机没有收到FIN(在模拟这个问题是,我用iptable丢掉了FIN包),在开启了keepalive的情况下,本机过一段时间会发送keepalive的探测包(<code>/proc/sys/net/ipv4/tcp_keepalive_time</code>),可以用netstat -to查看tcp相关的定时器,对方返回RST,出现Connection reset。也有可能对方没有任何响应(比如对方直接拔掉网线,本机也会收不到FIN,同样,所有的探测包也没有响应),探测几次后本机超时。</p>
<h2 id="close"><a href="#close" class="headerlink" title="close"></a>close</h2><p>受到linger选项的影响,在Java里很少出问题,先不说这个了。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2013/02/24/access-database-in-shell-script/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2013/02/24/access-database-in-shell-script/" class="post-title-link" itemprop="url">在Shell脚本里访问数据库</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2013-02-24 00:00:00" itemprop="dateCreated datePublished" datetime="2013-02-24T00:00:00+08:00">2013-02-24</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-08-03 17:08:41" itemprop="dateModified" datetime="2019-08-03T17:08:41+08:00">2019-08-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/" itemprop="url" rel="index"><span itemprop="name">编程语言</span></a></span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/Shell/" itemprop="url" rel="index"><span itemprop="name">Shell</span></a></span>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>在Shell脚本里也可以读写数据,而且很简单。</p>
<pre><code>echo select * from table | mysql -h host -u user -ppassword schema </code></pre><p>就是这么简单,如果语句比较多,还可以使用here document</p>
<pre><code>mysql -h host -u user -ppassword schema <<- END
select * from table1
insert into ....
END</code></pre><p>同样的道理,ftp也可以这样做,或者说,只要程序从标准输入读取,都可以这样做。对于有些程序,比如ssh,它并非从标准输入读取,这是就要用expect了。参见 (expect简明教程)[ ]</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://oditszapc.com/2013/02/24/how-to-launch-only-one-script-instance/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Jiang Jizhong">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="姜太公的记事本">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2013/02/24/how-to-launch-only-one-script-instance/" class="post-title-link" itemprop="url">如何只启动一个Shell脚本的实例</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2013-02-24 00:00:00" itemprop="dateCreated datePublished" datetime="2013-02-24T00:00:00+08:00">2013-02-24</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">Edited on</span>
<time title="Modified: 2019-08-03 17:08:49" itemprop="dateModified" datetime="2019-08-03T17:08:49+08:00">2019-08-03</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/" itemprop="url" rel="index"><span itemprop="name">编程语言</span></a></span>
,
<span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/language/Shell/" itemprop="url" rel="index"><span itemprop="name">Shell</span></a></span>
</span>
<br>
</div>
</header>
<div class="post-body" itemprop="articleBody">