優化的一些建議
1、儘量使用短的 key
當然在精簡的同時,不要忘了 key 的 “見名知意”。對於 value 有些也可精簡,比如性別使用 0、1。
2、避免使用 keys *
keys *, 這個命令是阻塞的,即操作執行期間,其它任何命令在你的實例中都無法執行。當 redis 中 key 數據量小時到無所謂,數據量大就很糟糕了。所以我們應該避免去使用這個命令。可以去使用 SCAN, 來代替。
3、在存到 Redis 之前先把你的數據壓縮下
redis 為每種數據類型都提供了兩種內部編碼方式,在不同的情況下 redis 會自動調整合適的編碼方式。
4、設置 key 有效期
我們應該儘可能的利用 key 有效期。比如一些臨時數據(短信校驗碼),過了有效期 Redis 就會自動為你清除!
5、選擇回收策略 (maxmemory-policy)
當 Redis 的實例空間被填滿了之後,將會嘗試回收一部分 key。根據你的使用方式,強烈建議使用 volatile-lru(默認) 策略 —— 前提是你對 key 已經設置了超時。但如果你運行的是一些類似於 cache 的東西,並且沒有對 key 設置超時機制,可以考慮使用 allkeys-lru 回收機制,具體講解查看 。maxmemory-samples 3 是說每次進行淘汰的時候 會隨機抽取 3 個 key 從裡面淘汰最不經常使用的(默認選項)
maxmemory-policy 六種方式 :
- volatile-lru:只對設置了過期時間的 key 進行 LRU(默認值)
- allkeys-lru : 是從所有 key 裡 刪除 不經常使用的 key
- volatile-random:隨機刪除即將過期 key
- allkeys-random:隨機刪除
- volatile-ttl : 刪除即將過期的
- noeviction : 永不過期,返回錯誤
6、使用 bit 位級別操作和 byte 字節級別操作來減少不必要的內存使用。
- bit 位級別操作:GETRANGE, SETRANGE, GETBIT and SETBIT
- byte 字節級別操作:GETRANGE and SETRANGE
7、儘可能地使用 hashes 哈希存儲。
8、當業務場景不需要數據持久化時,關閉所有的持久化方式可以獲得最佳的性能。
9、想要一次添加多條數據的時候可以使用管道。
10、限制 redis 的內存大小(64 位系統不限制內存,32 位系統默認最多使用 3GB 內存)
數據量不可預估,並且內存也有限的話,儘量限制下 redis 使用的內存大小,這樣可以避免 redis 使用 swap 分區或者出現 OOM 錯誤。(使用 swap 分區,性能較低,如果限制了內存,當到達指定內存之後就不能添加數據了,否則會報 OOM 錯誤。可以設置 maxmemory-policy,內存不足時刪除數據。)
11、SLOWLOG [get/reset/len]
- slowlog-log-slower-than 它決定要對執行時間大於多少微秒 (microsecond,1 秒 = 1,000,000 微秒) 的命令進行記錄。
- slowlog-max-len 它決定 slowlog 最多能保存多少條日誌,當發現 redis 性能下降的時候可以查看下是哪些命令導致的。
優化實例分析
管道性能測試
redis 的管道功能在命令行中沒有,但是 redis 是支持管道的,在 java 的客戶端 (jedis) 中是可以使用的:
示例代碼
<code> @Test
public
void
NOTUsePipeline
() throws Exception { Jedis jedis = JedisUtil.getJedis ();long
start_time = System.currentTimeMillis ();for
(int
i =0
; i 10000; i++) { jedis.set
("aa_"
+i, i+""
); } System.out
.println (System.currentTimeMillis ()-start_time); } @Test
public
void
usePipeline
() throws Exception { Jedis jedis = JedisUtil.getJedis ();long
start_time = System.currentTimeMillis (); Pipeline pipelined = jedis.pipelined ();for
(int
i =0
; i 10000; i++) { pipelined.set
("cc_"
+i, i+""
); } pipelined.sync (); System.out
.println (System.currentTimeMillis ()-start_time); }/<code>
hash 的應用
示例:我們要存儲一個用戶信息對象數據,包含以下信息: key 為用戶 ID,value 為用戶對象(姓名,年齡,生日等)如果用普通的 key/value 結構來存儲,主要有以下 2 種存儲方式:
- 將用戶 ID 作為查找 key, 把其他信息封裝成一個對象以序列化的方式存儲 缺點:增加了序列化 / 反序列化的開銷,引入複雜適應系統(Complex adaptive system,簡稱 CAS)修改其中一項信息時,需要把整個對象取回,並且修改操作需要對併發進行保護。
- 用戶信息對象有多少成員就存成多少個 key-value 對 雖然省去了序列化開銷和併發問題,但是用戶 ID 為重複存儲。
- Redis 提供的 Hash 很好的解決了這個問題,提供了直接存取這個 Map 成員的接口。Key 仍然是用戶 ID, value 是一個 Map,這個 Map 的 key 是成員的屬性名,value 是屬性值。( 內部實現:Redis Hashd 的 Value 內部有 2 種不同實現,Hash 的成員比較少時 Redis 為了節省內存會採用類似一維數組的方式來緊湊存儲,而不會採用真正的 HashMap 結構,對應的 value redisObject 的 encoding 為 zipmap, 當成員數量增大時會自動轉成真正的 HashMap, 此時 encoding 為 ht )。
Instagram 內存優化
Instagram 可能大家都已熟悉,當前火熱的拍照 App,月活躍用戶 3 億。四年前 Instagram 所存圖片 3 億多時需要解決一個問題:想知道每一張照片的作者是誰(通過圖片 ID 反查用戶 UID),並且要求查詢速度要相當的快,如果把它放到內存中使用 String 結構做 key-value:
<code>HSET
"mediabucket:1155"
"1155315"
"939"
HGET"mediabucket:1155"
"1155315"
"939"
/<code>
測試:1 百萬數據會用掉 70MB 內存,3 億張照片就會用掉 21GB 的內存。當時 (四年前) 最好是一臺 EC2 的 high-memory 機型就能存儲(17GB 或者 34GB 的,68GB 的太浪費了), 想把它放到 16G 機型中還是不行的。
Instagram 的開發者向 Redis 的開發者之一 Pieter Noordhuis 詢問優化方案,得到的回覆是使用 Hash 結構。具體的做法就是將數據分段,每一段使用一個 Hash 結構存儲.
由於 Hash 結構會在單個 Hash 元素在不足一定數量時進行壓縮存儲,所以可以大量節約內存。這一點在上面的 String 結構裡是不存在的。而這個一定數量是由配置文件中的 hash-zipmap-max-entries 參數來控制的。經過實驗,將 hash-zipmap-max-entries 設置為 1000 時,性能比較好,超過 1000 後 HSET 命令就會導致 CPU 消耗變得非常大。
<code>HSET
"mediabucket:1155"
"1155315"
"939"
HGET"mediabucket:1155"
"1155315"
"939"
/<code>
測試:1 百萬消耗 16MB 的內存。總內存使用也降到了 5GB。當然我們還可以優化,去掉 mediabucket:key 長度減少了 12 個字節。
<code>HSET
"1155"
"315"
"939"
HGET"1155"
"315"
"939"
/<code>
啟動時 WARNING 優化
在我們啟動 redis 時,默認會出現如下三個警告:
- 一、修改 linux 中 TCP 監聽的最大容納數量
<code>WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn isset
to
thelower
value
of
128.
/<code>
在高併發環境下你需要一個高 backlog 值來避免慢客戶端連接問題。注意 Linux 內核默默地將這個值減小到 /proc/sys/net/core/somaxconn 的值,所以需要確認增大 somaxconn 和 tcp_max_syn_backlog 兩個值來達到想要的效果。
<code>echo511
>/proc/sys
/net/core
/somaxconn
/<code>
注意:這個參數並不是限制 redis 的最大鏈接數。如果想限制 redis 的最大連接數需要修改 maxclients,默認最大連接數為 10000
- 二、修改 linux 內核內存分配策略
<code>錯誤日誌:WARNING overcommit_memory isset
to
0
! Backgroundsave
may failunder
low
memory
condition.To
fix this issueadd
'vm.overcommit_memory = 1'
to
/etc/sysctl.confand
then
rebootor
run the command'sysctl vm.overcommit_memory=1'
/<code>
原因: redis 在備份數據的時候,會 fork 出一個子進程,理論上 child 進程所佔用的內存和 parent 是一樣的,比如 parent 佔用的內存為 8G,這個時候也要同樣分配 8G 的內存給 child, 如果內存無法負擔,往往會造成 redis 服務器的 down 機或者 IO 負載過高,效率下降。所以內存分配策略應該設置為 1(表示內核允許分配所有的物理內存,而不管當前的內存狀態如何)。
內存分配策略有三種
可選值:0、1、2。
- 0, 表示內核將檢查是否有足夠的可用內存供應用進程使用;如果有足夠的可用內存,內存申請允許;否則,內存申請失敗,並把錯誤返回給應用進程。
- 1, 不管需要多少內存,都允許申請。
- 2, 只允許分配物理內存和交換內存的大小 (交換內存一般是物理內存的一半)。
- 三、關閉 Transparent Huge Pages (THP)
THP 會造成內存鎖影響 redis 性能,建議關閉
<code>Transparent HugePages :用來提高內存管理的性能 Transparent Huge Pages 在32
位的 RHEL6
中是不支持的 執行命令 echo never >/sys/kernel
/mm/transparent
_hugepage/enabled 把這條命令添加到這個文件中 /etc/rc.local/<code>