簡(jiǎn)介
Ruby On Rails 框架自它提出之日起就受到廣泛關(guān)注,在“不要重復(fù)自己”,“約定優(yōu)于配置”等思想的指導(dǎo)下,Rails 帶給 Web 開(kāi)發(fā)者的是極高的開(kāi)發(fā)效率。 ActiveRecord 的靈活讓你再也不用配置繁瑣的 Hibernate 即可實(shí)現(xiàn)非常易用的持久化,Github 和 Rubygems 上豐富多樣的 Rails 插件是 Rails 開(kāi)發(fā)高效率的又一有力保障。Rails 是一個(gè)真正徹底的 MVC(Model-View-Controller) 框架,Rails 清楚地將你的模型的代碼與你的控制器的應(yīng)用邏輯從 View 代碼中分離出來(lái)。Rails 開(kāi)發(fā)人員很少或者可能從未遇到過(guò)某些代碼該放于哪一層的困擾,在 Rails 的世界中,你的代碼的職責(zé)很清楚的被定位,你可以輕松的決定出它們應(yīng)該位于哪一層。Rails1.2 之后的版本開(kāi)始支持 Rest Service,通過(guò)這些內(nèi)嵌的特別,你在開(kāi)發(fā)你 Web 應(yīng)用,展現(xiàn)給用戶 HTML 的頁(yè)面的同時(shí),幾乎不費(fèi)吹灰之力,就可又提供基于 Rest API 的 Web Service,另外,你也可以方便的像使用基本數(shù)據(jù)的 Model 一樣的去消費(fèi)第三方提供的基于 Rest API 的 Web Service。
通過(guò)搜索引擎,你可以找到很多類(lèi)似于用 Ruby On Rails 十五分鐘創(chuàng)建 Blog 系統(tǒng)等類(lèi)似的使用 Rails 進(jìn)行快速開(kāi)發(fā)的文章。 Ruby On Rails 對(duì)于敏捷開(kāi)發(fā)過(guò)程的也是非常友好,在 Rails 框架中集成了 Unit Test, Function Test 對(duì)你的 Model 和應(yīng)用邏輯進(jìn)行測(cè)試。通過(guò)這些,你不需要再安裝任何插件或者程序庫(kù)便可方便的進(jìn)行測(cè)試驅(qū)動(dòng)開(kāi)發(fā),通過(guò) Watir 的支持,你可以輕松的用 Ruby 代碼實(shí)現(xiàn)基于瀏覽器的自動(dòng)測(cè)試。 另外,在 Rspec 等插件的支持下,你甚至可以進(jìn)行行為驅(qū)動(dòng)開(kāi)發(fā)(Behaviour Driven Development),讓你的測(cè)試代碼變得更加有意義,也更加容易被客戶所接受。
雖然 Rails 的優(yōu)點(diǎn)你可以一下子列出很多,你也可能會(huì)拿出用 Java/Hibernate/Spring/Struts 和 Ruby On Rails 開(kāi)發(fā)同樣功能 Web 應(yīng)用程序的代碼行比較來(lái)舉例,或者提供進(jìn)行 Java/Hibernate/Spring/Struts 和 Ruby On Rails 開(kāi)發(fā)所需要接受的培訓(xùn)資料的書(shū)籍的對(duì)比照片來(lái)舉例,但我們都不可避免的會(huì)面對(duì)可伸縮性(Scalability)的問(wèn)題。很多 Web 應(yīng)用會(huì)面對(duì)大量的用戶群,這樣的應(yīng)用會(huì)遇到很大的并發(fā)帶來(lái)的性能的考驗(yàn)。 Rails 在這一點(diǎn)上,并沒(méi)有讓所有人信服,依然有很多的系統(tǒng)架構(gòu)師和工程師對(duì) Rails 是否適用于開(kāi)發(fā)高負(fù)載高并發(fā)的 Web 應(yīng)用持懷疑的態(tài)度。但不可否認(rèn),隨著 twitter, friends for sale,basecamp 這類(lèi)大負(fù)載量的應(yīng)用的出現(xiàn),Rails 也越來(lái)越被認(rèn)識(shí)是可擴(kuò)展的,這些成熟的應(yīng)用也告訴我們,開(kāi)發(fā)出同時(shí)處理數(shù)百萬(wàn)用戶請(qǐng)求的 Rails 應(yīng)用是可能的。Ruby On Rails 框架在可伸縮性上為人詬病無(wú)非集中于以下幾點(diǎn),Ruby 語(yǔ)言本身性能問(wèn)題,Ruby On Rails 缺少成熟的高性能的應(yīng)用服務(wù)器,對(duì)數(shù)據(jù)庫(kù)擴(kuò)展的支持,互聯(lián)網(wǎng)上缺乏可熟可靠的網(wǎng)絡(luò)提供商等等。 本文將從這些點(diǎn)出發(fā),介紹具可伸縮性的 Rails 應(yīng)用程序的部署架構(gòu),以及開(kāi)發(fā)高性能的 Rails 應(yīng)用程序的一些比較好的具體實(shí)踐。
通常的 Web2.0 應(yīng)用,特別是高負(fù)載的應(yīng)用,除了 Web 和應(yīng)用服務(wù)器選擇 , 負(fù)載均衡這類(lèi)部署需要面對(duì)的問(wèn)題之外,通常還必須得面對(duì)后臺(tái)任務(wù),高性能全文搜索這些開(kāi)發(fā)上的問(wèn)題,這些在 Java 或者 PHP 這些比較成熟的開(kāi)發(fā)環(huán)境里面都有比較成熟的方案,開(kāi)發(fā)和架構(gòu)人員通常都會(huì)有多種選擇,結(jié)合具體應(yīng)用做出架構(gòu)設(shè)計(jì)。在新興的 Rails 社區(qū),這些還并不完善和成熟,本文將介紹一些高性能可伸縮的 Rails 應(yīng)用程序的開(kāi)發(fā)和部署的具體實(shí)踐,針對(duì)通常 Web 2.0 網(wǎng)站所遇到的具體問(wèn)題做出分析和解決方案,旨在給 Rails 開(kāi)發(fā)者提供具體的參考。本文將介紹的內(nèi)容可以用下圖來(lái)綜合表示:
圖 1. 本文總體結(jié)構(gòu)
使用 Nginx+Passenger 來(lái)替代 Apache+Mongrel 來(lái)部署 Rails 應(yīng)用
事實(shí)上,進(jìn)行 Rails 部署可以描述得非常簡(jiǎn)單,我們需要一些機(jī)器配置我們的環(huán)境,為了性能和成本,我們理所當(dāng)然選用 Linux 系列的服務(wù)器,可選的有 CentOS,Debian, Ubuntu Server 等。本文的所有程序,命令以及代碼如非特別說(shuō)明,都默認(rèn)是以 Linux 為基本環(huán)境的。 然后我們需要數(shù)據(jù)庫(kù)存儲(chǔ)我們的數(shù)據(jù),我們可以選用免費(fèi)的 Mysql,然后我們便開(kāi)始開(kāi)發(fā)我們的 Web 應(yīng)用的 Rails 代碼,部署時(shí),我們需要支持 Ruby On Rails 的應(yīng)用服務(wù)器讓我們的 Rails 代碼跑起來(lái),這可能是 Rails 自帶的 Webrick,當(dāng)然這個(gè)可能性極小,也可能是 FastCGI, 或者是 Mongrel,Thin, Passenger 等等,然后我們通過(guò) Apache,Nginx,Lighttpd,HAProxy 之類(lèi)的 Web 服務(wù)器訪問(wèn)我們的應(yīng)用服務(wù)器,這個(gè)訪問(wèn)可以直接通過(guò) HTTP 協(xié)議,也可以是 FastCGI,或者是自定義的其它協(xié)議,如此這般,我們便可以通過(guò)瀏覽器訪問(wèn)我們的應(yīng)用程序了。如下圖所示,這便是通常的 Rails 應(yīng)用程序部署結(jié)構(gòu)。
圖 2. Rails 應(yīng)用程序部署結(jié)構(gòu)
對(duì)于前端的 Web Server,Apache 是事實(shí)上的工業(yè)標(biāo)準(zhǔn),在 Web 服務(wù)器市場(chǎng),是占有率最高的,全球大量的網(wǎng)站采用 Apache 來(lái)部署他們的應(yīng)用,Apache 是一款成熟穩(wěn)定的 Web 服務(wù)器,功能非常強(qiáng)大,提供對(duì)幾乎所有 Web 開(kāi)發(fā)語(yǔ)言和框架的擴(kuò)展支持,在對(duì) Rails 框架的支持上,我們可以采用 mod_fcgid 模塊,通過(guò) FastCGI 協(xié)議與 Rails 進(jìn)程通訊?;蛘呃?mod_proxy_balancer 對(duì)后端的獨(dú)立的 Rails 服務(wù)器如 Thin Cluster,Mongrel Cluster 或者 Apach/Nginx+Passenger 進(jìn)行 HTTP 分發(fā)。但 Apache 作為一個(gè)通用的服務(wù)器,在性能上和一些輕量型的 Web 服務(wù)器相差甚遠(yuǎn)。Apache 的 mod_proxy_balancer 模塊的分發(fā)性能不高,比 Nginx 或者 HAproxy 都相差很多,另外,Apache 目前并不支持 Event(事件)模型,它僅支持 Prefork(進(jìn)程)模式和 Worker(線程)模式,每處理一個(gè)鏈接,就需要?jiǎng)?chuàng)建一個(gè)進(jìn)程或線程,而一些輕量級(jí) Web 服務(wù)器如 Nginx 和 Lighttpd,則都很好地利用內(nèi)核的事件機(jī)制提高性能,極大減少線程或進(jìn)程數(shù)量,降低系統(tǒng)負(fù)載。
Nginx 是一個(gè)輕量級(jí)的高效快速的 Web 服務(wù)器,它作為 HTTP 服務(wù)器和反向代理服務(wù)器時(shí)都具有很高的性能。Nginx 可以在大多數(shù) Unix like OS 上編譯運(yùn)行,并有 Windows 移植版。 Nginx 選擇了 Epoll 和 Kqueue 作為開(kāi)發(fā)模型,它能夠支持高達(dá) 50,000 個(gè)并發(fā)連接數(shù)的響應(yīng),可以在內(nèi)部直接支持 Rails 和 PHP 程序?qū)ν膺M(jìn)行服務(wù)。另外,Nginx 作為負(fù)載均衡服務(wù)器,也可以支持作為 HTTP 代理服務(wù)器對(duì)外進(jìn)行服務(wù) , Nginx 不論是系統(tǒng)資源開(kāi)銷(xiāo)還是 CPU 使用效率都比 Apache 要好很多。當(dāng)你打開(kāi) Nginx 官方網(wǎng)站,你會(huì)發(fā)現(xiàn)一個(gè)如此知名的 Web 服務(wù)器產(chǎn)品的官方主頁(yè)竟然如此簡(jiǎn)單,其實(shí),Nginx 本身就是一個(gè)安裝非常簡(jiǎn)單,配置文件非常簡(jiǎn)潔,甚至可以在配置文件中使用 Perl 語(yǔ)法。
在處理靜態(tài)文件上, Apache 和 Nginx 都可以勝任。但對(duì)于應(yīng)用服務(wù)器或者是前端的負(fù)載均衡服務(wù)器,我們推薦 Nginx 而不是相對(duì) Nginx 較為重量級(jí)的 Apache。
對(duì)于應(yīng)用服務(wù)器,Mongrel 一度是最流行的部署方式,它的 HTTP 協(xié)議的解析部分是用 C 語(yǔ)言編寫(xiě)的,效率上有所保證。Mongrel 使用了 Ruby 的用戶線程機(jī)制來(lái)實(shí)現(xiàn)多線程并發(fā) , 但是 Ruby 并不是本地線程 ,Rails 也不是線程安全的,因此 Mongrel 在執(zhí)行 Rails 代碼的過(guò)程中,完全是加鎖的狀態(tài),那和單進(jìn)程其實(shí)也沒(méi)有太大差別。所有我們?cè)谑褂?Mongrel 來(lái)部署 Rails 應(yīng)用程序時(shí),一般是在后臺(tái)啟動(dòng)一個(gè) mongrel_cluster 來(lái)啟動(dòng)多個(gè) Mongrel 進(jìn)程,如我們?cè)?mongrel_cluster.yml 中進(jìn)行如下配置可以在 8000~8009 端口啟動(dòng) 10 個(gè) Mongrel 進(jìn)程。
清單 1. Mongrel_cluster.yml 配置
---
cwd: /var/www/test_app
log_file: log/Mongrel.log
port: "8000"
environment: production
debug: false
pid_file: log/mongrel.pid
servers: 10
這樣的部署方式實(shí)際上也限制了 Mongrel 處理大并發(fā)應(yīng)用的能力,在處理大數(shù)據(jù)量請(qǐng)求的時(shí)候,Mongrel 進(jìn)程經(jīng)常被掛過(guò),但并發(fā)數(shù)一多,就會(huì)出現(xiàn)所有的 Mongrel 進(jìn)都會(huì)被掛過(guò)的情況,這樣前端的服務(wù)器便得不到返回,整個(gè) Web 應(yīng)用就陷入癱瘓。所以,Mongrel 并沒(méi)有被廣泛采用,很多網(wǎng)站寧愿堅(jiān)持使用最古老的 FastCGI 的部署方式也不使用 Mongrel 來(lái)部署應(yīng)用,而且 Mongrel 也停止更新很久,Mongrel 的開(kāi)發(fā)者已完全不再進(jìn)行 Mongrel 的開(kāi)發(fā)了。所以,Mongrel 并不是現(xiàn)在最推薦的 Rails 應(yīng)用服務(wù)器。
Passenger 是類(lèi)似于 mod_php 的 Rails 運(yùn)行環(huán)境,而不是 Mongrel 那樣是獨(dú)立的 Http 服務(wù)器。Passenger 對(duì)目前主流的 apache 和 Nginx 兩大 Web 服務(wù)器都有支持。Passenger 使用起來(lái)極其方便,而且它具有較高的性能,從 Passenger 官方網(wǎng)站公布的測(cè)試結(jié)果來(lái)看,Passenger 的性能要優(yōu)于 Mongrel 服務(wù)器,目前來(lái)說(shuō),Passenger 無(wú)疑是最好的選擇。Passenger 繼承了 Rails"不重復(fù)自己"的慣例,通過(guò) Passenger 部署應(yīng)用程序,你僅僅需要將 Rails 項(xiàng)目程序文件上傳到目標(biāo)服務(wù)器,甚至都不需要重啟服務(wù)器,非常簡(jiǎn)單。
要在 Nginx 環(huán)境下安裝運(yùn)行 Passenger, 你僅僅需要如下操作:
清單 2. 安裝 Passenger
gem install passenger
Passenger-install-nginx-module
下面的代碼展示了在 Nginx 上配置 Passenger:
清單 3. 在 Nginx 上配置 Passenger
http {
...
server {
listen 80;
server_name www.test.com;
root /var/www/test/public;
passenger_enabled on;
}
...
}
通過(guò) Nginx+Passenger 構(gòu)建 Ruby On Rails 的應(yīng)用服務(wù)器可以得到顯著的性能提升,同時(shí),還可以采用 Ruby Enterprise Edition 來(lái)提升 Ruby 本身的性能。這個(gè)版本也是由 Phusion 開(kāi)發(fā)的,采用 copy-on-write 的垃圾回收策略,并使用 tcmalloc 來(lái)改善內(nèi)存分配,他們的網(wǎng)站公布的數(shù)據(jù),使用 Ruby Enterprise Edition 能比普通的 Ruby 版本減少 33% 的內(nèi)存消耗。
Nginx+Passenger 就部署在一臺(tái)機(jī)器上的應(yīng)用服務(wù)器,但并發(fā)過(guò)大時(shí),一臺(tái)機(jī)器并不足以來(lái)提供這樣的處理能力,這個(gè)時(shí)候,一般會(huì)做負(fù)載均衡,這和通常的負(fù)載均衡策略并無(wú)二異,Nginx 在使用反向代理來(lái)實(shí)現(xiàn)負(fù)載均衡的能力上要強(qiáng)于 Apache 的反向代理模塊,我們可以在多臺(tái) Nginx+Passenger 的前端可以再增加一臺(tái) Nginx 的 Web 服務(wù)器。甚至為了更強(qiáng)的處理能力,我們也可以采用 LVS 來(lái)進(jìn)行負(fù)載均衡。
使用 Starling 和 Workling 去異步執(zhí)行 Rails 后臺(tái)任務(wù)
在進(jìn)行 Web 應(yīng)用開(kāi)發(fā)時(shí),每個(gè) Web 請(qǐng)求都需要迅速的得到返回,這時(shí)候有些計(jì)算量比較大的操作可以放在后臺(tái)去異步執(zhí)行,比如大量的數(shù)據(jù)更新操作,只需要在 Web 應(yīng)用中進(jìn)行觸發(fā),然后在后臺(tái)進(jìn)行執(zhí)行。而有些操作則需要定期去執(zhí)行,比如數(shù)據(jù)的備份,一些數(shù)據(jù)的統(tǒng)計(jì)分析,圖片的處理,郵件的發(fā)送等等。這些操作如果放在 Web 應(yīng)用中即時(shí)返回顯然是不合適的,而且也會(huì)帶來(lái)機(jī)器的負(fù)載很?chē)?yán)重,對(duì)于 Rails 應(yīng)用程序來(lái)說(shuō),除了影響用戶體驗(yàn),這樣的操作還會(huì)阻塞 Rails 服務(wù)器實(shí)例,從而帶來(lái)整體性能的下降。對(duì)于這類(lèi)操作,我們可以使用一個(gè)任務(wù)隊(duì)列,將需要執(zhí)行的操作依次入隊(duì),然后在后臺(tái)再啟動(dòng)進(jìn)程進(jìn)隊(duì)列中取出這些任務(wù),并執(zhí)行,隊(duì)列可以使用數(shù)據(jù)庫(kù),Memcached, ActiveMQ 或者 Amazon SQS 來(lái)實(shí)現(xiàn),而后端進(jìn)程則可以使用 Rails 里面的 cronjob+script/runner 或者 BackgrounDRb 等等來(lái)操作。這里要介紹的解決方案則是采用 twitter 開(kāi)發(fā)人員貢獻(xiàn)出來(lái)的采用 Memcache 協(xié)議的 Starling 消息隊(duì)列和 Workling 插件來(lái)進(jìn)行實(shí)現(xiàn)。
Starling 是 twitter 開(kāi)發(fā)團(tuán)隊(duì)從 twitter 項(xiàng)目抽象出來(lái)開(kāi)源的 Rails 插件,雖然說(shuō) Starling 并不完全就是 twitter 的線上版本所用的插件,但我們也可以足以相信 Starling 的性能和應(yīng)對(duì)高并發(fā)的處理能力。類(lèi)似的插件還有 backgroundrb,background job, background_fu。backgroundrb 是使用 drb 實(shí)現(xiàn)隊(duì)列的消息傳遞,但它還有一個(gè)問(wèn)題,更新隊(duì)列的時(shí)候,backgroundrb 使用的是悲觀鎖,在大訪問(wèn)量的情況下,這種情況是不容允許的。 background job 和 background_fu 則是基于數(shù)據(jù)庫(kù)的消息隊(duì)列,在大負(fù)載量的情況下數(shù)據(jù)庫(kù)的性能也不容易得到保證。而 Starling 是基于 Memcached 協(xié)議的消息隊(duì)列,效率更高,也更容易伸縮,通常你可以在每臺(tái)應(yīng)用服務(wù)器上都運(yùn)行一個(gè) Starling 服務(wù)器,并在同一臺(tái)機(jī)器或者其它機(jī)器上去運(yùn)行后臺(tái)程序與之交互。
我們通過(guò)如下命令來(lái)安裝 Starling:
清單 4. 安裝 Starling
gem sources -a http://gems.github.com/
sudo gem install starling-starling
mkdir /var/spool/starling
在讀取 Starling Server 時(shí),我們需要 memcache-client,這個(gè) gem 的 1.5.0 版本有一些問(wèn)題,在 fiveruns-memcache-client 得到修正,這個(gè) fiveruns-memcache-client gem 在 starling-starling gem 中是作為依賴項(xiàng)自動(dòng)安裝的。
安裝完 Starling 之后,使用 sudo Starling -d -p 15151 這個(gè)命令來(lái)啟動(dòng)它,啟動(dòng)時(shí)用 -p 參數(shù)來(lái)指定所要使用的端口,一般加 -d 參數(shù)使它以 daemon 方式在后臺(tái)運(yùn)行:
為了明白 Starling 的機(jī)制和 Starling 究竟做了哪些工作,在啟動(dòng)了 Starling 之后,我們可以使用我們打開(kāi) irb 下面的程序來(lái)進(jìn)行簡(jiǎn)單的測(cè)試:
進(jìn)行簡(jiǎn)單的測(cè)試
清單 5. 測(cè)試 Starling
>> require 'starling'
=> true
>> Starling = Starling.new('127.0.0.1:15151)
=> MemCache: 1 servers, ns: nil, ro: false
>> Starling.set('test_queue', 123)
=> nil
>> loop { puts Starling.get('test_queue'); sleep 1 }
123
nil
nil
...
這里我們可以看到確實(shí)啟動(dòng)了 Server,然后我們向這里插入數(shù)據(jù),我們用一個(gè)循環(huán)去訪問(wèn)這個(gè)隊(duì)列,最后的輸出便是我們想要的結(jié)果。
接下來(lái)我們安裝 workling:
清單 6. 安裝 workling
script/plugin install git://github.com/purzelrakete/workling.git
Workling 支持多種方式來(lái)進(jìn)行后臺(tái)任務(wù)操作,其中就包括上面已經(jīng)安裝的 Starling,安裝好 Starling 后,我們需要在 Rails 應(yīng)用程序中的 environment.rb 加上以下代碼來(lái)配置 Workling 使用 Starling:
清單 7. 使用 workling
Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
Workling 的配置文件在 workling.yml, 和其它 Rails 的配置文件類(lèi)似,workling.yml 也可以針對(duì)不同產(chǎn)品模式進(jìn)行不同的模式,這里僅列出 production 的配置。
清單 8. Workling 配置
production:
listens_on:localhost:15151, localhost:15152, localhost:15153
sleep_time: 2
reset_time: 30
memcache_options:
namespace: myapp
listens_on 參數(shù)即為 workling_client 去訪問(wèn)的 Starling 啟動(dòng)的地址和端口,這里可以允許多個(gè) Starling 地址,這就意味著你啟動(dòng)多個(gè) Starling 服務(wù)器,而用一個(gè) workling_client 去調(diào)用。sleep_time 即為 workling_client 去隊(duì)列中取數(shù)據(jù)的等待時(shí)間,reset_time 則定義了如果出現(xiàn) memcache 錯(cuò)誤時(shí),workling_client 等待去重建和服務(wù)器連接的時(shí)間。 在 memcache_options 的 namespace 參數(shù)則定義了所使用的命名空間,這在同一臺(tái)服務(wù)器如果為不同的 Rails 應(yīng)用啟動(dòng)同一個(gè) Starling 服務(wù)器時(shí)是非常有用的。
用 script/workling_client start 腳本便可以啟動(dòng) workling_client 進(jìn)程,有時(shí)候我們覺(jué)得一個(gè) workling_client 不夠用,我們可以修改 script/workling_client start 來(lái)支持多個(gè) workling_client 實(shí)例,這樣每運(yùn)行 script/workling_client start 一次都會(huì)新啟動(dòng)一個(gè) workling_client 實(shí)例。
清單 9. 多 client 的 Workling 配置
options = {
:app_name => "workling",
:ARGV => ARGV,
:dir_mode => :normal,
:dir => File.join(File.dirname(__FILE__), '..', 'log'),
:log_output => true,
:multiple => true,
:backtrace => true,
:monitor => true
}
使用 Memcached 和 cache-money 來(lái)緩存數(shù)據(jù)
Rails 自身提供四種緩存方式,即 Page Cache, Action Cache,F(xiàn)ragment Cache 和 ActiveRecord Cache 這三種緩存。Page Cache 是最高效的緩存機(jī)制,他把整個(gè)頁(yè)面以靜態(tài)頁(yè)面 HTML 的形式進(jìn)行緩存,這對(duì)于不會(huì)經(jīng)常發(fā)生變化的頁(yè)面是非常有效的。Action Cache 是對(duì)某個(gè) action 進(jìn)行緩存,與 Page Cache 的區(qū)別在于:HTTP 請(qǐng)求會(huì)經(jīng)過(guò) Rails 應(yīng)用服務(wù)器,直到所有的 before filters 都被處理,這種緩存就能處理 Page Cache 無(wú)法處理的如需要登錄驗(yàn)證的頁(yè)面,可以所驗(yàn)證的步驟加入 before filter 中,F(xiàn)ragment Cache 則為了緩存頁(yè)面中的某一部分,同一個(gè)頁(yè)面中的不同部分還可以采用不同的過(guò)期策略。對(duì)于 Rails 本身的緩存機(jī)制,我們可以寫(xiě) sweeper 進(jìn)行過(guò)期和清除的處理。ActiveRecord Cache 則是較新版本 Rails 中新推出的對(duì) ActiveRecord 的緩存機(jī)制,使用 SQL 查詢緩存,對(duì)于同一 action 里面同一 SQL 語(yǔ)句的數(shù)據(jù)庫(kù)操作會(huì)使用緩存。
Rails 的緩存機(jī)制能非常有效的提升網(wǎng)站性能,Rails 默認(rèn)是將緩存存在于文件系統(tǒng)中,這并不是適合生產(chǎn)環(huán)境下的存儲(chǔ)方式,文件 IO 的效率有限,Rails 還支持在同一進(jìn)程的內(nèi)存中保存 Cache,但如果有多個(gè) Rails application,它們之間不能共享緩存。我們這里推薦的是以 MemCached 的方式進(jìn)行存儲(chǔ),這也是目前是流行的緩存存儲(chǔ)方式。
Memcached 是由 Danga Interactive 開(kāi)發(fā),用于提升 LiveJournal.com 訪問(wèn)速度的。LiveJournal.com 每秒有幾千次動(dòng)態(tài)頁(yè)面訪問(wèn)量,用戶 700 萬(wàn)。Memcached 是一個(gè)具有極高性能的分布式內(nèi)存對(duì)象緩存系統(tǒng) , 基于一個(gè)存儲(chǔ)鍵 / 值對(duì)的哈希表。其守護(hù)進(jìn)程(daemon)是用 C 寫(xiě)的 , Memcached 將數(shù)據(jù)庫(kù)負(fù)載大幅度降低,更好的分配資源,更快速訪問(wèn)??梢杂酶鞣N其它語(yǔ)言去實(shí)現(xiàn)客戶端。上文的介紹中已經(jīng)安裝了 Rails 的 Memcached 客戶端,因?yàn)槲覀冎恍枰?Rails 應(yīng)用程序中做如下配置:
清單 10. Memcached 配置
config.cache_store = :mem_cache_store, 'localhost:11211'
便可以進(jìn)行使用 MemCached 進(jìn)行緩存數(shù)據(jù)。除了 Rails 本身的緩存機(jī)制,我們還直接用 Rails.cache 操作 Memcached 進(jìn)行數(shù)據(jù)緩存,如,我們讀取所有 blog 的數(shù)量,可以如下使用緩存:
清單 11. 使用 Rails.cache
blogs_count = Rails.cache.fetch("blogs_count") do
Blog.count
end
Rails 自身的 ActiveRecord 作用有限,只適用同一個(gè) action 中的 SQL 查詢語(yǔ)句進(jìn)行緩存,我們需要一個(gè)更強(qiáng)大的 ActiveRecord 緩存,而 cache-money 更是為了解決如此問(wèn)題而推出的。當(dāng) twitter 網(wǎng)站變得越來(lái)越穩(wěn)定,逐漸擺脫被人拿來(lái)作為"Rails 無(wú)法擴(kuò)展的"典型例子的陰影時(shí),人們便期待 twitter 開(kāi)發(fā)團(tuán)隊(duì)能向 Rails 社區(qū)有更多的貢獻(xiàn),cache-money 便是在 Starling 之后 twitter 團(tuán)隊(duì)貢獻(xiàn)出來(lái)的另一個(gè)插件。cache-money 和 Hibernate 的二級(jí)緩存類(lèi)似,是一個(gè)讀寫(xiě)式(write-through)緩存。在 ActiveRecord 對(duì)象更新的時(shí)候不是將緩存中的數(shù)據(jù)清除,而是直接將更新的內(nèi)容寫(xiě)入到緩存中去。
cache-money 有許多很棒的特性,如:緩存自動(dòng)清除機(jī)制 ( 利用 after_save/after_destroy) ;支持事務(wù),由于 Rails 的 Active Record 沒(méi)有提供 after_commit 機(jī)制,目前常見(jiàn)的緩存插件在高并發(fā)下會(huì)出現(xiàn)緩存更新競(jìng)爭(zhēng)沖突,而這個(gè)特性對(duì)于解決這個(gè)問(wèn)題會(huì)很有幫助,可以通過(guò) gem 來(lái)安裝 cache-money:
清單 12. 安裝 cache-money
gem sources -a http://gems.github.com
sudo gem install nkallen-cache-money
require 'cache_money'
清單 13. 配置 config/memcached.yml
production:
ttl: 604800
namespace: ...
sessions: false
debug: false
servers: localhost:11211
development:
....
清單 14. 使用 config/initializers/cache_money.rb 來(lái)初始化
config = YAML.load(IO.read(File.join(Rails_ROOT, "config", "Memcached.yml")))[Rails_ENV]
$memcache = MemCache.new(config)
$memcache.servers = config['servers']
$local = Cash::Local.new($memcache)
$lock = Cash::Lock.new($memcache)
$cache = Cash::Transactional.new($local, $lock)
class ActiveRecord::Base
is_cached :repository => $cache
end
使用 cache-money 非常方便,不需要額外的操作,只需要在你的 Model 里面進(jìn)行簡(jiǎn)單的配置,如:
清單 15. 配置 Model 來(lái)使用 cache_money
class User ActiveRecord::Base
index :name
end
class Post ActiveRecord::Base
index [:title, :author]
end
class Article ActiveRecord::Base
version 7
index ...
end
然后便可以跟以前一樣使用 Rails ActiveRecord 各種方法以及事務(wù)操作。如果你改變了數(shù)據(jù)庫(kù)的表結(jié)構(gòu),你可以改變 Model 的版本號(hào)來(lái)使以前的緩存失效,而不需要重啟 Memcached 服務(wù)器。
使用 Sphinx+LibMMSeg+Ultrasphinx 進(jìn)行全文搜索
很多應(yīng)用會(huì)有全文搜索的需求,當(dāng)然你可以直接集成 google 或者其它搜索引擎提供的搜索服務(wù),但如果你要更好的控制你的搜索結(jié)果,或者對(duì)你的搜索結(jié)果進(jìn)行再次利用,你恐怕必須得自己實(shí)現(xiàn)全文搜索了。在進(jìn)行中文全文搜索時(shí),一般要考慮兩個(gè)方面的問(wèn)題,即所使用搜索工具的性能問(wèn)題,以及中文分詞的準(zhǔn)備度。在 Java 的世界里,Lucene 是做全文搜索絕對(duì)的權(quán)威和首選,雖然它本身沒(méi)有對(duì)中文分詞很好的支持,但有很多第三方插件可以利用來(lái)提高中文分詞的準(zhǔn)備率和性能。Ferret 一度是最流行的 Rails 全文搜索插件,但本文推薦是效率更高的 Sphinx。Sphinx 是俄羅斯人 Andrew Aksyonoff 開(kāi)發(fā)的,這個(gè)詞的意思“獅身人面”,它能在一兩分鐘的時(shí)間內(nèi)完成數(shù)百萬(wàn)條記錄的索引,并在毫秒級(jí)的時(shí)間類(lèi)返回搜索結(jié)果。 Sphinx 和數(shù)據(jù)庫(kù)集成良好,可以通過(guò)配置文件,直接用來(lái)對(duì)數(shù)據(jù)庫(kù)的數(shù)據(jù)進(jìn)行索引,另外,Sphinx 開(kāi)發(fā)了一個(gè) SphinxSE 數(shù)據(jù)庫(kù)引擎,可以在編譯 Mysql 的時(shí)候直接編譯到 Mysql 里面去來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)級(jí)別的高效能索引。在 Rails 中使用 Sphinx 可以通過(guò) Ultrasphinx 插件,Rails 開(kāi)發(fā)人員可以使用它來(lái)很方便地調(diào)用 Sphinx 的功能。
可以從這里 http://www.sphinxsearch.com/downloads.html 下載 Sphinx
在安裝好 Sphinx 后可以直接從 Rubyforge 上安裝 Ultrasphinx:
清單 16. 安裝 ultrasphinx
Ruby script/plugin install svn://Rubyforge.org/var/svn/fauna/ultrasphinx/trunk
LibMMSeg 就是一個(gè)中文分詞程序,當(dāng)前最新版本是 0.7.3,采用 C++ 開(kāi)發(fā),分詞算法采用的是“復(fù)雜最大匹配 (Complex maximum matching)”,同時(shí)支持 Linux 平臺(tái)和 Windows 平臺(tái),切分速度大約在 300K/s(PM-1.2G),LibMMSeg 從 0.7.2 版本開(kāi)始,作者提供了 Ruby 調(diào)用的接口,所以我們可以直接在 Ruby 中用 LibMMSeg 進(jìn)行分詞,相當(dāng)方便。LibMMSeg 可以通過(guò) http://www.coreseek.cn/opensource/mmseg/ 來(lái)下載安裝。
用戶可以通過(guò)修改詞典文件增加自己的自定義詞,以提高分詞法在某一具體領(lǐng)域的切分精度,系統(tǒng)默認(rèn)的詞典文件在 data/unigram.txt中。 然后通過(guò) mmseg -u unigram.txt這個(gè)命令來(lái)產(chǎn)生一個(gè)名為 unigram.txt.uni的文件,將該文件改名為 uni.lib,完成詞典的構(gòu)造。需要注意的是,unigram.txt必須為 UTF-8 編碼。
LibMMSeg 的開(kāi)發(fā)者為了更好的讓 Sphinx 使用 LibMMSeg 進(jìn)行中文分詞,為 Sphinx 開(kāi)發(fā)了相關(guān)的補(bǔ)丁,從這里 http://www.coreseek.cn/opensource/Sphinx/ 下載兩個(gè)補(bǔ)丁文件:
http://www.coreseek.com/uploads/sources/sphinx-0.98rc2.zhcn-support.patch
http://www.coreseek.com/uploads/sources/fix-crash-in-excerpts.patch
然后打上補(bǔ)?。?br />
清單 17. 安裝 Sphinx 補(bǔ)丁
cd sphinx-0.9.8-rc2
patch -p1 ../sphinx-0.98rc2.zhcn-support.patch
patch -p1 ../fix-crash-in-excerpts.patch
安裝完這幾個(gè)插件和補(bǔ)丁之后,我們便可以進(jìn)行配置來(lái)讓 Rails 應(yīng)用程序來(lái)支持全文搜索了,
首先我們將 ultrasphinx 插件目錄下的 vendor/plugins/ultrasphinx/examples/default.base復(fù)制到:config/ultrasphinx/default.base,打開(kāi)這個(gè)文件,將其中的:
charset_type = utf-8改為:charset_type = zh_cn.utf-8來(lái)支持中文字符的全文檢索, 并且在 charset_type 設(shè)置的下面加入一行:
charset_dictpath = /home/test/Search/lib,這個(gè)就是上文講到的 uni.lib 字典所在的路徑,然后刪除所有 charset_table 相關(guān)的設(shè)置。
在 Rails 應(yīng)用程序中的 Model 代碼,加入全文檢索支持:
如有一個(gè) Model 為 Article,其中有兩個(gè)屬性叫做 title,body,我希望對(duì)這兩個(gè)屬性做全文檢索,便可以在 article.rb 中加入一行:
清單 18. 使用 ultrasphinx
is_indexed :fields => ['created_at','title', 'body']
進(jìn)行完這個(gè)配置后,我們可以使用 rake ultrasphinx:configure 這個(gè)命令來(lái)生成Sphinx 的配置文件,這條命令在 config/ultrasphinx 下創(chuàng)建了一個(gè) development.conf,這個(gè)文件就是 Sphinx 的配置文件。并用rake ultrasphinx:index 這個(gè)命令來(lái)創(chuàng)建索引。rake ultrasphinx:daemon:start 和 rake ultrasphinx:daemon:stop 則對(duì)應(yīng)著Sphinx 的searchd服務(wù)的啟動(dòng)和停止。searchd 會(huì)在 3313 端口啟動(dòng)一個(gè) searchd,搜索請(qǐng)求將會(huì)全部發(fā)送到這個(gè)端口來(lái)執(zhí)行。我們?cè)诳刂婆_(tái)中進(jìn)行簡(jiǎn)單的測(cè)試:
清單 19. 測(cè)試全文索引
search = Ultrasphinx::Search.new(:class_names => 'Article')
search.run
Search.results
一切運(yùn)行正常后,我們便可以在 action 的代碼中進(jìn)行全文搜索了。
使用 Capistrano 進(jìn)行快速部署
在進(jìn)行 Rails 部署的時(shí)候你可以直接從 svn 或者 git 下面更新代碼,運(yùn)行 db:migrate 來(lái)進(jìn)行數(shù)據(jù)庫(kù)的更新,然后進(jìn)行這樣那樣的操作后,再啟動(dòng)服務(wù)器,便可進(jìn)行部署,即便你只有一臺(tái)機(jī)器,你也會(huì)覺(jué)得太麻煩,如果你需要多臺(tái)機(jī)器來(lái)運(yùn)行,那你可能就會(huì)覺(jué)得每次手工部署都是一場(chǎng)惡夢(mèng),你可以使用 shell 腳本來(lái)簡(jiǎn)化部署的程序。在用 Rails 開(kāi)發(fā)應(yīng)用時(shí),你可以使用 Capistrano 插件來(lái)進(jìn)行更簡(jiǎn)單的部署工作。簡(jiǎn)單來(lái)說(shuō),Capistrano 是一個(gè)通過(guò) SSH 并行的在多臺(tái)機(jī)器上執(zhí)行相同命令的工具,使用用來(lái)安裝一整批機(jī)器。 它通過(guò)一個(gè)個(gè)已有的和用戶自定制的任務(wù)讓部署過(guò)程簡(jiǎn)單化。
清單 20. 安裝 Capistrano
gem sources -a http://gems.github.com/
gem install Capistrano
然后在 config/deploy.rb 中配置要部署的服務(wù)器的地址,各種服務(wù)器的角色以及每個(gè)服務(wù)器統(tǒng)一的用戶名和密碼,如下面的樣例配置:
清單 21. 配置 Capistrano
set :application, "test_app" # 應(yīng)用的名稱(chēng)
set :scm_username, "test" # 資源庫(kù)的用戶名
set :scm_password, 'test' # 資源庫(kù)的密碼
set :repository, Proc.new {"--username #{scm_username}
--password #{scm_password} svn://localhost/test_app/trunk"}
# 資源庫(kù)
set :user, "test" # 服務(wù)器 SSH 用戶名
set :password, 'test' # 服務(wù)器 SSH 密碼
set :deploy_to, "/var/www/#{application} "
# 在服務(wù)器上的部署路徑,默認(rèn)的部署路徑是 /u/apps/#{application}
role :Web, 'Web.test_app.com' # 前端 Web 服務(wù)器
role :app, 'app1.test_app.com', 'app2.test_app.com', 'app3.test_app.com' #Rails 應(yīng)用服務(wù)器
role :db, 'app1.test_app.com', :primary => true
# 運(yùn)行 migrate 腳本的機(jī)器,通常為其中一臺(tái)應(yīng)用服務(wù)器。
在使用 Capistrano 進(jìn)行部署的時(shí)候,通常是這樣使用 cap sometask來(lái)運(yùn)行任務(wù)。你可以先用 cap -h查看所有的選項(xiàng),并用 cap -T查看現(xiàn)有的所有任務(wù)。如 cap migrate則在 role 為 db 的機(jī)器上執(zhí)行 rake db:migrate命令。使用 Capistrano 的更多資料可以參考 http://wiki.capify.org 這個(gè)網(wǎng)站。另外,Capistrano 還可以使用在非 Rails 環(huán)境下進(jìn)行自動(dòng)部署,在配置好 ruby 環(huán)境和 Capistrano 插件后,再安裝下面的插件即可:
清單 22. 非 Rails 環(huán)境使用 Capistrano
gem sources -a http://gems.github.com/
gem install leehambley-railsless-deploy
結(jié)束語(yǔ)
本文著重使用 Ruby On Rails 來(lái)開(kāi)發(fā)和部署 Web 應(yīng)用時(shí)一些有用的具體實(shí)踐,沒(méi)有具體去介紹一些通常應(yīng)用程序都需要面對(duì)的普遍問(wèn)題,如數(shù)據(jù)庫(kù)的優(yōu)化和分布式部署,這是一個(gè)大并發(fā)的 Web 應(yīng)用都需要面對(duì)和解決的問(wèn)題,比如可以采用 master-slave 的方式去部署分布式的數(shù)據(jù)庫(kù),或者采用分庫(kù)或者分表的方式對(duì)數(shù)據(jù)庫(kù)進(jìn)行拆分。另外,在運(yùn)行 Rails 服務(wù)器或者其它后臺(tái)應(yīng)用程序時(shí),通過(guò)還需要另外的進(jìn)程去進(jìn)行監(jiān)控,如用 God 去監(jiān)控 Rails 進(jìn)程也是一個(gè) Rails 應(yīng)用通常都會(huì)采用的策略。另外,很多時(shí)候,可以采用更敏捷更輕量級(jí)的 Rack 去代替 Rails 來(lái)進(jìn)行更高效的開(kāi)發(fā)的提供服務(wù)。并且,Engineyard ,Joyent 以及 Heroku 等這類(lèi) Rails 網(wǎng)絡(luò)提供商的涌現(xiàn)也在相當(dāng)程度上堅(jiān)定了用 Rails 開(kāi)發(fā)和部署大規(guī)模大并發(fā) Web 應(yīng)用的信心。雖然 Ruby On Rails 自身的缺陷不可避免, 但是開(kāi)發(fā)可伸縮的高性能的應(yīng)用程序并不是不可能的。本文希望能夠幫助 Rails 開(kāi)發(fā)人員快速掌握一些具體實(shí)踐,能夠編寫(xiě)出并部署性能高伸縮性強(qiáng)的 Web 應(yīng)用程序。
您可能感興趣的文章:- 在Ruby on Rails中使用AJAX的教程
- 使用Ruby on Rails快速開(kāi)發(fā)web應(yīng)用的教程實(shí)例
- 詳細(xì)解析Ruby中的變量