一、 為什麼 Redis 那麼快?
Redis 是基于記憶體的單程序單線程模型的 KV 資料庫,由 C 語言編寫,官方提供的資料是可以達到 100000+ QPS。
- 完全基于記憶體,絕大部分請求是純粹的記憶體操作,非常快速;
- 資料結構簡單,對資料操作也簡單,Redis 中的資料結構是專門進行設計的;
- 采用單線程,避免了不必要的上下文切換和競争條件,也不存在多程序或者多線程導緻的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導緻的性能消耗;
- 使用多路 I/O 複用模型,非阻塞 IO;
- 使用底層模型不同,它們之間底層實作方式以及與用戶端之間通信的應用協定不一樣,Redis直接自己建構了 VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
二、Redis 主從之間的資料是怎麼同步的?
你啟動一台 slave 的時候,它會發送一個 psync 指令給 master ,如果是這個 slave 第一次連接配接到 master,它會觸發一個全量複制。master 就會啟動一個線程,生成 RDB 快照,還會把新的寫請求都緩存在記憶體中,RDB 檔案生成後,master 會将這個 RDB 發送給 slave 的,slave 拿到之後做的第一件事情就是寫進本地的磁盤,然後加載進記憶體,然後 master 會把記憶體裡面緩存的那些新指令都發給 slave。
repl-blacklog-size 複制積壓緩沖區過小
主從複制過程中,複制積壓緩沖區裡面存放的資料為以下三個時間點的資料:
1)master 執行 rdb bgsave 産生 snapshot 的時間
2)master 發送 rdb 到 slave網絡傳輸時間
3)slave load rdb 檔案把資料恢複到記憶體的時間
如果在主從複制過程中(rdb 全量同步),在上面 3 個時間點産生的資料大于 repl-blacklog-size,則主從複制失敗,需要重新進行全量複制,是以要合理的設定 repl-blacklog-size 的大小。
repl-timeout 主從複制逾時
以下三種情況認為複制逾時:
1)slave 角度,在 repl-timeout 時間内沒有收到 master SYNC 傳輸的 rdb snapshot 資料;
2)slave 角度,在 repl-timeout 沒有收到 master 發送的資料包或者 ping;
3)master 角度,在 repl-timeout 時間沒有收到 REPCONF ACK 确認資訊。
當 redis 檢測到 repl-timeout 逾時(預設值 60s),将會關閉主從之間的連接配接,redis slave 發起重建立立主從連接配接的請求,對于記憶體資料集比較大的系統,可以增大 repl-timeout 參數。
三、Redis 的 AOF 重寫?
Redis 的 AOF 機制有點類似于 Mysql binlog,是 Redis 的提供的一種持久化方式(另一種是RDB),它會将所有的寫指令按照一定頻率(no, always, every seconds)寫入到日志檔案中,當 Redis 停機重新開機後恢複資料庫。
随着 AOF 檔案越來越大,裡面會有大部分是重複指令或者可以合并的指令(100次incr = set key 100), 重寫減少 AOF 日志尺寸,減少記憶體占用,加快資料庫恢複時間。
redis fork 的子程序完成 AOF 重寫工作之後,它會向父程序發送一個信号,父程序在接收到該信号之後,會調用一個信号處理函數,并執行以下工作:
- 将 AOF 重寫緩沖區中的所有内容寫入到新的 AOF 檔案中,保證新 AOF 檔案儲存的資料庫狀态和伺服器目前狀态一緻。
- 對新的 AOF 檔案進行改名,原子地覆寫現有 AOF 檔案,完成新舊檔案的替換
- 繼續處理用戶端請求指令。
在整個 AOF 背景重寫過程中,隻有信号處理函數執行時可能會對 Redis 主程序造成阻塞,在其他時候,AOF 背景重寫都不會阻塞主程序。解決方向大概有:
- 将 no-appendfsync-on-rewrite 設定為 yes,表示在日志重寫時,不進行指令追加操作,而隻是将指令放在重寫緩沖區裡,避免與指令的追加造成磁盤 IO 上的沖突,但是在 rewrite 期間的 AOF 有丢失的風險。
- 設定 auto-aof-rewrite-percentage = 0,關閉自動重寫,選擇在低峰期定時執行。
- 給目前 Redis 執行個體添加 slave 節點,目前節點設定為 master, 然後 master 節點關閉 AOF 重寫,slave 節點開啟 AOF 重寫。這樣的方式的風險是如果 master 挂掉,尚沒有同步到 slave 的資料會丢失。
六、redis-server 的 timeout、tcp-keepalive 分析
redis 的 timeout、tcp-keepalive 兩個參數預設值都是0。
tcp-keepalive:TCP 心跳包,如果設定為非零,則在與用戶端缺乏通訊的時候使用 SO_KEEPALIVE 發送 tcp acks 給用戶端;如果設定成零值,redis-server 将不使用 TCP 協定棧提供的保活機制,會認為用戶端連接配接一直存在,不會有 TCP 的 KEEPALIVE 封包在 redis 用戶端和服務端傳輸。下面一段話摘自 TCP/IP 詳解:

timeout:指定在一個 client 空閑多少秒之後關閉連接配接,該值是 redis-server 應用程式的行為,與 tcp 協定棧無關。
七、其他
redis 叢集中,如果從節點與主節點的斷開時間過長,會觸發全量同步,全量同步時主節點會 dump rdb 檔案,可能觸發主從切換,影響叢集穩定性。
注意 redis 号稱的單線程隻是處理我們的網絡請求或檔案事件分派時隻有一個線程來處理,redis-server 肯定不止隻有一個線程。
IO 多路複用,這裡的多路指的是多個請求,複用指的是複用同一個線程。
常見的過期政策:
- FIFO(First In First On,先進先出):根據緩存被存儲的時間,離目前最遠的資料優先被淘汰;
- LRU(Least Recently Used,最近最少使用):根據最近被使用的時間,離目前最遠的資料優先被淘汰;
- LFU(Least Frequently Used,最不經常使用):在一段時間内,緩存被使用次數最少的會被淘汰;
因為 Redis 是基于記憶體的操作,CPU 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是集器記憶體的大小或者網絡帶寬。
Redis Cluster 劃分了 16384 個槽位,每個節點負責其中的一部分資料,都會存儲槽位的資訊。通路某個具體的資料 Key,可以根據本地的槽位來确定需要連接配接的節點。
Spring 封裝的
stringRedisTemplate.getConnectionFactory().getConnection()
擷取 pool 中的 redisConnection 後,不會釋放或者退還到連接配接池中,雖然業務已處理完畢 redisConnection 已經空閑,但是 pool 中的 redisConnection 的狀态還沒有回到 idle 狀态。我們可以使用下面代碼來執行:
stringRedisTemplate.execute(new RedisCallback<Cursor>() {
@Override
public Cursor doInRedis(RedisConnection connection) throws DataAccessException {
return connection.scan(options);
}
});
或者使用完 connection 後執行來釋放 Connection。
RedisConnectionUtils.releaseConnection(conn, factory);