thrift之TTransport層的緩存?zhèn)鬏旑怲BufferedTransport和緩沖基類TBufferBase
本節(jié)主要介紹緩沖相關(guān)的傳輸類,緩存的作用就是為了提高讀寫的效率。Thrift在實現(xiàn)緩存?zhèn)鬏數(shù)臅r候首先建立一個緩存的基類,然后需要實現(xiàn)緩存功能的類都可以直接從這個基類繼承。下面就詳細(xì)分析這個基類以及一個具體的實現(xiàn)類。
緩存基類TBufferBase
緩存基類就是讓傳輸類所有的讀寫函數(shù)都提供緩存來提高性能。它在通常情況下采用memcpy來設(shè)計和實現(xiàn)快路徑的讀寫訪問操作,這些操作函數(shù)通常都是小、非虛擬和內(nèi)聯(lián)函數(shù)。TBufferBase是一個抽象的基類,子類必須實現(xiàn)慢路徑的讀寫函數(shù)等操作,慢路徑的讀寫等操作主要是為了在緩存已經(jīng)滿或空的情況下執(zhí)行。首先看看緩存基類的定義,代碼如下:
class TBufferBase : public TVirtualTransport<TBufferBase> { public: uint32_t read(uint8_t* buf, uint32_t len) {//讀函數(shù) uint8_t* new_rBase = rBase_ + len;//得到需要讀到的緩存邊界 if (TDB_LIKELY(new_rBase <= rBound_)) {//判斷緩存是否有足夠的數(shù)據(jù)可讀,采用了分支預(yù)測技術(shù) std::memcpy(buf, rBase_, len);//直接內(nèi)存拷貝 rBase_ = new_rBase;//更新新的緩存讀基地址 return len;//返回讀取的長度 } return readSlow(buf, len);//如果緩存已經(jīng)不能夠滿足讀取長度需要就執(zhí)行慢讀 } uint32_t readAll(uint8_t* buf, uint32_t len) { uint8_t* new_rBase = rBase_ + len;//同read函數(shù) if (TDB_LIKELY(new_rBase <= rBound_)) { std::memcpy(buf, rBase_, len); rBase_ = new_rBase; return len; } return apache::thrift::transport::readAll(*this, buf, len);//調(diào)用父類的 } void write(const uint8_t* buf, uint32_t len) {//快速寫函數(shù) uint8_t* new_wBase = wBase_ + len;//寫入后的新緩存基地址 if (TDB_LIKELY(new_wBase <= wBound_)) {//判斷緩存是否有足夠的空間可以寫入 std::memcpy(wBase_, buf, len);//內(nèi)存拷貝 wBase_ = new_wBase;//更新基地址 return; } writeSlow(buf, len);//緩存空間不足就調(diào)用慢寫函數(shù) } const uint8_t* borrow(uint8_t* buf, uint32_t* len) {//快速路徑借 if (TDB_LIKELY(static_cast<ptrdiff_t>(*len) <= rBound_ - rBase_)) {//判斷是否足夠借的長度 *len = static_cast<uint32_t>(rBound_ - rBase_); return rBase_;//返回借的基地址 } return borrowSlow(buf, len);//不足就采用慢路徑借 } void consume(uint32_t len) {//消費函數(shù) if (TDB_LIKELY(static_cast<ptrdiff_t>(len) <= rBound_ - rBase_)) {//判斷緩存是否夠消費 rBase_ += len;//更新已經(jīng)消耗的長度 } else { throw TTransportException(TTransportException::BAD_ARGS, "consume did not follow a borrow.");//不足拋異常 } } protected: virtual uint32_t readSlow(uint8_t* buf, uint32_t len) = 0;//慢函數(shù) virtual void writeSlow(const uint8_t* buf, uint32_t len) = 0; virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = 0; TBufferBase() : rBase_(NULL) , rBound_(NULL) , wBase_(NULL) , wBound_(NULL) {}//構(gòu)造函數(shù),把所有的緩存空間設(shè)置為NULL void setReadBuffer(uint8_t* buf, uint32_t len) {//設(shè)置讀緩存空間地址 rBase_ = buf;//讀緩存開始地址 rBound_ = buf+len;//讀緩存地址界限 } void setWriteBuffer(uint8_t* buf, uint32_t len) {//設(shè)置寫緩存地址空間 wBase_ = buf;//起 wBound_ = buf+len;//邊界 } virtual ~TBufferBase() {} uint8_t* rBase_;//讀從這兒開始 uint8_t* rBound_;//讀界限 uint8_t* wBase_;//寫開始地址 uint8_t* wBound_;//寫界限 };
從TBufferBase定義可以看出,它也是從虛擬類繼承,主要采用了memcpy函數(shù)來實現(xiàn)緩存的快速讀取,在判斷是否有足夠的緩存空間可以操作時采用了分支預(yù)測技術(shù)來提供代碼的執(zhí)行效率,且所有快路徑函數(shù)都是非虛擬的、內(nèi)聯(lián)的小代碼量函數(shù)。下面繼續(xù)看看一個具體實現(xiàn)緩存基類的一個子類的情況!
TBufferedTransport
緩存?zhèn)鬏旑愂菑木彺婊惱^承而來,它對于讀:實際讀數(shù)據(jù)的大小比實際請求的大很多,多余的數(shù)據(jù)將為將來超過本地緩存的數(shù)據(jù)服務(wù);對于寫:數(shù)據(jù)在它被發(fā)送出去以前將被先寫入內(nèi)存緩存。
緩存的大小默認(rèn)是512字節(jié)(代碼:static const int DEFAULT_BUFFER_SIZE = 512;),提供多個構(gòu)造函數(shù),可以只指定一個傳輸類(另一層次的)、也可以指定讀寫緩存公用的大小或者分別指定。因為它是一個可以實際使用的緩存類,所以需要實現(xiàn)慢讀和慢寫功能的函數(shù)。它還實現(xiàn)了打開函數(shù)open、關(guān)閉函數(shù)close、刷新函數(shù)flush等,判斷是否有數(shù)據(jù)處于未決狀態(tài)函數(shù)peek定義和實現(xiàn)如下:
bool peek() { if (rBase_ == rBound_) {//判斷讀的基地址與讀邊界是否重合了,也就是已經(jīng)讀取完畢 setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//是:重新讀取底層來的數(shù)據(jù) } return (rBound_ > rBase_);//邊界大于基地址就是有未決狀態(tài)數(shù)據(jù) }
下面繼續(xù)看看慢讀函數(shù)和慢寫函數(shù)的實現(xiàn)細(xì)節(jié)(快讀和快寫繼承基類的:也就是默認(rèn)的讀寫都是直接從緩存中讀取,所謂的快讀和快寫)。慢讀函數(shù)實現(xiàn)如下(詳細(xì)注釋):
uint32_t TBufferedTransport::readSlow(uint8_t* buf, uint32_t len) { uint32_t have = rBound_ - rBase_;//計算還有多少數(shù)據(jù)在緩存中 // 如果讀取緩存中已經(jīng)存在的數(shù)據(jù)不能滿足我們, // 我們(也僅僅在這種情況下)應(yīng)該才從慢路徑讀數(shù)據(jù)。 assert(have < len); // 如果我們有一些數(shù)據(jù)在緩存,拷貝出來并返回它 // 我們不得不返回它而去嘗試讀更多的數(shù)據(jù),因為我們不能保證 // 下層傳輸實際有更多的數(shù)據(jù), 因此嘗試阻塞式讀取它。 if (have > 0) { memcpy(buf, rBase_, have);//拷貝數(shù)據(jù) setReadBuffer(rBuf_.get(), 0);//設(shè)置讀緩存,基類實現(xiàn)該函數(shù) return have;//返回緩存中已經(jīng)存在的不完整數(shù)據(jù) } // 在我們的緩存中沒有更多的數(shù)據(jù)可用。從下層傳輸?shù)玫礁嘁赃_(dá)到buffer的大小。 // 注意如果len小于rBufSize_可能會產(chǎn)生多種場景否則幾乎是沒有意義的。 setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//讀取數(shù)據(jù)并設(shè)置讀緩存 // 處理我們已有的數(shù)據(jù) uint32_t give = std::min(len, static_cast<uint32_t>(rBound_ - rBase_)); memcpy(buf, rBase_, give); rBase_ += give; return give; }
慢讀函數(shù)主要考慮的問題就是緩存中還有一部分?jǐn)?shù)據(jù),但是不夠我們需要讀取的長度;還有比較麻煩的情況是雖然現(xiàn)在緩存中沒有數(shù)據(jù),但是我們從下層傳輸去讀,讀取的長度可能大于、小于或等于我們需要讀取的長度,所以需要考慮各種情況。下面繼續(xù)分析慢寫函數(shù)實現(xiàn)細(xì)節(jié):
void TBufferedTransport::writeSlow(const uint8_t* buf, uint32_t len) { uint32_t have_bytes = wBase_ - wBuf_.get();//計算寫緩存區(qū)中已有的字節(jié)數(shù) uint32_t space = wBound_ - wBase_;//計算剩余寫緩存空間 // 如果在緩存區(qū)的空閑空間不能容納我們的數(shù)據(jù),我們采用慢路徑寫(僅僅) assert(wBound_ - wBase_ < static_cast<ptrdiff_t>(len)); //已有數(shù)據(jù)加上需要寫入的數(shù)據(jù)是否大于2倍寫緩存區(qū)或者緩存區(qū)為空 if ((have_bytes + len >= 2*wBufSize_) || (have_bytes == 0)) { if (have_bytes > 0) {//緩存大于0且加上需要再寫入數(shù)據(jù)的長度大于2倍緩存區(qū) transport_->write(wBuf_.get(), have_bytes);//先將已有數(shù)據(jù)寫入下層傳輸 } transport_->write(buf, len);//寫入這次的len長度的數(shù)據(jù) wBase_ = wBuf_.get();//重新得到寫緩存的基地址 return; } memcpy(wBase_, buf, space);//填充我們的內(nèi)部緩存區(qū)為了寫 buf += space; len -= space; transport_->write(wBuf_.get(), wBufSize_);//寫入下層傳輸 assert(len < wBufSize_); memcpy(wBuf_.get(), buf, len);//拷貝剩余的數(shù)據(jù)到我們的緩存 wBase_ = wBuf_.get() + len;//重新得到寫緩存基地址 return; }
慢寫函數(shù)也有棘手的問題,就是我們應(yīng)該拷貝我們的數(shù)據(jù)到我們的內(nèi)部緩存并且從那兒發(fā)送出去,或者我們應(yīng)該僅僅用一次系統(tǒng)調(diào)用把當(dāng)前內(nèi)部寫緩存區(qū)的內(nèi)容寫出去,然后再用一次系統(tǒng)調(diào)用把我們當(dāng)前需要寫入長度為len的數(shù)據(jù)再次寫入出去。如果當(dāng)前緩存區(qū)的數(shù)據(jù)加上我們這次需要寫入數(shù)據(jù)的長度至少是我們緩存區(qū)長度的兩倍,我們將不得不至少調(diào)用兩次系統(tǒng)調(diào)用(緩存區(qū)為空時有可能例外),那么我們就不拷貝了。否則我們就是按順序遞加的。具體實現(xiàn)分情況處理,最后我們在看看慢借函數(shù)的實現(xiàn),借相關(guān)函數(shù)主要是為了實現(xiàn)可變長度編碼。慢借函數(shù)實現(xiàn)細(xì)節(jié)如下:
const uint8_t* TBufferedTransport::borrowSlow(uint8_t* buf, uint32_t* len) { (void) buf; (void) len; return NULL;//默認(rèn)返回空 }
在這個類我們可以看出,它什么也沒有做,只是簡單的返回NULL,所以需要阻塞去借。按照官方的說法,下面兩種行為應(yīng)該當(dāng)前的版本中實現(xiàn),在將來的版本可能會發(fā)生改變:
如果需要借的長度最多為緩存區(qū)的長度,那么永遠(yuǎn)不會返回NULL。依賴底層傳輸,它應(yīng)該拋出一個異常或者永遠(yuǎn)不會掛掉;
一些借用請求可能內(nèi)部字節(jié)拷貝,如果借用的長度最多是緩存區(qū)的一半,那么不去內(nèi)部拷貝。為了優(yōu)化性能保存這個限制。
浙公網(wǎng)安備 33010602011771號