目录
序列化 (ENCODE/DECODE)
当一个结构通过网络发送或写入磁盘时,它被编码为一串字节。可序列化结构具encode
和 decode
方法,将结构体序列化后存入bufferlist和从bufferlist读出字节串反序列出结构体。
向结构中添加字段
在Ceph代码中有很多例子,这里有一个例子。
class AcmeClass { int member1; std::string member2; void encode(bufferlist &bl) { ENCODE_START(1, 1, bl); ::encode(member1, bl); ::encode(member2, bl); ENCODE_FINISH(bl); } void decode(bufferlist::iterator &bl) { DECODE_START(1, bl); ::decode(member1, bl); ::decode(member2, bl); DECODE_FINISH(bl); } };
ENCODE_START 宏写入标头 说明version和 compat_version(初值均为 1)。每当对encode进行更改时,version就会增加。仅当更改会影响decode时compat_version才会增加 - 比如新结构体只在尾部添加字段,不会影响旧结构体的解析,因此在结构末尾添加字段的更改不需要增加 compat_version。
DECODE_START 宏采用一个参数,指定encode代码可以处理的最新消息版本。这与消息中编码的 compat_version 进行比较,如果消息太新,则会抛出异常。因为对 compat_verison 的更改很少,所以在添加字段时通常不需要担心。
兼容性考虑
在实践中,对编码的更改通常只涉及在encode和decode函数的末尾添加所需的字段,并增加 ENCODE_START 和 DECODE_START 中的版本。例如,以下是向 AcmeClass 添加第三个字段的方法:
class AcmeClass { int member1; std::string member2; std::vector<std::string> member3; void encode(bufferlist &bl) { ENCODE_START(2, 1, bl); ::encode(member1, bl); ::encode(member2, bl); ::encode(member3, bl); ENCODE_FINISH(bl); } void decode(bufferlist::iterator &bl) { DECODE_START(2, bl); ::decode(member1, bl); ::decode(member2, bl); if (struct_v >= 2) { ::decode(member3, bl); } DECODE_FINISH(bl); } };
请注意,compat_version 没有改变,因为encode的消息仍然可以被仅理解version 1 的代码版本解码decode——它们会忽略我们对 member3 进行encode的增加的尾随字节。
在 decode 函数中,对新字段的decode是有条件的:这是因为我们可能仍会收到没有该字段的旧版本消息。 struct_v 变量是由 DECODE_START 宏设置的本地变量。
原文:https://docs.ceph.com/en/latest/dev/encoding/
Ceph中的序列化
https://www.cnblogs.com/goldd/p/6610554.html
作为主要和磁盘、网络打交道的分布式存储系统,序列化是最基础的功能之一,今天我们来看一下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,就必须在全局命名空间中定义以下两个方法:
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相关结构实现如下。
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 集合数据类型的序列化
集合数据类型序列化的基本思路包括两步:
- 序列化集合大小,
- 序列化集合内的所有元素
例如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类为例:
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两个成员。
@UESTC