Redis時延問題,慢查詢優化


關於Jedis連接池

使用普通jedis對象和jedis連接池之間的差別


 

關於Redis中比較耗時的命令

keys、sort,exists等命令

1.keys [pattern]模式查詢 O(n) :

keys hello* 以hello開頭的key值。

2.sort 主要對List,Set,Zset來進行排序。

sort命令的時間復雜度是O(n+mlog(m)),其中n是排序列表(集合和有序集合)中元素的個數,m是返回元素的個數。Redis在排序前會建立一個長度為n的的容器來存儲待排序元素,雖然是一個臨時的過程,但是多個較大數據的排序操作則會嚴重影響系統的性能。(這里返回的元素的個數才是真正需要排序的, 但底層到底采用的什么算法來排序的呢?
在使用這個命令的時候:
1.盡可能減少排序鍵中的元素個數,降低n
2.使用Limit參數只獲取需要的數據,降低n
3.如果要排序的數據量較大,盡可能使用store名來緩存結果。
對於以上問題的解決方案:
     在redis的設計中體現的非常明顯,redis的純內存操作,epoll非阻塞IO事件處理,這些快的放在一個線程中搞定,而持久化,AOF重寫、Master-slave同步數據這些耗時的操作就單開一個進程來處理,不要慢的影響到快的;
      同樣,既然需要使用keys這些耗時的操作,那么我們就將它們剝離出去,比如單開一個redis slave結點,專門用於keys、sort等耗時的操作,這些查詢一般不會是線上的實時業務,查詢慢點就慢點,主要是能完成任務,而對於線上的耗時快的任務沒有影響

3.exists key_name:查詢key是否存在

redis本身是key-value的形式,時間復雜度本來是O(1),但是為什么會超時呢?

     我們發現在EXISTS命令處理函數中實現了清除過期key的主動策略,會先調用expireIfNeeded函數檢查要訪問的key是否過期,如果過期就delete掉這個key。del命令在刪除元素很多的復合數據類型(list、hash、zset、set)時是一個很耗時的操作。由於存在元素很多的zset,和ZADD一樣,在刪除zset時需要一個一個遍歷所有元素,時間復雜度是大O(n)。由於這個刪除操作在EXISTS命令的處理函數中執行,所以導致EXISTS耗時過長。

    上面主要的原因還是我們處理過期key的方式的問題,我們處理過期key:

     1.定期刪除:redis默認是每隔 100ms 就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果過期就刪除。注意這里是隨機抽取的。為什么要隨機呢?你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設置過期時間的 key 的話,就會給 CPU 帶來很大的負載!

      2.惰性刪除 :定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉。所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在內存里,除非你的系統去查一下那個 key,才會被redis給刪除掉。然后我們在做每一個操作的時候,都會執行exists keyname 的操作,如果遇到了過期key,我們會在這里面執行刪除key的操作,所以非常花時間。

    針對刪除大key這個問題:

  1.我們可以選擇適當增加過期時間,但不能從根本上解決問題

   2.redis作者提供了解決方案,具體就是使用異步線程對大key進行刪除操作,避免阻塞主線程。

 

4.smembers命令:用於獲取集合全集,如果一個集合中保存了千萬條數據量,一次取回會造成處理線程的長時間阻塞,時間復雜度O(n)

解決方案:

和sort,keys等命令不一樣,smembers可能是線上實時應用場景中使用頻率非常高的一個命令,這里分流一招並不適合,我們更多的需要從設計層面來考慮;
在設計時,我們可以控制集合的數量,將集合數一般保持在500個以內;
比如原來使用一個鍵來存儲一年的記錄,數據量大,我們可以使用12個鍵來分別保存12個月的記錄,或者365個鍵來保存每一天的記錄,將集合的規模控制在可接受的范圍;

如果不容易將集合划分為多個子集合,而堅持用一個大集合來存儲,那么在取集合的時候可以考慮使用SRANDMEMBER key [count];隨機返回集合中的指定數量,當然,如果要遍歷集合中的所有元素,這個命令就不適合了;

5.生成RDB快照文件時,save命令會帶來阻塞:

 當然我們采用bgsave來fork()一個子進程來做數據持久化的bgsave,雖然在redis底層我們采用寫時復制策略copy-on-write(為子進程創建虛擬空間結構,復制父進程的虛擬空間結構,不分配物理內存,就有點像只復制地址,這樣可以極大的提高redis性能,但如果對父進程有寫入操作了,那么我們還是要對子進程復制父進程的物理內存,這是非常耗時的,所以在bgsave命令的時候不要對父進程寫入)。

  在極端的情況下,父進程內存空間特別大,它的頁表大小也會有點大,即使不復制物理內存,也可能很耗時哦。

6.mset,mget也是O(n)

redis慢查詢

對於一個redis命令生命周期:

 

 慢查詢的配置:

1.慢查詢的語句會被放在一個隊列里面

slowlog-max-len這個慢查詢隊列的長度,這個隊列放在內存中不會被持久化(需要定期持久化慢查詢)

2.慢查詢閾值

slowlog-log-slower-than(微秒)當大於這個時間的時候會被放在慢查詢隊列里面

慢查詢命令:

slowlog get [n]:獲取慢查詢隊列,獲取前n條慢查詢的數據。

slowlog len:獲取慢查詢隊列長度

slowlog reset:清空慢查詢隊列

 對於AOF阻塞定位:

info persistence

有aof_delayed_fsync:100可以看到進行了多少個這樣的命令。

pipeline:解決耗時操作的最簡單的操作:(簡單來說就是減少了客戶端發送請求的數量,可以在一定程度上幫助提高性能)

Redis 管道技術可以在服務端未響應時,客戶端可以繼續向服務端發送請求,並最終一次性讀取所有服務端的響應,簡單描述就是一次性可以發送多個請求。

1.pipeline不是原子操作,其中的所有請求還是按原來的順序,但中間可能插入其他的請求。

2.pipeline每次只能作用在一個Redis節點上

沒有pipline

     Jedis jedis = new Jedis("127.0.0.1",6379);
     for(int i=0;i<1000;i++) {
         jedis.hset("hashkey:"+i,"field"+i,"value"+i);
     }

使用pipline

     Jedis jedis = new Jedis("127.0.0.1",6379);
     for(int i=0;i<100;i++) {
         Pipeline pipeline = jedis.pipelined();
         for(int j=i*100;j<(i+1)*100;j++) {
             pipeline.hset("hashkey:"+i,"field"+i,"value"+i);
         }
         pipeline.syncAndReturnAll();
     }

我們也可以通過pipline高效插入:方在2.6版本推出了一個新的功能-pipe mode,即將支持Redis協議的文本文件直接通過pipe導入到服務端。

1.新建文本文件,創建redis命令

SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN

2. 將這些命令轉化成Redis Protocol。

因為Redis管道功能支持的是Redis Protocol,而不是直接的Redis命令。

3.利用管道插入:

cat data.txt | redis-cli --pipe


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM