Redis性能優化

  本文開始會講解一下redis的基本優化,而後會舉一些優化示例代碼或實例。最後講解一下,默認啓動redis時,所報的一些警示錯誤java

1、優化的一些建議linux

一、儘可能使用短的keyredis

固然在精簡的同時,不要爲了key的「見名知意」。對於value有些也可精簡,好比性別使用0、1。數組

二、避免使用keys *服務器

  keys *, 這個命令是阻塞的,即操做執行期間,其它任何命令在你的實例中都沒法執行。當redis中key數據量小時到無所謂,數據量大就很糟糕了。因此咱們應該避免去使用這個命令。能夠去使用SCAN,來代替。併發

三、在存到Redis以前先把你的數據壓縮下dom

redis爲每種數據類型都提供了兩種內部編碼方式,在不一樣的狀況下redis會自動調整合適的編碼方式。tcp

四、設置key有效期高併發

咱們應該儘量的利用key有效期。好比一些臨時數據(短信校驗碼),過了有效期Redis就會自動爲你清除!post

五、選擇回收策略(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 : 永不過時,返回錯誤

六、使用bit位級別操做和byte字節級別操做來減小沒必要要的內存使用

bit位級別操做:GETRANGE, SETRANGE, GETBIT and SETBIT
byte字節級別操做:GETRANGE and SETRANGE

七、儘量地使用hashes哈希存儲

八、當業務場景不須要數據持久化時,關閉全部的持久化方式能夠得到最佳的性能

  數據持久化時須要在持久化和延遲/性能之間作相應的權衡.

九、想要一次添加多條數據的時候可使用管道

十、限制redis的內存大小(64位系統不限制內存,32位系統默認最多使用3GB內存) 

 數據量不可預估,而且內存也有限的話,儘可能限制下redis使用的內存大小,這樣能夠避免redis使用swap分區或者出現OOM錯誤。(使用swap分區,性能較低,若是限制了內存,當到達指定內存以後就不能添加數據了,不然會報OOM錯誤。能夠設置maxmemory-policy,內存不足時刪除數據)

十一、SLOWLOG [get/reset/len]

slowlog-log-slower-than 它決定要對執行時間大於多少微秒(microsecond,1秒 = 1,000,000 微秒)的命令進行記錄。
slowlog-max-len 它決定 slowlog 最多能保存多少條日誌,當發現redis性能降低的時候能夠查看下是哪些命令致使的。

2、管道測試

redis的管道功能在命令行中沒有,可是redis是支持管道的,在java的客戶端(jedis)中是可使用的:

示例代碼:

//注:具體耗時,和自身電腦有關(博主是在虛擬機中運行的數據)
/**
 * 不使用管道初始化1W條數據
 * 耗時:3079毫秒
 * @throws Exception
 */
@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);
}

/**
 * 使用管道初始化1W條數據
 * 耗時:255毫秒
 * @throws Exception
 */
@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);
}

hash的應用

 示例:咱們要存儲一個用戶信息對象數據,包含如下信息:
   key爲用戶ID,value爲用戶對象(姓名,年齡,生日等)若是用普通的key/value結構來存儲,主要有如下2種存儲方式:

一、將用戶ID做爲查找key,把其餘信息封裝成一個對象以序列化的方式存儲
缺點:增長了序列化/反序列化的開銷,引入複雜適應系統(Complex adaptive system)修改其中一項信息時,須要把整個對象取回,而且修改操做須要對併發進行保護。

二、用戶信息對象有多少成員就存成多少個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:

HSET "mediabucket:1155" "1155315" "939"
HGET "mediabucket:1155" "1155315"
"939"

測試: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消耗變得很是大

HSET "mediabucket:1155" "1155315" "939"
HGET "mediabucket:1155" "1155315"
"939"

測試:1百萬消耗16MB的內存。總內存使用也降到了5GB。固然咱們還能夠優化,去掉mediabucket:key長度減小了12個字節。

HSET "1155" "315" "939"
HGET "1155" "315"
"939"

3、優化案例

一、修改linuxTCP監聽的最大容納數量

WARNING: The TCP backlog setting of 511 cannot be enforced because 
/proc/sys/net/core/somaxconn is set to the lower value of 128.

在高併發環境下你須要一個高backlog值來避免慢客戶端鏈接問題。注意Linux內核默默地將這個值減少到/proc/sys/net/core/somaxconn的值,因此須要確認增大somaxconn和tcp_max_syn_backlog兩個值來達到想要的效果。
echo 511 > /proc/sys/net/core/somaxconn
注意:這個參數並非限制redis的最大連接數。若是想限制redis的最大鏈接數須要修改maxclients,默認最大鏈接數爲10000

二、修改linux內核內存分配策略

錯誤日誌:WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. 
To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or 
run the command 'sysctl vm.overcommit_memory=1

   redis在備份數據的時候,會fork出一個子進程,理論上child進程所佔用的內存和parent是同樣的,好比parent佔用的內存爲8G,這個時候也要一樣分配8G的內存給child,若是內存沒法負擔,每每會形成redis服務器的down機或者IO負載太高,效率降低。因此內存分配策略應該設置爲 1(表示內核容許分配全部的物理內存,而無論當前的內存狀態如何)。
內存分配策略有三種
可選值:0、一、2。
0, 表示內核將檢查是否有足夠的可用內存供應用進程使用;若是有足夠的可用內存,內存申請容許;不然,內存申請失敗,並把錯誤返回給應用進程。
1, 無論須要多少內存,都容許申請。
2, 只容許分配物理內存和交換內存的大小(交換內存通常是物理內存的一半)。

三、關閉Transparent Huge Pages(THP)

THP會形成內存鎖影響redis性能,建議關閉

Transparent HugePages :用來提升內存管理的性能
Transparent Huge Pages在32位的RHEL 6中是不支持的
執行命令 echo never > /sys/kernel/mm/transparent_hugepage/enabled
把這條命令添加到這個文件中/etc/rc.local

參考:http://blog.xiaoxiaomo.com/2016/05/02/Redis-優化詳解/