中国领先的IT技术网站
|
|

Ceph中序列化的设计与实现

作为主要和磁盘、网络打交道的分布式存储系统,序列化是最基础的功能之一,今天我们来看一下Ceph中序列化的设计与实现。

作者:袁冬来源:ustack博客|2015-05-20 10:05

沙龙活动 | 去哪儿、陌陌、ThoughtWorks在自动化运维中的实践!10.28不见不散!


作为主要和磁盘、网络打交道的分布式存储系统,序列化是最基础的功能之一,今天我们来看一下Ceph中序列化的设计与实现。

Ceph中序列化的设计与实现

1 Ceph序列化的方式

序列化(ceph称之为encode)的目的是将数据结构表示为二进制流的方式,以便通过网络传输或保存在磁盘等存储介质上,其逆过程称之为反序列化(ceph称之为decode)。 例如对于字符串“abc”,其序列化结果为8个字节(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,就必须在全局命名空间中定义以下两个方法:

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

在此基础上,序列化的使用就变得非常容易 。 即对于任意可序列化的类型T的实例instance_T,都可以通过以下语句:

  1. ::encode(instance_T, instance_bufferlist);

将instance_T序列化并保存到bufferlist类的实例instance_bufferlist中。

以下代码演示了将一个时间戳以及一个inode序列化到一个bufferlist中。

  1. utime_t timestamp; 
  2.  
  3. inode_t inode; 
  4.  
  5. bufferlist bl; 
  6.  
  7. ::encode(timetamp, bl) 
  8.  
  9. ::encode(inode, bl); 

bufferlist类(定义于include/buffer.h)是ceph核心的缓存类,用于保存序列化结果、数据缓存、网络通讯等,可以将bufferlist理解为一个可变长度的char数组。关于bufferlist的设计与实现,可以参考《Ceph中Bufferlist》。

序列化后的数据可以通过反序列化方法读取,例如以下代码片段从一个bufferlist中反序列化一个时间戳和一个inode(前提是该bl中已经被序列化了一个utime_t和一个inode,否则会报错)。

  1. bufferlist::iterator bl; 
  2.  
  3. ::decode(timetamp, bl) 
  4.  
  5. ::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相关结构实现如下。

  1. struct ceph_mds_request_head { 
  2.  
  3. __le64 oldest_client_tid; 
  4.  
  5. __le32 mdsmap_epoch; 
  6.  
  7. __le32 flags; 
  8.  
  9. __u8 num_retry, num_fwd; 
  10.  
  11. __le16 num_releases; 
  12.  
  13. __le32 op; 
  14.  
  15. __le32 caller_uid, caller_gid; 
  16.  
  17. __le64 ino; 
  18.  
  19. } __attribute__ ((packed)); 
  20.  
  21. 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中。相关代码为:

  1. template 
  2.  
  3. inline void encode_raw(const T& t, bufferlist& bl) 
  4.  
  5.  
  6. bl.append((char*)&t, sizeof(t)); 
  7.  
  8.  
  9. template 
  10.  
  11. inline void decode_raw(T& t, bufferlist::iterator &p) 
  12.  
  13.  
  14. p.copy(sizeof(t), (char*)&t); 
  15.  

2.3 集合数据类型的序列化

集合数据类型序列化的基本思路包括两步:

  1. 序列化集合大小,
  2. 序列化集合内的所有元素

例如vector& v的序列化方法:

  1. template 
  2.  
  3. inline void encode(const std::vector& v, bufferlist& bl) 
  4.  
  5.  
  6. __u32 n = v.size(); 
  7.  
  8. encode(n, bl); 
  9.  
  10. for (typename std::vector::const_iterator p = v.begin(); p != v.end(); ++p) 
  11.  
  12. encode(*p, bl); 
  13.  

其中元素的序列化通过调用该元素的encode方法实现。

常用集合数据类型的序列化已经由Ceph实现,位于include/encoding.h中,包括以下集合类型:

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

集合类型的序列化方法皆为基于泛型(模板类)的实现方式,适用于所有泛型派生类。

2.4 复杂数据类型的序列化

除以上两种业务无关的数据类型外,其它数据类型的序列化实现包括两部分:

在类型内部现实encode方法,

将类型内部的encode方法重定义为全局方法。

以下以utime_t类为例:

  1. class utime_t { 
  2.  
  3. struct { 
  4.  
  5. __u32 tv_sec, tv_nsec; 
  6.  
  7. } tv; 
  8.  
  9. void encode(bufferlist &bl) const { 
  10.  
  11. ::encode(tv.tv_sec, bl); 
  12.  
  13. ::encode(tv.tv_nsec, bl); 
  14.  
  15.  
  16. void decode(bufferlist::iterator &p) { 
  17.  
  18. ::decode(tv.tv_sec, p); 
  19.  
  20. ::decode(tv.tv_nsec, p); 
  21.  
  22.  
  23. }; 
  24.  
  25. WRITE_CLASS_ENCODER(utime_t) 
  26.  
  27. utime_t内部实现了encode和decode两个方法,WRITE_CLASS_ENCODER宏函数将这两个方法转化为全局方法。 
  28.  
  29. WRITE_CLASS_ENCODER宏函数定义于include/encoding.h中,其定义如下: 
  30.  
  31. #define WRITE_CLASS_ENCODER(cl) \ 
  32.  
  33. inline void encode(const cl &c, bufferlist &bl, uint64_t features=0) { \ 
  34.  
  35. ENCODE_DUMP_PRE(); c.encode(bl); ENCODE_DUMP_POST(cl); } \ 
  36.  
  37. inline void decode(cl &c, bufferlist::iterator &p) { c.decode(p); } 

复杂数据结构内部的encode方法的实现方式通常是调用其内部主要数据结构的encode方法,例如utime_t类的encode方法实际上是序列化内部的tv.tv_sec和tv.tv_nsec两个成员。

原文链接:https://www.ustack.com/blog/cephxuliehua/

【编辑推荐】

  1. 使用Ceph作为OpenStack的后端存储配置过程详解
  2. 历经十年:关于Ceph现状与未来的一些思考
  3. Ceph集群磁盘无剩余空间的解决方法
  4. 比较Swift与HDFS 话Ceph本质
  5. 关于Ceph中Bufferlist的设计与使用
【责任编辑:Ophira TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

标准C++开发入门与编程实践

本书着重介绍标准C++语言,即1998年由ISO正式推出的关于C++的国际性标准版本。 本书从最基础的编程语言概念讲起,共分6篇24章。前4篇完整...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× Python最火的编程语言