如何用redis/memcache做Mysql緩存層?

目前公司的一個項目,資料庫用的是Mysql,正在考慮用redis/memcached做資料庫的緩存層,目前的想法就是在讀DB前,先讀緩存層,如果有直接返回,如果沒有再讀DB,然後寫入緩存層並返回。

不過,要是直接在應用層加入緩存的代碼,感覺修改量大,修改維護也麻煩,因此想把應用層和緩存層的代碼分開。不知道這種想法正確否?想看看別人的代碼是如何實現的,有沒有相關的開源項目可以學習啊


1.首先明確是不是一定要上緩存,當前架構的瓶頸在哪裡,若瓶頸真是資料庫操作上,再繼續往下看。

2.明確memcached和redis的區別,到底要使用哪個。前者終究是個緩存,不可能永久保存數據(LRU機制),支持分散式,後者除了緩存的同時也支持把數據持久化到磁碟等,redis要自己去實現分散式緩存(貌似最新版本的已集成),自己去實現一致性hash。因為不知道你們的應用場景,不好說一定要用memcache還是redis,說不定用mongodb會更好,比如在存儲日誌方面。

3.緩存量大但又不常變化的數據,比如評論。

4.你的思路是對的,清晰明了,讀DB前,先讀緩存,如果有直接返回,如果沒有再讀DB,然後寫入緩存層並返回。

5.考慮是否需要主從,讀寫分離,考慮是否分散式部署,考慮是否後續水平伸縮。

6.想要一勞永逸,後續維護和擴展方便,那就將現有的代碼架構優化,按你說的替換資料庫組件需要改動大量代碼,說明當前架構存在問題。可以利用現有的一些框架,比如SpringMVC,將你的應用層和業務層和資料庫層解耦。再上緩存之前把這些做好。

7.把讀取緩存等操作做成服務組件,對業務層提供服務,業務層對應用層提供服務。

8.保留原始資料庫組件,優化成服務組件,方便後續業務層靈活調用緩存或者是資料庫。

9.不建議一次性全量上緩存,最開始不動核心業務,可以將邊緣業務先換成緩存組件,一步步換至核心業務。

10.

刷新內存,以memcached為例,新增,修改和刪除操作,一般採用lazy load的策略,即新增時只寫入資料庫,並不會馬上更新Memcached,而是等到再次讀取時才會載入到Memcached中,修改和刪除操作也是更新 資料庫,然後將Memcached中的數據標記為失效,等待下次讀取時再載入。


大方向兩種方案:

1.腳本同步:

自己寫腳本將資料庫數據寫入到redis/memcached。

這就涉及到實時數據變更的問題(mysql row binlog的實時分析),binlog增量訂閱Alibaba 的canal ,以及緩存層數據 丟失/失效 後的數據同步恢復問題。

2.業務層實現:

先讀取nosql緩存層,沒有數據再讀取mysql層,並寫入數據到nosql。

nosql層做好多節點分散式(一致性hash),以及節點失效後替代方案(多層hash尋找相鄰替代節點),和數據震蕩恢復了。


轉載陳皓(左耳朵耗子)的一篇文章

原文鏈接:緩存更新的套路

看到好些人在寫更新緩存數據代碼時,先刪除緩存,然後再更新資料庫,而後續的操作會把數據再裝載的緩存中。然而,這個是邏輯是錯誤的。試想,兩個並發操作,一個是更新操作,另一個是查詢操作,更新操作刪除緩存後,查詢操作沒有命中緩存,先把老數據讀出來後放到緩存中,然後更新操作更新了資料庫。於是,在緩存中的數據還是老的數據,導致緩存中的數據是髒的,而且還一直這樣臟下去了。

我不知道為什麼這麼多人用的都是這個邏輯,當我在微博上發了這個貼以後,我發現好些人給了好多非常複雜和詭異的方案,所以,我想寫這篇文章說一下幾個緩存更新的Design Pattern(讓我們多一些套路吧)。

