大容量类Redis存储--Pika介绍
嘉賓介紹
大家好,首先自我介紹一下,我是360 web平臺-基礎架構組的宋昭,負責大容量類redis存儲pika的和分布式存儲Bada的開發工作,這是我的github和博客地址,平時歡迎指正交流^^
我的github:https://github.com/KernelMaker
我的博客:http://kernelmaker.github.io
下面是pika的github,歡迎關注
https://github.com/Qihoo360/pika
Pika介紹
pika是360 DBA和基礎架構組聯合開發的類redis存儲系統, 使用Redis協議,兼容redis絕大多數命令(String,Hash,List,ZSet,Set),用戶不需要修改任何代碼, 就可以將服務遷移至pika.
pika主要是使用持久化存儲來解決redis在內存占用超過50G,80G時遇到的如啟動恢復時間長,主從同步代價大,硬件成本貴等問題,并且在對外用法上盡可能做到與redis一致,用戶基本上對后端是redis或pika無感知
既然pika要做到兼容redis并解決redis在大容量時的各種問題,那么首先要面對的問題便是如何從redis遷移到pika,畢竟現在redis的使用非常廣泛,如果從redis遷移到pika很麻煩,那應該也不會有多少人用了
從redis遷移到pika需要經過幾個步驟?
開發需要做的:
基本不用做任何事情
dba需要做的:
1.dba遷移redis數據到pika
2.dba將redis的數據實時同步到pika,確保redis與pika的數據始終一致
3.dba切換lvs后端ip,由pika替換redis
注:pika提供redis_to_pika工具,通過aof來將db和實時增量數據同步到pika
遷移過程中需要停業務/業務會受到影響嗎:
不會
注
由于pika的數據存在硬盤上,故單線程的性能肯定不如redis, 但pika使用多線程來盡可能的彌補數據讀寫性能較之redis內存讀寫的差異, 線程數比較多的情況下, 某些數據結構的性能會優于redis
pika肯定不會是一個完全優于redis的方案, 和redis相比也有弊端,只是在某些場景下面更適合. 所以目前公司內部redis, pika 是共同存在的方案, DBA會根據業務的場景挑選合適的方案
本次分享分成6個部分
背景
為什么做pika(大容量redis遇到的問題)
pika架構
pika的創新及優化
pika的優勢及不足
總結
背景
redis提供了豐富的多數據結構的接口, 在redis之前, 比如memcache,都認為后端只需要存儲kv的結構就可以, 不需要感知這個value里面的內容, 用戶需要使用的話通過json_encode,json_decode等形式進行數據的讀取就行. 但是其實redis做了一個微創新, 提供了多數據結果的支持, 讓服務端寫代碼起來更加的方便了
因此redis在公司的使用率也是越來越廣泛, 用戶不知不覺把越來越多的數據存儲在redis中, 隨著用戶的使用, DBA發現有些redis實例的大小也是越來越大, 發現在redis實例內存使用比較大的情況下, 遇到的問題也會越來越多, 因此和我們一起實現了大容量redis的解決方案
最近半年公司每天redis 的訪問情況
Imgur
redis 架構方案
Imgur
為什么做pika(大容量redis遇到的問題)
恢復時間長
我們線上的redis一般同時開啟rdb和aof. 我們知道aof的作用是實時的記錄用戶的寫入操作, rdb是redis某一時刻數據的完整快照. 那么恢復的時候一般是通過rdb+aof的方式進行恢復, 根據我們線上的情況50G redis恢復時間需要差不多40~70分鐘(取決于服務器性能)
一主多從, 主從切換代價大
redis在主庫掛掉以后, 從庫升級為新的主庫. 那么切換主庫以后, 所有的從庫都需要跟新主做一次全同步, 代價非常大
緩沖區寫滿問題
為了實現部分同步,redis使用了repl_backlog來緩存部分同步命令,repl_backlog默認1M。 當主從之間網絡有故障, 同步出現延遲了大于1M以后, slave丟失了master的同步點,就會觸發全同步的過程. 如果多個從庫同時觸發全同步的過程, 在全同步的過程中,redis會將同步點之后的增量請求給每一個slave緩存一份,在寫入量大的情況下很容易就將主庫給拖死,當然你也可以把repl_backlog調大來緩解,比如2G,不過對全內存的redis而言,這2G的內存代價也不小
內存太貴
我們一般線上使用的redis機器是64G, 96G. 我們只會使用80%的空間.
如果一個redis的實例是50G, 那么基本一臺機器只能運行一個redis實例. 因此特別的浪費資源
總結: 可以看到在redis比較小的情況下, 這些問題都不是問題, 但是當redis容量上去以后. 很多操作需要的時間也就越來越長了
pika 整體架構
Imgur
主要組成:
1. 網絡模塊 pink
2. 線程模型
3. 存儲引擎 nemo
4. 日志模塊 binlog
5. 主從同步模塊
pink 網絡模塊
* 基礎架構團隊開發網絡編程框架, 支持pb, redis等等協議. 提供了對thread的封裝, 用戶定義不同thread的行為, 使用更加清晰
* 支持單線程模型, 多線程worker模型
* github 地址: https://github.com/baotiao/pink
線程模型
Imgur
pika使用的是多線程模型,使用多個工作線程來進行讀寫操作,線程分為11種:
PikaServer:主線程
DispatchThread:監聽端口1個端口,接收用戶連接請求
ClientWorker:存在多個(用戶配置),每個線程里有若干個用戶客戶端的連接,負責接收處理用戶命令并返回結果,每個線程執行寫命令后,追加到binlog中
Trysync:嘗試與master建立首次連接,并在以后出現故障后發起重連
ReplicaSender:存在多個(動態創建銷毀,本master節點掛多少個slave節點就有多少個),每個線程根據slave節點發來的同步偏移量,從binlog指定的偏移開始實時同步命令給slave節點
ReplicaReceiver:存在1個(動態創建銷毀,一個slave節點同時只能有一個master),將用戶指定或當前的偏移量發送給master節點并開始接收master實時發來的同步命令,在本地使用和master完全一致的偏移量來追加binlog,然后分發給多個BinlogBGWorker中的一個來執行
BinlogBGWorker:存在多個(用戶配置),ReplicaReceiver將命令按key取hash分配給其中的一個BinlogBGWorker,它負責真正執行命令
SlavePing:slave用來向master發送心跳進行存活檢測
HeartBeat:master用來接收所有slave發送來的心跳并回復進行存活檢測
bgsave:后臺dump線程
scan:后臺掃描keyspace線程
purge:后臺刪除binlog線程
存儲引擎 nemo
pika的存儲引擎是基于Rocksdb實現的. 封裝了String,Hash, List, ZSet, Set等數據結構
我們知道redis是需要支持多數據結構的, 而rocksdb只是一個kv的接口, 那么我們如何實現的呢?
比如對于Hash數據結構:
對于每一個Hash存儲,它包括hash鍵(key),hash鍵下的域名(field)和存儲的值 (value).
nemo的存儲方式是將key和field組合成為一個新的key,將這個新生成的key與所要存儲的value組成最終落盤的kv鍵值對。同時,對于每一個hash鍵,nemo還為它添加了一個存儲元信息的落盤kv,它保存的是對應hash鍵下的所有域值對的個數。
每個hash鍵、field、value到落盤kv的映射轉換
Imgur
每個hash鍵的元信息的落盤kv的存儲格式
Imgur
比如對于List 數據結構:
顧名思義,每個List結構的底層存儲也是采用鏈表結構來完成的。對于每個List鍵,它的每個元素都落盤為一個kv鍵值對,作為一個鏈表的一個節點,稱為元素節點。和hash一樣,每個List鍵也擁有自己的元信息。
每個元素節點對應的落盤kv存儲格式
Imgur
每個元信息的落盤kv的存儲格式
Imgur
其他的數據結構實現的方式也類似, 通過將數據結構拆分為一個個獨立的KV, 存儲到rocksdb 里面去. 從而實現多數據結構的結構
日志模塊 binlog
pika的主從同步是使用Binlog來完成的.
binlog 本質是順序寫文件, 通過Index + offset 進行同步點檢查.
解決了同步緩沖區太小的問題
支持全同步 + 增量同步
master執行完一條寫命令就將命令追加到Binlog中,ReplicaSender將這條命令從Binlog中讀出來發送給slave,slave的ReplicaReceiver收到該命令,執行,并追加到自己的Binlog中.
當發生網絡閃斷或slave掛掉重啟時, slave僅需要將自己當前的Binlog Index + offset 發送給master,master找到后從該偏移量開始同步后續命令
為了防止讀文件中寫錯一個字節則導致整個文件不可用,所以pika采用了類似leveldb log的格式來進行存儲,具體如下:
Imgur
主從同步模塊
Imgur
上圖是一個主從同步的一個過程(即根據主節點數據庫的操作日志,將主節點數據庫的改動同步到從節點的數據庫上),從圖中可以看出,每一個從節點在主節點下都有一個唯一對應的BinlogSenderThread
主要模塊:
WorkerThread:接受和處理用戶的命令;
BinlogSenderThread:負責順序地向對應的從節點發送在需要同步的命令;
BinlogReceiverModule: 負責接受主節點發送過來的同步命令
Binglog:用于順序的記錄需要同步的命令
主要的工作過程:
1.當WorkerThread接收到客戶端的命令,按照執行順序,添加到Binlog里;
2.BinglogSenderThread判斷它所負責的從節點在主節點的Binlog里是否有需要同步的命令,若有則發送給從節點;
3.BinglogReceiverModule模塊則做以下三件事情:
a. 接收主節點的BinlogSenderThread發送過來的同步命令;
b. 把接收到的命令應用到本地的數據上;
c. 把接收到的命令添加到本地Binlog里
至此,一條命令從主節點到從節點的同步過程完成
BinLogReceiverModule的工作過程:
Imgur
上圖是BinLogReceiverModule的組成,從圖中可以看出BinlogReceiverModule由一個BinlogReceiverThread和多個BinlogBGWorker組成。
BinlogReceiverThread:負責接受由主節點傳送過來的命令,并分發給各個BinlogBGWorker,若當前的節點是只讀狀態(不能接受客戶端的同步命令),則在這個階段寫Binlog
BinlogBGWorker:負責執行同步命令;若該節點不是只讀狀態(還能接受客戶端的同步命令),則在這個階段寫Binlog(在命令執行之前寫)
BinlogReceiverThread接收到一個同步命令后,它會給這個命令賦予一個唯一的序列號(這個序列號是遞增的),并把它分發給一個BinlogBGWorker;而各個BinlogBGWorker則會根據各個命令的所對應的序列號的順序來執行各個命令,這樣也就保證了命令執行的順序和主節點執行的順序一致了
之所以這么設計主要原因是:
配備多個BinlogBGWorker是可以提高主從同步的效率,減少主從同步的滯后延遲;
讓BinlogBGWorker在執行執行之前寫Binlog可以提高命令執行的并行度;
在當前節點是非只讀狀態,讓BinglogReceiverThread來寫Binlog,是為了讓Binglog里保存的命令順序和命令的執行順序保持一致;
綜上所述,正是因為這樣的架構及實現,pika可以較好的解決上面說到redis在大數據量下的不足:
恢復時間長
pika的存儲引擎是nemo, nemo使用的是rocksdb, rocksdb啟動不需要加載全部數據, 只需要加載recover log文件就可以啟動, 因此恢復時間非常快
一主多從, 主從切換代價大
在主從切換的時候, 新主確定以后, 從庫會用當前的偏移量嘗試與新主做一次部分同步, 如果部分同步不成功才做全同步. 這樣盡可能的減少全同步次數
緩沖區寫滿問題
pika不是用內存buffer進行同步數據的緩存, 而是記錄在本地的binlog上, binlog的大小可配,遠遠大于內存可以使用的上限,因此不會出現把緩沖區寫滿的問題,減少無用的全同步次數
內存昂貴問題
pika的存儲引擎nemo使用的是rocksdb, rocksdb同時使用內存和磁盤減少對內存的依賴. 同時我們盡可能使用SSD盤來存放數據, 盡可能跟上redis的性能.
pika的創新及優化
多數據結構key的快速刪除
以Hash為例,redis一個Hash key可能包含百萬或者千萬的field,對于Hash key的刪除,redis首先從db dict中刪除掉這個key,然后立刻返回,該key對應的hash空間采用惰性的方式來慢慢回收,而我們知道,pika的是將Hash結構轉換成一個個KV來存儲的,刪除一個Hash Key就等于要刪除其對應的千萬field,此時用戶的一個del操作等價于引擎千萬次的del操作,當初做的時候我們有如下考量:
Solution 1:阻塞刪除,這也是目前其他類似Pika項目的主要解決方案,直到把Hash key對應的所有field key全部刪除才返回給用戶
優點:易實現
缺點:阻塞處理,影響服務
Solution 2:刪除meta key之后立刻返回,其他的field key后臺起線程慢慢刪除
優點:速度快
缺點:使用場景受限,如果用戶刪除某個Hash key之后又立刻插入這個key,則此時還未刪除的field key會被無當做新key的一部分,出錯
上述兩種方案皆不可行,我們最終在rocksdb上做了改動,使它支持多數據結構版本的概念
最終解決方案:
Hash Key的元信息增加版本,表示當前key的有效版本;
操作:
Put:查詢元信息,獲得key的最新版本,后綴到val;
Get:查詢元信息,獲得key的最新版本,過濾低版本的數據;
Del:key的元信息版本號+1即可;
Iterator: 迭代時,查詢key的版本,過濾舊版本數據;
Compact:數據的實際刪除是在Compact過程中,根據版本信息過濾;
通過對rocksdb的修改,pika實現了對多數據結構key的秒刪功能,并且將真正的刪除操作交給了compact來減少顯示調用引擎del造成的多次操作(插入del record及compact)
快照式備份
不同于Redis,Pika的數據主要存儲在磁盤中,這就使得其在做數據備份時有天然的優勢,可以直接通過文件拷貝實現
流程:
打快照:阻寫,并在這個過程中或的快照內容
異步線程拷貝文件:通過修改Rocksdb提供的BackupEngine拷貝快照中文件,這個過程中會阻止文件的刪除
Imgur
這樣的備份速度基本等同于cp的速度,降低了備份的代價
后續優化:不過目前pika正在嘗試使用硬鏈建立checkpoint來實現數據的更快備份(秒級),并且減少備份數據的空間占用(從之前的2倍優化到不到2倍),更好的支持超大容量存儲
過期支持
redis的過期是通過將需要過期的key在多存一份,記錄它的過期時間,然后每次讀取時進行比較來完成的,這樣的實現簡單,但基于內存的讀寫都很快不會有性能問題,目前其他類似pika的開源項目也采用這樣的方式,將過期key在db多存一份,不過不同于redis,這些項目的db也是落盤,采用這樣簡單粗暴的方式無形中又多了一次磁盤讀,影響效率,那么pika是如何解決的呢?
pika通過給修改rocksdb Set、Get接口并且新增compact filter,給每個value增加ttl后綴,并且在Get的時候來進行過濾,將真正的過期刪除交給compact(基于ttl來Drop),在磁盤大容量的前提下,使用額外空間來減少磁盤讀取次數,提高效率
空間回收
rocksdb 默認的compact 策略是在寫放大, 讀放大, 空間放大的權衡. 那么DBA同學當然希望盡可能減少空間的使用, 因此DBA希望能夠隨時觸發compact, 而又盡可能的不影響線上的使用, 而rocksdb 默認的手動compact 策略是最高優先級的, 會阻塞線上的正常流程的合并, 因此我們修改了rocksdb compact的部分邏輯,低優先級手動compact優先級,使得自動compact可以打斷手動compact,來避免level 0文件數量過多而造成的rocksdb主動停寫. pika支持DBA隨時compact
方便的運維
pika較之其他類似開源項目,還有一個優勢就是它可以方便的運維,例如
1. pika的binlog可以配置按個數或者按天來刪除,提供工具來支持不用再啟實例來進行指定節點binlog的實時備份,支持binlog恢復數據到指定某一秒(正在做)
2. 支持info命令來查看后臺任務的執行狀態(bgsave,purgelogs,keyscan)
3. 支持monitor
4. 支持通過redis aof和monitor來遷移數據
5. 支持config set來動態修改配置項
6. 支持多用戶(admin及普通用戶)及命令黑名單,可以禁止掉不想讓普通用戶使用的命令
7. 支持不活躍客戶端的自動刪除,支持慢日志,client kill all,readonly開關
8. 支持快照式備份及手動compact
9. 等等...
pika在追求盡可能高的性能及穩定性的同時,還注重使用者的使用體驗,一個產品即使擁有再給力的性能如果不可運維我想也不會有人想用,所以pika會不斷發現并解決使用上的問題,使得它更好用
pika的優勢及不足
pika相對于redis,最大的不同就是pika是持久化存儲,數據存在磁盤上,而redis是內存存儲,由此不同也給pika帶來了相對于redis的優勢和劣勢
優勢:
容量大:Pika沒有Redis的內存限制, 最大使用空間等于磁盤空間的大小
加載db速度快:Pika 在寫入的時候, 數據是落盤的, 所以即使節點掛了, 不需要rdb或者aof,pika 重啟不用重新加載數據到內存而是直接使用已經持久化在磁盤上的數據, 不需要任何數據回放操作(除去少量rocksdb自身的recover),這大大降低了重啟成本。
備份速度快:Pika備份的速度大致等同于cp的速度(拷貝數據文件后還有一個快照的恢復過程,會花費一些時間),目前已經開發完更快更省空間的秒級備份,即將投入使用,這樣在對于百G大庫的備份是快捷的,更快的備份速度更好的解決了主從的全同步問題
劣勢:
由于Pika是基于內存和文件來存放數據, 所以性能肯定比Redis低一些, 但是我們一般使用SSD盤來存放數據, 盡可能跟上Redis的性能。
總結
如果用戶的業務場景數據比較大,Redis會出現上面說到的那些問題,如果這些問題對用戶來說不可容忍,那么可以考慮使用pika。
我們對pika整體進行了性能測試,結果如下:
服務端配置:
處理器:24核 Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
內存:165157944 kB
操作系統:CentOS release 6.2 (Final)
網卡:Intel Corporation I350 Gigabit Network Connection
客戶端配置:
同服務端
測試結果:
pika配置18個worker,用40個客戶端;
1. 寫性能:
方法:客戶端依次執行set、hset、lpush、zadd、sadd接口寫入數據,每個數據結構10000個key;
結果:qps 110000
2. 讀性能:
方法:客戶端一次執行get、hget、lindex、zscore、smembers,每個數據結構5000000個key;
結果:qps 170000
單數據結構性能用戶可以依據自己的需求來測試,數據結構間的性能比較大致是:
String > Hash = Set > ZSet > List
在實際使用中,大多數場景下pika的性能大約是Redis的50%~80%,在某些特定場景下,例如range 500,pika的性能只有redis的20%,針對這些場景我們仍然在改進
在360內部使用情況:
粗略的統計如下:
實例數160個
當前每天承載的總請求量超過100億
當前承載的數據總量約3TB
wiki
github 地址:
https://github.com/Qihoo360/pika
github wiki:
https://github.com/Qihoo360/pika/wiki/pika介紹
Q&A
Q1:元信息是跟key一起存儲的嗎?
A1:是的
Q2:快照的生成依賴于什么?會阻塞讀寫嗎?
A2:快照是后臺生成的,阻塞的地方只是在最開始取當前db狀態和同步點信息的時候,非常短
Q3:為什么能做到基本上不阻塞?
A3:因為pika的數據本來就是存在磁盤上的,備份就等同于文件拷貝.只要在拷貝前計算一下該拷貝那些文件,然后就可以后臺搞了
Q4:看hashset的實現,只記錄那些信息,是怎么處理hgetall的
A4:比如hash有5個field,那么在pika存儲,除了元信息之外,真正數據是這么存的:key+field1 -> value1,key+field2-> value2等等,在hgetall的時候,只需要通過rocksdb的iterator,seek到key,然后迭代便可取出所有的field了
Q5:當存儲超過SSD空間后,怎么擴容呢?
A5: pika受制于底下引擎的限制,不支持擴容,如果連ssd都不夠用了,就只能靠掛lvm來解決了
Q6:那新的數據如何融合到db里
A6:只需要給新的實例啟動前,配置文件db路徑為備份的目錄,然后啟動新實例即可
Q7:依賴于rocksdb_backup機制?
A7:是的,目前是基于rocksdb_backup做的,不過我們最新的秒備份已經差不多做完了,可以直接通過硬鏈接省去文件的拷貝,以達到更快的備份速度及更少的空間占用
Q8:dispatch 和 clientWorker 這種生產者消費者模型對性能的影響。
A8:clientworker之間是互不影響的,dispatch在把某個連接分配給其中一個worker時僅與worker有極小概率的沖突(dispatch已分發完一輪連接,又回到這個worker,并且該worker此時也在操作自己的任務隊列),這樣的模型是比較通用的做法(與MC類似),性能瓶頸不會出現在這里
Q9:對hash類型的讀取、修改、增加、刪除任意部分field的過程及性能。
A9:性能比string接口稍有下降,因為多了一個元信息的讀寫,過程的話無非就是先讀元信息,在讀寫真正的數據等等,說起來比較多,感興趣的話可以下來交流^^
Q10:binlog有對操作做可重入處理嗎
A10:binlog里記錄的就是用戶發來的redis命令,所以不是冪等的
Q11:存放元數據的那個key如何不成為瓶頸
A11: 這樣的設計元信息的key的確會成為瓶頸,不過對于比較熱的元數據key,頻繁更新會讓他駐留在rocksdb的memtable中,這樣可以彌補一下讀寫性能
Q12:請問講師使用的是redis cluster嗎?為何只有一個master并且50g那么大?在截圖那個qps下,會遇到什么瓶頸?
A12: 目前在公司redis cluster的應用不是很廣泛,所以大的業務很容易將redis內存撐到50G,截圖的qps,根據上面的介紹,性能梯度是String>Hash=Set>ZSet>List,之所以可以到達10w+的qps,也是因為String,Hash,Set這樣的接口操作和redis相比差不多,在worker數多的情況下,String甚至還高于redis,所以整體上看qps還不錯,不過對于List這樣的數據結構,實現中就是基于kv來做的list,所以性能低于前面的數據結構
Q13:在多線程情況下,對事務的處理是怎么實現的
A13:pika上層會對寫操作加行鎖,來確保對同一個key的寫db和寫binlog不被打斷
Q14:hashset每次hset都要讀取count值嗎
A14:是的,因為版本號的原因,每一次都需要讀取元信息
Q15:binlog可以理解為redis的aof文件么?存儲的都是命令日志記錄?
A15:是的,和aof差不多,存儲的都是用戶命令,不過除了做aof的功能,在主從同步中它也承擔了redis中repl_backlog的功能
總結
以上是生活随笔為你收集整理的大容量类Redis存储--Pika介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CISCO ACL配置全解
- 下一篇: 杨元庆:ChatGPT为例的AIGC将带