1、引子
引出本次討論的原因是dojo.io.IframeIO的問題。在一個比較大的Javascript應用中使用了dojo,dojo.io是dojo實現的非常好的一個地方,因為dojo.io用一個通用的接口封裝了XmlHttp、Iframe、ScriptSrc這幾種主流方式(Facade模式),是處理Ajax應用IO的很好選擇。但是項目實際部署以后發現經常出現操作無響應的情況,經過反復排查發現是使用的dojo.io.IframeIO的排隊沒有超時造成的。
那么我們就要尋找問題再哪里呢?
2、問題
首先我們看到了dojo.io的bind方法中支持通用的timeout和timeoutSeconds參數,這兩個參數從語意上看是為了給你的IO增加超時機制。當初Ajaxian上有一篇介紹實際部署的Ajax項目需要注意的穩定性問題,其中比較重要的一條就是要給你的通訊排隊增加超時,而dojo.io的這種設計正體現了這種觀點。那么我們還是首先分析一下為什么要排隊和為什么要超時機制。
io通訊分同步和異步兩種方式,同步即需要阻塞等待,異步即無阻塞等待(異步回調)。我們在使用XML Http的時候可以選擇asynchronous為true和false,大家都知道它們就是是否同步。使用的地方大家都很熟悉了,比如兩個function有關,它們的調用需要保證嚴格順序,那么就應該同步通訊,前一個完了再調后一個。反之如果幾個function之間無關,那么就可以異步通訊,減少用戶瀏覽器的阻塞和等待。
OK,這種模型很清楚和簡單。可是,實際上就XMLHttp而言,我們一般盡量不使用asynchronous=false,因為這樣瀏覽器會在傳輸的時候失去對用戶的響應,用戶感覺比較差。對于需要嚴格順序的通訊我們使用另外一種方式,那就是回調的時候再調用下面一個函數,這樣也可以保持嚴格順序,形成調用鏈。這種方式其實就是排隊模型 。在dojo中有一個queueBind方法,實際上就是包裝過的調用鏈。有了這些機制就可以滿足我們日常的編程模型了,似乎很完美了。
可是,問題在于并非所有io都可以提供排隊和非排隊的方式進行通訊。比如iframe和ScriptSrc的方式實際上都是不支持非排隊的,因為這些通訊本質上都是同步的(或者說假異步),在只有一個Dom元素(iframe或者script元素)進行通訊的時候其實所有的io都必須排隊。也就是說可憐的iframe和scirptSrc方式都只有排隊……而不能不排隊……。這似乎也沒什么問題,但是問題來了……
HTTP通訊并非可靠的通訊,它們都是最大努力的傳輸方式,也就是說丟包是不可避免的,在發生丟包的時候如果恰巧你的通訊進行了排隊而此時又沒有超時……那么你慘了,就出現了北京早晚場出現的堵車現象,往往僅僅是一輛輛車刮蹭了,后面的車就永久的成為了化石,多么的悲壯呀。
所以,排隊的超時就非常重要了!一次通訊一定要有超時時間,這樣在阻塞時可以把阻塞消除。
dojo在BrowserIO(XMLHttp)和ScriptSrcIO里面實現了超時機制,但是在IframeIO中沒有提供,這可能是個歷史遺留。
而引子中所說的問題正來自這個不公平的待遇,iFrame的bind實際上就是queueBind(因為dojo的iFrameIO只用了一個iFrame),而又沒有超時機制……,然后應用部署在實際網絡條件后就有很多用戶遇到了操作無響應的問題。
3、解決
找到問題并理清思路離解決就非常近了。
dojo的BrowserIO(XMLHttp)和ScriptSrcIO都使用了一種類似onFlight的方式檢查超時。就是紀錄通訊開始時間,然后傳輸時開始輪訓檢查是否超時。不直接用setTimeout檢查通訊超時是因為如果有多個ScriptSrc或者XMLHttp通訊的時候可以防止互相沖突。
我們也可以比較簡單的向IframeIO中添加添加這樣的功能:
OK,這里我偷懶了……。因為我們的項目只有一個iFrame作io,所以我們沒有使用onFlight方式,使用了setTimeout直接檢查的方式。大家先笑納,待簡單修正把IframeIO也修改為onFlight的方式(然后提交dojo?)。
如此看有點虎頭蛇尾,實際上就是這樣,問題很大解決方法很小。我們經常遇到的就是這樣的問題。
4、尾聲
最后其實還是要回到javascript應用中的io問題上來。這里我還要簡單寫一下我們常用到的io方式,我身邊還有很多同事不了解所以然。
XML Http是這幾個io中最好的一個,因為它是一個完整的IO實現,有狀態回調機制,有abort等處理方法,但是相對來說有幾個限制:瀏覽器支持、文件上傳、跨域。
瀏覽器支持現在倒不算個問題,Ajax應用普遍是IE 5.5+、FF 1.0+、Opera 8+這樣子,它們都支持XMLHttp了。文件上傳用XMLHttp也比較難解決,雖然可以用客戶端切分的方法,但是用的人實際上很少(在這點上技術復雜性超過了iframe很多)。
XMLHttp最大的限制是跨域,很嚴格,甚至不能跨子域,這給我們的通訊帶來了巨大的限制。因為很多時候我們要使用proxy或者負載均衡的分布式來解決這個問題,這就增加了部署復雜性。
相對來說iframe則放松了這樣的限制,你可以跨子域(比如www.abc.com和ajax.abc.com和cde.xxx.abc.com),我們可以在文檔里面通過domain="abc.com"來強制用根域做js執行的域。但是這種做法也有麻煩,比如Firefox 1.0.x的一些版本如果domain="abc.com"執行以后會造成XMLHttp通訊失敗,此時我們就沒法在用iframe的同時用XMLHttp了。
iframe的另外一種好處是它比較容易解決文件上傳的問題,只要對form指向一下target為iframe就可以無刷新上傳了,這幾乎是最簡單的方案。
但是iframe的限制還是滿多的,它比較復雜,在Tencent這樣的倒霉瀏覽器中設置了所有鏈接都在新窗口中打開后會讓應用很難看并不可用,它沒有完全的解決跨域問題等等。
為了解決iframe的不能跨根域的問題,引入了ScriptSrc的方式。就是指<script src="xxx.js" id="scriptIo"></script>,然后動態修改src這樣的方式。為了瀏覽器兼容,此種方式下需要動態的創建script節點,然后再刪除,否則內容不會載入(非IE的情況)。所以技術復雜度相對也比較高。但是這種方式可以完全解決跨域問題,你可以將src指向任何地方!
那么缺點是ScriptSrc在動態刪節點的時候潛在存在內存泄露,據稱不太好解決。而更大的問題是如果傳輸json的話,必須用var json = {property:value};這樣的形勢,對json有了污染,不太好。
所以,對于io的方案主要來說幾個點:
a、跨域:ScriptSrc完全跨域,iFrame跨子域,XmlHttp不跨域。
b、文件:傳文件iframe比較簡單,ScriptSrc和XmlHttp都需要客戶端切分get上去,比較麻煩。
c、生命周期控制:XmlHttp比較完善,而iFrame可以模擬但是不完全,ScriptSrc則更難。
需要代碼請去JavaEye下載:D
http://www.javaeye.com/topic/82169
That's all。本來想No fluff just staff的寫這篇東西,但是無奈嘴碎寫的優點亂,請大家見諒。關于本問題的解決希望大家多提意見,互相交流。