日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Web應(yīng)用的緩存設(shè)計模式

ORM緩存引言

創(chuàng)新互聯(lián)公司是一家以網(wǎng)絡(luò)技術(shù)公司,為中小企業(yè)提供網(wǎng)站維護、網(wǎng)站設(shè)計、成都網(wǎng)站設(shè)計、網(wǎng)站備案、服務(wù)器租用、空間域名、軟件開發(fā)、小程序制作等企業(yè)互聯(lián)網(wǎng)相關(guān)業(yè)務(wù),是一家有著豐富的互聯(lián)網(wǎng)運營推廣經(jīng)驗的科技公司,有著多年的網(wǎng)站建站經(jīng)驗,致力于幫助中小企業(yè)在互聯(lián)網(wǎng)讓打出自已的品牌和口碑,讓企業(yè)在互聯(lián)網(wǎng)上打開一個面向全國乃至全球的業(yè)務(wù)窗口:建站聯(lián)系熱線:18982081108

從10年前的2003年開始,在Web應(yīng)用領(lǐng)域,ORM(對象-關(guān)系映射)框架就開始逐漸普及,并且流行開來,其中最廣為人知的就是Java的開源ORM框架Hibernate,后來Hibernate也成為了EJB3的實現(xiàn)框架;2005年以后,ORM開始普及到其他編程語言領(lǐng)域,其中最有名氣的是Ruby on rails框架的ORM - ActiveRecord。如今各種開源框架的ORM,乃至ODM(對象-文檔關(guān)系映射,用在訪問NoSQLDB)層出不窮,功能都十分強大,也很普及。

然而圍繞ORM的性能問題,也一直有很多批評的聲音。其實ORM的架構(gòu)對插入緩存技術(shù)是非常容易的,我做的很多項目和產(chǎn)品,但凡使用ORM,緩存都是標(biāo)配,性能都非常好。而且我發(fā)現(xiàn)業(yè)界使用ORM的案例都忽視了緩存的運用,或者說沒有意識到ORM緩存可以帶來巨大的性能提升。

ORM緩存應(yīng)用案例

我們?nèi)ツ暧幸粋€老產(chǎn)品重寫的項目,這個產(chǎn)品有超過10年歷史了,數(shù)據(jù)庫的數(shù)據(jù)量很大,多個表都是上千萬條記錄,最大的表記錄達到了9000萬條,Web訪問的請求數(shù)每天有300萬左右。

老產(chǎn)品采用了傳統(tǒng)的解決性能問題的方案:Web層采用了動態(tài)頁面靜態(tài)化技術(shù),超過一定時間的文章生成靜態(tài)HTML文件;對數(shù)據(jù)庫進行分庫分表,按年拆表。動態(tài)頁面靜態(tài)化和分庫分表是應(yīng)對大訪問量和大數(shù)據(jù)量的常規(guī)手段,本身也有效。但它的缺點也很多,比方說增加了代碼復(fù)雜度和維護難度,跨庫運算的困難等等,這個產(chǎn)品的代碼維護歷來非常困難,導(dǎo)致bug很多。

進行產(chǎn)品重寫的時候,我們放棄了動態(tài)頁面靜態(tài)化,采用了純動態(tài)網(wǎng)頁;放棄了分庫分表,直接操作千萬級,乃至近億條記錄的大表進行SQL查詢;也沒有采取讀寫分離技術(shù),全部查詢都是在單臺主數(shù)據(jù)庫上進行;數(shù)據(jù)庫訪問全部使用ActiveRecord,進行了大量的ORM緩存。上線以后的效果非常好:單臺MySQL數(shù)據(jù)庫服務(wù)器CPU的IO Wait低于5%;用單臺1U服務(wù)器2顆4核至強CPU已經(jīng)可以輕松支持每天350萬動態(tài)請求量;最重要的是,插入緩存并不需要代碼增加多少復(fù)雜度,可維護性非常好。

