好湿?好紧?好多水好爽自慰,久久久噜久噜久久综合,成人做爰A片免费看黄冈,机机对机机30分钟无遮挡

主頁 > 知識庫 > web 應(yīng)用中常用的各種 cache詳解

web 應(yīng)用中常用的各種 cache詳解

熱門標(biāo)簽:漯河電銷 AI電銷機(jī)器人 線路 淮安自動外呼系統(tǒng)供應(yīng)商 中牟外呼系統(tǒng)違法嗎 巫師3地圖標(biāo)注魔力之所 外呼線路從哪里出來的 柯城手機(jī)地圖如何做地圖標(biāo)注 天津外呼系統(tǒng)怎么收費(fèi) 征服者企業(yè)地圖標(biāo)注

本文以Nginx,Rails,Mysql,Redis作為例子,換成其他web服務(wù)器,語言,數(shù)據(jù)庫,緩存服務(wù)都是類似的。
以下是3層的示意圖,方便后續(xù)引用:

1. 客戶端緩存

一個客戶端經(jīng)常會訪問同一個資源,比如用瀏覽器訪問網(wǎng)站首頁或查看同一篇文章,或用app訪問同一個api,如果該資源和他之前訪問過的沒有任何改變,就可以利用http規(guī)范中的304 Not Modified 響應(yīng)頭(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5),直接用客戶端的緩存,而無需在服務(wù)器端再生成一次內(nèi)容。
在Rails里面內(nèi)置了fresh_when這個方法,一行代碼就可以完成:

class ArticlesController
 def show
  @article = Article.find(params[:id])
  fresh_when :last_modified => @article.updated_at.utc, :etag => @article
 end
end

下次用戶再訪問的時候,會對比request header里面的If-Modified-Since和If-None-Match,如果相符合,就直接返回304,而不再生成response body。

但是這樣會遇到一個問題,假設(shè)我們的網(wǎng)站導(dǎo)航有用戶信息,一個用戶在未登陸專題訪問了一下,然后登陸以后再訪問,會發(fā)現(xiàn)頁面上顯示的還是未登陸狀態(tài)。或者在app訪問一篇文章,做了一下收藏,下次再進(jìn)入這篇文章,還是顯示未收藏狀態(tài)。解決這個問題的方法很簡單,將用戶相關(guān)的變量也加入到etag的計算里面:

  fresh_when :etag => [@article.cache_key, current_user.id]
  fresh_when :etag => [@article.cache_key, current_user_favorited]

另外提一個坑,如果nginx開啟了gzip,對rails執(zhí)行的結(jié)果進(jìn)行壓縮,會將rails輸出的etag header干掉,nginx的開發(fā)人員說根據(jù)rfc規(guī)范,對proxy_pass方式處理必須這樣(因?yàn)閮?nèi)容改變了),但是我個人認(rèn)為沒這個必要,于是用了粗暴的方法,直接將src/http/modules/ngx_http_gzip_filter_module.c這個文件里面的這行代碼注釋掉,然后重新編譯nginx:

  //ngx_http_clear_etag(r); 

或者你可以選擇不改變nginx源代碼,將gzip off掉,將壓縮用Rack中間件來處理:

  config.middleware.use Rack::Deflater

除了在controller里面指定fresh_when以外,rails框架默認(rèn)使用Rack::ETag middleware,它會自動給無etag的response加上etag,但是和fresh_when相比,自動etag能夠節(jié)省的只是客戶端時間,服務(wù)器端還是一樣會執(zhí)行所有的代碼,用curl來對比一下。
Rack::ETag自動加入etag:

curl -v http://localhost:3000/articles/1
 Etag: "bf328447bcb2b8706193a50962035619"
 X-Runtime: 0.286958
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
 X-Runtime: 0.293798
用fresh_when:

 
curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'
 X-Runtime: 0.033884

2. Nginx緩存

有一些資源可能會被調(diào)用很多,又無關(guān)用戶狀態(tài),并且很少改變,比如新聞app上的列表api,購物網(wǎng)站上ajax請求分類菜單,可以考慮用Nginx來做緩存。
主要有2種實(shí)現(xiàn)方法:
A. 動態(tài)請求靜態(tài)文件化
在rails請求完成以后,將結(jié)果保存成靜態(tài)文件,后續(xù)請求就會直接由nginx提供靜態(tài)文件內(nèi)容,用after_filter來實(shí)現(xiàn)一下:

