讀多寫少的場景下引發的問題?
引入 copyonwrite 思想解決問題!
copyonwrite思想在kafka原始碼中的運用
大家可以設想一下現在我們的記憶體裡有乙個arraylist,這個arraylist預設情況下肯定是執行緒不安全的,要是多個執行緒併發讀和寫這個arraylist可能會有問題。
好,問題來了,我們應該怎麼讓這個arraylist變成執行緒安全的呢?
有乙個非常簡單的辦法,對這個arraylist的訪問都加上執行緒同步的控制。
比如說一定要在synchronized**段來對這個arraylist進行訪問,這樣的話,就能同一時間就讓乙個執行緒來操作它了,或者是用readwritelock讀寫鎖的方式來控制,都可以。
我們假設就是用readwritelock讀寫鎖的方式來控制對這個arraylist的訪問。
這樣多個讀請求可以同時執行從arraylist裡讀取資料,但是讀請求和寫請求之間互斥,寫請求和寫請求也是互斥的。
**大概就是類似下面這樣:
public object read()
public void write()
大家想想,類似上面的**有什麼問題呢?
最大的問題,其實就在於寫鎖和讀鎖的互斥。假設寫操作頻率很低,讀操作頻率很高,是寫少讀多的場景。
那麼偶爾執行乙個寫操作的時候,是不是會加上寫鎖,此時大量的讀操作過來是不是就會被阻塞住,無法執行?
這個就是讀寫鎖可能遇到的最大的問題。
這個時候就要引入copyonwrite思想來解決問題了。
他的思想就是,不用加什麼讀寫鎖,鎖統統給我去掉,有鎖就有問題,有鎖就有互斥,有鎖就可能導致效能低下,你阻塞我的請求,導致我的請求都卡著不能執行。
那麼他怎麼保證多執行緒併發的安全性呢?
很簡單,顧名思義,利用「copyonwrite」的方式,這個英語翻譯成中文,大概就是「寫資料的時候利用拷貝的副本來執行」。
你在讀資料的時候,其實不加鎖也沒關係,大家左右都是乙個讀罷了,互相沒影響。
問題主要是在寫的時候,寫的時候你既然不能加鎖了,那麼就得採用乙個策略。
假如說你的arraylist底層是乙個陣列來存放你的列表資料,那麼這時比如你要修改這個陣列裡的資料,你就必須先拷貝這個陣列的乙個副本。
然後你可以在這個陣列的副本裡寫入你要修改的資料,但是在這個過程中實際上你都是在操作乙個副本而已。
這樣的話,讀操作是不是可以同時正常的執行?這個寫操作對讀操作是沒有任何的影響的吧!
關鍵問題來了,那那個寫執行緒現在把副本陣列給修改完了,現在怎麼才能讓讀執行緒感知到這個變化呢?
關鍵點來了,劃重點!這裡要配合上volatile關鍵字的使用。
volatile關鍵字的使用,核心就是讓乙個變數被寫執行緒給修改之後,立馬讓其他執行緒可以讀到這個變數引用的最近的值,這就是volatile最核心的作用。
所以一旦寫執行緒搞定了副本陣列的修改之後,那麼就可以用volatile寫的方式,把這個副本陣列賦值給volatile修飾的那個陣列的引用變數了。
只要一賦值給那個volatile修飾的變數,立馬就會對讀執行緒可見,大家都能看到最新的陣列了。
下面是jdk裡的 copyonwritearraylist 的原始碼。
大家看看寫資料的時候,他是怎麼拷貝乙個陣列副本,然後修改副本,接著通過volatile變數賦值的方式,把修改好的陣列副本給更新回去,立馬讓其他執行緒可見的。
// 這個陣列是核心的,因為用volatile修飾了
// 只要把最新的陣列對他賦值,其他執行緒立馬可以看到最新的陣列
private transient volatile object array;
public boolean add(e e) finally
}
然後大家想,因為是通過副本來進行更新的,萬一要是多個執行緒都要同時更新呢?那搞出來多個副本會不會有問題?
當然不能多個執行緒同時更新了,這個時候就是看上面原始碼裡,加入了lock鎖的機制,也就是同一時間只有乙個執行緒可以更新。
那麼更新的時候,會對讀操作有任何的影響嗎?
絕對不會,因為讀操作就是非常簡單的對那個陣列進行讀而已,不涉及任何的鎖。而且只要他更新完畢對volatile修飾的變數賦值,那麼讀執行緒立馬可以看到最新修改後的陣列,這是volatile保證的。
private e get(object a, int index)
這樣就完美解決了我們之前說的讀多寫少的問題。
如果用讀寫鎖互斥的話,會導致寫鎖阻塞大量讀操作,影響併發效能。
但是如果用了copyonwritearraylist,就是用空間換時間,更新的時候基於副本更新,避免鎖,然後最後用volatile變數來賦值保證可見性,更新的時候對讀執行緒沒有任何的影響!
在kafka的核心原始碼中,有這麼乙個場景,客戶端在向kafka寫資料的時候,會把訊息先寫入客戶端本地的記憶體緩衝,然後在記憶體緩衝裡形成乙個batch之後再一次性傳送到kafka伺服器上去,這樣有助於提公升吞吐量。
這個資料結構就是核心的用來存放寫入記憶體緩衝中的訊息的資料結構,要看懂這個資料結構需要對很多kafka核心原始碼裡的概念進行解釋,這裡先不展開。
但是大家關注一點,他是自己實現了乙個copyonwritemap,這個copyonwritemap採用的就是copyonwrite思想。
我們來看一下這個copyonwritemap的原始碼實現:
// 典型的volatile修飾普通map
private volatile mapmap;
@override
public synchronized v put(k k, v v)
@override
public v get(object k)
所以kafka這個核心資料結構在這裡之所以採用copyonwritemap思想來實現,就是因為這個map的key-value對,其實沒那麼頻繁更新。
也就是topicpartition-deque這個key-value對,更新頻率很低。
但是他的get操作卻是高頻的讀取請求,因為會高頻的讀取出來乙個topicpartition對應的deque資料結構,來對這個佇列進行入隊出隊等操作,所以對於這個map而言,高頻的是其get操作。
這個時候,kafka就採用了copyonwrite思想來實現這個map,避免更新key-value的時候阻塞住高頻的讀操作,實現無鎖的效果,優化執行緒併發的效能。
記憶體的COPY ON WRITE機制
剛了解到。趕快記下來。每個程式都有自己的資料段,段。補充 dll有自己的資料段,但沒有自己的堆疊。乙個dll,被很多程式呼叫,為什麼 段共享,資料段不共享?就是因為 記憶體的copy on write 機制 程式1呼叫這個dll,執行過程中,dll的資料段肯定會改變,那記憶體就會被copy乙份,原來...
我對CopyOnWrite的思考
copyonwrite後文中表述為cow copyonwrite容器即寫的時候複製乙個新的容器進行寫 通俗的理解是當我們往乙個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行copy,複製出乙個新的容器,然後在新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。我們需要了...
hadoop的使用場合與不使用場合
1.在web中頁面的快速響應中不適合用hadoop 響應時間在ms級別 2.大量的小檔案處理不使用用hadoop 元資料較多,而且元資料是儲存在namenode中的,記憶體占用大 3.hdfs中的block 乙個block只是可以儲存乙個檔案 但是乙個檔案可以由多個塊組成,預設情況下乙個block有...