總之,采用ORM緩存是Web應(yīng)用提升性能一種有效的思路,這種思路和傳統(tǒng)的提升性能的解決方案有很大的不同,但它在很多應(yīng)用場景(包括高度動態(tài)化的SNS類型應(yīng)用)非常有效,而且不會顯著增加代碼復(fù)雜度,所以這也是我自己一直偏愛的方式。因此我一直很想寫篇文章,結(jié)合示例代碼介紹ORM緩存的編程技巧。

今年春節(jié)前后,我開發(fā)自己的個人網(wǎng)站項目,有意識的大量使用了ORM緩存技巧。對一個沒多少訪問量的個人站點來說,有些過度設(shè)計了,但我也想借這個機會把常用的ORM緩存設(shè)計模式寫成示例代碼,提供給大家參考。我的個人網(wǎng)站源代碼是開源的,托管在github上:robbin_site

ORM緩存的基本理念

·我在2007年的時候?qū)戇^一篇文章,分析ORM緩存的理念:ORM對象緩存探討 ,所以這篇文章不展開詳談了,總結(jié)來說,ORM緩存的基本理念是:

·以減少數(shù)據(jù)庫服務(wù)器磁盤IO為最終目的,而不是減少發(fā)送到數(shù)據(jù)庫的SQL條數(shù)。實際上使用ORM,會顯著增加SQL條數(shù),有時候會成倍增加SQL。

·數(shù)據(jù)庫schema設(shè)計的取向是盡量設(shè)計 細顆粒度 的表,表和表之間用外鍵關(guān)聯(lián),顆粒度越細,緩存對象的單位越小,緩存的應(yīng)用場景越廣泛

盡量避免多表關(guān)聯(lián)查詢,盡量拆成多個表單獨的主鍵查詢,盡量多制造 n + 1 條查詢,不要害怕“臭名昭著”的 n + 1 問題,實際上 n + 1 才能有效利用ORM緩存

利用表關(guān)聯(lián)實現(xiàn)透明的對象緩存

在設(shè)計數(shù)據(jù)庫的schema的時候,設(shè)計多個細顆粒度的表,用外鍵關(guān)聯(lián)起來。當(dāng)通過ORM訪問關(guān)聯(lián)對象的時候,ORM框架會將關(guān)聯(lián)對象的訪問轉(zhuǎn)化成用主鍵查詢關(guān)聯(lián)表,發(fā)送 n + 1條SQL。而基于主鍵的查詢可以直接利用對象緩存。

我們自己開發(fā)了一個基于ActiveRecord封裝的對象緩存框架:second_level_cache ,從這個ruby插件的名稱就可以看出,實現(xiàn)借鑒了Hibernate的二級緩存實現(xiàn)。這個對象緩存的配置和使用,可以看我寫的ActiveRecord對象緩存配置 。

下面用一個實際例子來演示一下對象緩存起到的作用:訪問我個人站點的首頁。 這個頁面的數(shù)據(jù)需要讀取三張表:blogs表獲取文章信息,blog_contents表獲取文章內(nèi)容,accounts表獲取作者信息。三張表的model定義片段如下,完整代碼請看models :

 
 
 
  1. class Account < ActiveRecord::Base  
  2.   acts_as_cached  
  3.   has_many :blogs  
  4. end 
  5.  
  6. class Blog < ActiveRecord::Base  
  7.   acts_as_cached  
  8.   belongs_to :blog_content, :dependent => :destroy   
  9.   belongs_to :account, :counter_cache => true 
  10. end 
  11.  
  12. class BlogContent < ActiveRecord::Base  
  13.   acts_as_cached  
  14. end 

傳統(tǒng)的做法是發(fā)送一條三表關(guān)聯(lián)的查詢語句,類似這樣的:

 
 
 
  1. SELECT blogs.*, blog_contents.content, account.name   
  2.     FROM blogs   
  3.     LEFT JOIN blog_contents ON blogs.blog_content_id = blog_contents.id   
  4.     LEFT JOIN accounts ON blogs.account_id = account.id  

往往單條SQL語句就搞定了,但是復(fù)雜SQL的帶來的表掃描范圍可能比較大,造成的數(shù)據(jù)庫服務(wù)器磁盤IO會高很多,數(shù)據(jù)庫實際IO負載往往無法得到有效緩解。

