關於 ruby / rails 的線程模型

inu 的項目中有一個導入功能,將用戶從瀏覽器、del.icio.us 導出的收藏條目導入到 inu 收藏夾中。這個功能推出以來,用戶的反響並不好,其主要原因在於:速度慢,考驗用戶的耐心。

速度慢的問題,根本原因在於 model 層需要做的工作非常多,也是目前不完善的架構以及比較特殊的需求導致的,可以說不能從根本上解決。每次導入一條記錄,都需要更新好幾個表,本身 ruby 在目前虛擬機下效率並不高,所以導入的速度並不理想。

那麼,退而求其次,可以設置一個直觀的進度條來告訴用戶目前導入的進度,可以在很大程度上緩解用戶的焦慮、不信任的心理。

解決方案是確定了,可是問題由此而來:ruby 是單線程的。

嚴格來說,應該是目前的虛擬機還不支持真正多線程的 ruby 應用。雖然 ruby 中確實有 Thread 類 ,但是查閱相關文檔可以知道,這個類是一個僞 Thread,不同於真正的多線程。如果通過 join 將子線程掛到當前的主線程中(某個 mongrel 進程),那麼該線程在結束之前會等待所有的子線程結束。通常情況下這種機制可以模擬出和其他語言相類似的線程模型。但是在 rails 中,有新的問題產生:無論是 FastCGI 還是 Mongrel ,都是一個請求獨佔一個進程,每次請求都會創建新的 Controller 實例,請求結束後該實例銷燬。也就是說,在 Controller 內無論是不使用線程、或是把子線程掛到主線程上,在所有操作結束之前,當前進程一直處於 block 狀態。

很可怕!

通常用 thread 是在不影響主線程的情況下處理耗時很多的操作。但是這種機制明顯不能應付這種需求。一臺服務器上 mongrel 數量有限,都被阻塞了,相當於服務器在這段時間內不能接收其他的請求。

那麼是否讓 thread 單獨執行就可以了呢?答案也是否定的。在單獨的線程中,Model 類將不再是 Model。其實意思是:線程中的 ActiveRecord 類不再具有標表間關聯(目前我測試出來就這麼多,可能還會失去更多東西),只是一個普通的實體類。對於這樣的導入,無疑就是毀滅性的。

至此,可以確定,這個應用不可避免的要阻塞一個 mongrel 了。那麼如果要反應當前導入的進度,就是要發起一個新的請求,查詢導入狀態。如果這個請求發送到 rails ,意味着用戶的導入要佔用 2 個 mongrel ,服務器不可能有這麼多的資源。

最終我採取了一種折中的方案,導入時創建一個可訪問的靜態資源,查詢進度的功能由這個靜態資源來完成,而 web 應用對於靜態資源的訪問則由 apache 來完成。

至此,問題算是解決了,雖然算不上完美。期望 ruby 的虛擬機能夠儘快支持 multi-thread 吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章