cache原始碼分析三 evacuate機制的實現

2021-07-31 16:38:29 字數 3863 閱讀 2474

原文:

之前分析過,trafficserver的cache機制本質上是將cache視為乙個ring buffer,迴圈順序向cache寫入內容。同時我們也說過,trafficserver對大檔案與小檔案的儲存方式是不相同的。對於小檔案,head與body是放在乙個整體儲存的,而對於大檔案,head與body是分開儲存的,同時body又會被分為幾個fragment進行儲存。

問題1:當讀取大檔案時,剛剛讀完head,而body部分由於cache寫而被覆蓋導致丟失,此次讀取勢必失敗。

解決方案:在cache寫時,每次將正在讀的大檔案重新寫乙份。。

問題2:如何將正在讀的大檔案重新寫入cache?由於大檔案是乙個整體,同時悲催的是,它們又被分成幾個fragment與乙個head儲存,在寫時,需要考慮原子性。

解決方案:為了解決原子性,可以將head與body作為兩部分。如果head正在讀,則對head執行evcuate操作。由於body可以由多個fragment組成,如果第乙個fragment正在讀,就判定body正在讀,對所有fragment執行evacuate操作。

問題3:如何實現evacuate機制?在對body的fragments執行evacuate操作時,如何確定下一次該寫哪個fragment?

解決方案:trafficserver使用乙個hash list來實現evacuate機制,在**中這個結構名為evacuate,為了區分,我稱之為evacuate桶。對於正在讀的檔案,將其meta data資訊加入到hash list中,同時使用乙個引用計數來控制讀取的次數,每讀取一次,就+1,而讀取完成後,則-1。如果計數器的值為0,則從hash list中刪除。當cache寫時,會檢視要覆蓋的區域對應的hash list內容,執行evacuate操作。為了實現大檔案evacuate的原子性,trafficserver額外使用了乙個hash list來儲存body的第乙個fragment,名為lookaside,這裡我稱之為lookaside buffer。當所有fragment都寫成功後,才更新第乙個fragment的dir等資訊。這樣,當body沒有evacuate完成時,head仍使用以前的body,由此可以避免evacuate body過程中讀取失敗。

evacuate具體定義:將滿足條件的正在讀取的檔案內容重新寫入cache,同時更新索引資訊,以避免cache寫導致檔案內容被覆蓋而丟失。可以將evacuate機制看作是磁碟儲存上的lru。

evacuate相關函式

1. 錯誤處理

trafficserver週期掃瞄evacuate桶,將已經失效的資訊從evacuate桶中刪除。它的實現是,將cache分為16等分,每次寫完1/16大小的cache,就會掃瞄一次evacuate桶。

evacuate桶是按照雜湊法實現的hash表,表中每乙個桶是乙個list,對應cache中

evacuation_bucket_size大小的

儲存區域。該函式負責剔除乙個桶中狀態錯誤的block

evacuate_cleanup_blocks

將1/16大小的cache對應的evacuate桶中狀態錯誤的block刪除

evacuate_cleanup

evacuate_cleanup->scan_for_pinned_documents->更新scan_pos資訊,下次

write_pos大於scan_pos,則再次

執行periodic_scan

periodic_scan

這個功能預設是不啟用的。它的作用是掃瞄所有dir資訊,從中找出在此次掃瞄區域內的dir,如果它的pin值滿足要求,

就執行force_evacuate_head。通過修改

records.config檔案中proxy.config.cache.permit.pinning值為1開

啟這個功能

scan_for_pinned_documents

當cache寫滿時,執行該操作。具體操作dir_lookaside_cleanup->dir_clean_vol->periodic_scan,從

lookaside buffer中,cache索引中,evacuate桶等中清除狀態錯誤的資訊

agg_wrap

2 向evacuate桶中新增希望執行evacuate的檔案

(1) 當正在讀取大檔案時,如果要向讀取的cache區域寫入新資料,此時會發生evacuate。vol::begin_read將要讀取的大檔案資訊加入evacuate桶中,如果已經在evacuate桶中,則增加引用計數,當這次大檔案讀取結束,執行vol::close_read減少引用計數,如果為0,從evacuate桶中移除。這個過程是防禦性質的,如果在cache開始執行evacute時,大檔案資訊已經從evacuate桶中移除,則不會執行evacuate。

(2) 還有一種方式是force_evacuate_head。如果正在讀取的檔案在cache的位置相對cache當前寫位置在(cache_size * evacute percent/100)範圍之內,就強制執行evacuate。這種方式一定會發生evacuate。

traffic_line -s "proxy.config.cache.hit_evacuate_percent"-v 5

預設evacuate percent為0,就是不執行force_evacuate_head,通過上面的命令可以修改percent的值。

相關**:

//file: iocore/cache/cacheread.cc  cachevc::openreadstarthead

#ifdef hit_evacuate

if (vol->within_hit_evacuate_window(&dir) &&

(!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit))

#endif 

//file: iocore/cache/cacheread.cc cachevc::openreadstartearliest

#ifdef hit_evacuate

if (vol->within_hit_evacuate_window(&earliest_dir) &&

(!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit))

#endif

//file: iocore/cache/cacheread.cc cachevc::openreadclose

#ifdef hit_evacuate

if (f.hit_evacuate && dir_valid(vol, &first_dir) && closed > 0)

}#endif

3. 發生evacuate

在cache每次寫時,都會對要寫的這塊區域執行evacuate,具體**在iocore/cache/cachewrite.cc中的cachevc::aggwrite中:

// evacuate space

off_t end = header->write_pos + agg_buf_pos + evacuation_size;

//對將要寫入的cache區域執行evacuate

if (evac_range(header->write_pos, end, !header->phase) < 0)

goto lwait;

//如果要寫的內容超出了cache大小,指標指向cache頭部,此時需要對頭部的那部分將被覆蓋的區域執行evacuate

if (end > skip + len)

if (evac_range(start, start + (end - (skip + len)), header->phase))

goto lwait;

Leveldb原始碼分析3 Cache

leveldb 實現了乙個lrucache。和標準的lrucache一樣沒有什麼特別之處。lrucache內部有乙個hash表用於快速查詢key對應的雙向鍊錶,雙向鍊錶中記錄了value的值。利用雙線鍊錶實現lrucache的演算法在 作業系統 中描述的很清楚,這裡不再描述。hashtable不是c...

redux原始碼分析(三) 原始碼部分

下面是每個部分的一些解讀 createstore apicreatestore reducer,initialstate enhancer 曾經非常好奇這個函式的第二個引數到底是initialstate還是enhancer,因為見過兩種寫法都有的,以為是版本問題。看了原始碼才發現,都可以的。如果你不...

gSOAP 原始碼分析 三

gsoap 原始碼分析 三 2012 5 25 flyfish 一 http請求方法 get 請求獲取 request uri 所標識的資源 post 在 request uri 所標識的資源後附加新的資料 head 請求獲取由 request uri 所標識的資源的響應訊息報頭 put 請求伺服器...