捨得網支撐1000萬pv/數據庫緩存系統java
系統主要是構建在hibernate之上的高效數據庫緩存系統,其中包含了分佈式解決方案,該系統已經應用在捨得網上了,沒有發現大問題,本人也相信該系統已經足夠強大,應付數百萬IP/天的應用都不是問題。mysql
代碼看上去很簡單,其實倒是兩年經驗的總結,整過過程也遇到了不少難點,最後一一解決了。本系統很是簡潔易用,主程序BaseManager.java不到1000行代碼,用「精悍」來形容絕對不爲過,1000行代碼卻包含了數據庫對象的緩存、列表和長度的緩存、按字段散列緩存、update延時更新、自動清除列表緩存等功能,用它來實現像論壇、博客、校友錄、交友社區等絕大部分應用網站都足夠了。算法
如今進入正題。。。。。sql
爲何要用緩存?若是問這個問題說明你仍是新手,數據庫吞吐量畢竟有限,每秒讀寫5000次了不得了,若是不用緩存,假設一個頁面有100個數據庫操做,50個用戶併發數據庫就歇菜,這樣最多能支撐的pv也就50*3600*15=270萬,並且數據庫服務器累得半死,搞很差何時就累死了。個人這套緩存系統比單獨用memcached作緩存還要強大,至關於在memcached上再作了兩級緩存,你們都知道memcached很強了,可是吞吐量仍是有限,每秒20000次get和put當遇到超大規模的應用時仍是會歇菜,本地HashMap每秒可執行上百萬次put和get,在這上面損耗的性能幾乎能夠忽略不記了。舒適提示:能不用分佈式的時候就不要用分佈式,非用分佈式的時候再考慮用memcached,個人緩存系統在這方面都已經實現了,改個配置就能夠了,有興趣的能夠仔細測試測試!數據庫
通常數據庫緩存在我看來包含四種。第一種:單個對象的緩存(一個對象就是數據庫一行記錄),對於單個對象的緩存,用HashMap就能夠了,稍微複雜一點用LRU算法包裝一個HashMap,再複雜一點的分佈式用memcached便可,沒什麼太難的;第二種:列表緩存,就像論壇裏帖子的列表;第三種:長度的緩存,好比一個論壇板塊裏有多少個帖子,這樣才方便實現分頁。第四種:複雜一點的group,sum,count查詢,好比一個論壇裏按點擊數排名的最HOT的帖子列表。第一種比較好實現,後面三種比較困難,彷佛沒有通用的解決辦法,我暫時以列表緩存(第二種)爲例分析。緩存
mysql和hibernate的底層在作通用的列表緩存時都是根據查詢條件把列表結果緩存起來,可是隻要該表的記錄有任何變化(增長/刪除/修改),列表緩存要所有清除,這樣只要一個表的記錄常常變化(一般狀況都會這樣),列表緩存幾乎失效,命中率過低了。服務器
本人想了一個辦法改善了列表緩存,當表的記錄有改變時,遍歷全部列表緩存,只有那些被影響到的列表緩存纔會被刪除,而不是直接清除全部列表緩存,好比在一個論壇版(id=1)裏增長了一個帖子,那麼只要清除id=1這個版對應的列表緩存就能夠了,版id=2就不用清除了。這樣處理有個好處,能夠緩存各類查詢條件(如等於、大於、不等於、小於)的列表緩存,但也有個潛在的性能問題,因爲須要遍歷,CPU符合比較大,若是列表緩存最大長度設置成10000,兩個4核的CPU每秒也只能遍歷完300屢次,這樣若是每秒有超過300個insert/update/delete,系統就吃不消了,此路不通。併發
在前面兩種解決辦法都不完美的狀況下,本人和同事通過幾個星期的思索,總算得出了根據表的某幾個字段作散列的緩存辦法,這種辦法無需大規模遍歷,因此CPU符合很是小,因爲這種列表緩存按照字段作了散列,因此命中率極高。思路以下:每一個表有3個緩存Map(key=value鍵值對),第一個Map是對象緩存A,在A中,key是數據庫的id,Value是數據庫對象(也就是一行數據);第二個Map是通用列表緩存B,B的最大長度通常1000左右,在B中,key是查詢條件拼出來的String(如start=0,length=15#active=0#state=0),Value是該條件查詢下的全部id組成的List;第三個Map是散列緩存C,在C中,key是散列的字段(如根據userId散列的話,其中某個key就是userId=109這樣的String)組成的String,value是一個和B相似的HashMap。其中只有B這個Map是須要遍歷的,不知道說明白了沒有,看完小面這個例子應該就明白了,就用論壇的回覆表做說明,假設回覆表T中假設有字段id,topicId,postUserId等字段(topicId就是帖子的id,postUserId是發佈者id)。負載均衡
第一種狀況,也是最經常使用的狀況,就是獲取一個帖子對應的回覆,sql語句應該是象分佈式
select id from T where topicId=2008 order by createTime desc limit 0,5
select id from T where topicId=2008 order by createTime desc limit 5,5
select id from T where topicId=2008 order by createTime desc limit 10,5
的樣子,那麼這種列表很顯然用topicId作散列是最好的,把上面三個列表緩存(能夠是N個)都散列到key是topicId=2008這一個Map中,當id是2008的帖子有新的回覆時,系統自動把key是topicId=2008的散列Map清除便可。因爲這種散列不須要遍歷,所以能夠設置成很大,例如100000,這樣10萬個帖子對應的全部回覆列表均可以緩存起來,當有一個帖子有新的回覆時,其他99999個帖子對應的回覆列表都不會動,緩存的命中率極高。
第二種狀況,就是後臺須要顯示最新的回覆,sql語句應該是象
select id from T order by createTime desc limit 0,50
的樣子,這種狀況不須要散列,由於後臺不可能有太多人訪問,經常使用列表也不會太多,因此直接放到通用列表緩存B中便可。
第三種狀況,獲取一個用戶的回覆,sql語句象
select id from T where userId=2046 order by createTime desc limit 0,15
select id from T where userId=2046 order by createTime desc limit 15,15
select id from T where userId=2046 order by createTime desc limit 30,15
的樣子,那麼這種列表和第一種狀況相似,用userId作散列便可。
第四種狀況,獲取一個用戶對某個帖子的回覆,sql語句象
select id from T where topicId=2008 and userId=2046 order by createTime desc limit 0,15
select id from T where topicId=2008 and userId=2046 order by createTime desc limit 15,15
的樣子,這種狀況比較少見,通常以topicId=2008爲準,也放到key是topicId=2008這個散列Map裏便可。
那麼最後的緩存結構應該是下面這個樣子:
緩存A是:
Key鍵(long型)Value值(類型T)
11Id=11的T對象
22Id=22的T對象
133Id=133的T對象
……
列表緩存B是:
Key鍵(String型)Value值(ArrayList型)
from T order by createTime desc limit 0,50ArrayList,對應取出來的全部id
from T order by createTime desc limit 50,50ArrayList,對應取出來的全部id
from T order by createTime desc limit 100,50ArrayList,對應取出來的全部id
……
散列緩存C是:
Key鍵(String型)Value值(HashMap)
userId=2046Key鍵(String型)Value值(ArrayList)
userId=2046#0,5id組成的List
userId=2046#5,5id組成的List
userId=2046#15,5id組成的List
……
userId=2047Key鍵(String型)Value值(ArrayList)
userId=2047#0,5id組成的List
userId=2047#5,5id組成的List
userId=2047#15,5id組成的List
……
userId=2048Key鍵(String型)Value值(ArrayList)
userId=2048#topicId=2008#0,5id組成的List
userId=2048#5,5id組成的List
userId=2048#15,5id組成的List
……
……
總結:這種緩存思路能夠存儲大規模的列表,緩存命中率極高,所以能夠承受超大規模的應用,可是須要技術人員根據自身業務邏輯來配置須要作散列的字段,通常用一個表的索引鍵作散列(注意順序,最散的字段放前面),假設以userId爲例,能夠存儲N個用戶的M種列表,若是某個用戶的相關數據發生變化,其他N-1個用戶的列表緩存紋絲不動。以上說明的都是如何緩存列表,緩存長度和緩存列表思路徹底同樣,如緩存象select count(*) from T where topicId=2008這樣的長度,也是放到topicId=2008這個散列Map中。若是再配合好使用mysql的拆表和memcached,加上F5設備作分佈式負載均衡,該系統對付像1000萬IP/天這種規模級的應用都足夠了。
若是以爲不錯,請把該文貼到本身的博客中或者收藏本文,謝謝