一份針對於新手的多執行緒實踐--進階篇

crossoverJie發表於2018-10-31

前言

在上文《一份針對於新手的多執行緒實踐》留下了一個問題:

這只是多執行緒其中的一個用法,相信看到這裡的朋友應該多它的理解更進一步了。

再給大家留個閱後練習,場景也是類似的:

在 Redis 或者其他儲存介質中存放有上千萬的手機號碼資料,每個號碼都是唯一的,需要在最快的時間內把這些號碼全部都遍歷一遍。

有想法感興趣的朋友歡迎在文末留言參與討論??。

網友們的方案

我在公眾號以及其他一些平臺收到了大家的回覆,果然是眾人拾柴火焰高啊。

一份針對於新手的多執行緒實踐--進階篇


一份針對於新手的多執行緒實踐--進階篇


一份針對於新手的多執行緒實踐--進階篇


感謝每一位參與的朋友。

其實看了大家的方案大多都想到了資料肯定要分段,因為大量的資料肯定沒法一次性 load 到記憶體。

但怎麼載入就要考慮清楚了,有些人說放在資料庫中通過分頁的方式進行載入,然後將每頁的資料丟到一個執行緒裡去做遍歷。

其實想法挺不錯的,但有個問題就是:

這樣肯定會導致有一個主執行緒去遍歷所有的號碼,即便是分頁查詢的那也得全部查詢一遍,效率還是很低。

即便是分頁載入號碼用多執行緒,那就會涉及到鎖的問題,怎麼保證每個執行緒讀取的資料是互不衝突的。

但如果儲存換成 RedisString 結構這樣就更行不通了。

遍歷資料方案

有沒有一種利用多執行緒載入效率高,並且執行緒之間互相不需要競爭鎖的方案呢?

下面來看看這個方案:

首先在儲存這千萬號碼的時候我們把它的號段單獨提出來並冗餘儲存一次。

比如有個號碼是 18523981123 那麼就還需要儲存一個號段:1852398

這樣當我們有以下這些號碼時:

18523981123 18523981124 18523981125 13123874321 13123874322 13123874323

我們就還會維護一個號段資料為:

1852398 1312387

這樣我想大家應該明白下一步應當怎麼做了吧。

在需要遍歷時:

  • 通過主執行緒先把所有的號段載入到記憶體,即便是千萬的號碼號段也頂多幾千條資料。
  • 遍歷這個號段,將每個號段提交到一個 task 執行緒中。
  • 由這個執行緒通過號段再去查詢真正的號碼進行遍歷。
  • 最後所有的號段都提交完畢再等待所有的執行緒執行完畢即可遍歷所有的號碼。

這樣做的根本原因其實是避免了執行緒之間加鎖,通過號段可以讓每個執行緒只取自己那一部分資料。

可能會有人說,如果號碼持續增多導致號段的資料也達到了上萬甚至幾十萬這怎麼辦呢?

那其實也是同樣的思路,可以再把號段進行拆分。

比如之前是 1852398 的號段,那我繼續拆分為 1852

這樣只需要在之前的基礎上再啟動一個執行緒去查詢子號段即可,有點 fork/join 的味道。

這樣的思路其實也和 JDK1.7 中的 ConcurrentHashMap 類似,定位一個真正的資料需要兩次定位。

一份針對於新手的多執行緒實踐--進階篇

分散式方案

上面的方案也是由侷限性的,畢竟說到底還是一個單機應用。沒法擴充套件;處理的資料始終是有上限。

這個上限就和伺服器的配置以及執行緒數這些相關,說的高大上一點其實就是垂直擴充套件增加單機的處理效能。

因此隨著資料量的提升我們肯定得需要通過水平擴充套件的方式才能達到最好的效能,這就是分散式的方案。

假設我現在有上億的資料需要遍歷,但我當前的伺服器配置只能支撐一個應用啟動 N 個執行緒 5 分鐘跑5000W 的資料。

於是我水平擴充套件,在三臺伺服器上啟動了三個獨立的程式。假設一個應用能跑 5000W ,那麼理論上來說三個應用就可以跑1.5億的資料了。

但這個的前提還是和上文一樣:每個應用只能處理自己的資料,不能出現加鎖的情況(這樣會大大的降低效能)。

所以我們得對剛才的號段進行分組。

先通過一張圖來直觀的表示這個邏輯:

一份針對於新手的多執行緒實踐--進階篇

假設現在我有 9 個號段,那麼我就得按照圖中的方式把資料隔離開來。

第一個資料給應用0,第二個資料給應用1,第三個資料給應用2。後面的資料以此類推(就是一個簡單的取模運算)。

這樣就可以將號段均勻的分配給不同的應用來進行處理,然後每個應用再按照上文提到的將分配給自己的號段丟到執行緒池中由具體的執行緒去查詢、遍歷即可。

分散式帶來的問題

這樣看似沒啥問題,但一旦引入了分散式之後就不可避免的會出現 CAP 的取捨,這裡不做過多討論,感興趣的朋友可以自行搜尋。

首先要解決的一個問題就是:

這三個應用怎麼知道它自己應該取哪些號段的資料呢?比如 0 號應用就取 0 3 6(這個相當於號段的下標),難道在配置檔案裡配置嘛?

那如果資料量又增大了,對應的機器數也增加到了 5 臺,那自然 0 號應用就不是取 0 3 6 了(取模之後資料會變)。

所以我們得需要一個統一的排程來分配各個應用他們應當取哪些號段,這也就是資料分片

假設我這裡有一個統一的分配中心,他知道現在有多少個應用來處理資料。還是假設上文的三個應用吧。

在真正開始遍歷資料的時候,這個分配中心就會去告訴這三個應用:

你們要開始工作了啊,0 號應用你的工作內容是 0 3 6,1 號應用你的工作內容是 1 4 7,2 號應用你的工作內容是 2 5 8

這樣各個應用就知道他們所應當處理的資料了。

當我們新增了一個應用來處理資料時也很簡單,同樣這個分配中心知道現在有多少臺應用會工作。

他會再拿著現有的號段對 4(3+1臺應用) 進行取模然後對資料進行重新分配,這樣就可以再次保證資料分配均勻了。

只是分配中心如何知道有多少應用呢,其實也簡單,只要中心和應用之間通訊就可以了。比如啟動的時候呼叫分配中心的介面即可。

上面提到的這個分配中心其實就是一個常見的定時任務的分散式排程中心,由它來統一發起排程,當然分片只是它其中的一個功能而已(關於排程中心之後有興趣再細說)。

總結

本次探討了多執行緒的更多應用方式,如要是如何高效的執行。最主要的一點其實就是儘量的避免加鎖。

同時對分散式水平擴充套件談了一些處理建議,本次也是難得的一行程式碼都沒貼,大家感興趣的話在後面更新相關程式碼。

也歡迎大家留言討論。?

你的點贊與轉發是最大的支援。

一份針對於新手的多執行緒實踐--進階篇

相關文章