分散式超大規模資料的實時快速排序演算法

美得讓人心動發表於2019-03-02

引言

對資料進行處理的同學,經常會遇到排序需求,無論是記憶體資料還是磁碟資料。

對於單點的資料,我們的處理比較簡單,比如:

select field_a from table_b order by field_a limit 100, 10;
db.collection_b.find().sort({"field_a":1}).skip(100).limit(10);複製程式碼

儲存服務的處理流程一般可抽象如下:
image.png

資訊爆炸的時代,資料早已不是單點所能承載的了,資料一般分佈在大量節點上,假設某庫中的資料均勻地分佈在以下的所有節點上。

image.png

這時sort, limit的一般方法是選擇一箇中間節點或者中介軟體來做合併處理:
image.png

一般處理流程的動態表示如下:

t3.gif

我們將過程抽象,流程簡化如下:
image.png

注意第三步在資料節點中的查詢結果範圍為[0,skip+limit]。當我們想查詢[skip=1000000, limit=200]的資料,意味著需要在各節點

上先查詢[skip=0, limit=1000000+200]的資料,再由歸併服務對結果進行[skip=1000000, limit=200]的排序, 對儲存IO與網路IO的處理量級與skip成正比,

對於T級以上規模所資料處理,無法做到實時處理。

下面來討論另一種方式

分散式框架大牛學習交流群:697579751(沒有開發經驗勿加!)

理論基礎

在一般對資料的處理方法中,我們基於一個共同的假設:各資料儲存節點只具備簡單的對外查詢功能,相互之間的連線功能是很弱的,主要有主從,選舉,更一步的功能就少了。

現在我們要改變這一假設。

理論描述

假設各儲存節點具備相互對話的能力。比如,"hey,你那裡skip為100的資料是哪個", “好的,我這裡skip為100的數為m。”

對話分成幾種,第一種是擴散請求,當其中一節點收到一次請求後, 此節點會將請求迅速擴散到所有其他相關的節點。

第二種對話是應答式,簡單的你問我答型。

假設有一個排序全網排序請求,在某一節點獲得請求後,擴散給網路需要對此請求處理的請求,各節點在進經n次對話後,產生最終的結果。

概念定義

在一堆資料中,資料m前面有n-1個數,則m的排序索引為n。

image.png

通過問答式查詢,我們可以輕而易舉地獲得某個數在全網中的排序索引,只需要將各節點上排在此節點前的個數相加即可。

推導

簡單點,如果我們要在一批資料中查詢skip=100, limit=20的資料有哪些,我們的目標是在全網資料中獲取b,e.

b的索引為第100,

e的索引為120。

則所有在[b,e]之前的數都是我們的目標資料。

實際上還要考慮資料重複,即在b的索引為98個, b個數為4,e的索引118, e個數為5,則目標資料以[b, b]開頭, 以[e,e,e]結尾。

技術使用

某節點想知道資料m前面有多少個數, 則直接向其他資料節點傳送對話,所有節點(包含自身)只需要返回本節點中在m前面的資料個數, 假設各節點上的查詢結果個數為n1,n2,n3,...n10,

則全量資料中資料m前面的資料個數n=(n1+n2+n3+.....+n10)。

以資料m做為遞度物件, n做為結果向skip, skip+limit逼近,在全量資料中獲取最終的b,e。

架構設計

請求處理流程

image.png

如圖所示,對於一次請求我們會分成三個部分

image.png

節點確認階段

image.png

此階段確認哪些節點參與發現。

結果同步階段

image.png

同步的過程是相互的,相互猜測,查詢對應資料的索引。

這一步是處理的核心步驟,通過相互確認,最終逼近索引在[100,120]之間的數是哪些。

結果合併

image.png

各資料節點將資料結果同步出去,如果skip=100萬, limit=20,最多也就同步20條資料,
不再於skip正成比。

模型假設

假設存在m個節點,各節點上的資料都是各自排好序的,各節點間平均來回時間為t1,
單次查詢確認程式執行時間為t2,

每次確認的資料個數為p,假設結果確認階段平均某節點的對外請求次數不在於s。