這裡,我們先不討論更新緩存和更新數據這兩個事是一個事務的事,或是會有失敗的可能,我們先假設更新資料庫和更新緩存都可以成功的情況(我們先把成功的代碼邏輯先寫對)。

更新緩存的的Design Pattern有四種:Cache aside, Read through, Write through, Write behind caching,我們下面一一來看一下這四種Pattern。

Cache Aside Pattern

這是最常用最常用的pattern了。其具體邏輯如下:

  • 失效:應用程序先從cache取數據,沒有得到,則從資料庫中取數據,成功後,放到緩存中。
  • 命中:應用程序從cache中取數據,取到後返回。
  • 更新:先把數據存到資料庫中,成功後,再讓緩存失效。

注意,我們的更新是先更新資料庫,成功後,讓緩存失效。那麼,這種方式是否可以沒有文章前面提到過的那個問題呢?我們可以腦補一下。

一個是查詢操作,一個是更新操作的並發,首先,沒有了刪除cache數據的操作了,而是先更新了資料庫中的數據,此時,緩存依然有效,所以,並發的查詢操作拿的是沒有更新的數據,但是,更新操作馬上讓緩存的失效了,後續的查詢操作再把數據從資料庫中拉出來。而不會像文章開頭的那個邏輯產生的問題,後續的查詢操作一直都在取老的數據。

Read/Write Through Pattern

我們可以看到,在上面的Cache Aside套路中,我們的應用代碼需要維護兩個數據存儲,一個是緩存(Cache),一個是資料庫(Repository)。所以,應用程序比較啰嗦。而Read/Write Through套路是把更新資料庫(Repository)的操作由緩存自己代理了,所以,對於應用層來說,就簡單很多了。可以理解為,應用認為後端就是一個單一的存儲,而存儲自己維護自己的Cache。

Read Through

Read Through 套路就是在查詢操作中更新緩存,也就是說,當緩存失效的時候(過期或LRU換出),Cache Aside是由調用方負責把數據載入入緩存,而Read Through則用緩存服務自己來載入,從而對應用方是透明的。

Write Through

Write Through 套路和Read Through相仿,不過是在更新數據時發生。當有數據更新的時候,如果沒有命中緩存,直接更新資料庫,然後返回。如果命中了緩存,則更新緩存,然後再由Cache自己更新資料庫(這是一個同步操作)

下圖自來Wikipedia的Cache詞條。其中的Memory你可以理解為就是我們例子里的資料庫。

Write Behind Caching Pattern

Write Behind 又叫 Write Back。一些了解Linux操作系統內核的同學對write back應該非常熟悉,這不就是Linux文件系統的Page Cache的演算法嗎?是的,你看基礎這玩意全都是相通的。所以,基礎很重要,我已經不是一次說過基礎很重要這事了。

Write Back套路,一句說就是,在更新數據的時候,只更新緩存,不更新資料庫,而我們的緩存會非同步地批量更新資料庫。這個設計的好處就是讓數據的I/O操作飛快無比(因為直接操作內存嘛 ),因為非同步,write backg還可以合併對同一個數據的多次操作,所以性能的提高是相當可觀的。

但是,其帶來的問題是,數據不是強一致性的,而且可能會丟失(我們知道Unix/Linux非正常關機會導致數據丟失,就是因為這個事)。在軟體設計上,我們基本上不可能做出一個沒有缺陷的設計,就像演算法設計中的時間換空間,空間換時間一個道理,有時候,強一致性和高性能,高可用和高性性是有衝突的。軟體設計從來都是取捨Trade-Off。

另外,Write Back實現邏輯比較複雜,因為他需要track有哪數據是被更新了的,需要刷到持久層上。操作系統的write back會在僅當這個cache需要失效的時候,才會被真正持久起來,比如,內存不夠了,或是進程退出了等情況,這又叫lazy write。

在wikipedia上有一張write back的流程圖,基本邏輯如下:

再多嘮叨一些

