Redis 原理及混合持久化
上一篇文章我們對redis的基本類型及相應的應用場景做了了解,那你知道redis 為什么這么快嗎?redis 宕機了怎么辦?redis 的持久化方式有哪些?如果你是云服務廠商,你怎么優化你的云版的redis呢?
本文將對redis 的相關原理進行分析。
線程 IO 模型
redis 是單線程程序的,除了 Redis 之外,Node.js 也是單線程,Nginx 也是單線程,但是它們都是服務器高性能的典范。
redis 單線程也這么快,也要歸功于非阻塞IO和 多路復用器。
非阻塞IO
能讀多少讀多少,取決于內核為套接字分配的讀緩沖區內部的數據字節數。
能寫多少寫多少,取決于內核為套接字分配的寫緩存區空閑空間的字節數。
事件輪詢(多路復用)
非阻塞 IO 有個問題,那就是線程要讀數據,結果讀了一部分就返回了,線程如何知道
何時才應該繼續讀。也就是當數據到來時,線程如何得到通知。寫也是一樣,如果緩沖區滿
了,寫不完,剩下的數據何時才應該繼續寫,線程也應該得到通知。
最簡單的事件輪詢API,如下圖所示。select函數,它是操作系統提供的。在描述符特別多的情況下,性能特別差。現代的操作系統多路復用改為:epoll(linux) kqueue(freeBsd 和 macosx)。
目前支持I/O多路復用的系統調用有 select,pselect,poll,epoll,I/O多路復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,pselect,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
select 和epoll的區別
select
首先是垮平臺的,select本質上是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理,
select最大的缺陷就是單個進程所打開的FD是有一定限制的,它由FD_SETSIZE設置,默認值是1024
對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低。
epoll
是2.6 內核提出的,是 select 和poll 的增強版,epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
它的原理其實就是epoll支持水平觸發和邊緣觸發,最大的特點在于邊緣觸發,它只告訴進程哪些fd剛剛變為就緒態,并且只會通知一次。
還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,
一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。
它的有點優點:
1、沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口)。
2、效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。
內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。
單線程如何處理相應請求?
通過 指令隊列,相應隊列,和定時任務的設計來相應請求。
指令隊列
客戶端指令通過隊列來排隊進行順序處理,先到先服務
響應隊列
等隊列里面有數據了再把客戶端fd 放進去,避免浪費cpu
定時任務
服務器處理要響應 IO 事件外,還要處理其它事情。比如定時任務就是非常重要的一件事。如果線程阻塞在 select 系統調用上,定時任務將無法得到準時調度。
那 Redis 是如何解決這個問題的呢?
Redis 的定時任務會記錄在一個稱為最小堆的數據結構中。這個堆中,最快要執行的任務排在堆的最上方。在每個循環周期,Redis 都會將最小堆里面已經到點的任務立即進行處理。處理完畢后,將最快要執行的任務還需要的時間記錄下來,這個時間就是 select 系統調用的 timeout 參數。因為 Redis 知道未來 timeout 時間內,沒有其它定時任務需要處理,所以可以安心睡眠 timeout 的時間。
Nginx 和 Node 的事件處理原理和 Redis 也是類似的。
通信協議
Redis 的請求也相當于一次rpc 的調用,rpc中,通信協議的設計是非常重要的。,Redis 的作者認為數據庫系統的瓶頸一般不在于網絡流量,而是數據庫自身內部邏輯處理上。所以即使 Redis 使用了浪費流量的文本協議,依然可以取得極高的訪問性能。Redis
將所有數據都放在內存,用一個單線程對外提供服務,單個節點在跑滿一個 CPU 核心的情況下可以達到了 10w/s 的超高 QPS。
RESP(Redis Serialization Protocol)
RESP 是 Redis 序列化協議的簡寫。它是一種直觀的文本協議,優勢在于實現異常簡
單,解析性能極好。Redis 協議里有大量冗余的回車換行符,但是這不影響它成為互聯網技術領域非常受歡
迎的一個文本協議。有很多開源項目使用 RESP 作為它的通訊協議。在技術領域性能并不總
是一切,還有簡單性、易理解性和易實現性,這些都需要進行適當權衡。
關于持久化
一款優秀的中間件都會考慮到持久化,Redis 的數據全部在內存里,如果突然宕機,數據就會全部丟失,因此必須有一種機制來保證 Redis 的數據不會因為故障而丟失,這種機制就是 Redis 的持久化機制
Redis 的持久化機制有兩種,第一種是RDB(快照)(Redis DataBase),第二種是 AOF(Append Only File)日志。
RDB原理:
redis 使用操作系統的多進程(cow copy on write【寫時復制】)機制來實現快照的持久化;手動命令save,bgsave;配置文件設置.
fork 多進程
調用glibc 的函數 fork產生一個子進程,快照持久化交個子進程處理.子進程剛剛產生時和父進程共享代碼段和數據段,隨著父進程修改操作持續進行,more 共享頁面被分離出來,創建進程變快;內存增長,不會超過原來的2倍.另外一個 Redis 實例里冷數據占的比例往往是比較高的,所以很少會出現所有的頁面都會被分離,被分離的往往只有其中一部分頁面。每個頁面的大小只有 4K,一個 Redis 實例里面一般都會有成千上萬的頁面子進程的數據沒有變化,可以安心的遍歷數據,進行序列化寫磁盤。
AOF原理
AOF 日志存儲的是 Redis 服務器的順序指令序列,AOF 日志只記錄對內存進行修改的指令記錄。隨著操作的進行,指令文本會越來越大。需要對AOF 重寫,進行日志瘦身,redis 提供bgrewriteaof 指令對AOF 進行瘦身,原理是開辟一個子進程對內存進行遍歷,序列化一個新的aof+這段時間的增量。Linux 的glibc 提供了 fsync(int fd) 強制從內核緩存刷到磁盤,fsync 很慢,一般生產環境每隔一秒執行一次,可配置。
redis 是先執行指令再寫日志,leveldb\hbase 相反。
關于redis 運維的一些經驗,不要在主節點進行持久化,要在從節點進行持久化,因為持久化是一個很耗時的IO操作。
關于混合持久化
為什么要進行混合持久化?首先fork 動作需要copy 頁表,大內存場景下阻塞server,百ms 或秒級 latency spike(延遲尖峰),比較適合每天定時的 全備場景。部分云廠商的方案比如:redis+rocksDB。從以下幾個方面來分析。
數據寫入
寫入Redis 內存,并記錄增量,后臺線程異步應用增量到RocksDB
數據可靠性
RocksDB 包含全量數據,徹底避免Fork 調用,啟動時,從RocksDB 加載全量索引信息到內存
數據讀取
數據在redis 內存,直接讀取,數據不在Redis 內存,整體換入Redis 再讀取,針對簡單命令,可以直接從Rocks DB 讀取。
關于RcoksDB,RcoksDB可嵌入的,持久型的key-value 存儲,
RocksDB項目起源于Facebook的一個實驗項目,該項目旨在開發一個與快速存儲器(尤其是閃存)存儲數據性能相當的數據庫軟件,以應對高負載服務。這是一個c++庫,可用于存儲鍵和值,可以是任意大小的字節流。它支持原子讀和寫。RocksDB具有高度靈活的配置功能,可以通過配置使其運行在各種各樣的生產環境,包括純內存,Flash,硬盤或HDFS。它支持各種壓縮算法,并提供了便捷的生產環境維護和調試工具。
數據結構編碼
每個key對應一個RocksDB metakey,存儲key 的元數據。所有的metakey 相鄰存儲,啟動加載效率高,復雜key的每個元素在RocksDB 里面對應一個Datakey,元素在RocksDB 相鄰存儲,訪問效率高。簡化String 類型,metakey 與Datakey 合并優化IO。
數據交換模型(SWAP MODE)
內存數據換出:后臺定期檢查是否超哥內存使用閥值,根據訪問評率、大小等選擇key 預備淘汰。
磁盤數據換入:某個key 被頻繁讀取或有o(n) 的讀取操作,整個key 換入的RocksDB,加速訪問;key 寫入時,數據不再內存,整體換入內存;默認后臺多線程異步換入。
主備同步&遷移
全量同步:使用RocksDB checkoutpoint 代替RDB,避免fork 系統調用,備接收到checkoutpoint 數據只需加載meta key
增量同步:從 checkoutpoint 對應aof binlog 位點繼續增量同步,增量同步斷開,直接從斷開位置繼續同步,無需觸發全量同步。
一些其他的知識
管道
技術的本質:客戶端提供,pipline 包含多條指令;客戶端改變管道中的指令列表的讀寫順序,可以大幅節省IO 時間。自帶壓力測試工具 redis-beanchmark。普通 set 5w qps
小對象壓縮
如果redis 使用內存不超過4G,使用32bit 編譯。