我的做法如下,完整代碼請看home.rb :

 
 
 
  1. @blogs = Blog.order('id DESC').page(params[:page]) 

這是一條分頁查詢,實際發(fā)送的SQL如下:

 
 
 
  1. SELECT * FROM blogs ORDER BY id DESC LIMIT 20 

轉(zhuǎn)成了單表查詢,磁盤IO會小很多。至于文章內(nèi)容,則是通過blog.content的對象訪問獲得的,由于首頁抓取20篇文章,所以實際上會多出來20條主鍵查詢SQL訪問blog_contents表。就像下面這樣:

 
 
 
  1. DEBUG -  BlogContent Load (0.3ms)  SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 29 LIMIT 1  
  2. DEBUG -  BlogContent Load (0.2ms)  SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 28 LIMIT 1  
  3. DEBUG -  BlogContent Load (1.3ms)  SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 27 LIMIT 1  
  4. ......  
  5. DEBUG -  BlogContent Load (0.9ms)  SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 10 LIMIT 1 

但是主鍵查詢SQL不會造成表的掃描,而且往往已經(jīng)被數(shù)據(jù)庫buffer緩存,所以基本不會發(fā)生數(shù)據(jù)庫服務(wù)器的磁盤IO,因而總體的數(shù)據(jù)庫IO負載會遠遠小于前者的多表聯(lián)合查詢。特別是當(dāng)使用對象緩存之后,會緩存所有主鍵查詢語句,這20條SQL語句往往并不會全部發(fā)生,特別是熱點數(shù)據(jù),緩存命中率很高:

 
 
 
  1. DEBUG -  Cache read: robbin/blog/29/1  
  2. DEBUG -  Cache read: robbin/account/1/0  
  3. DEBUG -  Cache read: robbin/blogcontent/29/0  
  4. DEBUG -  Cache read: robbin/account/1/0  
  5. DEBUG -  Cache read: robbin/blog/28/1  
  6. ......  
  7. DEBUG -  Cache read: robbin/blogcontent/11/0  
  8. DEBUG -  Cache read: robbin/account/1/0  
  9. DEBUG -  Cache read: robbin/blog/10/1  
  10. DEBUG -  Cache read: robbin/blogcontent/10/0  
  11. DEBUG -  Cache read: robbin/account/1/0  

拆分n+1條查詢的方式,看起來似乎非常違反大家的直覺,但實際上這是真理,我實踐經(jīng)驗證明:數(shù)據(jù)庫服務(wù)器的瓶頸往往是磁盤IO,而不是SQL并發(fā)數(shù)量。因此 拆分n+1條查詢本質(zhì)上是以增加n條SQL語句為代價,簡化復(fù)雜SQL,換取數(shù)據(jù)庫服務(wù)器磁盤IO的降低 當(dāng)然這樣做以后,對于ORM來說,有額外的好處,就是可以高效的使用緩存了。

#p#

按照column拆表實現(xiàn)細粒度對象緩存

數(shù)據(jù)庫的瓶頸往往在磁盤IO上,所以應(yīng)該盡量避免對大表的掃描。傳統(tǒng)的拆表是按照row去拆分,保持表的體積不會過大,但是缺點是造成應(yīng)用代碼復(fù)雜度很高;使用ORM緩存的辦法,則是按照column進行拆表,原則一般是:

·將大字段拆分出來,放在一個單獨的表里面,表只有主鍵和大字段,外鍵放在主表當(dāng)中

·將不參與where條件和統(tǒng)計查詢的字段拆分出來,放在獨立的表中,外鍵放在主表當(dāng)中

按照column拆表本質(zhì)上是一個去關(guān)系化的過程。主表只保留參與關(guān)系運算的字段,將非關(guān)系型的字段剝離到關(guān)聯(lián)表當(dāng)中,關(guān)聯(lián)表僅允許主鍵查詢,以Key-Value DB的方式來訪問。因此這種緩存設(shè)計模式本質(zhì)上是一種SQLDB和NoSQLDB的混合架構(gòu)設(shè)計

