ceph crush算法不一樣的理解,新增刪除osd造成少量數據遷移

                   摘要:之前對ceph的crush算法的理解,很多時候沒有針對問題來考慮。我現在就提出幾個問題

                  1,ceph怎麼做到的每次down掉一個osd或者新增一個osd後,只有少量的數據遷移?而不是所有數據都在osd之間來回遷移?(據說sheepdog是大量遷移,所以這個就別拿來和ceph比性能了,連彈性擴容都做不到。)

                  2,一塊數據A寫入pg1(osd1),現在新增osd了,導致pg1遷移到osd2上,那麼下次需要讀數據A,還能找到數據位置嗎?

                  3,一個oid映射到pgid,是怎麼哈希映射的?一個文件名真的可以哈希後得到文件夾的名字?真像網上說的那樣,是文件名哈希後,用哈希值value除以文件夾總數取餘數(oid%pg_num) ?

 

一,怎麼把一個文件名映射到文件夾?(也就是object怎麼映射到pg)

           假如我有1個文件,需要哈希映射到1000個文件夾。 拿ceph來說,就是1個object怎麼映射到1000個pg。

網上的說法:

         1,hash(object) 得到一個哈希值 value 。

          2,用這個value除以10000,取餘數。

           這樣的做法確實也可以讓object隨機映射到1000個pg中的一個。但是隻是知道了1000個pg的第幾個pg,還要根據算出的序號,去1000個pg中查詢出pgid。這樣做效率是不行的。ceph一貫履行不需查找,算算就好的承諾。

 

ceph的做法:

           1,每次創建一個pool的時候讓pg的id變得連續。如果16個pg,那pgid就從0~f 。如果1024個pg,那麼pdid就從0~3ff。

下圖中0.3ff 前面的0 表示 poolid。

0。

          

                  2,通過object取得的哈希值value &mask運算,得出pgid的值。

             mask爲pg數量-1,因爲pgid是從0開始的,所以需要-1

              這樣無論怎麼計算pgid始終爲0~mask之間。也就是pg總數裏的任意一個值。這樣object會被均勻的分配到任意一個pg

 

二,crush算法怎麼做到每次有osd新增和刪除,只有少量的pg產生遷移,而不是所有的pg在osd之間重新定位?

                  1,一個pg請求的定位,會選擇一個機房,然後在機房裏選擇一個機架,機架上選擇一個服務器,服務器上選擇一個osd。完成pgid-->osdid的映射。

                2, 以上的機房,機架,服務器,osd。我們都可以認爲是一個item。那怎麼在10個item中選擇一個,是通過哈希值處於10取餘數的方式嗎?

當然不是,如果這樣做。就會導致新增,刪除osd,集羣裏的所有pg都需要遷移。因爲新增osd後,哈希的範圍變了,ceph集羣裏所有的pg都需要重新哈希選擇位置。

               3,ceph提供了多種選擇一個item的算法,這些算法統稱bucket算法。下面比較多種bucket算法對於osd變動,造成的數據遷移量。

橫軸代表osd新增和刪除的數量,縱軸代表pg發生的遷移率。可以發現RUSH_P在新增和刪除上呈現2個極端,新增osd的時候pg遷移率很小。

而刪除osd的時候遷移率很大,我們平常默認使用的straw是比較這種的方案。爲圖中的黃色線條。也就是說pg的遷移率和bucket算法有關。

 

            4,那bucket算法爲什麼在新增刪除osd的時候呈現少量的遷移率?下面我們把crush straw算法的代碼提取出來測試研究。

 

#include<iostream>
#include <stdio.h>
#include <time.h>
using namespace std;
typedef unsigned int __u32;
#define crush_hash_seed 1315423911

