一對一直播平臺原始碼,該擴容時就擴容
Redis的擴容步驟
當一旦需要進行擴容時,此時會使用到dict中的ht[1],一對一直播平臺原始碼的Redis的擴容步驟如下所示。
1、計算ht[1]的容量size,即擴容後的容量,ht[1]的容量為大於等於ht[0].used * 2且同時為2的冪次方的最小值;
2、為ht[1]設定size,sizemask欄位的值,初始化used欄位為0,併為dictEntry陣列分配空間;
3、將dict的rehashidx欄位設定為0,表示此時開啟漸進式rehash,Redis會透過漸進式rehash的方式逐步將ht[0]上的dictEntry遷移到ht[1]上;
4、當ht[0]的所有鍵值對全部存放到ht[1]中後,釋放ht[0]的記憶體空間,然後ht[1]變為ht[0]。
特別注意,上述的步驟僅針對正常的擴容,如果是ht[0]的初始化,則與上述的步驟稍有不同,這裡不再贅述。當dict中鍵值對特別多時,rehash會特別耗時,所以Redis採用一種漸進式rehash的方式來完成擴容,dict中的rehashidx欄位用於記錄當前已經rehash到的雜湊桶的索引,而漸進式rehash就是Redis不會一次性將ht[0]上的鍵值對遷移到ht[1]上,而是會在某些時間點遷移一部分,這些時間點如下所示。
1、當對資料進行增刪改查時會從ht[0]遷移一個雜湊桶到ht[1]上;
2、Redis會定時的從ht[0]遷移一部分雜湊桶到ht[1]上。
特別注意,如果在漸進式rehash的過程中有新的鍵值對新增,那麼會直接新增到ht[1]中。
下面將結合Redis原始碼對一對一直播平臺原始碼中Redis的擴容步驟進行學習。在第一節中已知,執行擴容邏輯的方法是dict.c檔案的dictExpand()方法,其原始碼實現如下所示。
int dictExpand(dict *d, unsigned long size) { // 如果正在rehash或者ht[0]的當前大小大於了擴容後的大小的最小值 // 此時返回狀態碼1,表示擴容發生異常 if (dictIsRehashing(d) || d->ht[0].used > size) return DICT_ERR; // n就是擴容後的雜湊表 dictht n; // 得到一個大於等於size的滿足2的冪次方的最小值作為n的容量 unsigned long realsize = _dictNextPower(size); // 如果擴容後的雜湊表的容量與老雜湊表容量一樣 // 此時返回狀態碼1,表示擴容發生異常 if (realsize == d->ht[0].size) return DICT_ERR; // 為n設定容量size n.size = realsize; // 為n設定掩碼sizemask n.sizemask = realsize-1; // 為n的陣列分配空間 n.table = zcalloc(realsize*sizeof(dictEntry*)); // 初始化n的當前大小used為0 n.used = 0; // 如果是初始化雜湊表,那麼直接將ht[0]置為n if (d->ht[0].table == NULL) { d->ht[0] = n; return DICT_OK; } // 執行到這裡,表明是非初始化雜湊表的擴容,將ht[1]置為n d->ht[1] = n; // 將dict的rehashidx欄位設定為0,表示開啟漸進式rehash d->rehashidx = 0; return DICT_OK; }
dictExpand()方法主要完成的邏輯就是為ht[1]設定size,sizemask欄位的值,初始化used欄位為0,併為dictEntry陣列分配空間,最後將dict的rehashidx欄位設定為0以開啟漸進式rehash。下面再結合原始碼看一下什麼時候進行鍵值對的遷移,首先在第一節中分析dictAddRaw()方法時有提到,dictAddRaw()方法一開始就會判斷當前是否處於rehash階段,如果正在rehash,則觸發一次雜湊桶的遷移操作,這個遷移操作對應的方法是dict.c檔案的_dictRehashStep()方法,其原始碼實現如下。
static void _dictRehashStep(dict *d) { if (d->iterators == 0) dictRehash(d,1); }
繼續看dictRehash()方法的實現。
// 引數n表示本次rehash需要遷移的雜湊桶個數 int dictRehash(dict *d, int n) { // 允許遍歷的最大空桶數 int empty_visits = n*10; // 如果沒有在進行漸進式rehash,則返回 if (!dictIsRehashing(d)) return 0; // 在ht[0]當前大小不為0的前提下 // 需要遷移多少個雜湊桶,就迴圈多少次,每次迴圈遷移一個雜湊桶 while(n-- && d->ht[0].used != 0) { dictEntry *de, *nextde; // rehashidx的值不能大於等於ht[0]的容量 assert(d->ht[0].size > (unsigned long)d->rehashidx); // 如果雜湊表ht[0]的rehashidx位置的雜湊桶是空,則繼續遍歷下一個雜湊桶 // 如果遍歷的空桶數達到了empty_visits,則本次rehash結束,直接返回 while(d->ht[0].table[d->rehashidx] == NULL) { d->rehashidx++; if (--empty_visits == 0) return 1; } // 得到ht[0]的rehashidx位置的雜湊桶 de = d->ht[0].table[d->rehashidx]; // 遍歷並將rehashidx位置的雜湊桶的連結串列上的節點全部遷移到ht[1]上 while(de) { uint64_t h; nextde = de->next; // 將連結串列節點的鍵的hash值與ht[1]的掩碼相與得到當前節點在ht[1]上的索引 h = dictHashKey(d, de->key) & d->ht[1].sizemask; // 使用頭插法插入ht[1] de->next = d->ht[1].table[h]; d->ht[1].table[h] = de; // ht[0]的當前大小減1 d->ht[0].used--; // ht[1]的當前大小加1 d->ht[1].used++; // 繼續遷移連結串列的下一節點 de = nextde; } // 全部遷移完成後,將ht[0]的rehashidx位置置為空 d->ht[0].table[d->rehashidx] = NULL; d->rehashidx++; } // 判斷是否將ht[0]上的鍵值對全部遷移到了ht[1] if (d->ht[0].used == 0) { // 如果ht[0]上的鍵值對全部遷移到了ht[1] // 先釋放ht[0]的陣列空間 zfree(d->ht[0].table); // 然後將ht[0]置為ht[1] d->ht[0] = d->ht[1]; // 重置ht[1] // 即將ht[1]的陣列置為空,容量,掩碼和當前大小全部置為0 _dictReset(&d->ht[1]); // 將dict的rehashidx欄位設定為-1,表示rehash結束 d->rehashidx = -1; return 0; } return 1; }
dictRehash()方法有兩個引數,第一個是需要進行rehash的dict,第二個是需要遷移的雜湊桶的個數,可知如果是對一對一直播平臺原始碼的資料的增刪改查而觸發的rehash,需要遷移的雜湊桶的個數為1。在dictRehash()方法一開始就定義了一個最大空桶數,其值為本次遷移數的10倍,因為在遍歷雜湊表時,可能會遇到很多空桶,所以為了避免遍歷大量空桶而帶來的時間消耗,Redis規定本次rehash遷移時,如果遇到的空桶數達到了本次需要遷移的雜湊桶數的10倍,則停止遷移並返回。在dictRehash()方法中對於每一個雜湊桶的遷移,其實就是遍歷這個雜湊桶上的連結串列,將每個連結串列節點重新基於ht[1]計算一個索引並遷移到ht[1]上。在dictRehash()方法的最後需要判斷一下是否將ht[0]上的資料全部遷移到了ht[1]上,如果已經全部完成遷移,此時需要先釋放老的ht[0]的陣列空間,然後將ht[0]置為ht[1],接著重置ht[1]即將其陣列置為空,容量,掩碼和當前大小全部置為0,最後將dict的rehashidx欄位設定為-1,表示rehash結束。
除了對一對一直播平臺原始碼的資料的增刪改查會呼叫到dictRehash()方法來遷移雜湊桶外,Redis也會定時的呼叫到dictRehash()方法來遷移雜湊桶,這個定時任務方法是server.c檔案的serverCron()方法,在該方法中會呼叫到server.c檔案的databasesCron()方法,該方法會處理Redis資料庫中的增量執行的後臺操作,這些操作中就包括漸進式rehash,所以在databasesCron()方法中又透過呼叫server.c檔案的incrementallyRehash()方法來執行rehash,接著又在incrementallyRehash()方法中呼叫到了dict.c檔案的dictRehashMilliseconds()方法,在dictRehashMilliseconds()方法中就真正呼叫到了dictRehash()方法來執行遷移雜湊桶的邏輯,dictRehashMilliseconds()方法的原始碼實現如下所示。
int dictRehashMilliseconds(dict *d, int ms) { long long start = timeInMilliseconds(); int rehashes = 0; // 在1毫秒的時間內迴圈進行遷移 // 每次迴圈遷移100個雜湊桶 while(dictRehash(d,100)) { rehashes += 100; if (timeInMilliseconds()-start > ms) break; } return rehashes; }
那麼至此,Redis的擴容步驟的原始碼就分析完畢。
以上就是一對一直播平臺原始碼,該擴容時就擴容, 更多內容歡迎關注之後的文章