下面看一個實際的例子:文章的內(nèi)容content字段是一個大字段,該字段不能放在blogs表中,否則會造成blogs表過大,表掃描造成較多的磁盤IO。我實際做法是創(chuàng)建blog_contents表,保存content字段,schema簡化定義如下:

 
 
 
  1. CREATE TABLE `blogs` (  
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,  
  3.   `title` varchar(255) NOT NULL,  
  4.   `blog_content_id` int(11) NOT NULL,  
  5.   `content_updated_at` datetime DEFAULT NULL,  
  6.   PRIMARY KEY (`id`),  
  7. );  
  8.  
  9. CREATE TABLE `blog_contents` (  
  10.   `id` int(11) NOT NULL AUTO_INCREMENT,  
  11.   `content` mediumtext NOT NULL,  
  12.   PRIMARY KEY (`id`)  
  13. ); 

blog_contents表只有content大字段,其外鍵保存到主表blogs的blog_content_id字段里面。

model定義和相關(guān)的封裝如下:

 
 
 
  1. class Blog < ActiveRecord::Base  
  2.   acts_as_cached  
  3.   delegate :content, :to => :blog_content, :allow_nil => true 
  4.  
  5.   def content=(value)  
  6.     self.blog_content ||= BlogContent.new  
  7.     self.blog_content.content = value  
  8.     self.content_updated_at = Time.now  
  9.   end 
  10. end 
  11.  
  12. class BlogContent < ActiveRecord::Base  
  13.   acts_as_cached  
  14.   validates :content, :presence => true 
  15. end      

在Blog類上定義了虛擬屬性content,當(dāng)訪問blog.content的時候,實際上會發(fā)生一條主鍵查詢的SQL語句,獲取blog_content.content內(nèi)容。由于BlogContent上面定義了對象緩存acts_as_cached,只要被訪問過一次,content內(nèi)容就會被緩存到memcached里面。

這種緩存技術(shù)實際會非常有效,因為: 只要緩存足夠大,所有文章內(nèi)容可以全部被加載到緩存當(dāng)中,無論文章內(nèi)容表有多么大,你都不需要再訪問數(shù)據(jù)庫了 更進一步的是: 這張大表你永遠都只需要通過主鍵進行訪問,絕無可能出現(xiàn)表掃描的狀況 為何當(dāng)數(shù)據(jù)量大到9000萬條記錄以后,我們的系統(tǒng)仍然能夠保持良好的性能,秘密就在于此。

還有一點非常重要: 使用以上兩種對象緩存的設(shè)計模式,你除了需要添加一條緩存聲明語句acts_as_cached以外,不需要顯式編寫一行代碼 有效利用緩存的代價如此之低,何樂而不為呢?

以上兩種緩存設(shè)計模式都不需要顯式編寫緩存代碼,以下的緩存設(shè)計模式則需要編寫少量的緩存代碼,不過代碼的增加量非常少。

寫一致性緩存

寫一致性緩存,叫做write-through cache,是一個CPU Cache借鑒過來的概念,意思是說,當(dāng)數(shù)據(jù)庫記錄被修改以后,同時更新緩存,不必進行額外的緩存過期處理操作。但在應(yīng)用系統(tǒng)中,我們需要一點技巧來實現(xiàn)寫一致性緩存。來看一個例子:

我的網(wǎng)站文章原文是markdown格式的,當(dāng)頁面顯示的時候,需要轉(zhuǎn)換成html的頁面,這個轉(zhuǎn)換過程本身是非常消耗CPU的,我使用的是Github的markdown的庫。Github為了提高性能,用C寫了轉(zhuǎn)換庫,但如果是非常大的文章,仍然是一個耗時的過程,Ruby應(yīng)用服務(wù)器的負載就會比較高。

