-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1494 lines (1176 loc) · 86.9 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>
<head>
<meta charset="utf-8">
<title>Satans17</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="og:type" content="blog">
<meta name="og:title">
<meta name="og:url" content="http://satans17.github.io//">
<meta name="og:image">
<meta name="og:site_name" content="Satans17">
<meta name="og:description">
<meta name="twitter:card" content="summary">
<link rel="alternative" href="/atom.xml" title="Satans17" type="application/atom+xml">
<link rel="icon" href="/favicon.png">
<!-- <link href="http://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css"> -->
<link rel="stylesheet" href="/css/style.css" type="text/css">
<!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
<div id="container">
<div id="wrap">
<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/" id="logo">Satans17</a>
</h1>
</div>
<div id="header-inner" class="inner">
<nav id="main-nav">
<a id="main-nav-toggle" class="nav-icon"></a>
<a class="main-nav-link" href="/">Home</a>
<a class="main-nav-link" href="/archives">Archives</a>
<a class="main-nav-link" href="/about">About</a>
</nav>
<nav id="sub-nav">
<a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
<a id="nav-search-btn" class="nav-icon" title="Search"></a>
</nav>
<div id="search-form-wrap">
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" results="0" class="search-form-input" placeholder="Search"><input type="submit" value="" class="search-form-submit"><input type="hidden" name="q" value="site:http://satans17.github.io/"></form>
</div>
</div>
</div>
</header>
<div class="outer">
<section id="main">
<article id="post-node稳定性的研究心得" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2014/05/04/node稳定性的研究心得/" class="article-date">
<time datetime="2014-05-04T03:06:14.000Z" itemprop="datePublished">May 4 2014</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2014/05/04/node稳定性的研究心得/">Node稳定性的研究心得</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>目前大部分Web服务器,如Apache,都使用多线程的方式响应多用户请求,即一个线程服务一个用户请求。这种模式其中一个好处是,当某个请求的线程上抛出的异常没被捕获,只会影响当前这个线程,不会影响其他请求。</p>
<p>由于Node执行在单线程上,一旦线程上抛的异常没有被捕获,就会引起整个进程的崩溃。所以对Node服务的异常处理、保证进程的稳定性非常重要。</p>
<p>再好的程序员也不敢保证自己的代码不出现异常,除了尽可能捕获可能出现的异常,我们还需要通过一些规范减少异常发生,通过单元测试辅助我们验证代码,通过一些工具保证服务的稳定性。下面我从这几个方面探讨如何保证Node的稳定性。</p>
<h2 id="一_异常捕获">一 异常捕获</h2>
<p>提升稳定性最直接的方式就是尽可能的捕捉异常,Node提供3种方式。</p>
<h3 id="1-1_try/catch">1.1 try/catch</h3>
<p>在大多数语言中,try/catch是捕获异常的好手,能确保我们的代码不进入不可控流程。但是由于Node回调/异步的特性,我们无法通过try/catch来捕捉所有的异常,看下面的示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="keyword">try</span> {
process.nextTick(<span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"error"</span>);
});
} <span class="keyword">catch</span> (err) {
<span class="comment">//can not catch it</span>
console.log(err);
}
</pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="keyword">try</span> {
setTimeout(<span class="function"><span class="keyword">function</span><span class="params">()</span>{</span>
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"error"</span>);
},<span class="number">1</span>)
} <span class="keyword">catch</span> (err) {
<span class="comment">//can not catch it</span>
console.log(err);
}
</pre></td></tr></table></figure>
<p>上面的代码没有像预期的那样帮我们捕获异常,后果就是这个未被捕获的异常导致整个Node进程crash,所以Node中try/catch的方式不是那么管用。<br>如果有一种方法能帮我们全局捕获异常,Node服务就不会轻易挂掉了。Node确实提供了这种方式,不过却不能完全满足我们的需求。</p>
<h3 id="1-2_uncaughtException">1.2 uncaughtException</h3>
<p>当一个异常未被捕获,冒泡回归到事件循环中就会触发uncaughtException事件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre>process.on(<span class="string">'uncaughtException'</span>, <span class="function"><span class="keyword">function</span><span class="params">(err)</span> {</span>
console.error(<span class="string">'Error caught in uncaughtException event:'</span>, err);
});
<span class="keyword">try</span> {
setTimeout(<span class="function"><span class="keyword">function</span><span class="params">()</span>{</span>
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"error"</span>);
},<span class="number">1</span>)
} <span class="keyword">catch</span> (err) {
<span class="comment">//can not catch it</span>
console.log(err);
}
</pre></td></tr></table></figure>
<p>只要给uncaughtException配置了回调,Node进程不会异常退出,但异常发生的上下文已经丢失,我们无法给出友好的返回,比如告诉用户哪里出问题了。而且由于uncaughtException事件发生后,会丢失当前环境的堆栈,可能导致Node不能正常进行内存回收,从而导致内存泄露。所以,uncaughtException的正确使用姿势是,当uncaughtException触发,记录error日志,然后结束Node进程,我们通过日志监控,报警及时解决异常。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
</pre></td><td class="code"><pre>process.on(<span class="string">'uncaughtException'</span>, <span class="function"><span class="keyword">function</span><span class="params">(err)</span> {</span>
<span class="comment">// 记录日志</span>
logger(err);
<span class="comment">// 结束进程</span>
process.exit(<span class="number">1</span>);
});
</pre></td></tr></table></figure>
<h3 id="1-3_domain">1.3 domain</h3>
<p>为了弥补try/catch和uncaughtException的不足,在node v0.8+版本的时候,发布了一个模块<code>domain</code>,这个模块能捕捉异步回调中出现的异常。<br>看下面的示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="code"><pre><span class="keyword">var</span> d = domain.create();
process.on(<span class="string">'uncaughtException'</span>, <span class="function"><span class="keyword">function</span><span class="params">(err)</span> {</span>
console.error(err);
});
d.on(<span class="string">'error'</span>, <span class="function"><span class="keyword">function</span><span class="params">(err)</span> {</span>
console.error(<span class="string">'Error caught by domain:'</span>, err);
});
d.run(<span class="function"><span class="keyword">function</span><span class="params">()</span> {</span>
process.nextTick(<span class="function"><span class="keyword">function</span><span class="params">()</span> {</span>
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"test domain"</span>);
});
});
</pre></td></tr></table></figure>
<p>运行代码我们会发现,异常会被domain捕获到,uncaughtException不会被触发。虽然我们对domain模块寄予厚望,不过目前domain模块的评级为“Unstable”,因为存在不少性能以及稳定性问题。<br>关于domain的详细介绍,可以看看以下几篇文章:</p>
<ul>
<li><a href="http://cnodejs.org/topic/516b64596d38277306407936" target="_blank">Node.js 异步异常的处理与domain模块解析</a></li>
<li><a href="http://www.alloyteam.com/2013/12/node-js-series-exception-caught/" target="_blank">Node.js异常捕获额一些实践</a></li>
<li><a href="http://www.atatech.org/articles/16061" target="_blank">Node.js之异常处理</a></li>
</ul>
<h2 id="二_阻止异常发生">二 阻止异常发生</h2>
<p>我们当然无法阻止异常的发生,这里说的阻止异常发生,是希望大家能养成良好的编码习惯,严谨的思维逻辑,来尽可能减少代码抛出未捕获异常。</p>
<h3 id="2-1_良好的异常处理习惯">2.1 良好的异常处理习惯</h3>
<ul>
<li>异步API编写规范:由于异步调用中回调函数里的异常无法被外部捕获,所以我们将API内部发生的异常作为第一个参数传递给回调函数,包括NodeJS官方的API是遵循这个规范的。</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
</pre></td><td class="code"><pre>fs.readFile(<span class="string">'/t.txt'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(err, data)</span> {</span>
<span class="keyword">if</span> (err) <span class="keyword">throw</span> err;
console.log(data);
});
</pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre><span class="comment">// 不推荐的做法</span>
<span class="function"><span class="keyword">function</span> <span class="title">fun</span><span class="params">(options,callback)</span>{</span>
<span class="keyword">if</span>(!options{
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">".."</span>)
}
}
<span class="comment">// 推荐的做法</span>
<span class="function"><span class="keyword">function</span> <span class="title">fun</span><span class="params">(options,callback)</span>{</span>
<span class="keyword">if</span>(!options){
callback(err,<span class="literal">null</span>)
}
}
</pre></td></tr></table></figure>
<ul>
<li>严格校验用户的输入</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
</pre></td><td class="code"><pre><span class="function"><span class="keyword">function</span> <span class="title">doSth</span><span class="params">(cb)</span>{</span>
<span class="keyword">if</span>(<span class="keyword">typeof</span> cb === <span class="string">'function'</span>){
cb();
}
}
</pre></td></tr></table></figure>
<ul>
<li>使用try/catch处理可能出现异常的代码</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="keyword">var</span> obj;
<span class="keyword">try</span>{
obj = <span class="built_in">JSON</span>.parse(<span class="string">''</span>)
}<span class="keyword">catch</span>(e){
obj = {};
}
</pre></td></tr></table></figure>
<ul>
<li>不要直接在controller中抛异常,应该用500等状态更友好的返回错误</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="comment">// 不推荐的做法</span>
app.get(<span class="string">'/item.html'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(req, res, next)</span> {</span>
<span class="keyword">if</span>(!query[<span class="string">"id"</span>]){
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'no item id'</span>);
}
});
</pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="comment">// 推荐的做法</span>
app.get(<span class="string">'/item.html'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(req, res, next)</span> {</span>
<span class="keyword">if</span>(!query[<span class="string">"id"</span>]){
next();
}
});
</pre></td></tr></table></figure>
<h3 id="2-2_单元测试">2.2 单元测试</h3>
<p>单元测试的重要性想必所有人都清楚,JS代码跑在浏览器端的时候我们未必会做,因为异常通常只会影响部分人使用的部分功能,不足以引起很多人的重视。JS代码跑在服务端情况完全不一样了,一旦出现异常,影响的是所有的用户,所以单元测试就显得非常重要。</p>
<p>至于如何在NodeJS中写单元测试,可以看看两位大神的分享:</p>
<ul>
<li>苏千:<a href="http://fengmk2.cnpmjs.org/ppt/unittest-and-bdd-in-nodejs-with-mocha.html#slide-0" target="_blank">UnitTest in Nodejs 实战Nodejs单元测试</a></li>
<li>朴灵:<a href="http://html5ify.com/unittesting/slides/index.html#/" target="_blank">NODE.JS UNIT TESTING</a></li>
</ul>
<h3 id="2-3_记录日志">2.3 记录日志</h3>
<p>还是那句说,谁也不敢保证自己的代码不出现异常,因为有运行环境,网络环境等各种不稳定因素,所以建立健全的排查和跟踪机制就显得很重要,而日志就是实现这种机制的关键。<br>阿里线上环境已经有完善的日志监控体现,我们要做的就是去学会如何使用他。</p>
<p>ali-logger:<a href="http://search.npm.taobao.net/package/ali-logger" target="_blank">http://search.npm.taobao.net/package/ali-logger</a></p>
<h2 id="三_多进程架构">三 多进程架构</h2>
<h3 id="3-1_cluster">3.1 cluster</h3>
<p>cluster模块用于创建共享端口的多进程模式,这种模式使多个进程间共享一个监听状态的socket,并由系统将accept的connection分配给不同的子进程。<br>文档上有一个简单的示例:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="code"><pre><span class="keyword">var</span> cluster = <span class="built_in">require</span>(<span class="string">'cluster'</span>);
<span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);
<span class="keyword">var</span> numCPUs = <span class="built_in">require</span>(<span class="string">'os'</span>).cpus().length;
<span class="keyword">if</span> (cluster.isMaster) {
<span class="comment">// Fork workers.</span>
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < numCPUs; i++) {
cluster.fork();
}
cluster.on(<span class="string">'exit'</span>, <span class="function"><span class="keyword">function</span><span class="params">(worker, code, signal)</span> {</span>
console.log(<span class="string">'worker '</span> + worker.process.pid + <span class="string">' died'</span>);
});
} <span class="keyword">else</span> {
<span class="comment">// Workers can share any TCP connection</span>
<span class="comment">// In this case its a HTTP server</span>
http.createServer(<span class="function"><span class="keyword">function</span><span class="params">(req, res)</span> {</span>
res.writeHead(<span class="number">200</span>);
res.end(<span class="string">"hello world\n"</span>);
}).listen(<span class="number">8000</span>);
}
</pre></td></tr></table></figure>
<p>利用<code>cluster</code>,我们可以根据CPU的数量创建多个worker进程,用户的请求会被分配到不同的进程上,如果某个进程出现异常,可以直接将这个进程crash掉,而不会影响其他进程。<br>关于<code>cluster</code>实现原理的介绍文章非常多,想深入了解的可以自行搜索一下。</p>
<h4 id="3-1-1_graceful_+_recluster">3.1.1 graceful + recluster</h4>
<p>数据产品中使用graceful + recluster两个模块实现多进程和服务器稳定性的工作,分享一下使用方法:</p>
<ul>
<li><a href="https://www.npmjs.org/package/graceful" target="_blank">graceful</a> : 当uncaughtException触发后,延迟一段时间退出进程</li>
<li><a href="https://www.npmjs.org/package/recluster" target="_blank">recluster</a> : 实现多进程,并且当有进程退出实现自动重启。</li>
</ul>
<p><strong>app.js</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="code"><pre><span class="keyword">var</span> app = <span class="built_in">require</span>(<span class="string">'../app'</span>);
<span class="keyword">var</span> graceful = <span class="built_in">require</span>(<span class="string">'graceful'</span>);
<span class="keyword">var</span> server = app.listen(app.get(<span class="string">'port'</span>), <span class="function"><span class="keyword">function</span><span class="params">()</span> {</span>
debug(<span class="string">'Express server listening on port '</span> + server.address().port);
});
graceful({
server: server,
killTimeout: <span class="number">30000</span>,
error:<span class="function"><span class="keyword">function</span><span class="params">(e)</span>{</span>
logger.error(e);
}
});
</pre></td></tr></table></figure>
<p><strong>server.js</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre><span class="keyword">var</span> recluster = <span class="built_in">require</span>(<span class="string">'recluster'</span>),
path = <span class="built_in">require</span>(<span class="string">'path'</span>);
<span class="keyword">var</span> cluster = recluster(path.join(__dirname, <span class="string">'app.js'</span>));
cluster.run();
process.on(<span class="string">'SIGUSR2'</span>, <span class="function"><span class="keyword">function</span><span class="params">()</span> {</span>
console.log(<span class="string">'Got SIGUSR2, reloading cluster...'</span>);
cluster.reload();
});
console.log(<span class="string">"spawned cluster, kill -s SIGUSR2"</span>, process.pid, <span class="string">"to reload"</span>);
</pre></td></tr></table></figure>
<p>开发环境,直接通过app启动:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="variable">$ </span>node app.js
</pre></td></tr></table></figure>
<p>生产环境, 启动多个 worker:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="variable">$ </span>node server.js
</pre></td></tr></table></figure>
<h4 id="3-1-2_pm2">3.1.2 pm2</h4>
<p>如果你觉得graceful + recluster的方式太复杂,那么<a href="https://github.com/Unitech/pm2" target="_blank">pm2</a>肯定是你最理想的选择。<br>pm2非常强大,生产环境使用pm2启动你的Node服务是个不错的选择,他能自动利用你的多核cup,完善的监控,日志记录等等..</p>
<p>pm2使用非常简单:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="variable">$ </span>npm install pm2<span class="variable">@latest</span> -g
</pre></td></tr></table></figure>
<figure class="highlight sh"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="variable">$ </span>pm2 start app.js
</pre></td></tr></table></figure>
<figure class="highlight sh"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="variable">$ </span>pm2 list
</pre></td></tr></table></figure>
<p><img src="https://github.com/unitech/pm2/raw/master/pres/pm2-list.png" alt="pm2"></p>
<p>如果你想更多的了解pm2,可以直接看pm2的文档:<a href="https://github.com/Unitech/pm2" target="_blank">https://github.com/Unitech/pm2</a></p>
<p>不过正是由于pm2功能太过强大,我们没有选择pm2,因为他太复杂了,感觉还没有能力驾驭他,特别是万一出现问题,我们还不知道如何解决。也许我们的顾虑是多余的,不过需要一点时间去了解他。</p>
<h2 id="小结">小结</h2>
<p>刚开始学习Node的使用,对Node确实有点小担心,因为使用别的语言做Web服务,根本不用担心因为一个错误导致整个服务crash的问题。<br>随着对Node的了解,我掌握了一些技巧,也打消了一些顾虑。<br>不过毕竟Node应用经验有限,所以欢迎大家一起探讨,积累更多宝贵的经验。</p>
</div>
<footer class="article-footer">
<a data-url="http://satans17.github.io/2014/05/04/node稳定性的研究心得/" data-id="l4k1nnwxhejspxvi" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Node.js/">Node.js</a></li></ul>
</footer>
</div>
</article>
<article id="post-也谈基于nodejs的全栈式开发(基于nodejs的前后端分离)" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2014/04/04/也谈基于nodejs的全栈式开发(基于nodejs的前后端分离)/" class="article-date">
<time datetime="2014-04-04T09:20:45.000Z" itemprop="datePublished">Apr 4 2014</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2014/04/04/也谈基于nodejs的全栈式开发(基于nodejs的前后端分离)/">也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离)</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>随着不同终端(pad/mobile/pc)的兴起,对开发人员的要求越来越高,纯浏览器端的响应式已经不能满足用户体验的高要求,我们往往需要针对不同的终端开发定制的版本。为了提升开发效率,前后端分离的需求越来越被重视,后端负责业务/数据接口,前端负责展现/交互逻辑,同一份数据接口,我们可以定制开发多个版本。</p>
<p>这个话题最近被讨论得比较多,阿里有些BU也在进行一些尝试。讨论了很久之后,我们团队决定探索一套基于NodeJS的前后端分离方案,过程中有一些不断变化的认识以及思考,记录在这里,也希望看到的同学参与讨论,帮我们完善。</p>
<h2 id="一、什么是前后端分离?">一、什么是前后端分离?</h2>
<p>最开始组内讨论的过程中我发现,每个人对前后端分离的理解不一样,为了保证能在同一个频道讨论,先就什么是”前后端分离”达成一致。</p>
<p>大家一致认同的前后端分离的例子就是SPA(Single-page application),所有用到的展现数据都是后端通过异步接口(AJAX/JSONP)的方式提供的,前端只管展现。<br>从某种意义上来说,SPA确实做到了前后端分离,但这种方式存在两个问题:</p>
<ul>
<li>WEB服务中,SPA类占的比例很少。很多场景下还有同步/同步+异步混合的模式,SPA不能作为一种通用的解决方案。</li>
<li>现阶段的SPA开发模式,接口通常是按照展现逻辑来提供的,有时候为了提高效率,后端会帮我们处理一些展现逻辑,这就意味着后端还是涉足了view层的工作,不是真正的前后端分离。</li>
</ul>
<p>SPA式的前后端分离,是从物理层做区分。(认为只要是客户端的就是前端,服务器端的就是后端)这种分法已经无法满足我们前后端分离的需求,我们认为从职责上划分才能满足目前我们的使用场景:</p>
<ul>
<li>前端:负责View和Controller层</li>
<li>后端:只负责Model层,业务处理/数据等。</li>
</ul>
<p>为什么去做这种职责的划分,后面会继续探讨。</p>
<h2 id="二、为什么要前后端分离?">二、为什么要前后端分离?</h2>
<p>关于这个问题,玉伯的文章<a href="https://github.com/lifesinger/lifesinger.github.com/issues/184" target="_blank">Web研发模式演变</a>中解释得非常全面,这里我根据自己的理解概括一下:</p>
<h4 id="2-1_现有开发模式的适用场景">2.1 现有开发模式的适用场景</h4>
<p>玉伯提到的几种开发模式,各有各的适用场景,没有哪一种完全取代另外一种。</p>
<ul>
<li>比如后端为主的MVC,做一些同步展现的业务效率很高,但是遇到同步异步结合的页面,与后端开发沟通起来就会比较麻烦。</li>
<li>Ajax为主SPA型开发模式,比较适合开发app类型的场景,但是只适合做app,因为SEO等问题不好解决,对于很多类型的系统,这种开发方式也过重。</li>
</ul>
<h4 id="2-2_前后端职责不清">2.2 前后端职责不清</h4>
<p>在业务逻辑复杂的系统里,我们最怕维护前后端通吃那种人写的代码,因为没有约束,M-V-C每一层都可能出现别的层的代码,日积月累,完全没有维护性可言。<br>虽然前后端分离没办法完全解决这种问题,但是可以大大缓解。因为从物理层次上保证了你不可能这么做。</p>
<h4 id="2-3_开发效率问题">2.3 开发效率问题</h4>
<p>淘宝的web基本上都是基于MVC框架webx,架构决定了前端只能依赖后端。<br>所以我们的开发模式依然是,前端写好静态demo,后端翻译成vm模版,这种模式的问题就不说了,被吐槽了很久。<br>直接基于后端的项目开发也很痛苦,一是后端开发环境很重,配置安装使用都很麻烦,而且慢。为了解决这个问题,我们发明了各种工具,比如vmarket,但是前端还是要写vm,而且依赖后端数据,效率依然不高。<br>另外,后端也没法摆脱对展现的强关注,从而专心于业务逻辑层的开发。<br>套页面,引脚本就不说了,还要经常让他们改个时间戳,搞个vmcommon,他们也非常痛苦。</p>
<h4 id="2-4_对前端发挥的局限">2.4 对前端发挥的局限</h4>
<p>例如,性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作才能碰撞出火花。但是后端由于框架限制,有时候很难做,比如bigpipe,长链接之类的。</p>
<p>为了解决以上提到的一些问题,我们进行了很多尝试,开发了各种工具,但始终没有太多起色,主要是因为我们只能在后端给我们划分的那一小块空间去发挥。只有真正做到前后端分离,把选择权交还给前端,我们才能彻底解决以上问题。</p>
<h2 id="三、怎么做前后端分离?">三、怎么做前后端分离?</h2>
<p>怎么做前后端分离,其实第一节中已经有了答案:</p>
<ul>
<li>前端:负责View和Controller层</li>
<li>后端:只负责Model层,业务处理/数据等。</li>
</ul>
<p><img src="http://gtms01.alicdn.com/tps/i1/T1qg9oFu4iXXXk_Dc5-555-263.png" alt="MVC分"></p>
<p>试想一下,如果前端掌握了Controller,我们可以做url design,我们可以根据场景决定在服务端同步渲染,还是根据view层数据输出json数据,我们还可以根据表现层需求很容易的做bigpipe,comet,socket等等,完全是需求决定使用方式。</p>
<h4 id="3-1_基于NodeJS“全栈”式开发">3.1 基于NodeJS“全栈”式开发</h4>
<p>如果想实现上图的分层,就必然需要一种web服务帮我们实现以前后端做的事情,于是就有了标题提到的“基于NodeJS的全栈式开发”</p>
<p><img src="http://gtms03.alicdn.com/tps/i3/T1xW8OFrXkXXXK71TW-590-611.png" alt="Node 带来的全栈时代"></p>
<p>这张图看起来简单而且很好理解,但没尝试过,会有很多疑问。</p>
<ul>
<li>SPA模式中,后端已供了所需的数据接口,view前端已经可以控制,为什么要多加NodeJS这一层?</li>
<li>多加一层,性能怎么样?</li>
<li>多加一层,前端的工作量是不是增加了?</li>
<li>多加一层就多一层风险,怎么破?</li>
<li>NodeJS什么都能做,为什么还要JAVA?</li>
</ul>
<p>这些问题要说清楚不容易,下面说下我的认识过程。</p>
<h4 id="3-2_为什么要增加一层NodeJS?">3.2 为什么要增加一层NodeJS?</h4>
<p>现阶段我们主要以后端MVC的模式进行开发,这种模式严重阻碍了前端开发效率,也让后端不能专注于业务开发。<br>解决方案是让前端能控制Controller层,但是如果在现有技术体系下很难做到,因为不可能让所有前端都学java,安装后端的开发环境,写VM。<br>NodeJS就能很好的解决这个问题,我们无需学习一门新的语言,就能做到以前开发帮我们做的事情,一切都显得那么自然。</p>
<h4 id="3-3_性能问题">3.3 性能问题</h4>
<p>分层就涉及每层之间的通讯,肯定会有一定的性能损耗。但是合理的分层能让职责清晰、也方便协作,会大大提高开发效率。分层带来的损失,一定能在其他方面的收益弥补回来。<br>另外,一旦决定分层,我们可以通过优化通讯方式、通讯协议,尽可能把损耗降到最低。</p>
<p><strong>举个例子:</strong><br>detail静态化之后,还是有不少需要实时获取的信息,比如优化、物流、促销等等,因为这些信息在不同业务系统中,所以需要前端发送5,6个异步请求来回填这些内容。<br>有了NodeJS之后,前端可以在NodeJS中去代理这5个异步请求,还能很容易的做bigpipe,这块的优化能让整个渲染效率提升很多。<br>可能在PC上你觉得发5,6个异步请求也没什么,但是在无线端,在客户手机上建立一个http请求开销很大,有了这个优化,性能一下提升好几倍。</p>
<p>淘宝详情基于NodeJS的优化我们正在进行中,上线之后我会分享一下优化的过程。</p>
<h4 id="3-4_前端的工作量是否增加了?">3.4 前端的工作量是否增加了?</h4>
<p>相对于只切页面/做demo,肯定是增加了一点,但是当前模式下有联调、沟通环节,这个过程非常花时间,也容易出bug,还很难维护。<br>所以,虽然工作量会增加一点,但是总体开发效率会提升很多。</p>
<p>另外,测试成本可以节省很多。以前开发的接口都是针对表现层的,很难写测试用例。如果做了前后端分离,甚至测试都可以分开,一拨人专门测试接口,一拨人专注测试UI(这部分工作甚至可以用工具代替)。</p>
<h4 id="3-5_增加Node层带来的风险怎么控制?">3.5 增加Node层带来的风险怎么控制?</h4>
<p>这个担心有点多余,随着Node大规模使用,SCM/PE/安全,都会解决进行。<br>他们会帮助我们在申请,测试,发布,风控几个层面保证稳定性</p>
<h4 id="3-6_Node什么都能做,为什么还要JAVA?">3.6 Node什么都能做,为什么还要JAVA?</h4>
<p>我们的初衷是做前后端分离,如果考虑这个问题就有点违背我们的初衷了。即使用Node替代Java,我们也没办法保证不出现今天遇到的种种问题,比如职责不清。我们的目的是分层开发,专业的人,专注做专业的事。基于JAVA的基础架构已经非常强大而且稳定,而且更适合做现在架构的事情。</p>
<h2 id="四、淘宝基于Node的前后端分离">四、淘宝基于Node的前后端分离</h2>
<p><img src="http://gtms03.alicdn.com/tps/i3/T1OMsAFApcXXaI5uU7-800-521.jpg" alt="淘宝基于NodeJS的前后端分离"></p>
<p>上图是我理解的淘宝基于Node的前后端分离分层,以及Node的职责范围。简单解释下:</p>
<ul>
<li>最上端是服务端,就是我们常说的后端。后端对于我们来说,就是一个接口的集合,服务端提供各种各样的接口供我们使用。因为有Node层,也不用局限是什么形式的服务。对于后端开发来说,他们只用关心业务代码的接口实现。</li>
<li>服务端下面是Node应用。</li>
<li>Node应用中有一层Model Proxy与服务端进行通讯。这一层主要目前是抹平我们对不同接口的调用方式,封装一些view层需要的Model。</li>
<li>( Node层还能轻松实现原来vmcommon,tms等需要)</li>
<li>Node层要使用什么框架由开发者自己决定。不过推荐使用express+xTemplate的组合,xTemplate能做到前后端公用。</li>
<li>怎么用Node大家自己决定,但是令人兴奋的是,我们终于可以使用Node轻松实现我们想要的输出方式:JSON/JSONP/RESTful/HTML/BigPipe/Comet/Socket/同步、异步,想怎么整就怎么整,完全根据你的场景决定。</li>
<li>浏览器层在我们这个架构中没有变化,也不希望因为引入Node改变你以前在浏览器中开发的认知。</li>
<li>引入Node,只是把本该就前端控制的部分交由前端掌控。</li>
</ul>
<p>这种模式我们已经有两个项目在开发中,虽然还没上线,但是无论是在开发效率,还是在性能优化方面,我们都已经尝到了甜头。</p>
<h2 id="五、我们还需要要做什么?">五、我们还需要要做什么?</h2>
<ul>
<li>把Node的开发流程集成到淘宝现有的SCM流程中。</li>
<li>基础设施建设,比如session,logger等通用模块。</li>
<li>最佳开发实践</li>
<li>线上成功案例</li>
<li>大家对Node前后端分离概念的认识</li>
<li>安全</li>
<li>性能</li>
<li>…</li>
</ul>
<p>技术上不会有太多需要去创新和研究的,已经有非常多现成的积累。其实关键是一些流程的打通和通用解决方案的积累,相信随着更多的项目实践,这块慢慢会变成一个稳定的流程。</p>
<h2 id="六、“中途岛”">六、“中途岛”</h2>
<p>虽然“基于NodeJS的全栈式开发”模式很让人兴奋,但是把基于Node的全栈开发变成一个稳定,让大家都能接受的东西还有很多路要走,我们正在进行的“中途岛”项目就是为了解决这个问题。虽然我们起步不久,但是离目标已经越来越近!!</p>
</div>
<footer class="article-footer">
<a data-url="http://satans17.github.io/2014/04/04/也谈基于nodejs的全栈式开发(基于nodejs的前后端分离)/" data-id="a6cvuo5969ml8v0r" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-移动开发前端框架选型" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2014/01/06/移动开发前端框架选型/" class="article-date">
<time datetime="2014-01-06T08:36:21.000Z" itemprop="datePublished">Jan 6 2014</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2014/01/06/移动开发前端框架选型/">移动开发前端框架选型</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>随着公司无线ALL IN,前端同学们或多或少都在接触一些无线的业务。<br>即使没做过无线的同学,基本上也听过关于无线开发的一些难点,比如省电、流量、速度、兼容性等等</p>
<p>这些点给我们的印象就是,我们开发的webapp就一定得更小,更快!<br>我觉得这个没问题,在PC上,我们也一直在探索怎么把这两点做到极致!</p>
<p>所以,很多之前用KISSY做PC开发的同学会有一点疑虑:做无线开发我们还用KISSY吗?<br>也有一些同学问我,KISSY有没有针对移动开发的版?<br>已经用了KISSY做移动开发的同学,KISSY的体积成为了他们的心病,因为<a href="http://zeptojs.com/" target="_blank">zepto</a>才9.2KB(when gzipped)</p>
<p>其实几周前,我也在纠结KISSY那几十KB的大小<br>还发起过一个讨论<a href="http://www.atatech.org/qa/detail/17973" target="_blank">无线web性能衡量的一些标是什么?</a><br>经过一段时间的实践,关于这个问题我有一些自己的看法。<br>下面我通过一些数据和我的经验聊下我的看法。</p>
<h3 id="关于兼容性代码">关于兼容性代码</h3>
<p>KISSY是一款全终端全浏览器支持的JavaScript框架。<br>所以大家一定会想,KISSY中一定有大量的兼容性代码,对于移动开发,这些代码都是多余的。<br>KISSY有兼容性代码没错,但是!做移动开发,其实我们只是不用兼容低版本IE,其他的兼容都还是需要的。<br>如果大家看过KISSY源码,KISSY是有条件加载的,在高级浏览器中,KISSY不会载入IE哪些兼容性代码。<br>虽然不是非常彻底,但是真没多出多少代码。<br>如果你不相信,认为彻底去掉IE的兼容性代码可以让体积压缩很多,我们可以看看JQuery。</p>
<p><img src="http://gtms01.alicdn.com/tps/i1/T1VQ0BFxNhXXc_Bkby-500-293.jpg" alt="jquery两个版本大小对比"></p>
<ul>
<li>jquery-1.10.2<ul>
<li>压缩前:273KB</li>
<li>压缩后:93KB</li>
</ul>
</li>
<li>jquery-2.0.3<ul>
<li>压缩前:242KB</li>
<li>压缩后:84KB</li>
</ul>
</li>
</ul>
<p>大家都知道juqery2.x是不支持IE6/7/8的<br>压缩后体积相差9KB,这个差值比我想象的要少很多。<br>在功能相当的情况下,指望不兼容IE6/7/8就能让代码体积较少很多,貌似也不是很现实。</p>
<h3 id="关于体积">关于体积</h3>
<p>KISSY常用的功能有seed,dom,evnet,node,anim,ajax,我看了下大小</p>
<ul>
<li>KISSY Core(seed+dom+event+node+anim+ajax)<ul>
<li>压缩后:131(42+89)KB</li>
<li>GZIP: 47.9KB</li>
</ul>
</li>
</ul>
<p>功能类似的组合有seajs+jquery,seajs+zepto,我与KISSY core对比下大小</p>
<p><img src="http://gtms01.alicdn.com/tps/i1/T1f6RAFq4dXXcZmAfy-500-296.jpg" alt="KISSY core,JQuery,ZEPTO,"></p>
<p>zepto,jquery都加上了seajs 6.6KB</p>
<p>咋一看,在体积上KISSY位居榜首<br>不过这里得强调下,虽然功能类似,KISSY功能比另外两个强大很多。</p>
<p>很多是经常用到的功能,当然你也可以自己实现,但是你能保证稳定性,兼容性,以及向后兼容吗?</p>
<p>47.9到底有多大呢,有参照物或许能让你感觉更直观,以下数据都是gzip之后的数据。</p>
<p><img src="http://gtms01.alicdn.com/tps/i1/T1MwpxFAheXXc_Bkby-500-293.jpg" alt="KISSY core大小参照"></p>
<p>KISSY core的体积与大部分网站的总体积比起来也还好<br>像etao这样的就显得略蛋疼,网站23%的大小是KISSY core的大小。</p>
<p>对于“大小”这事,看了上面的图,大家是不是有了更直观的认识?</p>
<p>有些同学说KISSY还是太大,这么大的体积,可能会导致手机运行变慢<br>这个假设我暂时没法反驳<br>但是,咱们来看看大名鼎鼎的sencha touch</p>
<p>打开这个demo你可以看到sencha touch的体积<br><a href="http://cdn.sencha.io/touch/sencha-touch-2.3.1/built-examples/touchtomatoes/index.html" target="_blank">http://cdn.sencha.io/touch/sencha-touch-2.3.1/built-examples/touchtomatoes/index.html</a></p>
<p>sencha-touch-all-debug.js(2.3.1)</p>
<ul>
<li>3.5M</li>
<li>912KB(gzip)</li>
</ul>
<p>是的,你没看错,是3.5M,gzip之后也有将近1M。</p>
<p>有些同学可能会说,环境不一样,老外用的手机好,网络也好!<br>手机好不好不是很清楚,网络确实比我们好。<br>但是,关于网络,我也正好有想吐槽的。</p>
<h3 id="关于2G网络">关于2G网络</h3>
<p>2g网络真的很慢,可以去我写的demo体验一下<br><a href="http://www.atatech.org/article/detail/11922/786" target="_blank">http://www.atatech.org/article/detail/11922/786</a></p>
<p>有些同学规划产品前,把2G网络当作一个首要的衡量标准,认为一定要保证2G网络的体验。<br>为了省几十KB的流程,甚至不惜砍功能,改方案!<br>真的有必要吗亲?</p>
<p>2G用户一个月30M流量,刷微信都来不够<br>你的产品对于这部分用户真的这么重要吗?<br>真的有必要为了他们牺牲3g,wifi用户的体验么?<br>我觉得可以多掂量一下!</p>
<p>有时候就应该分清主次,学会放弃,大踏步的迈出步子!!<br>据说即将发布的淘宝主客户端4.0版本不支持android2.3,android开发的同学是不是因此可以不用996了!</p>
<h3 id="适合移动开发的前端框架">适合移动开发的前端框架</h3>
<p>这个节标题是很多同学的一个疑问,但是这种说法是有问题的。<br>做框架选型,首先得看你的需求,只有适合某个需求的框架:</p>
<ul>
<li>如果仅仅是一个资讯类的适合手机终端的站点,例如sina.cn,那么你要考虑的是加载速度</li>
<li>如果是一个功能复杂的webapp,你得考虑整体的架构和性能</li>
<li>如果你要的是一个跨终端、跨浏览器的响应式解决方案,那么你就得考虑兼容性</li>
</ul>
<p>如果你想找一个库同事解决上面所有问题,貌似不太显示,就是鱼和熊掌不可兼得。<br>简单分析下,很容易找到适合自己场景的:</p>
<ul>
<li>如果是第一种需求,zepto这种小库比较适合,反正也不会用到很多功能,就算是原生写,也没多少代码</li>
<li>第二种需求,JQueryMobile,KISSY,Sencha这样的框架是个不错的注意。你需要用到大量的特性,组件支持,还要保证项目的架构稳定。</li>
<li>第三种需求,我暂时还没有想到比KISSY更好的 :)</li>
</ul>
<p>反正很难有一个库满足所有需求,所以一切皆权衡!!</p>
<h3 id="关于KISSY_for_mobile">关于KISSY for mobile</h3>
<p>有一个好消息,风驰团队发起了一个KISSY MINI的分支项目,目标是功能比seajs+zepto强大,但是体积与他们相当。<br>等这个项目发布,做一套这样简单的展示型的网站再也不用纠结了</p>
<p>按照承玉的思路,KISSY本身近期应该还是会以全终端全浏览器兼容的思路发展。<br>不过承玉近期主要的工作都是在做移动相关的组件支持,到时候会提供一整套webapp的解决方案,有需求的同学敬请期待吧!</p>
<h3 id="最后">最后</h3>
<p>上面说了这么多,真的不是在忽悠大家用KISSY<br>只是想让大家有一个清醒的,直观的认识。</p>
<p>如果你觉得seajs,zepto,jquery,sencha中的任何一个更适合你的需要场景<br>放心大胆的用,不用纠结这纠结哪的<br>那个产品不是几个月重构一次,有问题了咱再改<br>不要为了几十KB的体积纠结得饭都吃不下<br>放心大胆大踏步的去做!<br>阿里无线起步晚,现阶段,我们最重要的任务是把步子迈出去,把产品做出来!!</p>
<hr>
<p>文章同时发布在<a href="http://blog.kissyui.com/2014/01/06/移动框架选型/" target="_blank">http://blog.kissyui.com/</a></p>
</div>
<footer class="article-footer">
<a data-url="http://satans17.github.io/2014/01/06/移动开发前端框架选型/" data-id="xuzdhck20q55nmz5" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-viewport笔记" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2013/12/09/viewport笔记/" class="article-date">
<time datetime="2013-12-09T14:32:38.000Z" itemprop="datePublished">Dec 9 2013</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2013/12/09/viewport笔记/">viewport笔记</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>对于准备做手机h5开发的同学来说,页面布局应该用多大,是困扰我们的第一个问题。<br>我们看到iphone5s的参数介绍里写着 <strong>主屏分辨率: 1136x640像素</strong> ,<br>这个分辨率能赶上很多PC机19寸显示器的分辨率了,但是按照这个分辨率来布局显然不行</p>
<p>有些同学看过资料,知道加上下面这句,手机上看网页内容会变得“正常”</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre>1
</pre></td><td class="code"><pre><span class="tag"><<span class="title">meta</span> <span class="attribute">name</span>=<span class="value">"viewport"</span> <span class="attribute">content</span>=<span class="value">"width=device-width,initial-scale=1"</span>></span>
</pre></td></tr></table></figure>
<p>那么这个设置到底做了哪些事情?<br>相关的介绍非常多,不过一直没仔细研究,很多问题都是一知半解。<br>这两天花了点时间梳理了一下,搞明白了一些概念。<br>以下是我的学习笔记:</p>
<hr>
<h2 id="一、主屏分辨率和主屏尺寸">一、主屏分辨率和主屏尺寸</h2>
<p><code>主屏分辨率</code>和<code>主屏尺寸</code>是我们最关注的手机pad等设备的参数,这两个参数是设备硬件参数。例如 IPHONE 5S :</p>
<ul>
<li>主屏分辨率: 1136x640像素</li>
<li>主屏尺寸: 4.0英寸</li>
</ul>
<p>在PC显示器上,我们通常都是按照设备主屏的分辨率来做页面布局的。例如之前的主流分辨率是1024*768,我们基本上都采用定宽950px居中布局。<br>不过PC机的显示器是19英寸,如果在手机上也按照主屏分辨率布局,后果就是把以前19英寸上显示的内容缩小到4英寸的大小,显然我们无法看清显示的内容。<br>所以,首先我们得了解分辨率以及物理尺寸两个概念。</p>
<h3 id="1-1_主屏分辨率">1.1 主屏分辨率</h3>
<p>主屏分辨率也叫<strong>设备分辨率(Device Resolution)</strong></p>
<p>我们通常说的分辨率就是指设备分辨率,这个分辨率是指每英寸的面积上可产生的像素点,分辨率越高代表可以将画面显示得更精细。<br>(有时候获取的显示器分辨率,其实是指桌面设定的分辨率,而不是显示器的物理分辨率。大多数情况下,我们设置的分辨率与物理分辨率一致,显示效果才最佳。)</p>
<p><em>度量单位 PPI(pixels per inch)</em></p>
<p>显示器的分辨率,我们一般都是按照例如1024<em>768这种说法,但其实分辨率是有自己的单位的。可能是大家觉得1024</em>768这种说法比PPI直观。<br>换算也很简单:</p>
<blockquote>
<p> 假设显示器的屏幕大小为14英寸,即对角线长度为14英寸,<br> 纵横比为3:4,<br> 则水平方向长度为 14*(4/5)=11.2英寸<br> 从而显示分辨率为 1024/11.2 = 91.4 PPI。</p>
</blockquote>
<p>所谓Retina屏,其实就是PPI高到人眼识别的极限,下面会简单讲一下。</p>
<p><em>DPI和PPI的区别</em></p>
<p>DPI(dots per inch)是一个专属于打印机的墨点级分辨率单位,其意指每英寸所打印的墨点的数目。打印机输出的每个像素都是由若干个不同颜色的墨点拼合而成的。可以认为墨点是更小的像素,称为子像素。<br>关于DPI与PPI的区别三两句也讲不清楚,有兴趣的可以搜索下相关资料。</p>
<p><em>设备分辨率</em></p>
<ul>
<li>可以通过screen.width/screen.height来查看设备的分辨率。(上面提到过,在PC上获取的设备分辨率有可能是用户设置的桌面分辨率)</li>
<li>设备的分辨率与浏览器设置无关,比如我们进行了缩放。</li>
<li>手机的设备分辨率,与我们写页面布局,几乎没有什么关系。</li>
<li>某些场景下设备分辨率又被称为“设备像素”,下面还有一个关于“CSS像素”的概念</li>
</ul>
<h3 id="1-2_屏幕尺寸">1.2 屏幕尺寸</h3>
<p>屏幕尺寸就是指屏幕的物理尺寸,一般都是以英寸为单位,也有以寸为单位的。<br>注意英寸和寸的区别:1英寸=0.762寸。记得以前很多奸商在这个单位上做文章,忽悠消费者。<br>屏幕尺寸根我们要讨论的页面布局几乎没有什么关系,所以不详细讨论。</p>
<h3 id="1-3_Retina">1.3 Retina</h3>
<blockquote>
<p> <a href="http://zh.wikipedia.org/wiki/Retina显示屏" target="_blank">Retina显示屏</a>(英文:Retina Display)具备足够高像素密度而使到人体肉眼无法分辨其中单独像素点的液晶屏,最初采用该种屏幕的产品iPhone 4由执行长史蒂夫·乔布斯于WWDC2010发布,其屏幕分辨率为960×640(每英寸像素数326ppi)。这种分辨率在正常观看距离下足以使人肉眼无法分辨其中的单独像素。</p>
</blockquote>
<p>Retina的标准为:人眼看不到看不见单个物理像素点。</p>
<p>因为不同设备在使用状态下与人眼的距离不同,所以不同设备满足Retina标准的PPI也会不同。距离越小,PPI要求越高。</p>
<p>在iPad(3rd-Gen)发布会上,苹果给出了Retina设计标准的公式:</p>
<p><img src="http://gtms01.alicdn.com/tps/i1/T1YNjYFf8dXXbrqnbb-146-51.png" alt="-"><br><img src="http://gtms01.alicdn.com/tps/i1/T1HCTUFfXdXXb5KEDE-684-532.png" alt="-"></p>
<p>其中a代表人眼视角,h代表像素间距(pixel pitch),其实就是一个像素的大小,d代表肉眼与屏幕的距离。符合以上条件的屏幕可以使肉眼看不见单个物理像素点。</p>
<p>而人眼视角小于或等于1角分(π/10800 弧度)的情况下,无法看见单个像素点。于是可得出Retina标准的PPT计算公式为:<img src="http://gtms01.alicdn.com/tps/i1/T14ZMaFhldXXXuwlrj-181-33.png" alt="-"></p>
<p>所以假如人眼与屏幕的尺寸为11英寸,那么通过公式可得PPI = 312,而实际上 iPhone 4/4S 的 PPI 是 330,符合retina的要求。</p>
<h2 id="二、像素、设备像素、CSS像素">二、像素、设备像素、CSS像素</h2>
<p>要搞清楚文章最开始提出的问题,这个三个名词一定要弄明白。<br>hax写了一篇文章<a href="http://hax.iteye.com/blog/374323" target="_blank">像素(px)到底是个什么单位</a>,非常有助于我们理解这三个名词,建议读一读。<br>下面我也按照我的理解分析一下。</p>
<h3 id="2-1_像素">2.1 像素</h3>
<p>引用<a href="http://zh.wikipedia.org/wiki/像素" target="_blank">维基百科-像素</a></p>
<blockquote>
<p>像素,又称画素,为图像显示的基本单位,译自英文“pixel”,pix是英语单词picture的常用简写,加上英语单词“元素”element,就得到pixel,故“像素”表示“图像元素”之意,有时亦被称为pel(picture element)。每个这样的信息元素不是一个点或者一个方块,而是一个抽象的采样。仔细处理的话,一幅图像中的像素可以在任何尺度上看起来都不像分离的点或者方块;但是在很多情况下,它们采用点或者方块显示。每个像素可有各自的颜色值,可采三原色显示,因而又分成红、绿、蓝三种子像素(RGB色域),或者青、品红、黄和黑(CMYK色域,印刷行业以及打印机中常见)。照片是一个个采样点的集合,故而单位面积内的像素越多代表分辨率越高,所显示的图像就会接近于真实物体。</p>
</blockquote>
<h3 id="2-2_设备像素">2.2 设备像素</h3>
<p>设备像素其实就是指上面讲过的设备分辨率。之前听有些同学理解手机上的布局,说要以设备像素为准,其实是不对的,应该是visual viewport,后面会讲到。</p>
<h3 id="2-3_CSS像素">2.3 CSS像素</h3>
<p>CSS像素就是指我们在写页面布局用得最多的单位(px)。<br>CSS像素是浏览器使用的抽象单位,用来在网页上绘制内容,CSS像素占的物理空间大小是不确定的。<br>在PC上,浏览器会给我们默认计算好1px占多少物理尺寸,以便让我们看得更舒服。<br>浏览器对页面进行缩放,其实是缩放CSS像素占的物理空间大小</p>
<p>另外,hax的文章中提到,有些同学不建议使用px作为布局单位其实存疑的,因为不同的设备、浏览器会计算好1px所占的物理空间大小,我们无需关心太多。</p>
<h2 id="三、viewport">三、viewport</h2>
<h3 id="3-1_viewport">3.1 viewport</h3>
<p><strong>viewport产生的背景</strong></p>
<p>几年前,我们做的网站都是为PC浏览器而设计的,几乎没考虑到手机屏幕,所以用手机浏览为PC定制的网站会出现问题。<br>固定宽度的网站,比如经典950的,网页会出现横向滚动条,这还好,至少可以浏览。<br>但如果是浏览流动布局的网页那情况会非常糟糕,设想一个宽度为 30% 的侧边栏对于 320px 手机屏幕而言也就 96px,只能容纳八个 12px 的汉字,可阅读性非常差。</p>
<p>为了让手机也能获得良好的网页浏览体验,Apple 找到了一个办法:在移动版的 Safari(iOS平台)中定义了 viewport meta 标签,它的作用就是创建一个虚拟的窗口(viewport),而且这个虚拟窗口的分辨率接近于桌面显示器,Apple 将其定位为 980px。</p>
<p>我们通常把这个虚拟的窗口叫,布局窗口(layout viewport)。<br>例如我们写一个宽度为100%的页面在iPhone5s上浏览,页面宽度是980px。</p>
<p>在Apple实现viewport后,其他浏览器也加入了对viewport meta的支持,但彼此间还是有些差异,差异最大的是layout viewport的大小上:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre>1
2
3
4
</pre></td><td class="code"><pre>Safari iPhone: <span class="number">980</span>px
<span class="label">Opera:</span> <span class="number">850</span>px
Android WebKit: <span class="number">800</span>px
<span class="label">IE:</span> <span class="number">974</span>px
</pre></td></tr></table></figure>
<p>在没有viewport设置的情况下,我们很容易可以拿到布局窗口的值:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre>1
2
</pre></td><td class="code"><pre>console.log(document.documentElement.clientWidth);
console.log(document.documentElement.clientHeight);
</pre></td></tr></table></figure>
<p><strong>设置viewport</strong></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre>1
2
3
</pre></td><td class="code"><pre><span class="tag"><<span class="title">head</span>></span>
<span class="tag"><<span class="title">meta</span> <span class="attribute">name</span>=<span class="value">"viewport"</span> <span class="attribute">content</span>=<span class="value">"width=device-width, initial-scale=1.0, user-scalable=no"</span>/></span>
<span class="tag"></<span class="title">head</span>></span>
</pre></td></tr></table></figure>
<p>这个是最常见的一条 viewport meta 代码,将 viewport 定义为:宽度为设备宽度,初始缩放比例为 1 倍,禁止用户缩放。</p>
<p>viewport 全部属性如下:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="attribute">width</span>: <span class="string">viewport宽度</span>
<span class="attribute">height</span>: <span class="string">viewport高度</span>
<span class="attribute">initial-scale</span>: <span class="string">初始缩放比例</span>
<span class="attribute">maximum-scale</span>: <span class="string">最大缩放比例</span>
<span class="attribute">minimum-scale</span>: <span class="string">最小缩放比例</span>
<span class="attribute">user-scalable</span>: <span class="string">是否允许用户缩放</span>
</pre></td></tr></table></figure>
<p>详细介绍可以看看<a href="https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html#//apple_ref/doc/uid/TP40008193-SW1" target="_blank">Safari对viewport的实现</a></p>
<p>虽然布局窗口(layout viewport)解决了手机浏览PC定制网页的问题,但是移动时代,很多网站就是为移动定制的。<br>所以,在无线时代,可视窗口(visual viewport)的大小才是我们真正关系的。</p>
<h3 id="layout_viewport_(布局窗口)_&_visual_viewport_(可视窗口)">layout viewport (布局窗口) & visual viewport (可视窗口)</h3>
<p>关于什么是layout viewport 什么是visual viewport,两个viewport的故事《<a href="http://weizhifeng.net/viewports.html" target="_blank">1</a>》《<a href="http://weizhifeng.net/viewports2.html" target="_blank">2</a>》 中有比较详细的介绍</p>
<p>引用他们的两个图,便于我们理解:</p>
<p><img src="http://gtms01.alicdn.com/tps/i1/T17ScdFcddXXbE78n0-380-519.jpg" alt="Alt text"><br><img src="http://gtms01.alicdn.com/tps/i1/T18_b4FXxdXXbE78n0-380-519.jpg" alt="Alt text"></p>
<p>顺便引用下他们的比如:</p>
<blockquote>
<p>把layout viewport想像成为一张不会变更大小或者形状的大图。现在想像你有一个小一些的框架,你通过它来看这张大图。(译者:可以理解为「管中窥豹」)这个小框架的周围被不透明的材料所环绕,这掩盖了你所有的视线,只留这张大图的一部分给你。你通过这个框架所能看到的大图的部分就是visual viewport。当你保持框架(缩小)来看整个图片的时候,你可以不用管大图,或者你可以靠近一些(放大)只看局部。你也可以改变框架的方向,但是大图(layout viewport)的大小和形状永远不会变。</p>
</blockquote>
<p>layout viewport 和 visual viewport 是我们理解viewport的关键。但是看了各种文章,也都没给他们一个确切的定义,而且都比较绕。<br>我是这么理解这两个概念的:</p>
<blockquote>
<p> 在没有viewport这个概念之前,页面布局只有visual viewport这一个概念,无论是手机还是PC机。PC上的visual viewport通常是1280这么大的分辨率,但是由于手机屏幕比较小,通常只有320。为了在PC上定制的网页在手机上浏览有比较好的体验,于是引用了layout viewport这个概念。</p>
</blockquote>
<h2 id="四、width=device-width">四、width=device-width</h2>
<p>字面意思是设置宽度(布局宽度)等于设备宽度,但在实际中大多数浏览器都给出了个定值320px;<br>这句话里面有三个关键词:</p>
<ul>
<li>大多数: 大多数包括safari,老一点android上的chrome,随着4,5寸屏的市场份额增加,这个值也在变大。</li>
<li>320px: 为什么是320px呢,据说这个值源于 Apple,因为早期 iPhone 的分辨率为 320px × 480px,大量为 iPhone 量身定制的网站都设置了width=device-width,并且按照宽度 320px 来设计制作,所以其他浏览器加入 viewport 支持时为了兼容性也将 device-width 定义为了 320px。</li>
<li>浏览器: 最开始我以为device-width就是设备的某个值,至少同一个设备这个值是滚动的,测试后发现,同一个设备不同的浏览器这个值竟然不同。比如我的三星E120L(刷了小米系统),原生浏览器是320px,但是UC,QQ都是360px;</li>
</ul>
<p><strong>不同型号的手机这么多,浏览器也很多,意味着device-width的值也会很多,手机页面到底应该按照什么宽度来设计呢?</strong></p>
<p>估计很多同学只关心这个问题的<strong>结论</strong>,说下我的观点吧:<br>我觉得这和PC时代的思路是一样的,只是我们又走了一次这个轮回。<br>记得最早PC显示器的分辨率是800<em>600,于是多数网页是按照760的定宽来设计的<br>后来1024</em>768成了主流,于是950定宽成了设计标准。</p>
<p>现在手机主流就是320px,如果做定宽的设计,320px这个取值没错。<br>不过遇到更大分辨率的手机,比如480px,大家似乎不太能接受固定320px然后居中对齐,所以我们通常是100%宽度来布局的。</p>
<p>大家似乎也都是这么做的!</p>
<hr>
<p>终于整理完了,虽然自己读起来感觉也不是那么通顺,不过如果大家决定彻底弄懂相关的概念,我的大纲到是可以参考下。<br>另外,以上很多内容都是引用网上的,加上了一些自己的理解,可能有些结论有误,欢迎探讨和指正。</p>
</div>
<footer class="article-footer">
<a data-url="http://satans17.github.io/2013/12/09/viewport笔记/" data-id="ehpe6zu0ojl0li9c" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-请各位小伙伴们帮忙做一个简单的测试" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2013/11/21/请各位小伙伴们帮忙做一个简单的测试/" class="article-date">
<time datetime="2013-11-21T10:08:28.000Z" itemprop="datePublished">Nov 21 2013</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2013/11/21/请各位小伙伴们帮忙做一个简单的测试/">请各位小伙伴们帮忙做一个简单的测试</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>大家都说2g/3g网络慢,这个测试就是想直观的看看到底有多“慢”。测试步骤很简单:</p>
<p><strong>1. 关闭手机wifi</strong><br>为了保证测试数据的准确性,请确保你已经关闭了wifi.</p>
<p><strong>2. 扫描二维码</strong><br><img src="http://gtms01.alicdn.com/tps/i1/T1t6G5Fc8gXXcW27rz-256-256.png" alt="测试链接"></p>
<p><strong>3. 开始测试”</strong><br>2g网络下可以能需要等待1分钟,会消耗你几百KB的流量。</p>
<p><strong>4. 等待测试完成,提交测试结果</strong><br>提前请选择正确的测试结果。</p>
<p>如果最后能拿到有用的测试数据和结果,你也将会成为测试结果的受益人,再次感谢大家!</p>
</div>
<footer class="article-footer">
<a data-url="http://satans17.github.io/2013/11/21/请各位小伙伴们帮忙做一个简单的测试/" data-id="mr0fzigibtc51pmx" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-css3-transitions-你可能不知道的知识点" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2013/11/02/css3-transitions-你可能不知道的知识点/" class="article-date">
<time datetime="2013-11-02T12:28:49.000Z" itemprop="datePublished">Nov 2 2013</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2013/11/02/css3-transitions-你可能不知道的知识点/">CSS3 Transitions 你可能不知道的知识点</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<h2 id="如何临时让transition失效">如何临时让transition失效</h2>
<p>我们给一个element设置了transition效果,但某些特殊情况,我们希望让transition临时失效。<br>我们第一反应就是先移除transition设置,等其他属性设置完成之后再还原transition设置。<br>但浏览器有时候会让我们感觉事与愿违<br>看下面这段代码,你觉得会不会有transition动画效果?</p>
<figure class="highlight"><table><tr><td class="gutter"><pre>1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="tag"><<span class="title">div</span> <span class="attribute">id</span>=<span class="value">"test"</span>></span><span class="tag"></<span class="title">div</span>></span>
<span class="tag"><<span class="title">script</span>></span><span class="javascript">
window.onload = <span class="function"><span class="keyword">function</span><span class="params">()</span>{</span>
<span class="keyword">var</span> div = document.getElementById(<span class="string">"test"</span>);
div.style.width=<span class="string">"500px"</span>;