節點確認時間為 t1

結果確認階段時間<=s*(t1+t2)

結果合併階段時間為t1

則總共所需時間為 (2 + s)t1 + st2

從上面結果得出,請求所需時間與節點個數不成正比,與節點間的平均網路時間及演算法次數相關。

假設各節點在同一個區域網中,相互間的來回網路時間t1<1ms, 程式執行時間t2 < 1ms,單節點對外請求次數不超過100,

則總共所需求時間不超過 (2+100)+100=202ms

理論要求

如果希望在200ms內完成一次查詢,則平均某個節點對外請求次數不超過100,對應的查詢資料總次數則不超過100*p,假設p為100,則總次確認的總次數可以達到10000次。

下面我們來模擬一次真實的操作吧。

模擬操作

資料準備

假設對應的資料為正整型,在10個節點中查詢skip=100, limit=20的所有資料。

則我們要通過對話確認索引分別為100,120的數為哪個。

考慮到資料重複,我們為各個數建立向量(資料,索引,個數),假設索引為100的數為b,個數為c(b), 索引為120的數為e, 個數為c(e),則我們所要獲得的向量為( b,100,c(b)), (e, 120, c(e))。

首輪

由於所有資料都是正整型,則我們知道最小的數為0, 最大的數為2^31,

因此第一次待確認佇列裡可以包含[0, max=2*31]在所有節點上的情況,得到(0, i(0), c(0)), (max, i(max), c(max))。

同時為了更好地得到逼近效果,先做一次全範圍猜測,比如max/100做猜測,

以得到(max/100, i(max/100), c(max/100), (max2/100, i(max2/100), c(max*2/100)), .......(max^99/100,i(max^99/100),c(max^99/100))。

其實0可以認為是max0, 則第一次做逼近的資料可以是(nmax/100), n~[0,100]。

目標逼近

經過第一輪猜測後,全網路都知道了(n*max/100), n~[0,100]對應的向量。

存在2個數n1, n2滿足 i(n1max/100=s1) + c(s1)<=100, i(n2max/100=s2) + c(s2)>=100.(如果不存在n2, 則表明不存在這個數,其全域性索引>=100, 因此結果為空,直接跳到資料合併階段)

存在2個數n3, n4滿足 i(n3max/100=s3) + c(s3)<=120, i(n4max/100=s4) + c(s4)>=120.(如果不存在n4, 則表明不存在這個數,其全域性索引>=120, 假設存在n2, 則資料大於等於n2*max/100的數都是目標資料。)

則有 s1<= b <=s2, s3<=e<=s4,

我們對再[s1, s2], [s3, s4]做相應的逼近,直至獲取到最終b,e, 滿足 c(b) + i(b) <= 100, c(e) + i(e) <= 120。

提升規模

從上面的結論來看,資料規模對時間的影響不大,假設資料模組為T級或者P級, 直接影響的是查詢某個數在此資料節點上前面有多少個數。

為了降低響應時間,我們只需要設計好資料結構,以支援快速的向量查詢。

假設單個節點的資料是G級,假設我們用紅黑樹儲存,是T/P級,我們用B+數,假設每顆節點儲存著其子節點的個數。

我們以經黑樹為例:
image.png

如果需要獲取小於數150前面的個數,則只需要找到其所在左支遍歷的個數加上根節點的左側子節點個數。

轉化為程式碼即為:

function getCount(node, child){
    if (node.right == child) {
        return { node:node.parent, count:node.leftCount + 1 };
    }
    else {
        return { node:node.parent, count:0};
    }
}
node=node_150;
var count = node.leftCount;
var nc = {node:node, count:0};
while(node != null){
    nc = getCount(node.parent, node);
    node = nc.node;
    count += nc.count;
}
console.log("node 150's left count:"+count);
複製程式碼

結論

我們的理論目標環境:

1.資料分佈在大量的資料節點上,並且在節點上是有序的。

2.各節點間的網路延時不超過1mm。

在此分散式環境下可以實現對T/P級資料進行最多200ms延時的實時快速排序。

分散式框架大牛學習交流群:697579751(沒有開發經驗勿加!)


相關文章