#define crush_hashmix(a, b, c) do {   \
 a = a - b;  a = a - c;  a = a ^ (c >> 13); \
 b = b - c;  b = b - a;  b = b ^ (a << 8); \
 c = c - a;  c = c - b;  c = c ^ (b >> 13); \
 a = a - b;  a = a - c;  a = a ^ (c >> 12); \
 b = b - c;  b = b - a;  b = b ^ (a << 16); \
 c = c - a;  c = c - b;  c = c ^ (b >> 5); \
 a = a - b;  a = a - c;  a = a ^ (c >> 3); \
 b = b - c;  b = b - a;  b = b ^ (a << 10); \
 c = c - a;  c = c - b;  c = c ^ (b >> 15); \
} while (0)
//crush straw算法,參數1爲pgid,參數2爲一組item,參數3爲副本數
static __u32 crush_hash32_rjenkins1_3(__u32 a, __u32 b, __u32 c)
{
 __u32 hash = crush_hash_seed ^ a ^ b ^ c;
 __u32 x = 231232;
 __u32 y = 1232;
 crush_hashmix(a, b, hash);
 crush_hashmix(c, x, hash);
 crush_hashmix(y, a, hash);
 crush_hashmix(b, x, hash);
 crush_hashmix(y, c, hash);
 return hash;
}
int main()                        //對straw算法進行測試
{
 int testpgid[1000];
 srand((unsigned)time(NULL));
 for (int i = 0; i < 1000; i++)    //選取1000個pg進行測試,pgid取一個隨機數
 {
  testpgid[i] = rand();
 }
 //int testpgid[10] = {432879,32189,8879,3871,1048390,73167,46104,32169,34791,31280};
 for (int n = 0; n < 1000; n++)    //對1000個pg進行1000次item選舉測試
 {
  int i;
  unsigned int weight = 1;
  int item[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //針對10個item來模擬
  int high = 0;
  unsigned long long high_draw = 0;
  unsigned long long draw;
  int result;
  for (i = 0; i <10; i++)
  {
   draw = crush_hash32_rjenkins1_3(testpgid[n], item[i], 3);
   draw &= 0xffff;
   draw *= weight;
   if (i == 0 || draw > high_draw) {
    high = i;
    high_draw = draw;
   }
  }
  result = high;//result爲最終在10個item中選出的item號

  int j;
  int item_del[9] = { 1, 2, 3, 4, 5, 6, 8, 9, 10 };   //模擬刪掉一個item 7
  int high1 = 0;
  unsigned long long high_draw1 = 0;
  unsigned long long draw1;
  int result1;
  for (j = 0; j <9; j++)
  {
   draw1 = crush_hash32_rjenkins1_3(testpgid[n], item_del[j], 3);
   draw1 &= 0xffff;
   draw1 *= weight;
   if (j == 0 || draw1 > high_draw1) {
    high1 = j;
    high_draw1 = draw1;
   }
  }
  result1 = high1;//result1爲最終在9個item中選出的item號

  int k;
  int item_add[11] = { 1, 2, 3, 4, 5, 6, 7,8, 9, 10,11 }; //模擬新增一個item11
  int high2 = 0;
  unsigned long long high_draw2 = 0;
  unsigned long long draw2;
  int result2;
  for (k = 0; k <11; k++)
  {
   draw2 = crush_hash32_rjenkins1_3(testpgid[n], item_add[k], 3);
   draw2 &= 0xffff;
   draw2 *= weight;
   if (k == 0 || draw2 > high_draw2) {
    high2 = k;
    high_draw2 = draw2;
   }
  }
  result2 = high2;//result2爲最終在11個item中選出的item號
  cout << testpgid[n] << "        " << result << " " << result1 << " "<< result2<< endl;
 }         //打印的內容爲:pgid號,沒有osd變動時候選出的item, 刪除一個osd後選出的item, 新增一個osd後選出的item
 return 0;
}

                                                                                                                                                                                                                                        

             對1000個pg進行測試,每個pg在10個item裏選一個,先後刪除一個item,新增一個item測試。測試結果發現,不管是新增還是刪除item。

straw算法對某個item的選擇一直在堅持,只有少數時候會由於新增和刪除item產生變化,大部分時候不管刪除還是新增item,都堅持選擇原來的item。

下面爲測試結果。

可以看到大部分時候,不管新增刪除osd,straw算法都是堅持原來的選擇,只有少數時候會改變。

所以這就決定了。新增osd的時候,只有一小部分的pg需要重新定位。遷移位置。

 

          5, 我們通過算法的原理來分析下原因。straw算法如下:

static __u32 crush_hash32_rjenkins1_3(__u32 a, __u32 b, __u32 c)

從10個item裏選取一個哈希值最大item的作爲入選的item。

如果你新增一個item,影響10個item中最大值的那個可能性並不大。

你是班裏最高的,班裏來了一個新同學,新同學比你高的可能性並不大。絕大部分可能你還是最高的。

刪除一個item也是同樣的道理。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章