來源:https://developer.aliyun.com/article/705239
1 什么是KeyDB?
KeyDB是Redis的高性能分支,專注于多執行緒,記憶體效率和高吞吐量,除了多執行緒之外,KeyDB還具有僅在Redis Enterprise中可用的功能,例如Active Replication,FLASH存盤支持以及一些根本不可用的功能,例如直接備份到AWS S3,
KeyDB與Redis協議,模塊和腳本保持完全兼容性,這包括腳本和事務的原子性保證,由于KeyDB與Redis開發保持同步,因此KeyDB是Redis功能的超集,從而使KeyDB取代了現有Redis部署,
在相同的硬體上,KeyDB每秒可以執行的查詢數量是Redis的兩倍,而延遲卻降低了60%,Active-Replication簡化了熱備用故障轉移,使您可以輕松地在副本上分配寫操作并使用基于TCP的簡單負載平衡/故障轉移,KeyDB的高性能可讓您在更少的硬體上做更多的事情,從而降低了運營成本和復雜性,

在此處查看完整的基準測驗結果和設定資訊:
https://docs.keydb.dev/blog/2019/10/07/blog-post/
2 走進KeyDB
KeyDB專案是從redis fork出來的分支,眾所周知redis是一個單執行緒的kv記憶體存盤系統,而KeyDB在100%兼容redis API的情況下將redis改造成多執行緒,
專案git地址:
https://github.com/JohnSully/KeyDB
網上公開的技術細節比較少,本文基本是通過閱讀原始碼總結出來的,如有錯漏之處歡迎指正,
推薦一個開源免費的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
多執行緒架構
執行緒模型
KeyDB將redis原來的主執行緒拆分成了主執行緒和worker執行緒,每個worker執行緒都是io執行緒,負責監聽埠,accept請求,讀取資料和決議協議,如圖所示:

KeyDB使用了SO_REUSEPORT特性,多個執行緒可以系結監聽同個埠,
每個worker執行緒做了cpu綁核,讀取資料也使用了SO_INCOMING_CPU特性,指定cpu接收資料,
決議協議之后每個執行緒都會去操作記憶體中的資料,由一把全域鎖來控制多執行緒訪問記憶體資料,
主執行緒其實也是一個worker執行緒,包括了worker執行緒的作業內容,同時也包括只有主執行緒才可以完成的作業內容,在worker執行緒陣列中下標為0的就是主執行緒,
主執行緒的主要作業在實作serverCron,包括:
- 處理統計
- 客戶端鏈接管理
- db資料的resize和reshard
- 處理aof
- replication主備同步
- cluster模式下的任務
鏈接管理
在redis中所有鏈接管理都是在一個執行緒中完成的,在KeyDB的設計中,每個worker執行緒負責一組鏈接,所有的鏈接插入到本執行緒的鏈接串列中維護,鏈接的產生、作業、銷毀必須在同個執行緒中,每個鏈接新增一個欄位
int iel; /* the event loop index we're registered with */
用來表示鏈接屬于哪個執行緒接管,
KeyDB維護了三個關鍵的資料結構做鏈接管理:
clients_pending_write:執行緒專屬的鏈表,維護同步給客戶鏈接發送資料的佇列clients_pending_asyncwrite:執行緒專屬的鏈表,維護異步給客戶鏈接發送資料的佇列clients_to_close:全域鏈表,維護需要異步關閉的客戶鏈接
分成同步和異步兩個佇列,是因為redis有些聯動api,比如pub/sub,pub之后需要給sub的客戶端發送訊息,pub執行的執行緒和sub的客戶端所在執行緒不是同一個執行緒,為了處理這種情況,KeyDB將需要給非本執行緒的客戶端發送資料維護在異步佇列中,
同步發送的邏輯比較簡單,都是在本執行緒中完成,以下圖來說明如何同步給客戶端發送資料

如上文所提到的,一個鏈接的創建、接收資料、發送資料、釋放鏈接都必須在同個執行緒執行,異步發送涉及到兩個執行緒之間的互動,KeyDB通過管道在兩個執行緒中傳遞訊息:
int fdCmdWrite; //寫管道
int fdCmdRead; //讀管道
本地執行緒需要異步發送資料時,先檢查client是否屬于本地執行緒,非本地執行緒獲取到client專屬的執行緒ID,之后給專屬的執行緒管到發送AE_ASYNC_OP::CreateFileEvent的操作,要求添加寫socket事件,專屬執行緒在處理管道訊息時將對應的請求添加到寫事件中,如圖所示:

redis有些關閉客戶端的請求并非完全是在鏈接所在的執行緒執行關閉,所以在這里維護了一個全域的異步關閉鏈表,

鎖機制
KeyDB實作了一套類似spinlock的鎖機制,稱之為fastlock,
fastlock的主要資料結構有:
struct ticket
{
uint16_t m_active; //解鎖+1
uint16_t m_avail; //加鎖+1
};
struct fastlock
{
volatile struct ticket m_ticket;
volatile int m_pidOwner; //當前解鎖的執行緒id
volatile int m_depth; //當前執行緒重復加鎖的次數
};
使用原子操作__atomic_load_2,__atomic_fetch_add,__atomic_compare_exchange來通過比較m_active=m_avail判斷是否可以獲取鎖,fastlock提供了兩種獲取鎖的方式:
- try_lock:一次獲取失敗,直接回傳
- lock:忙等,每
1024 * 1024次忙等后使用sched_yield主動交出cpu,挪到cpu的任務末尾等待執行,
在KeyDB中將try_lock和事件結合起來,來避免忙等的情況發生,每個客戶端有一個專屬的lock,在讀取客戶端資料之前會先嘗試加鎖,如果失敗,則退出,因為資料還未讀取,所以在下個epoll_wait處理事件回圈中可以再次處理,

Active-Replica
KeyDB實作了多活的機制,每個replica可設定成可寫非只讀,replica之間互相同步資料,主要特性有:
- 每個replica有個uuid標志,用來去除環形復制
- 新增加rreplay API,將增量命令打包成rreplay命令,帶上本地的uuid
- key,value加上時間戳版本號,作為沖突校驗,如果本地有相同的key且時間戳版本號大于同步過來的資料,新寫入失敗,采用當前時間戳向左移20位,再加上后44位自增的方式來獲取key的時間戳版本號,
參考檔案:https://docs.keydb.dev/docs/commands
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551047.html
標籤:其他
上一篇:一個簡單的 rust 專案 使用 bevy 引擎 復刻 Flappy Bird 小游戲
下一篇:返回列表
