tn-零售平臺事業部-底層研發部-A組 php
今天,技術經理讓我處理一個問題:app首頁的產品列表,第9個,第11個,兩個產品如出一轍。html
先講一下項目的架構:mysql
注:每一個箭頭都有相反方向的對應箭頭,我懶,不畫了。redis
portal負責app首頁、頻道頁的大部分服務端功能,主要用於聚合信息,cms是中間層,調用搜索,獲取產品信息。ror是搜索項目,具體處理請求,經過solr獲取數據。portal,cms我都作過一部分,ror沒作過,具體細節不清楚,只知道是操做solr的。sql
一個請求的處理過程是這樣:APP send a request to portal, and portal query data from cache(redis) firstly, if get something from cache, send response to app, if not, send request to cms, then get data from cms. When cms get a request from portal, it query data from cache, if get nothing, send a request to ror and get data from it. m站有本身的服務端,是php的。php也是先查緩存, 再查portal。apache
能夠看出,這是多個微服務組成的系統,各個應用間經過restful方式相互調用。好處是解耦,壞處是重複造輪子,資源利用率不高,前先後後多少個緩存?緩存
在談談我是怎麼解決技術經理的問題的。首先,app是分頁,每頁10條,向portal請求產品列表的,第一頁的第9個,第二頁的第1個,兩個產品徹底同樣!我看了看m站,沒有出現問題,有可能php的緩存把它保護了。再看看cms,先是 cms不走緩存,發現app相同的產品排在第11個(第2頁第一個),而後cms走緩存,app相同的產品排在第9個。因而,我大概知道緣由了。portal緩存了第1頁的老數據,緩存了第2頁的新數據。我刪了portal的產品列表的緩存,問題解決了。服務器
我告訴經理解決了,問題的緣由是緩存不一致,方案是刪緩存。經理讓我想一想有沒有更好的方法。我想了想,將思考寫入此文。restful
首先,緩存不一致,是由於前先後後有不少緩存,每一個緩存的存活時間是1小時。因此會出現緩存不一致的問題,這本質上是分佈式一致性的問題。(那就有了分佈式鎖,分佈式一致性)。網絡
那麼,就有兩種思路:1,能不能只有一套緩存,portal,cms共用一套緩存。(我只能管到這兩個項目,其餘的我也不知道,也管不了,也沒有權限操做)那就不存在緩存不一致了。2,能夠保留多套緩存,但能不能提供 緩存更新通知 緩存同步\異步刷新?
思路一:
思路一是簡單粗暴的,還節省了緩存服務器,真省錢。前面提到,咱們是微服務,各應用間經過restful接口實現調用,這種方式有好處——業務邏輯封裝在應用內部,外部調用方不可見(高內聚),A系統出了問題,不會太多的影響B系統(低耦合)。壞處是每次調用須要一次http連接,創建連接是耗時的,還存在網絡故障、延時的風險,因此每一個應用本身有一套緩存,避免每次都要調接口,提高性能(因此就有了緩存不一致問題,即——分佈式一致性問題,關於分佈式cap,詳見http://www.cnblogs.com/bangerlee/p/5328888.html)。那麼如何作到只有一套緩存呢?見下圖:
如圖所示,大概都畫清楚了。四個黃條分表表明 過濾器-控制器-業務層-數據層。圖中,緩存放在了dao以後,其實,也能夠放在filter-controller之間,controller-business之間,business-dao之間。
先講dao以後。他要保證的核心,是確保:只有cms能往緩存寫數據,portal,以及其餘任何應用,都沒有寫權限!你能想象,cms剛寫完,portal就把它改了,刪了嗎?另外,因爲如今用的是redis,是單線程操做的,不用過多考慮併發場景下的異常狀況,memcache等多線程操做的cache就複雜了。除了保證,緩存只有cms能寫,還要考慮portal從緩存沒有讀到數據的情形。是緩存過時了?仍是緩存裏原本就不應存在此次請求對應的數據?針對這個問題,有兩種思路:
1 沒有獲取到緩存時,直接調用cms的restful接口,cms自己不讀緩存(portal就沒讀到,cms就不必讀了),而是直接從後臺獲取數據,若是有,返回給portal,能夠在返回給portal以前,同步/異步寫到緩存裏。若是併發量大,可使用redis setnx語句,避免屢次寫同一個key,也能夠將寫任務提交到任務隊列,提升性能。若是cms沒有從後臺獲取到數據,最好返回一個無害的默認值(降級),並設置緩存,記錄請求的上下文到日誌,便於往後分析。爲何呢?防止緩存擊穿。假設有這樣一個流程:惡意用戶用一個惡意入參請求portal,portal從緩存沒有取到數據,調用cms,cms調用ror,也沒取到,也沒設一個默認值到緩存,就返回了。這樣,惡意用戶重複這個流程100萬次,portal會取100萬次緩存,調100萬次cms,調100萬次ror,ror調100萬次solr,這會產生災難性的後果!若是在緩存設個默認值,時間不用長,5分鐘,30分鐘,避免緩存擊穿形成的嚴重後果。(時間不能長,是由於可能存入大量無害的默認信息,失效時間長了,影響緩存的容量以及性能。另外,惡意用戶短期內發現惡意請求不起破壞性效果,就極有可能換個惡意請求了)。固然,也能夠採用防刷、限流的方式,熔斷、降級,甚至封號,加黑名單。。。
2 portal始終只從緩存讀,沒讀到就直接返回null或無害的默認值。這就要cms保證緩存的完整性、及時性、有效性。cms必須將全量的數據存入緩存(只有這樣,才知足portal只從緩存取數據的基本條件)。緩存時間能夠爲永久,或者短期緩存。若是緩存時間短,如1h,則能夠作個定時任務,如半小時,定時更新全量的緩存,要保證半小時更新完全部緩存,這隻能儘可能保證數據的及時性,有效性。若是緩存永久,必須有一個通訊機制,讓cms知道要更新哪些數據,如mq等。另外,更新緩存要考慮數據備份,防止由於異常,致使緩存出問題。
緩存也可放在filter-controller之間,這裏離用戶最近,性能也最好。但這就至關於把business層處理完的數據存入了cache,這違反了只有cms有寫權限的原則!解決方法是將portal,cms兩個項目合併。但這就不是微服務了。緩存放在其餘地方也是同理。
以上是一套緩存的分析,下面分析多套緩存的方案。
多套緩存,即保持現有架構不變。那就要解決多個緩存之間的不一致問題——本質上是分佈式一致性的問題。關於分佈式cap,詳見http://www.cnblogs.com/bangerlee/p/5328888.html。當cms緩存失效、更新時,經過消息機制jms通知portal刪除或跟新緩存,這是異步的。若是對一致性要求不極致,這樣就能夠了,改形成本也小,加個mq就好了(若是要求更低,什麼都不用改,等緩存本身失效,如今就是這樣的)。若是要求強一致性,就要引入分佈式事務,能夠基於mysql,redis,或者zookeeper。我的建議基於curator,採用 分佈式鎖+分佈式事務, http://ifeve.com/zookeeper-leader/ http://curator.apache.org/ http://zookeeper.apache.org