大資料下的Distinct Count(二):Bitmap篇

安全劍客發表於2020-03-03
大資料(big data),IT行業術語,是指無法在一定時間範圍內用常規軟體工具進行捕捉、管理和處理的資料集合,是需要新處理模式才能具有更強的決策力、洞察發現力和流程最佳化能力的海量、高增長率和多樣化的資訊資產。

大資料下的Distinct Count(二):Bitmap篇大資料下的Distinct Count(二):Bitmap篇

1. Bitmap介紹

《程式設計珠璣》上是這樣介紹bitmap的:

Bitmap是一個十分有用的資料結構。所謂的Bitmap就是用一個bit位來標記某個元素對應的Value,而Key即是該元素。由於採用了Bit為單位來儲存資料,因此在記憶體佔用方面,可以大大節省。

簡而言之——用一個bit(0或1)表示某元素是否出現過,其在bitmap的位置對應於其index。《程式設計珠璣》給出了一個用bitmap做排序的例子:

/* Copyright (C) 1999 Lucent Technologies */
/* From 'Programming Pearls' by Jon Bentley */
/* bitsort.c -- bitmap sort from Column 1
* Sort distinct integers in the range [0..N-1]
*/
#include#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N / BITSPERWORD];
void set(int i) { a[i >> SHIFT] |= (1 << (i & MASK)); }
void clr(int i) { a[i >> SHIFT] &= ~(1 << (i & MASK)); }
int test(int i) { return a[i >> SHIFT] & (1 << (i & MASK)); }
int main() {
    int i;
    for (i = 0; i < N; i++)
        clr(i);
    /* Replace above 2 lines with below 3 for word-parallel init
    int top = 1 + N/BITSPERWORD;
    for (i = 0; i < top; i++)
    a[i] = 0;
    */
    while (scanf("%d", &i) != EOF)
        set(i);
    for (i = 0; i < N; i++)
        if (test(i))
            printf("%d\n", i);
    return 0;
}

上面程式碼中,用int的陣列儲存bitmap,對於每一個待排序的int數,其對應的index為其int值。

2. Distinct Count最佳化
index生成

為了使用bitmap做Distinct Count,首先需得到每個使用者(uid)對應(在bitmap中)的index。有兩種辦法可以得到從1開始編號index表(與uid一一對應):

  • hash,但是要找到無碰撞且hash值均勻分佈[1, +∞)區間的hash函式是非常困難的;
  • 維護一張uid與index之間的對映表,並增量更新
  • 比較兩種方法,第二種方法更為簡單可行。

    UV計算

    在index生成完成後,RDD[(uid, V)]與RDD[(uid, index)]join得到index化的RDD。bitmap的開源實現有EWAH,採用RLE(Run Length Encoding)壓縮,很好地解決了儲存空間的浪費。Distinct Count計算轉變成了求bitmap中1的個數:

// distinct count for rdd(not pair) and the rdd must be sorted in each partition
def distinctCount(rdd: RDD[Int]): Int = {
    val bitmap = rdd.aggregate[EWAHCompressedBitmap](new EWAHCompressedBitmap())(
      (u: EWAHCompressedBitmap, v: Int) => {
        u.set(v)
        u
      },
      (u1: EWAHCompressedBitmap, u2: EWAHCompressedBitmap) => u1.or(u2)
    )
    bitmap.cardinality()
}
// the tuple_2 is the index
def groupCount[K: ClassTag](rdd: RDD[(K, Int)]): RDD[(K, Int)] = {
    val grouped: RDD[(K, EWAHCompressedBitmap)] = rdd.combineByKey[EWAHCompressedBitmap](
      (v: Int) => EWAHCompressedBitmap.bitmapOf(v),
      (c: EWAHCompressedBitmap, v: Int) => {
        c.set(v)
        c
      },
      (c1: EWAHCompressedBitmap, c2: EWAHCompressedBitmap) => c1.or(c2))
    grouped.map(t => (t._1, t._2.cardinality()))
}

但是,在上述計算中,由於EWAHCompressedBitmap的set方法要求int值是升序的,也就是說RDD的每一個partition的index應是升序排列:

// sort pair RDD by value
def sortPairRDD[K](rdd: RDD[(K, Int)]): RDD[(K, Int)] = {
    rdd.mapPartitions(iter => {
      iter.toArray.sortWith((x, y) => x._2.compare(y._2) < 0).iterator
    })
}

為了避免排序,可以為每一個uid生成一個bitmap,然後在Distinct Count時將bitmap進行or運算亦可:

rdd.reduceByKey(_ or _)
    .mapValues(_._2.cardinality())

原文地址:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2678184/,如需轉載,請註明出處,否則將追究法律責任。

相關文章