class CategoriesController  ActionController::Base
 after_filter :generate_static_file, :only => [:index]

 def index
  @categories = Category.all
 end

 def generate_static_file
  File.open(Rails.root.join('public', 'categories'), 'w') do |f|
   f.write response.body
  end
 end
end

另外我們需要在任何分類更新的時候,刪除掉這個文件,避免緩存不刷新的問題:

class Category  ActiveRecord::Base
 after_save :delete_static_file
 after_destroy :delete_static_file

 def delete_static_file
  File.delete Rails.root.join('public', 'categories')
 end
end

Rails 4之前,處理這種生成靜態(tài)文件緩存可以用內(nèi)置的caches_page, rails 4之后變成了一個獨(dú)立gem actionpack-page_caching,和手工代碼對比一下,

class CategoriesController  ActionController::Base
 caches_page :index

 def update
  #...
  expire_page action: 'index'
 end
end

如果只有一臺服務(wù)器,這個方法簡單又實(shí)用,但是如果有多臺服務(wù)器,就會出現(xiàn)更新分類只能刷新自己本身這臺服務(wù)器緩存的問題,可以用nfs來共享靜態(tài)資源目錄解決,或者用第2種:

B. 靜態(tài)化到集中緩存服務(wù)
首先我們得讓Nginx有直接訪問緩存的能力:

 upstream redis {
  server redis_server_ip:6379;
 }

 upstream ruby_backend {
  server unicorn_server_ip1 fail_timeout=0;
  server unicorn_server_ip2 fail_timeout=0;
 }

 location /categories {
  set $redis_key $uri;
  default_type  text/html;
  redis_pass redis;
  error_page 404 = @httpapp;
 }

 location @httpapp {
  proxy_pass http://ruby_backend;
 }

Nginx首先會用請求的uri作為key去redis里面獲取,如果獲取不到(404)就轉(zhuǎn)發(fā)給unicorn進(jìn)行處理,然后改寫generate_static_file和delete_static_file方法:

 redis_cache.set('categories', response.body)
 redis_cache.del('categories')

這樣除了集中管理以外,還能夠設(shè)置緩存的失效時間,對于一些更新無時效性要求的數(shù)據(jù),就可以不用處理刷新機(jī)制,簡單地固定時間刷新一次:

 redis_cache.setex('categories', 3.hours.to_i, response.body)

3. 整頁緩存

Nginx緩存在處理帶參數(shù)資源或者有用戶狀態(tài)的請求時候,就非常難以處理,這個時候可以用到整頁緩存。
比如說分頁請求列表,我們可以將page參數(shù)加入到cache_path:

class CategoriesController
 caches_action :index, :expires_in => 1.day, :cache_path => proc {"categories/index/#{params[:page].to_i}"}
end

比如說我們只需要針對rss輸出進(jìn)行緩存8小時:

class ArticlesController
 caches_action :index, :expires_in => 8.hours, :if => proc {request.format.rss?}
end

再比如說對于非登陸用戶,我們可以緩存首頁:

class HomeController
 caches_action :index, :expires_in => 3.hours, :if => proc {!user_signed_in?}
end

4. 片段緩存

如果說前面2種緩存能夠用到的場景有限,那么片段緩存是適用性最廣的。

場景1:我們需要在每個頁面一段廣告代碼,用來顯示不同廣告,如果沒有使用片段緩存,那么每個頁面都會要去查詢廣告的代碼,并且花費(fèi)一定時間去生成html代碼:

- if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
 div.ad
  = advert.content

加了片段緩存以后,就可以少去這個查詢:

- cache "adverts/#{request.controller_name}/#{request.action_name}", :expires_in => 1.day do
 - if advert = Advert.where(:name => request.controller_name + request.action_name, :enable => true).first
  div.ad
   = advert.content

場景2:閱讀文章,文章的內(nèi)容可能比較長時間都不會改變,經(jīng)常變化可能是文章評論,就可以對文章主體部分加上片段緩存:

