類Redis大容量儲存-pika 主從複製原理之binlog
我們在《大容量類 Redis 儲存 — 有關 pika 的一切》裡介紹過pika的誕生、pika的特點、pika的核心以及pika的使用。本文來自pika使用者bigpyer(小米公司),他在文章中非常詳細的解析了pika同步邏輯中的重要檔案:“write2file”的資料儲存方式及實現原理,非常值得一看!
pika
pika 是 360 Web 平臺部 DBA 與基礎架構組合作開發的大容量類 Redis 儲存,pika 的出現並不是為了替代 Redis,而是 Redis 的場景補充。pika 力求在完全相容 Redis 協議、繼承 Redis 便捷運維設計的前提下通過持久化儲存的方式解決 Redis 在大容量場景下的問題,如恢復時間慢、主從同步代價高、單執行緒相對脆弱、承載資料較有限、記憶體成本高昂等。
pika主從複製原理之binlog
binlog相關的檔案包含兩部分: manifest和write2file,其中manifest記錄了日誌元資訊,包括當前日誌檔案編號、當前日誌檔案偏移量,write2file+num記錄了pika接收到的所有redis寫命令、引數。
檔案格式
manifest檔案格式:
日誌偏移量(8位元組)|con_offset(8位元組,未使用)|元素個數(4位元組,未使用)|日誌檔案編號(4位元組)。
Binlog檔案格式:
Binlog檔案固定大小為100MB,每個Binlog檔案由多個Block組成,每個Block大小固定為64KB,每一個寫redis命令稱為一個Record。一個Record可以分佈在多個Block中,但只會分佈在一個Binlog檔案裡,所以Binlog檔案有可能大於100MB。
Record格式:Header|Cmd
Header: Record Length(3位元組)|時間戳(4位元組)|記錄型別(1位元組)。
Cmd: redis命令的一部分或者全部,取決於當前Block剩餘空間是否可以存放該Record。
實現類
基本類
Version: 元資訊類,通過mmap與manifest檔案對映。
Binlog: 日誌類,通過mmap與write2file檔案對映。
PikaBinlogSenderThread: 日誌消費類,順序讀取日誌檔案內容,消費日誌。
基本操作
構造Binlog
//file_size可以在配置檔案指定,預設為100MB
Binlog::Binlog(const std::string& binlog_path, const int file_size)
1.1 建立binlog檔案目錄。
1.2 檢查log目錄下manifest檔案是否存在,不存在則新建。
1.3 根據manifest檔案初始化Version類。
1.4 根據manifest中的filenum找到對應的日誌文
件,根據pro_offset定位到檔案append的位置,初始化日誌指標、記錄日誌內容長度、Block塊數量。
更新當前日誌生產狀態
//pro_num: 日誌檔案編號
//pro_offset: 日誌檔案偏移量
//用在需要全量同步時更新slave例項對應的binlog資訊
Status Binlog::SetProducerStatus(uint32_t pro_num, uint64_t pro_offset)
2.1 刪除write2file0。
2.2 刪除write2file+pro_num。
2.3 構造新的write2file+pro_num檔案,填充pro_offset個空格,初始化version->pro_num為pro_num,version->pro_offset為pro_offset,並重新整理到manifest檔案中。
2.4 初始化當前filesize、block_offset。
更新當前日誌生產狀態
//filenum: 當前日誌編號
//pro_offset: 當前日誌偏移量
Status Binlog::GetProducerStatus(uint32_t* filenum, uint64_t* pro_offset)
3.1 讀取version中的pro_num、pro_offset並返回。
生產日誌
//Put->Produce->EmitPhysicalRecord
Status Binlog::Put(const std::string &item)
4.1檢查當前日誌檔案是否滿足切割條件,如果滿足則進行切割。
4.1.1 pro_num自增加1,初始化新的日誌檔案,version->pro_num=pro_num,version->pro_offset = 0,binlog->filesize = 0,binlog->block_offset = 0。
4.1.2 如果當前block剩餘大小<kHeaderSize(8位元組),則填充剩餘空間為’\x00″。
4.1.3 Produce是一個迴圈,保證在item大小超過kBlockSize時,可以進行多次EmitPhysicalRecord,完成item全部資料落入binlog檔案,迴圈正常退出的條件是left==0。
4.1.3.1 如果left<avail,代表當前block可以存放完整的item,則type=kFullType,呼叫EmitPhysicalRecord一次,迴圈退出。
4.1.3.2 如果left > avail,代表需要多個Block存放item,則第一次Type=kFirstType,呼叫EmitPhysicalRecord多次。
4.1.3.3 如果left > avail,且不是第一次EmitPhysicalRecord,則Type=kMiddleType,呼叫EmitPhysicalRecord多次。
4.1.4EmitPhysicalRecord。
4.1.4.1 拼接RecordHeader(3位元組長度+4位元組時間+1位元組Type),寫入資料,更新block_offset、pro_offset。
消費日誌
//scratch: 消費結果返回一個完整的redis cmd
//Consume->ReadPhysicalRecord,ReadPhysicalRecord每次讀取一個完整的Record,多個Record構成一個完整的redis cmd
Status PikaBinlogSenderThread::Consume(std::string &scratch)
5.1Consume是一個迴圈,可能多次呼叫ReadPhysicalRecord,迴圈退出的條件是讀取到的record_type==kFullType或record_type==kLastType。
5.1.1如果讀取到的kBlockSize-last_record_offset_ <= kHeaderSize代表讀到了Block的末尾,且為填充資料,skip掉。
5.1.2讀取資料,更新last_record_offset_,con_offset。
結束
以上就是pika主從複製媒介write2file的全部解析了,下篇文章將會對pika主從複製的工作流程進行拆解、分析,文章同樣來自bigpyer(小米公司),敬請期待!
文章來自微信公眾號:HULK一線技術雜談