1)上面講的這些Design Pattern,其實並不是軟體架構里的mysql資料庫和memcache/redis的更新策略,這些東西都是計算機體系結構里的設計,比如CPU的緩存,硬碟文件系統中的緩存,硬碟上的緩存,資料庫中的緩存。基本上來說,這些緩存更新的設計模式都是非常老古董的,而且歷經長時間考驗的策略,所以這也就是,工程學上所謂的Best Practice,遵從就好了。

2)有時候,我們覺得能做宏觀的系統架構的人一定是很有經驗的,其實,宏觀系統架構中的很多設計都來源於這些微觀的東西。比如,雲計算中的很多虛擬化技術的原理,和傳統的虛擬內存不是很像么?Unix下的那些I/O模型,也放大到了架構里的同步非同步的模型,還有Unix發明的管道不就是數據流式計算架構嗎?TCP的好些設計也用在不同系統間的通訊中,仔細看看這些微觀層面,你會發現有很多設計都非常精妙……所以,請允許我在這裡放句觀點鮮明的話——如果你要做好架構,首先你得把計算機體系結構以及很多老古董的基礎技術吃透了

3)在軟體開發或設計中,我非常建議在之前先去參考一下已有的設計和思路,看看相應的guideline,best practice或design pattern,吃透了已有的這些東西,再決定是否要重新發明輪子。千萬不要似是而非地,想當然的做軟體設計。

4)上面,我們沒有考慮緩存(Cache)和持久層(Repository)的整體事務的問題。比如,更新Cache成功,更新資料庫失敗了怎麼嗎?或是反過來。關於這個事,如果你需要強一致性,你需要使用「兩階段提交協議」——prepare, commit/rollback,比如Java 7 的XAResource,還有MySQL 5.7的 XA Transaction,有些cache也支持XA,比如EhCache。當然,XA這樣的強一致性的玩法會導致性能下降,關於分散式的事務的相關話題,你可以看看《分散式系統的事務處理》一文。

(全文完)


memcached怎麼樣mysql結合使用? - 郭無心的回答


1. 先調優,確認你的MySQL性能足夠好,重新調整架構的代價比較大;

2. 並不是所有數據都需要緩存,訪問頻率高,生成代價比較高的才考慮是否緩存,也就是說影響你性能瓶頸的考慮去緩存。

3. 修改量大不是問題,好好設計自己訪問資料庫和緩存的代碼。


前提:較少變更的數據才適合做緩存

緩存讀取流程:

1、先到緩存中查數據

2、緩存中不存在則到實際數據源中取,取出來後放入緩存

3、下次再來取同樣信息時則可直接從緩存中獲取

緩存更新流程:

1、更新資料庫

2、使緩存過期或失效,這樣會促使下次查詢數據時在緩存中查不到而重新從資料庫去一次。

通用緩存機制:

1、用查詢的方法名+參數作為查詢時的key value對中的key值

2、向memcache或redis之類的nosql資料庫(或者內存hashmap)插入數據

3、取數據時也用方法名+參數作為key向緩存數據源獲取信息

java實現可參考ehcache


