cache 的簡單認識與思考
之前對NOSQL的總結是:基本功能是key-value, 然后各自附加特殊功能. 現在簡單的來認識一下cache.
背景
大家都知道NoSQL, 大多數都是 key-value 型的數據庫.
有些內存型的數據庫甚至可以當做遠程cache來使用, 比如memcache, redis, 或者公司內部的TMEM等數據庫.
有時候,我們認為遠程cache依舊太慢,畢竟一次網絡操作來回至少幾十毫秒, 加上網上不穩定等因素, 本地cache還是很有必要的.
目前,我使用的本地cache往往是開一個共享內存, 然后服務會開若干進程, 這些進程同時讀寫本地cache, 基本上解決了服務高訪問量的問題.
這個本地cache使用的是一個通用的cache庫, 特點是使用hashtable實現, 缺點是key和value的長度是固定的.
固定的長度, 會導致幾個問題.
- key和value 最大長度限制.
- 很浪費空間, 所有的value占用相同的空間. 我們的value及時只有很小的長度, 占用的空間依舊很大.
后來, 我遇到這么一個問題: 有大量的數據, value在短時間內只有一個值:0或者1.
看到這個, 大家第一個想到的應該是位壓縮吧.
但是注意這里有兩個問題: 1.我們不能把所有的key儲存下來, 2.value只是在短時間內不變,過了時間就失效了.
其實, 這就是cache能夠滿足的事情, 只不過這個cache的value比較極端,只有幾字節.
再后來,我又遇到一個問題:有大量的數據, value在短時間內非常大,有時候高大30K.
這個問題cache還是可以滿足的, 只是這個cache的value又是另一個極端: value非常大.
針對這兩個問題, 我們需要改造cache, 來分別適應兩個極端情況.
小value極端
對于小value極端情況, 解決方案簡單點: 初始化共享內存時動態指定value的長度即可.
當然儲存的數據結構的參數也要調整一下, 目的是讓key在hash的時候足夠的分散, 畢竟我們的value長度可能還沒有key的長度長.
當然我們也可以換一下數據結構, 使用hash list 或者 平衡樹.不過一般情況下不要使用樹這個數據結構吧, 實現復雜(正確性難以保證),復雜度還是log級別的(和hash相比,還是差一個數量級).
大value極端
對于大value極端情況, 我們就不能簡單的增大value來解決問題了.
因為這個大value只是說明極端情況下, 有些value很大, 一般情況下, value還是中等大小的.
如果我們簡單增大value, 將會浪費很大內存(內存還是很寶貴的).
PS:我第一次簡單的增大value時,增大前占用共享內存是1.6G, 單純的增大value值,沒有減小節點數的時候, 機器瞬時死了,一查內存,申請了24G內存, 16G是虛擬內存,嚇尿了.
面對上面的問題,我們目前的儲存方式是不能解決的, 所以需要換一種儲存方式.
最直接的想法就是key和value分開儲存, 我們key的數據結構中只需要儲存value的一些元信息即可.
這個時候,對于value的儲存,很容易想到操作系統內存分配算法, 對,就是這個方法, 對value分頁, 然后保存每一頁的信息即可.
這個時候,再回過頭看看這個儲存方式, 可以支持任意大小的value值了, 對于小value, 浪費頂多不到一頁的內存.當然, 代價是我們需要另外預留一片內存,來管理value的頁塊.
尾記
這種value極小, 極大, 一般的情況在我的一個服務中都遇到了, 起初是固定value值比較小, 對于大value,直接報寫cache錯誤.后來增大value后, 寫cache正常了, 但是cache的節點數講了一個數量級:由30000變成2000, 根據監控, cache的利用率瞬時飆到80%.
目前比較偷懶的方法是把cache庫改造拆分成三個庫, 極小情況下使用極小value的cache庫, 極大情況下使用極大value的cache庫.
PS: 之前感覺共享內存很高大上, 看了源代碼, 發現我們先得到共享內存的指針,然后, 共享內存也僅僅只是一片內存而已了,怎么干還是自己說了算.
PS: 關于共享內存加鎖的問題, 不在該文章范圍內, 故不許考慮多寫加鎖問題.