- cache "articles/#{@article.id}/#{@article.updated_at.to_i}" do
 div.article
  = @article.content.markdown2html

節(jié)約了生成markdown語法轉(zhuǎn)換到html時間,這里用文章最后更新時間作為cache key的一部分,文章內(nèi)容如果有改變,緩存自動失效,默認(rèn)activerecord的cache_key方法也是用updated_at,你也可以加入更多的參數(shù),比如article上有評論數(shù)的counter cache,更新評論數(shù)的時候不會更新文章時間,可以將這個counter也加入到key的一部分

場景3:復(fù)雜頁面結(jié)構(gòu)的生成
數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜的頁面,在生成的時候避免不了大量的查詢和html渲染,用片段緩存,可以將這部分時間大大地節(jié)約,以我們網(wǎng)站游記頁面http://chanyouji.com/trips/109123(請?jiān)试S小小地打個廣告,帶點(diǎn)流量)來說:
需要獲取天氣數(shù)據(jù),照片數(shù)據(jù),文本數(shù)據(jù)等,同時還要生成meta,keyword等seo數(shù)據(jù),而這些內(nèi)容又是和其他動態(tài)內(nèi)容交叉,片段緩存就可以分開多個:

- cache "trips/show/seo/#{@trip.fragment_cache_key}", :expires_in => 1.day do
 title #{trip_name @trip}
 meta name="description" content="..."
 meta name="keywords" content="..."

body
 div
  ...
- cache "trips/show/viewer/#{@trip.fragment_cache_key}", :expires_in => 1.day do
 - @trip.eager_load_all

小貼士,我在trip對象里面加了一個eager_load_all方法,緩存沒有命中的時候,查詢的時候避免出現(xiàn)n+1問題:

 def eager_load_all
  ActiveRecord::Associations::Preloader.new([self], {:trip_days => [:weather_station_data, :nodes => [:entry, :notes => [:photo, :video, :audio]]]}).run
 end

小技巧1:帶條件的片段緩存
和caches_action不同,rails自帶的片段緩存是不支持條件的,比如說我們想未登陸用戶給他用片段緩存,而登陸用戶不使用,寫起來就很麻煩,我們可以改寫一下helper就可以了:

 def cache_if (condition, name = {}, cache_options = {}, block)
  if condition
   cache(name, cache_options, block)
  else
   yield
  end
 end

- cache_if !user_signed_in?, "xxx", :expires_in => 1.day do

小技巧2:關(guān)聯(lián)對象的自動更新
常使用對象update_at時間戳來作為cache key,可以在關(guān)聯(lián)對象上加上touch選項(xiàng),自動更新關(guān)聯(lián)對象時間戳,比如我們可以在更新或者刪除文章評論的時候,自動個更新:

class Article
 has_many :comments
end

class Comment
 belongs_to :article, :touch => true
end

5. 數(shù)據(jù)查詢緩存

通常來說web應(yīng)用性能瓶頸都出現(xiàn)在DB IO上,做好數(shù)據(jù)查詢緩存,減少數(shù)據(jù)庫的查詢次數(shù),可以極大提高整體響應(yīng)時間。
數(shù)據(jù)查詢緩存分2種:
A. 同一個請求周期內(nèi)的緩存
舉一個顯示文章列表的例子,輸出文章標(biāo)題和文章類別,對應(yīng)代碼如下

# controller
 def index
  @articles = Article.first(10)
 end

# view
- @articles.each do |article|
 h1 = article.name
 span = article.category.name

會發(fā)生10條類似的sql查詢:

SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = ?

rails內(nèi)置了query cache(https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),在同一個請求周期內(nèi),如果沒有update/delete/insert的操作,會對相同的sql查詢進(jìn)行緩存,如果文章類別都是相同的話,真正去查詢數(shù)據(jù)庫只會有1次。

如果文章類別都不一樣,就會出現(xiàn)N+1查詢問題(常見的性能瓶頸),rails推薦的解決方法是用Eager Loading Associations (http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)

 

 def index
  @articles = Article.includes(:category).first(10)
 end

查詢語句會變成