實現一個帶redis緩存的jdbc驅動...(逃


之前做過這個,是用redis的hashset做的,多台一主多從的環境下

啟動的時候先從資料庫里全部讀出來,存到redis里,分主庫和備庫,有周期刷新和手動刷新機制,周期刷新和手動刷新是刷新備庫,刷新完成後主備切換。主庫提供查詢功能,備庫是用來和資料庫同步的。查詢的時候先從redis查再從資料庫查,redis沒有就進行更新。

當時遇到的問題主要是

啟動載入的時間問題,另外如果使用redis持久化和分散式的話,從磁碟同步和主從同步期間redis都是不能提供查詢的。

緩存功能不管發生了什麼,核心業務都不應該有影響,這部分功能要剝離好,和redis連接異常,redis宕機都不能影響核心業務,而且要可恢復。

數據量的問題,多線程的問題,還有redis master slave切換時redis信息的維護,數據結構要設計好。


之前有個項目做過這個,結果是緩存命中率極低,後面改按需緩存,比如只針對部分表緩存,後面效果也一般,其實按業務緩存是最靠譜的,但是很多開發很懶,希望在架構上解決問題,你們懂得.

一般緩存分2種,一種是ID為key緩存,主要用於單條數據查詢,可以考慮基於orm實現。第二種是以查詢sql緩存,主要用於查詢大量數據。實現期間最需要注意的是緩存更新策略,這個問題解決了,基本上也就實現了大部分的需求,解決的不太好,會導致數據不同步的坑爹問題


如果是pg還有得救,用trigger結合pgmemcache實現write back。


使用頻繁的數據可以放,不常用的可以稍後在改


目前我用的方法是連讀帶寫,緩存里沒有就寫一份。按照業務訪問頻次設置一個失效時間,防止redis空間被浪費。

你考慮的問題可以這麼解決,做一個裝飾器,把表結構映射到緩存的數據結構,每次讀數據,裝飾器先檢查緩存是否存在,不存在讀庫就行了。


java的話可以用sql攔截器來做統一處理啊,解藕不會業務代碼混在一起,但是要判斷好事務的處理,某些需要事務裡面需要實時數據肯定不能讓他走緩存了


樓主有沒有聽說過AOP


這種老的項目,建議使用主動緩存,先把所有需要緩存的內容緩存起來,數據更新可以使用alibaba開源的canal來實現更新緩存,參考:https://github.com/alibaba/canal


同步緩存(如果業務合適)

先寫db,再寫redis,

只讀redis

2次寫出現不一致(可忽略不計),可從db恢復或其他方案

db是唯一持久化的地方


Haisql_memcache_1.0.35性能測試報告
目前版本的 Haisql_memcache 新功能方面沒有新增,但是性能優化後,有不少提高,版本號1.0.35主要是優化了內存分配機制,對於最常見的變數std::string,std::shared_ptr&等等都定義了自己的更高效率的內置資源池,實現更好的內存分配器和內存回收機制,第2個就是將各變數組合排列,將同時變化的放到一起,提高CPU cacheline的效率,第3個就是將網路層boost asio中的函數盡量使用更接近底層的調用函數receive/send等,減少調用的層次等等辦法。
目前查詢性能已經比 memcached 原生版本高64%,測試方法和測試軟體都是官方的 memcslap( ubuntu自帶安裝包 ) 。本機測試的時候,打開 top 可以明顯看到伺服器軟體 haisql_memcache 比測試軟體memcslap消耗的CPU資源小得多,說明了伺服器端代碼的效率確實是非常高。
一.與memcached性能對比的測試方法與結果:
1.運行伺服器
1) 運行 memcached, 需要指定8192M內存, 以便可以測試百萬以上的數據量, 指定埠號是1983,命令如下:
memcached -m 8192 -p 1983
2) 我們的程序是動態申請內存,因此,不需要指定預先分配的內存量, 直接運行就可以了,啟動運行伺服器命令如下:
./haisql_memcache_chinese
我們的軟體默認運行埠是1971
每次測試後需要清理運行環境, memcached直接ctrl_C退出就可以了.
我們的軟體因為退出時默認自動保存內存數據, 因此,需要在退出程序後,執行 rm *.txt;sync;sync 清理掉自動保存的數據, 相當於清空全部數據,這樣下次啟動時就是空數據表了.
2.使用 memcslap的測試情況
1)先測試 讀性能
測試軟體自動寫入1萬條記錄, 然後測試讀取900萬次, 我們的軟體執行時間是23.631秒, memcache執行時間是38.828秒, 我們的軟體比memcache快64%.
我們的軟體讀性能如下: 讀4.1Kbyte的數據包大小, 4核8線程3.4G主頻DDR3內存, 讀900萬次數據, 900並發, 花費時間 23.631秒, 讀性能38萬QPS。
讀性能 測試結果:
第1次是memcached的測試結果, 第2次是我們的軟體測試結果
guo@guo-desktop:~$ memcslap --concurrency=900 --servers=127.0.0.1:1983 --test=get
Threads connecting to servers 900
Took 38.828 seconds to read data

