Redis優化建議

alterem發表於2019-08-04

優化的一些建議

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)中是可以使用的:

img

示例程式碼

    //注:具體耗時,和自身電腦有關(博主是在虛擬機器中執行的資料)
    /**
     * 不使用管道初始化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,簡稱CAS)修改其中一項資訊時,需要把整個物件取回,並且修改操作需要對併發進行保護。

img

  • 使用者資訊物件有多少成員就存成多少個key-value對
    雖然省去了序列化開銷和併發問題,但是使用者ID為重複儲存。

img

  • Redis提供的Hash很好的解決了這個問題,提供了直接存取這個Map成員的介面。Key仍然是使用者ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值。( 內部實現:Redis Hashd的Value內部有2種不同實現,Hash的成員比較少時Redis為了節省記憶體會採用類似一維陣列的方式來緊湊儲存,而不會採用真正的HashMap結構,對應的value redisObject的encoding為zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding為ht )。

img

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"

啟動時WARNING優化

在我們啟動redis時,預設會出現如下三個警告:

img

  • 一、修改linux中TCP監聽的最大容納數量
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、1、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

原文出處

xiaoxiaomo -> http://blog.xiaoxiaomo.com/2016/05/02/Redis-%E4%BC%98%E5%8C%96%E8%AF%A6%E8%A7%A3/

相關文章