Ceph中的序列化

  做爲主要和磁盤、網絡打交道的分佈式存儲系統,序列化是最基礎的功能之一,今天咱們來看一下Ceph中序列化的設計與實現。
1. Ceph序列化的方式
  序列化(ceph稱之爲encode)的目的是將數據結構表示爲二進制流的方式,以便經過網絡傳輸或保存在磁盤等存儲介質上,其逆過程稱之爲反序列化(ceph稱之爲decode)。例如對於字符串「abc」,其序列化結果爲7個字節(bytes):
  03 00 00 00 61 62 63
  其中頭四個字節(03 00 00 00)表示字符串的長度爲3個字符,後3個字節(61 62 63)分別是字符「abc」的ASCII碼的16進製表示。Ceph採用little-endian的序列化方式,即低地址存放最低有效字節,因此32位整數0x12345678的序列化結果爲78 56 34 12。
  因爲序列化在整個系統中是很是基本,很是經常使用的功能,Ceph將其序列化方式設計爲一個同一的結構,即任意支持序列化的數據結構,都必須提供一對定義在全局命名空間上的序列化/反序列化(encode/decode)函數。例如,若是咱們定義了一個結構體inode,就必須在全局命名空間中定義如下兩個方法:node

encode(struct inode, bufferlist bl);
decode(struct inode, bufferlist::iterator bl);

  在此基礎上,序列化的使用就變得很是容易 。 即對於任意可序列化的類型T的實例instance_T,均可以經過如下語句:數組

::encode(instance_T, instance_bufferlist);

將instance_T序列化並保存到bufferlist類的實例instance_bufferlist中。
  如下代碼演示了將一個時間戳以及一個inode序列化到一個bufferlist中。緩存

utime_t timestamp;
inode_t inode;
bufferlist bl;
::encode(timetamp, bl)
::encode(inode, bl);

  bufferlist類(定義於include/buffer.h)是ceph核心的緩存類,用於保存序列化結果、數據緩存、網絡通信等,能夠將bufferlist理解爲一個可變長度的char數組。關於bufferlist的設計與實現,能夠參考《Ceph中Bufferlist》。
  序列化後的數據能夠經過反序列化方法讀取,例如如下代碼片斷從一個bufferlist中反序列化一個時間戳和一個inode(前提是該bl中已經被序列化了一個utime_t和一個inode,不然會報錯)。網絡

bufferlist::iterator bl;
::decode(timetamp, bl)
::decode(inode, bl);

2 數據結構的序列化
  Ceph爲其全部用到數據類型提供了序列化方法或反序列化方法,這些數據類型包括了絕大部分基礎數據類型(int、bool等)、結構體類型的序列化(ceph_mds_request_head等)、集合類型(vector、list、set、map等)、以及自定義的複雜數據類型(例如表示inode的inode_t等),如下分別介紹不一樣數據類型的序列化實現方式。
2.1 基本數據類型的序列化
  基本數據類型的序列化結果基本就是該類型在內存中的表示形式。基本數據類型的序列化方法使用手工編寫,定義在include/encoding.h中,包括如下類型:數據結構

__u8, __s8, char, bool
ceph_le64, ceph_le32, ceph_le16,
float, double,
uint64_t, int64_t, uint32_t, int32_t, uint16_t, int16_t,
string, char*

在手工編寫encode方法過程當中,爲了不重複代碼,藉助了WRITE_RAW_ENCODER和WRITE_INTTYPE_ENCODER兩個宏。
2.2 結構體類型的序列化
  結構體類型的序列化方法與基本數據類型的序列化方法一致,即便用結構體的內存佈局做爲序列化的形式。在結構體定義完成後,經過調用WRITE_RAW_ENCODER宏函數生成結構體的全局encode方法,例如結構體ceph_mds_request_head相關結構實現以下。app

struct ceph_mds_request_head {
 __le64 oldest_client_tid;
 __le32 mdsmap_epoch;
 __le32 flags;
 __u8 num_retry, num_fwd;
 __le16 num_releases;
 __le32 op;
 __le32 caller_uid, caller_gid;
 __le64 ino;
} __attribute__ ((packed));

WRITE_RAW_ENCODER(ceph_mds_request_head)
其中:
ceph_mds_request_head結構體定義在include/ceph_fs.h . WRITE_RAW_ENCODER(ceph_mds_request_head)語句位於include/types.h WRITE_RAW_ENCODER宏函數定義在include/encoding.h WRITE_RAW_ENCODER宏函數其實是經過調用encode_raw實現的,而encode_raw調用bufferlist的append的方法,經過內存拷貝,將數據結構放入到bufferlist中。相關代碼爲:分佈式

template<class T>
inline void encode_raw(const T& t, bufferlist& bl)
{
  bl.append((char*)&t, sizeof(t));
}
template<class T>
inline void decode_raw(T& t, bufferlist::iterator &p)
{
  p.copy(sizeof(t), (char*)&t);
}

2.3 集合數據類型的序列化
  集合數據類型序列化的基本思路包括兩步:
  1) 序列化集合大小,
  2) 序列化集合內的全部元素
  例如vector<T>& v的序列化方法:函數

template<class T>
inline void encode(const std::vector<T>& v, bufferlist& bl)
{
  __u32 n = v.size();
  encode(n, bl);
  for (typename std::vector<T>::const_iterator p = v.begin(); p != v.end(); ++p)
    encode(*p, bl);
}

其中元素的序列化經過調用該元素的encode方法實現。
  經常使用集合數據類型的序列化已經由Ceph實現,位於include/encoding.h中,包括如下集合類型:佈局

pair, triple
list, set, vector, map, multimap
hash_map, hash_set
deque

  集合類型的序列化方法皆爲基於泛型(模板類)的實現方式,適用於全部泛型派生類
2.4 複雜數據類型的序列化
  除以上兩種業務無關的數據類型外,其它數據類型的序列化實現包括兩部分: 在類型內部現實encode方法,將類型內部的encode方法重定義爲全局方法。
  如下以utime_t類爲例:ui

class utime_t {
 struct {
  __u32 tv_sec, tv_nsec;
 } tv;
 void encode(bufferlist &bl) const {
  ::encode(tv.tv_sec, bl);
  ::encode(tv.tv_nsec, bl);
 }
 void decode(bufferlist::iterator &p) {
  ::decode(tv.tv_sec, p);
  ::decode(tv.tv_nsec, p);
 }
};
WRITE_CLASS_ENCODER(utime_t)

  utime_t內部實現了encode和decode兩個方法,WRITE_CLASS_ENCODER宏函數將這兩個方法轉化爲全局方法。
  WRITE_CLASS_ENCODER宏函數定義於include/encoding.h中,其定義以下:

#define WRITE_CLASS_ENCODER(cl)
  inline void encode(const cl &c, bufferlist &bl, uint64_t features=0) {
    ENCODE_DUMP_PRE(); c.encode(bl); ENCODE_DUMP_POST(cl); }
  inline void decode(cl &c, bufferlist::iterator &p) { c.decode(p); }

  複雜數據結構內部的encode方法的實現方式一般是調用其內部主要數據結構的encode方法,例如utime_t類的encode方法其實是序列化內部的tv.tv_sec和tv.tv_nsec兩個成員。

相關文章
相關標籤/搜索