guo@guo-desktop:~$ memcslap --concurrency=900 --servers=127.0.0.1:1971 --test=get
Threads connecting to servers 900
Took 23.631 seconds to read data
2)測試 寫性能
測試軟體測試寫入100萬條記錄, 我們的軟體執行時間是3.136秒, memcache執行時間是4.078秒, 我們的軟體比memcache快30%.
我們的軟體寫性能如下: 寫4.1Kbyte的數據包大小, 4核8線程3.4G主頻DDR3內存, 寫100萬次數據, 100並發, 花費時間3.136秒, 寫性能32萬TPS.
寫性能 測試結果:
第1次是memcached的測試結果, 第2次是我們的軟體測試結果
guo@guo-desktop:~$ memcslap --concurrency=100 --servers=127.0.0.1:1983
Threads connecting to servers 100
Took 4.078 seconds to load data

guo@guo-desktop:~$ memcslap --concurrency=100 --servers=127.0.0.1:1971
Threads connecting to servers 100
Took 3.136 seconds to load data
二.與redis性能對比的測試方法與結果:
由於redis只支持單核,不支持多核,為了與redis做比較,專門找了一台雙核的機器,Intel(R) Pentium(R) CPU G3258 @ 3.20GHz,本機127.0.0.1測試, 相當於一個CPU用於測試,一個CPU用於伺服器, 以便測試對比一下與Redis的單核性能對比.
單核查詢性能還是比redis更快, 先插入10000條記錄,900連接,每連接10000次查詢,總共測試900萬次查詢,每次查詢value包大小4096位元組,redis花費93.46秒, 我們的軟體花費63.792秒, 測試方法都是官方的測試軟體,我們的單核性能比redis的單核性能快 46.5%.
測試情況如下:
guo@guo-desktop2:~$ redis-cli
127.0.0.1:6379&> flushall
OK
127.0.0.1:6379&> quit
guo@guo-desktop2:~$
guo@guo-desktop2:~$
guo@guo-desktop2:~$ redis-benchmark -t set -n 10000 -r 10000 -d 4096 -c 100
====== SET ======
10000 requests completed in 0.08 seconds
100 parallel clients
4096 bytes payload
keep alive: 1

91.61% &<= 1 milliseconds 99.99% &<= 2 milliseconds 100.00% &<= 2 milliseconds 125000.00 requests per second

guo@guo-desktop2:~$ redis-benchmark -t get -n 9000000 -r 10000 -d 4096 -c 900
====== GET ======
9000000 requests completed in 93.46 seconds
900 parallel clients
4096 bytes payload
keep alive: 1

0.00% &<= 4 milliseconds 65.86% &<= 5 milliseconds 72.01% &<= 6 milliseconds 72.98% &<= 7 milliseconds 73.56% &<= 8 milliseconds 97.70% &<= 9 milliseconds 99.45% &<= 10 milliseconds 99.72% &<= 11 milliseconds 99.80% &<= 12 milliseconds 99.97% &<= 13 milliseconds 100.00% &<= 14 milliseconds 100.00% &<= 14 milliseconds 96294.80 requests per second

guo@guo-desktop2:~$ memcslap --concurrency=900 --servers=127.0.0.1:1971 --test=get
Threads connecting to servers 900
Took 63.792 seconds to read data


菜鳥弱問。。。你們內存資料庫和mysql的同步機制能介紹下嗎?


如果在應用層和數據訪問層之間加個緩存就需要改大量代碼,那麼說明原來寫的就有問題。因為如果應用層只通過數據訪問層訪問數據的話,加緩存只需要修改數據訪問層的實現,對上介面是不變的


對於經常變更的數據沒有必要做緩存


推薦閱讀:

各種內存NoSQL,什麼情況下才有必要使用,SQL性能真的很糟糕?
怎樣理解分析王垠文章《SQL,NoSQL 以及資料庫的實質》的觀點?
Redis集群方案應該怎麼做?
MySQL為主,NoSQL為輔正成為網站技術的標配嗎?
NoSQL 會有注入問題嗎?

TAG:MySQL | Redis | NoSQL | 緩存 | Memcached |