我的解決辦法是緩存markdown原文轉(zhuǎn)換好的html頁面的內(nèi)容,這樣當(dāng)再次訪問該頁面的時候,就不必再次轉(zhuǎn)換了,直接從緩存當(dāng)中取出已經(jīng)緩存好的頁面內(nèi)容即可,極大提升了系統(tǒng)性能。我的網(wǎng)站文章最終頁的代碼執(zhí)行時間開銷往往小于10ms,就是這個原因。代碼如下:

 
 
 
  1. def md_content  # cached markdown format blog content  
  2.   APP_CACHE.fetch(content_cache_key) { GitHub::Markdown.to_html(content, :gfm) }  
  3. end 

這里存在一個如何進行緩存過期的問題,當(dāng)文章內(nèi)容被修改以后,應(yīng)該更新緩存內(nèi)容,讓老的緩存過期,否則就會出現(xiàn)數(shù)據(jù)不一致的現(xiàn)象。進行緩存過期處理是比較麻煩的,我們可以利用一個技巧來實現(xiàn)自動緩存過期:

 
 
 
  1. def content_cache_key  
  2.   "#{CACHE_PREFIX}/blog_content/#{self.id}/#{content_updated_at.to_i}" 
  3. end 

當(dāng)構(gòu)造緩存對象的key的時候,我用文章內(nèi)容被更新的時間來構(gòu)造key值,這個文章內(nèi)容更新時間用的是blogs表的content_updated_at字段,當(dāng)文章被更新的時候,blogs表會進行update,更新該字段。因此每當(dāng)文章內(nèi)容被更新,緩存的頁面內(nèi)容的key就會改變,應(yīng)用程序下次訪問文章頁面的時候,緩存就會失效,于是重新調(diào)用GitHub::Markdown.to_html(content, :gfm)生成新的頁面內(nèi)容。 而老的頁面緩存內(nèi)容再也不會被應(yīng)用程序存取,根據(jù)memcached的LRU算法,當(dāng)緩存填滿之后,將被優(yōu)先剔除。

除了文章內(nèi)容緩存之外,文章的評論內(nèi)容轉(zhuǎn)換成html以后也使用了這種緩存設(shè)計模式。具體可以看相應(yīng)的源代碼:blog_comment.rb

#p#

片段緩存和過期處理

Web應(yīng)用當(dāng)中有大量的并非實時更新的數(shù)據(jù),這些數(shù)據(jù)都可以使用緩存,避免每次存取的時候都進行數(shù)據(jù)庫查詢和運算。這種片段緩存的應(yīng)用場景很多,例如:

·展示網(wǎng)站的Tag分類統(tǒng)計(只要沒有更新文章分類,或者發(fā)布新文章,緩存一直有效)

·輸出網(wǎng)站RSS(只要沒有發(fā)新文章,緩存一直有效)

·網(wǎng)站右側(cè)欄(如果沒有新的評論或者發(fā)布新文章,則在一段時間例如一天內(nèi)基本不需要更新)

以上應(yīng)用場景都可以使用緩存,代碼示例:

 
 
 
  1. def self.cached_tag_cloud  
  2.   APP_CACHE.fetch("#{CACHE_PREFIX}/blog_tags/tag_cloud") do  
  3.     self.tag_counts.sort_by(&:count).reverse  
  4.   end 
  5. end 

