BlueStore原始碼分析之FreelistManager
前言
BlueStore直接管理裸裝置,需要自行管理空間的分配和釋放。
Stupid
和
Bitmap
分配器的結果是儲存在記憶體中的,分配結果的持久化是透過
FreelistManager
來做的。
一個block的狀態可以為 佔用和 空閒兩種狀態,持久化時只需要記錄一種狀態即可,便可以推匯出另一種狀態,BlueStore記錄的是空閒block。主要有兩個原因:一是回收空間的時候,方便空閒空間的合併;二是已分配的空間在Object中已有記錄。
FreelistManager最開始有
extent
和
bitmap
兩種實現,現在預設為bitmap實現,extent的實現已經廢棄。空閒空間持久化到磁碟也是透過RocksDB的Batch寫入的。FreelistManager將block按一定數量組成段,每個段對應一個k/v鍵值對,key為第一個block在磁碟實體地址空間的offset,value為段內每個block的狀態,即由0/1組成的點陣圖,1為空閒,0為使用,這樣可以透過與1進行異或運算,將分配和回收空間兩種操作統一起來。
目錄
<a name="chapter1"></a>通用介面
FreelistManager最主要的介面就是allocator和release。
virtual void allocate(
uint64_t offset, uint64_t length,
KeyValueDB::Transaction txn) = 0;
virtual void release(
uint64_t offset, uint64_t length,
KeyValueDB::Transaction txn) = 0;
<a name="chapter2"></a>資料結構
class BitmapFreelistManager : public FreelistManager {
// rocksdb key字首:meta_prefix為 B,bitmap_prefix為 b
std::string meta_prefix, bitmap_prefix;
// key-value DB的指標,封裝了rocksdb的操作
KeyValueDB *kvdb;
// rocksdb的merge操作:按位異或(xor)
ceph::shared_ptr<KeyValueDB::MergeOperator> merge_op;
// enumerate操作時加鎖
std::mutex lock;
// 裝置總大小
uint64_t size;
// 裝置總block數
uint64_t blocks;
// block大小:bdev_block_size,預設min_alloc_size
uint64_t bytes_per_block;
// 每個key包含多少個block, 預設128
uint64_t blocks_per_key;
// 每個key對應空間大小
uint64_t bytes_per_key;
// block掩碼
uint64_t block_mask;
// key掩碼
uint64_t key_mask;
bufferlist all_set_bl;
// 遍歷rocksdb key相關的成員
KeyValueDB::Iterator enumerate_p;
uint64_t enumerate_offset;
bufferlist enumerate_bl;
int enumerate_bl_pos;
};
<a name="chapter3"></a>初始化
BlueStore在初始化osd的時候,會執行mkfs,初始化FreelistManager(create/init),後續如果重啟程式,會執行mount操作,只會對FreelistManager執行init操作。
int BlueStore::mkfs()
{
......
r = _open_fm(true);
......
}
int BlueStore::_open_fm(bool create)
{
......
fm = FreelistManager::create(cct, freelist_type, db, PREFIX_ALLOC);
// 第一次初始化,需要固化meta引數
if (create) {
fm->create(bdev->get_size(), min_alloc_size, t);
}
......
int r = fm->init(bdev->get_size());
}
// create固化一些meta引數到kvdb中,init的時候,從kvdb讀取這些引數
int BitmapFreelistManager::create(uint64_t new_size, uint64_t min_alloc_size,
KeyValueDB::Transaction txn)
{
txn->set(meta_prefix, "bytes_per_block", bl); // min_alloc_size
txn->set(meta_prefix, "blocks_per_key", bl); // 128
txn->set(meta_prefix, "blocks", bl);
txn->set(meta_prefix, "size", bl);
}
// create/init 均會呼叫下面這個函式,初始化block/key的掩碼
void BitmapFreelistManager::_init_misc()
{
// 128 >> 3 = 16,每個block用1個bit表示。
// 即一個key的value對應128個block,需要16位元組。
bufferptr z(blocks_per_key >> 3);
memset(z.c_str(), 0xff, z.length());
all_set_bl.clear();
all_set_bl.append(z);
// 0x FFFF FFFF FFFF F000
block_mask = ~(bytes_per_block - 1);
bytes_per_key = bytes_per_block * blocks_per_key;
// 0xFFFF FFFF FFF8 0000
key_mask = ~(bytes_per_key - 1);
}
<a name="chapter4"></a>Merge
異或Merge介面實現:
// 繼承rocksdb merge介面:異或操作(xor)
struct XorMergeOperator : public KeyValueDB::MergeOperator {
// old_value不存在,那麼new_value直接賦值為rdata。
void merge_nonexistent(const char *rdata, size_t rlen,
std::string *new_value) override {
*new_value = std::string(rdata, rlen);
}
// old_value存在,則與rdata逐位異或xor。
void merge(const char *ldata, size_t llen, const char *rdata, size_t rlen,
std::string *new_value) override {
assert(llen == rlen);
*new_value = std::string(ldata, llen);
for (size_t i = 0; i < rlen; ++i) {
(*new_value)[i] ^= rdata[i];
}
}
// We use each operator name and each prefix to construct the
// overall RocksDB operator name for consistency check at open time.
string name() const override { return "bitwise_xor"; }
};
異或Merge介面應用:
bool Merge(const rocksdb::Slice& key,
const rocksdb::Slice* existing_value,
const rocksdb::Slice& value,
std::string* new_value,
rocksdb::Logger* logger) const override
{
// for default column family
// extract prefix from key and compare against each registered merge op;
// even though merge operator for explicit CF is included in merge_ops,
// it won't be picked up, since it won't match.
for (auto& p : store.merge_ops) {
if (p.first.compare(0, p.first.length(),
key.data(), p.first.length()) == 0 &&
key.data()[p.first.length()] == 0) {
// 如果old_value存在,那麼直接merge,否則直接替換。
if (existing_value) {
p.second->merge(existing_value->data(), existing_value->size(),
value.data(), value.size(),
new_value);
} else {
p.second->merge_nonexistent(value.data(), value.size(), new_value);
}
break;
}
}
return true;
}
最終呼叫Rocksdb的Batch的Merge方法。Batch可以實現簡單寫入和條件寫入的原子操作。
<a name="chapter5"></a>Allocate
分配和釋放空間兩種操作是完全一樣的,都是呼叫異或(Xor)操作,我們著重看
_xor
函式。
void BitmapFreelistManager::allocate(uint64_t offset, uint64_t length, KeyValueDB::Transaction txn)
{
_xor(offset, length, txn);
}
void BitmapFreelistManager::release(uint64_t offset, uint64_t length, KeyValueDB::Transaction txn)
{
_xor(offset, length, txn);
}
void BitmapFreelistManager::_xor(uint64_t offset, uint64_t length, KeyValueDB::Transaction txn)
{
// 注意offset和length都是以block邊界對齊
uint64_t first_key = offset & key_mask;
uint64_t last_key = (offset + length - 1) & key_mask;
if (first_key == last_key) { // 最簡單的case,此次操作對應一個段
bufferptr p(blocks_per_key >> 3); // 16位元組大小的buffer
p.zero(); // 置為全0
unsigned s = (offset & ~key_mask) / bytes_per_block; // 段內開始block的編號
unsigned e = ((offset + length - 1) & ~key_mask) / bytes_per_block; // 段內結束block的編號
for (unsigned i = s; i <= e; ++i) { // 生成此次操作的掩碼
p[i >> 3] ^= 1ull << (i & 7); // i>>3定位block對應位的位元組, 1ull<<(i&7)定位bit,然後異或將位設定位1
}
string k;
make_offset_key(first_key, &k); // 將記憶體內容轉換為16進位制的字元
bufferlist bl;
bl.append(p);
bl.hexdump(*_dout, false);
txn->merge(bitmap_prefix, k, bl); // 和目前的value進行異或操作
} else { // 對應多個段,分別處理第一個段,中間段,和最後一個段,首尾兩個段和前面情況一樣
// 第一個段
{
// 類似上面情況
......
// 增加key,定位下一個段
first_key += bytes_per_key;
}
// 中間段,此時掩碼就是全1,所以用all_set_bl
while (first_key < last_key) {
string k;
make_offset_key(first_key, &k);
all_set_bl.hexdump(*_dout, false);
txn->merge(bitmap_prefix, k, all_set_bl); // 和目前的value進行異或操作
// 增加key,定位下一個段
first_key += bytes_per_key;
}
// 最後一個段
{
// 和前面操作類似
}
}
}
xor函式看似複雜,全是位操作,仔細分析一下,分配和釋放操作一樣,都是將段的bit位和當前的值進行異或。一個段對應一組blocks,預設128個,在k/v中對應一組值。例如,當磁碟空間全部空閒的時候,k/v狀態如下: (b00000000,0x00), (b00001000, 0x00), (b00002000, 0x00)……b為key的字首,代表bitmap。
<a name="chapter6"></a>Release
釋放空間和分配空間都是一樣的操作。
void BitmapFreelistManager::release(uint64_t offset, uint64_t length, KeyValueDB::Transaction txn)
{
_xor(offset, length, txn);
}
作者:史明亞
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2675073/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- BlueStore原始碼分析之Stupid分配器原始碼
- BlueStore原始碼分析之事物狀態機原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之AbstractQueuedSynchronizer原始碼
- 原始碼分析之ArrayList原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- JUC之CountDownLatch原始碼分析CountDownLatch原始碼
- Dubbo之SPI原始碼分析原始碼
- Fresco原始碼分析之DraweeView原始碼View
- lodash原始碼分析之isArguments原始碼
- Fresco原始碼分析之Hierarchy原始碼
- 原始碼分析Kafka之Producer原始碼Kafka
- RecyclerView之SnapHelper原始碼分析View原始碼
- OpenGL 之 GPUImage 原始碼分析GPUUI原始碼
- lodash原始碼分析之isObjectLike原始碼Object
- 原始碼分析之 LinkedList原始碼
- Envoy原始碼分析之Dispatcher原始碼
- Redux原始碼分析之createStoreRedux原始碼
- MongoDB原始碼分析之MongosXFMongoDB原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- 原始碼分析之ThreadPoolExecutor原始碼thread
- Spark原始碼分析之MemoryManagerSpark原始碼
- Spark原始碼分析之BlockStoreSpark原始碼BloC
- Thrift之Protocol原始碼分析Protocol原始碼
- bootstrap原始碼分析之Carouselboot原始碼
- Bootstrap原始碼分析之dropdownboot原始碼
- jdk原始碼分析之PriorityQueueJDK原始碼
- jdk原始碼分析之WeakHashMapJDK原始碼HashMap
- jdk原始碼分析之ArrayListJDK原始碼
- jdk原始碼分析之HashMapJDK原始碼HashMap
- jdk原始碼分析之CopyOnWriteArrayListJDK原始碼
- DRF之Response原始碼分析原始碼
- Spark原始碼分析之DiskBlockMangaer分析Spark原始碼BloC