摘要:之前對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也是同樣的道理。