對全站文章的Tag云進行查詢,對查詢結(jié)果進行緩存

 
 
 
  1. <% cache("#{CACHE_PREFIX}/layout/right", :expires_in => 1.day) do %> 
  2.  
  3.  
  4.   <% Blog.cached_tag_cloud.select {|t| t.count > 2}.each do |tag| %> 
  5.   <%= link_to "#{tag.name}#{tag.count}".html_safe, url(:blog, :tag, :name => tag.name) %> 
  6.   <% end %> 
 
  • ......  
  • <% end %> 
  • 對全站右側(cè)欄頁面進行緩存,過期時間是1天。

    緩存的過期處理往往是比較麻煩的事情,但在ORM框架當(dāng)中,我們可以利用model對象的回調(diào),很容易實現(xiàn)緩存過期處理。我們的緩存都是和文章,以及評論相關(guān)的,所以可以直接注冊Blog類和BlogComment類的回調(diào)接口,聲明當(dāng)對象被保存或者刪除的時候調(diào)用刪除方法:

     
     
     
    1. class Blog < ActiveRecord::Base 
    2.   acts_as_cached  
    3.   after_save :clean_cache  
    4.   before_destroy :clean_cache  
    5.   def clean_cache  
    6.     APP_CACHE.delete("#{CACHE_PREFIX}/blog_tags/tag_cloud")   # clean tag_cloud  
    7.     APP_CACHE.delete("#{CACHE_PREFIX}/rss/all")               # clean rss cache  
    8.     APP_CACHE.delete("#{CACHE_PREFIX}/layout/right")          # clean layout right column cache in _right.erb  
    9.   end  
    10. end  
    11.  
    12. class BlogComment < ActiveRecord::Base 
    13.   acts_as_cached  
    14.   after_save :clean_cache  
    15.   before_destroy :clean_cache  
    16.   def clean_cache  
    17.     APP_CACHE.delete("#{CACHE_PREFIX}/layout/right")     # clean layout right column cache in _right.erb  
    18.   end  
    19. end    

    在Blog對象的after_save和before_destroy上注冊clean_cache方法,當(dāng)文章被修改或者刪除的時候,刪除以上緩存內(nèi)容。總之,可以利用ORM對象的回調(diào)接口進行緩存過期處理,而不需要到處寫緩存清理代碼。

    對象寫入緩存

    我們通常說到緩存,總是認為緩存是提升應(yīng)用讀取性能的,其實緩存也可以有效的提升應(yīng)用的寫入性能。我們看一個常見的應(yīng)用場景:記錄文章點擊次數(shù)這個功能。

    文章點擊次數(shù)需要每次訪問文章頁面的時候,都要更新文章的點擊次數(shù)字段view_count,然后文章必須實時顯示文章的點擊次數(shù),因此常見的讀緩存模式完全無效了。每次訪問都必須更新數(shù)據(jù)庫,當(dāng)訪問量很大以后數(shù)據(jù)庫是吃不消的,因此我們必須同時做到兩點:

    ·每次文章頁面被訪問,都要實時更新文章的點擊次數(shù),并且顯示出來

    ·不能每次文章頁面被訪問,都更新數(shù)據(jù)庫,否則數(shù)據(jù)庫吃不消

    對付這種應(yīng)用場景,我們可以利用對象緩存的不一致,來實現(xiàn)對象寫入緩存。原理就是每次頁面展示的時候,只更新緩存中的對象,頁面顯示的時候優(yōu)先讀取緩存,但是不更新數(shù)據(jù)庫,讓緩存保持不一致,積累到n次,直接更新一次數(shù)據(jù)庫,但繞過緩存過期操作。具體的做法可以參考blog.rb :

     
     
     
    1. # blog viewer hit counter  
    2. def increment_view_count  
    3.   increment(:view_count)        # add view_count += 1  
    4.   write_second_level_cache      # update cache per hit, but do not touch db  
    5.                                 # update db per 10 hits  
    6.   self.class.update_all({:view_count => view_count}, :id => id) if view_count % 10 == 0  
    7. end 

    increment(:view_count)增加view_count計數(shù),關(guān)鍵代碼是第2行write_second_level_cache,更新view_count之后直接寫入緩存,但不更新數(shù)據(jù)庫。累計10次點擊,再更新一次數(shù)據(jù)庫相應(yīng)的字段。另外還要注意,如果blog對象不是通過主鍵查詢,而是通過查詢語句構(gòu)造的,要優(yōu)先讀取一次緩存,保證頁面點擊次數(shù)的顯示一致性,因此 _blog.erb 這個頁面模版文件開頭有這樣一段代碼:

     
     
     
    1. <%   
    2.   # read view_count from model cache if model has been cached.  
    3.   view_count = blog.view_count  
    4.   if b = Blog.read_second_level_cache(blog.id)  
    5.     view_count = b.view_count  
    6.   end 
    7. %>  

    采用對象寫入緩存的設(shè)計模式,就可以非常容易的實現(xiàn)寫入操作的緩存,在這個例子當(dāng)中,我們僅僅增加了一行緩存寫入代碼,而這個時間開銷大約是1ms,就可以實現(xiàn)文章實時點擊計數(shù)功能,是不是非常簡單和巧妙?實際上我們也可以使用這種設(shè)計模式實現(xiàn)很多數(shù)據(jù)庫寫入的緩存功能。

    常用的ORM緩存設(shè)計模式就是以上的幾種,本質(zhì)上都是非常簡單的編程技巧,代碼的增加量和復(fù)雜度也非常低,只需要很少的代碼就可以實現(xiàn),但是在實際應(yīng)用當(dāng)中,特別是當(dāng)數(shù)據(jù)量很龐大,訪問量很高的時候,可以發(fā)揮驚人的效果。我們實際的系統(tǒng)當(dāng)中,緩存命中次數(shù):SQL查詢語句,一般都是5:1左右,即每次向數(shù)據(jù)庫查詢一條SQL,都會在緩存當(dāng)中命中5次,數(shù)據(jù)主要都是從緩存當(dāng)中得到,而非來自于數(shù)據(jù)庫了。

    其他緩存的使用技巧

    還有一些并非ORM特有的緩存設(shè)計模式,但是在Web應(yīng)用當(dāng)中也比較常見,簡單提及一下:

    用數(shù)據(jù)庫來實現(xiàn)的緩存

    在我這個網(wǎng)站當(dāng)中,每篇文章都標(biāo)記了若干tag,而tag關(guān)聯(lián)關(guān)系都是保存到數(shù)據(jù)庫里面的,如果每次顯示文章,都需要額外查詢關(guān)聯(lián)表獲取tag,顯然會非常消耗數(shù)據(jù)庫。在我使用的acts-as-taggable-on插件中,它在blogs表當(dāng)中添加了一個cached_tag_list字段,保存了該文章標(biāo)記的tag。當(dāng)文章被修改的時候,會自動相應(yīng)更新該字段,避免了每次顯示文章的時候都需要去查詢關(guān)聯(lián)表的開銷。

    HTTP客戶端緩存

    基于資源協(xié)議實現(xiàn)的HTTP客戶端緩存也是一種非常有效的緩存設(shè)計模式,我在2009年寫過一篇文章詳細的講解了:基于資源的HTTP Cache的實現(xiàn)介紹 ,所以這里就不再復(fù)述了。

    用緩存實現(xiàn)計數(shù)器功能

    這種設(shè)計模式有點類似于對象寫入緩存,利用緩存寫入的低開銷來實現(xiàn)高性能計數(shù)器。舉一個例子:用戶登錄為了避免遭遇密碼暴力破解,我限定了每小時每IP只能嘗試登錄5次,如果超過5次,拒絕該IP再次嘗試登錄。代碼實現(xiàn)很簡單,如下:

     
     
     
    1. post :login, :map => '/login' do  
    2.   login_tries = APP_CACHE.read("#{CACHE_PREFIX}/login_counter/#{request.ip}")  
    3.   halt 403 if login_tries && login_tries.to_i > 5  # reject ip if login tries is over 5 times  
    4.   @account = Account.new(params[:account])  
    5.   if login_account = Account.authenticate(@account.email, @account.password)  
    6.     session[:account_id] = login_account.id  
    7.     redirect url(:index)  
    8.   else 
    9.     # retry 5 times per one hour 
    10.     APP_CACHE.increment("#{CACHE_PREFIX}/login_counter/#{request.ip}", 1, :expires_in => 1.hour)  
    11.     render 'home/login' 
    12.   end 
    13. end 

    等用戶POST提交登錄信息之后,先從緩存當(dāng)中取該IP嘗試登錄次數(shù),如果大于5次,直接拒絕掉;如果不足5次,而且登錄失敗,計數(shù)加1,顯示再次嘗試登錄頁面。

    以上相關(guān)代碼可以從這里獲?。簉obbin_site

    原文鏈接:http://robbinfan.com/blog/38/orm-cache-sumup


    網(wǎng)頁標(biāo)題:Web應(yīng)用的緩存設(shè)計模式
    鏈接地址:http://www.dlmjj.cn/article/dhsepop.html