这篇文章主要来说明MongoDB的网络协议,总结性的说MongoDB通讯基于TCP之上,数据采用BSON封装。
关于TCP
TCP具有良好的拥塞控制,可靠传输等特性,比较适合数据库产品的通讯协议。一些对数据一致性,可靠性要求不高的产品也有采用UDP协议实现。如Redis,Memcached都支持UDP访问,但从实际的生产上来说,TCP来的更可靠,UDP的“不可靠”性质,反而会带来更多的运维负担,增加了排查问题的复杂性。
关于BSON
BSON作为JSON的一种扩展,支持了Binary的数据类型,日期数据等。相比较于Protocol Buffers而言,数据是Humman Readable。MongoDB经常提及的Documents,实际上就是BSON格式数据。同样的,支持嵌套的机制,BSON可以很好的映射成Object,这相对于表结构,在灵活性上提高了一大截。数据不在是扁平的,可以是树形的组织结构,比如:
{
"_id" : 1,
"name" : { "first" : "John", "last" : "Backus" },
"contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ],
"awards" : [
{
"award" : "W.W. McDowell Award",
"year" : 1967,
"by" : "IEEE Computer Society"
},
{ "award" : "Draper Prize",
"year" : 1993,
"by" : "National Academy of Engineering"
}
]
}
当然BSON也有非常讨厌的一些地方,比如编码后的数据过大,引入了过的括号,符号等。
Wire Protocol
TCP是一种Stream的通讯方式,每次请求之间没有间隔,数据源源不断的发来,那如何才能识别出一个完整的请求块?一般的解决方法是加上一个Header,Header的长度固定,用来描述余下的信息量,包括携带的信息长度。额外说明:MongoDB的网络协议都是little-endian。
参考util/net/message.h的代码:
struct Layout {
int32_t messageLength; // total message size, including this
int32_t requestID; // identifier for this message
int32_t responseTo; // requestID from the original request
// (used in responses from db)
int32_t opCode;
};
messageLength表示整个协议的长度,因为是头部,所以Client每次发送命令时都要先将数据写到Buffer里,得到完整的长度后才能通过TCP发送整个请求。MongoDB规定,messageLength不能大于48MB(1000计算),过大的请求包一般意味着过于复杂的请求类型,或者过大的Document,这与NoSQL的设计原则也是违背的。
requestID/responseTo每个请求都有一个ID标识,同一时刻不应该出现相同的requestID,Driver和Server通过这个字段来确认是否是同一个请求的上下文
opCode操作代码,支持的类型:request-opcodes
相关的解析代码在MessagingPort::recv(Message& m)函数内,首先读取固定长度的Header(Layout),读取到Header后,根据messageLength数值做个预判是否是其他的协议类型,预判全部通过后等待读取余下的协议(messageLength-4),如果SocketBuffer中数据不足,就会阻塞在这里,等待数据包完整到达。
从网络上获得了完整的数据后交给MyMessageHandler::process来处理接下来的命令,这时opCode开始发挥作用,assembleResponse函数会根据opcode的不同,按照不同的协议去解析出相应的对象,然后执行命令。最后按照同样的协议格式发送给Client响应。发给Client的responseTo设置为与请求命令的requestID相同,以便Driver对应到相应的上下文。
参考引用
关于作者:
杨成虎,阿里巴巴集团技术专家,擅长通过NoSQL存储系统、Cache系统去解决海量数据的互联网问题。2009年加入阿里巴巴,先后开发了阿里的小文件系统,KV存储系统,负责阿里Tair系统的开发与架构设计。2013年至今主导研发了阿里云分布式缓存服务OCS,目前仍致力于NoSQL产品的云服务化工作。
您好,我是一个mongdodb的初学者,最近在做网络协议的解析,想请教一个问题,我做一个代理服务器代理了mongodb,期间接收过来的数据包解析出来的opCode如果是通过可视化软件连接的解析出来都是query,也就是操作码2004, 如果通过命令行链接 都是操作码2013,不管输入什么指令,删除,新增,查询也好,这个操作码都是不会变的,但是他数据包中还是会根据指令变换,有insert,drop等。还望解答,十分感谢。
会不会是您的代码写的不大对
更多交流欢迎在公众号后台联系我们