標籤:分散式運算 大資料 hyper 技術
今天學習的是是2個log的檔案,2個檔案的實現功能都超出我原本理解的意思。開始時我以為就是記錄不同的類型的日誌,後來才慢慢的明白了額,slowLog記錄的是逾時的查詢記錄,而hyperloglog其實跟日誌一點關係都沒有,好吧,我再一次傻眼了,他其實是一種基數統計演算法,應該分開了看,hyper + loglog的計算。好,接下來,我們開始學習一下Redis代碼中是如何?的。
slowLog的官方解釋:
/* Slowlog implements a system that is able to remember the latest N * queries that took more than M microseconds to execute. * * The execution time to reach to be logged in the slow log is set * using the 'slowlog-log-slower-than' config directive, that is also * readable and writable using the CONFIG SET/GET command. * * The slow queries log is actually not "logged" in the Redis log file * but is accessible thanks to the SLOWLOG command. * *大致意思就是SlowLog記錄的是系統最近N個超過一定時間的查詢,就是比較耗時的查詢 * ----------------------------------------------------------------------------
裡面定義了一個slowLog entry的結構體:
/* This structure defines an entry inside the slow log list *//* 慢日誌結構體,將會插入到slowLogList,慢日誌列表中 */typedef struct slowlogEntry { robj **argv; int argc; //自身的id標識 long long id; /* Unique entry identifier. */ //query操作所消耗的時間,單位為nanoseconds long long duration; /* Time spent by the query, in nanoseconds. */ //查詢發發生的時間 time_t time; /* Unix time at which the query was executed. */} slowlogEntry;/* Exported API */void slowlogInit(void); /* slowlog初始化操作 */void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration); /* slowLogEntry壓入列表操作 *//* Exported commands *//* 開放給系統使用的命令 */void slowlogCommand(redisClient *c);
裡面定義的方法也非常簡單。初始化init方法和插入方法,在服務端的server端,維護了一個slowLog的列表,會按照時間順序插入逾時的查詢記錄,也就是slowLogEntry記錄:
/* Initialize the slow log. This function should be called a single time * at server startup. *//* slowLog的初始化操作 */void slowlogInit(void) {//建立slowLog的List server.slowlog = listCreate(); //第一個entry_id聲明為0 server.slowlog_entry_id = 0; listSetFreeMethod(server.slowlog,slowlogFreeEntry);}
插入列表的方法:
/* Push a new entry into the slow log. * This function will make sure to trim the slow log accordingly to the * configured max length. *//* 插入一個entry到slowLog列表中,如果時間超出給定的時間範圍時 */void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) { if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */ if (duration >= server.slowlog_log_slower_than) //如果entry的duration時間超出slowlog_log_slower_than時間,則添加 listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration)); /* Remove old entries if needed. */ while (listLength(server.slowlog) > server.slowlog_max_len) //如果列表長度已經超出slowLog的最大值,移除最後一個slowLogEntry listDelNode(server.slowlog,listLast(server.slowlog));}
slowLog就是這樣,非常簡單明了,重點學習一下hyperloglog,作為一種基數統計演算法,比如統計一篇莎士比亞的文章中,不同單詞出現的個數,如果按照平常我們想到的做法,把裡面的單詞都存到hashset中,求出容量即可,但是當面對的是海量資料的時候,這得佔據多大的記憶體呢,所以就有了後來我們說的“位元影像法“,位元影像可以快速、準確地擷取一個給定輸入的基數。位元影像的基本思想是使用雜湊函數把資料集映射到一個bit位,每個輸入元素與bit位是一一對應。這樣Hash將沒有產生碰撞衝突,並減少需要計算每個元素映射到1個bit的空間。雖然Bit-map大大節省了儲存空間,但當統計很高的基數或非常大的不同的資料集,它們仍然有問題。但是比較幸運的是,基數統計作為一個新興的領域,也已經有了許多開源演算法的實現,基數統計演算法的思想是用準確率換取空間,準確率可以稍稍差一點點,但是可以大大的縮減佔用的空間。下面在網上找了3個比較典型的基數統計演算法,這三種技術是:Java HashSet、Linear Probabilistic Counter以及一個Hyper LogLog Counter,我說其中的第二種和第三種。
Linear Probabilistic Counter線性機率計數器是高效的使用空間,並且允許實現者指定所需的精度水平。該演算法在注重空間效率時是很有用的,但你需要能夠控制結果的誤差。該演算法分兩步運行:第一步,首先在記憶體中分配一個初始化為都為0的Bit-map,然後使用雜湊函數對輸入資料中的每個條目進行hash計算,雜湊函數運算的結果是將每條記錄(或者是元素)映射到Bit-map的一個Bit位上,該Bit位被置為1;第二步,演算法計算空的bit位元量,並使用這個數輸入到下面的公式來進行估算:
n=-m ln Vn
注意:ln Vn=Loge(Vn) 自然對數
在公式中,m是 Bit-map的大小,Vn是空bit位和map的大小的比率。需要重點注意的是原始Bit-map的大小,可以遠小於預期的最大基數。到底小多少取決於你可以承受誤差的大小。因為Bit-map的大小m小於不同元素的總數將會產生碰撞。雖然碰撞可以節省空間的,但同時也造成了估算結果出現誤差。所以通過控制原始map的大小,我們可以估算碰撞的次數,以致我們將在最終結果中看到誤差有多大。
hyperLogLog提供了比上面效率更高的演算法。顧名思義,Hyper LogLog計數器就是估算Nmax為基數的資料集僅需使用loglog(Nmax)+O(1) bits就可以。如線性計數器的Hyper LogLog計數器允許設計人員指定所需的精度值,在Hyper LogLog的情況下,這是通過定義所需的相對標準差和預期要計數的最大基數。大部分計數器通過一個輸入資料流M,並應用一個雜湊函數設定h(M)來工作。這將產生一個S = h(M) of {0,1}^∞字串的可觀測結果。通過分割雜湊輸入資料流成m個子字串,並對每個子輸入資料流保持m的值可觀測 ,這就是相當一個新Hyper LogLog(一個子m就是一個新的Hyper LogLog)。利用額外的觀測值的平均值,產生一個計數器,其精度隨著m的增長而提高,這隻需要對輸入集合中的每個元素執行幾步操作就可以完成。其結果是,這個計數器可以僅使用1.5 kb的空間計算精度為2%的十億個不同的資料元素。與執行 HashSet所需的120 MB進行比較,這種演算法的效率很明顯。這就是傳說中的”如何僅用1.5KB記憶體為十億對象計數“。
Redis源碼分析(二十六)--- slowLog和hyperloglog