基於事件的Web應用
傳統的Web表單提交就是典型的基於事件的模式。換句話說,在Web表單裏輸入了不少數據(用戶輸入文本框,點選複選框,從列表中選中某些項等等),以後這些數據提交給服務器。這個場景中實際是一個單一的程序事件:使用POST方式將表單數據提交。這也是基於Ajax的Web應用的工做原理。 一次性發送大量數據
對於Ajax來講,是能夠和基於事件編程扯上一點關係。客戶端和服務器端之間有些交互能夠認爲是基於事件的。典型的場景是輸入一個省市代碼,發送請求到服務器得到城市和省的名稱。這裏經過XmlHttpRequest的Ajax並不須要將不少數據一次性扔給服務器。但這並不能改變大部分web應用都是基於頁面刷新這種模式的現狀。Ajax已經更普遍的用於不少有意思的視覺相關的交互,快速的做表單驗證,無刷新提交數據,這樣就能夠避免從新載入頁面。所以,儘管並未經過提交表單來發起一個真正的POST請求,經過Ajax能夠模擬POST表單提交。 坦率的講,這種傳統的Ajax交互方式也阻礙了Ajax程序員的創新。每次發送一個請求時(無論請求的數據多麼小),都會在網絡裏走一個來回。服務器必須針對這個請求做出響應,一般是開闢一個新的進程。所以,若是你真正置身於一個事件模型的環境中作開發,你可能須要經過發起10到15個單獨的小請求來保持你的頁面和服務器之間的聯繫,服務器也會爲之建立10到15個線程(可能更少,這取決於服務器處理新請求時分配線程池的策略),當這個數量乘以1000或者10000或者100000時(譯註:每一個頁面須要10個請求,那麼越多用戶訪問這個頁面,所發起的請求個數就會愈來愈多),就會出現內存溢出、邏輯交錯帶來的衝突、網絡癱瘓、系統崩潰這些問題。 前端
結果是,在大多數場景中,Web應用須要保持對事件的最小依賴。有一個折衷方案,就是服務器端程序的響應返回的不是一個微小的數據片斷,而是帶有更多冗餘數據結構的數據包,一般是JSON數據,這時就又遇到了eval()的問題。問題固然出在eval()身上,但這也和Web自己和服務器線程控制、包括頁面和服務器之間的HTTP請求和響應策略(至少在這個場景下)有密不可分的關係。
或許有些人對上文提到的問題不覺得然,由於你知道有不少方法來規避直接eval()帶來的問題,你會使用諸如JSON.parse()來代替eval()。一樣有不少使人信服的論據鼓勵咱們當心的使用eval()。這些東東都是值得進一步討論的。但無論怎樣,看一看eval()帶來了太多相似棧溢出(Stack Overflow)這類的問題吧,你會發現大部分程序員並未正確或者安全的使用eval()。這着實是一個問題。由於太多菜鳥程序員彷佛根本沒有意識到eval()的問題有多嚴重。
不斷的發送少許的數據
Node帶來了架構應用的新思路,咱們能夠基於Node採用事件模型來架構Web應用,或者說「小型的」事件模型。換句話說,你應當基於大量的事件發送大量的請求,每一個請求的數據包都很小,或者根據須要從後臺抓取少許數據,而不是發送不多的請求,每次請求都帶有大量的數據。在不少場景中,大多數狀況下你須要喚醒GUI程序(Java Swing程序員的GUI知識儲備能夠派上用場了)。所以,當用戶輸入姓氏和名字後,移步到下一個輸入框,這時就已經發起了一個請求來驗證輸入的用戶名是否已經存在。省市代碼、地址和電話號碼的驗證也是同理。頁面上每發生一個事件,都會產生一個請求和響應。
這有什麼不一樣嗎?爲何Node能夠作到,並規避了已有的線程問題?其實Node並無這麼神祕,Node官網充分解釋了其哲學:
Node的目標是提供一種構建可伸縮的網絡應用的方案,在hello world例子中,服務器能夠同時處理不少客戶端鏈接。Node和操做系統有一種約定,若是建立了新的連接,操做系統就將通知Node,而後進入休眠。若是有人建立了新的連接,那麼它(Node)執行一個回調,每個連接只佔用了很是小的(內存)堆棧開銷。 Node是無阻塞的,不會出現同源競爭線程的狀況(Node很是樂於處理即時的請求,發生了什麼事情,那就讓他發生吧),新請求到達服務器時,不須要爲這個請求單獨做什麼事情。Node僅僅是清閒的坐在那裏等待(請程序員
求的發生),有請求就處理請求。用很是簡單的代碼就能夠實現,而不用花費程序員寶貴的精力去實現一整套服務器端邏輯。web
沒錯,混亂不可避免
值得一提的是,非阻塞系統帶來的問題也會出如今這種編程模式中:一個進程(非線程)等待一個數據存儲操做,這時產生了另一個抓取與之無關的數據的操做,這個意外的操做會對現有的等待形成影響(譯註:做者的意思是說多個操做同時發生或者沒有按照預約順序發生時,會產生混亂,也就是說,操做自己並非原子性的)。但要注意,大多數基於事件的web編程模式都是「只讀的」!你大概也沒有遇到過經過「微請求」來修改數據的狀況,或者說很是罕見。相反,經過這種請求來驗證數據合法性、查詢數據的情形則很是常見。這種狀況下,最好直接根據請求做響應。數據庫自己會做加鎖操做,通常來說,一個優秀的數據庫徹底能夠高效的作到數據操做的加鎖解鎖,而不用服務器端的程序代碼去多作什麼。而Node又比操做系統處理線程的保持和釋放更加高效,使得服務器沒必要單獨爲「web響應」開闢一個進程。
此外,Node也計劃實現「進程分支」(process forking),HTML5 Web Workers API爲更復雜的進程控制提供了引擎(規範)支持。一樣,若是你採用基於事件的模型來架構web應用,你的程序可能至少有100多個場景須要線程的支持。最終你會發現,你的編程思路和思考問題的方式發生了改變,你的注意力將放在服務器端處理請求的邏輯上,而沒必要在意Node如何工做。 Node的用武之地
這裏咱們討論另一種web開發模式,無論是否是採用了Node、或者是否是採用了基於事件的編程模式,這都可有可無,由於這種模式實在過重要了。簡言之:對症下藥!歸納講就是,針對不一樣的問題採起不一樣的解決方案,而無論這種解決方案是否解決其餘問題。 思惟定勢
不止在web設計領域,在全部編程之中都存在某種思惟定勢。能夠這麼描述這種思惟定勢:你學到的、掌握的越多,你能解決的問題就越多,你所掌握的技能的應用場景也就越多。這看起來理所固然,除非你在技術上鑽研的更深。沒錯,學習新的語言和新的工具並普遍使用它總不是壞事。但每每會進入一個誤區,就是,由於你瞭解它,因此你使用它,而不是由於你所掌握的技能和工具是「最適合」你的業務的。 數據庫
咱們來看一下Ajax,關於Ajax已經有太多太多的討論了。咱們知道,Ajax爲無刷新的快速查詢請求提供了可靠的解決方案。而現在由於Ajax的濫用以致於過度替代了傳統的表單提交。咱們遇到一個新技術、學習它、掌握它、應用它,而後「濫用它」。畢竟不少業務場景僅僅須要傳統的表單提交,而不須要Ajax。提及來簡單,實際上還有成千上萬的濫用Ajax的案例場景,僅僅由於某個應用的開發工程師對Ajax的盲目尊崇。
一樣的,Node也面臨這樣一個問題。當你初識Node發現它的種種好處,就想處處使用它。就會一股腦的將PHP或Perl程序換成Node。結果呢?糟透了。其實你已經害上了強迫症,老是想將Node用於有違其設計初衷的場景中:使用JavaScript提交大量數據給Node,或者經過Node返回給JavaScript大量的JSON數據,交給前端去做eval(),或者乾脆使用Node做一個文件服務器用以返回HTML頁面或作HTTP重定向。
但這些場景均不是Node所擅長的。Node更擅長處理體積小的請求以及基於事件的I/O,使用Node解決客戶端和服務器之間的快速溝通,使用表單提交將大量的數據發送給服務器,使用PHP和Perl來處理重型數據庫操做以及動態HTML頁面的生成。使用Node運行於服務器端來處理體積不大的請求。無論是採用Rails仍是Spring以及各式各樣的服務端容器,只要按需索取便可。必定要明白你須要解決的問題是什麼,基於此採起最佳解決方案,而不是基於你當下所掌握的技能來解決遇到的問題。 Node的簡單的初衷
還有最後一點須要注意,當你愈來愈深刻你的編程時,你會發現你沒必要每一個工具、API和所使用的框架都達到精通。將刀用在刀刃上,不要將錘子當成鑽頭來使用。瞭解每一個工具所適用的場景和能解決的問題,而後找到這個工具的最適合的應用場景。若是你想變成超人式的通才(程序員每每什麼都想知道),你離「專家」也就愈來愈遠,所謂專家,就是指在一兩個方面達到很是精通。固然,每一個老闆都但願能找到超人式的通才,但這種人每每可遇不可求。
學習Node可能會有些吃力,可是很是值得的。爲何?由於你正在尋求基於JavaScript的web應用的解決方案。這意味着你已有的JavaScript編程技能不會丟掉,當你須要使用PHP或者Perl時,你必須從新學習一門新的語言,而Node沒必要如此大動干戈。學習新語言帶來的問題比學習他們帶來的好處要大的多。
學習Node所面臨的挑戰是,你須要更加活躍思惟,將程序拆成低耦合的小片斷,而後像組裝數組同樣的組裝他們。但Node和基於事件的I/O並不能解決全部問題,但肯定的是,不少關鍵問題,只能依靠Node來解決。編程