SELECT `categories`.* FROM `categories` WHERE `categories`.`id` in (?,?,?...)

B. 跨請求周期的緩存

同請求周期緩存所帶來性能優(yōu)化是很有限的,很多時候我們需要用跨請求周期的緩存,將一些常用的數(shù)據(jù)(比如User model)緩存,對于active record來說,利用統(tǒng)一的查詢接口來fetch cache,利用callback來expire cache,就很容易實(shí)現(xiàn),而且有一些現(xiàn)成的gem可以來用。

比如說 identity_cache (https://github.com/Shopify/identity_cache)

class User  ActiveRecord::Base
 include IdentityCache
end

class Article  ActiveRecord::Base
 include IdentityCache
 cached_belongs_to :user
end

# 都會命中緩存

User.fetch(1)
Article.find(2).user

這個gem的優(yōu)點(diǎn)是代碼實(shí)現(xiàn)簡單,cache設(shè)置靈活,也方便擴(kuò)展,缺點(diǎn)是需要用不同的查詢方法名(fetch),以及額外的關(guān)系定義。

如果想在無數(shù)據(jù)緩存的應(yīng)用無縫加入緩存功能,推薦@hooopo做的second_level_cache (https://github.com/hooopo/second_level_cache) 。

class User  ActiveRecord::Base
 acts_as_cached(:version => 1, :expires_in => 1.week)
end

#還是使用find方法,就會命中緩存

User.find(1)
#無需額外用不一樣的belongs_to定義
Article.find(2).user
實(shí)現(xiàn)原理是擴(kuò)展了active record底層arel sql ast處理 (https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/arel/wheres.rb)
它的優(yōu)點(diǎn)是無縫接入,缺點(diǎn)是擴(kuò)展比較困難,對于只獲取少量字段的查詢無法緩存。

6. 數(shù)據(jù)庫緩存

編輯中

這6種緩存,分布在客戶端到服務(wù)器端不同的位置,所能夠節(jié)約的時間也正好從多到少依次排列。

您可能感興趣的文章:
  • 分析Cache 在 Ruby China 里面的應(yīng)用情況

標(biāo)簽:內(nèi)江 大慶 南昌 棗莊 西雙版納 甘孜 克拉瑪依 河池

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《web 應(yīng)用中常用的各種 cache詳解》,本文關(guān)鍵詞  web,應(yīng)用,中常,用的,各種,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《web 應(yīng)用中常用的各種 cache詳解》相關(guān)的同類信息!
  • 本頁收集關(guān)于web 應(yīng)用中常用的各種 cache詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    主站蜘蛛池模板: 亚洲444444在线观看| 高辣h浪荡小说肥臀艳妇短篇| 亚洲精品鲁一鲁一区二区三区 | 国产精品 欧美激情| 日韩精品无码一区二区三区电影 | A片扒开双腿进入做爽爽| 91精品人妻一区二区三区蜜桃臀 | 亚洲国产精品wwewwww| 91黑料精品国产| 厨房里退掉短裙| 高清日本无a区| 开车视频小黄车视频app| 女子张腿让男人桶爽免费| 看一级外国毛片| 成人三级理论电影在线观看| 动漫版成年美女黄漫视频| 扒开学生的粉嫩小泬流水视频| 无遮挡黄动漫观看在线观看| 中国特黄毛片| 动漫美女强行被吸乳羞羞| 渔夫风流艳史免费观看HD| 亚洲国产精品影院| 三个黑人上我一个经过| 国产伦精一区二区三区| 爽?好紧?宝贝别夹大巴图片| 日本无码一区av午夜老司机| avtb.com| 小荡货又紧又爽h古代| 亚洲国产综合专区在线播一一| 又粗又硬又大又深又爽动态图| 侯府荡女H女奶水苏晚晴| 99热只有精品一区二区| 一级做a爰片久久毛片武则天| 女人下面喷水视频| 好爽?好紧?宝贝叫大声欧美| 欧美激情国内自拍偷| 娇妻趴在桌子边把屁股撅起来 | 警花被绑任男人玩下面| 乱一性一乱一交一视频| 武则天一级婬片A片| 爽?好大?快?深点自慰大学|