ceph网络通信分析--基于源码simple_server与simple_client
一、ceph网络通信框架
1.1 概述
分布式存储系统需要稳定的底层网络通信模块,用于集群内各节点间的通信以及与外界用户的通信。ceph的网络通信模块源码位于/src/msg之下,三个子目录中分别对应了三种不同的网络通信方式,包括simple,Async、XIO。
simple是稳定的方式,目前广泛应用于生产环境,simple方式为每一个链接都要创建接收线程与发送线程,但是随着集群规模的增大,这种方式会消耗大量的CPU资源,已经逐渐开始下滑。Async方式自从K版之后已经稳定,成为了默认的通信方式,这是一种异步多路IO复用的方式。XIO的方式基于第三方网络通信库accelio,目前仍处于实验阶段。本文主要以/src/test/中的simple_server和simple_client两个测试demo分析simple方式的实现与使用。
1.2 设计模式
订阅发布模式又名观察者模式,它意图是“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。以simple方式为例,simple通信需要messenger作为发布者,dispatcher及其子类作为订阅者。所以在创建messenger的时候,都会在其构造函数中调用ms_deliver_handle_fast_connect()
来告知dispatcher有新的connection建立,无论是新建或是重连。
void SimpleMessenger::init_local_connection()
{
local_connection->peer_addrs = *my_addrs;
local_connection->peer_type = my_name.type();
local_connection->set_features(CEPH_FEATURES_ALL);
ms_deliver_handle_fast_connect(local_connection.get());
}
1.3 通信的基础类
1.4 simple的通信机制
simple对于每一个连接,都会创建两个线程,其中一个用于监听和读取该终端的读事件,另一个用于写事件。读线程得到请求以后会解析网络流并开始构建消息,然后派发到后面的 Dispatcher。写线程在大部分时候会处于 Sleep 状态,直到有新的消息需要发送才会被唤醒。
Messenger是网络模块的核心数据结构,负责接收/发送消息。OSD主要有两个Messenger:ms_public处于与客户端的消息,ms_cluster处理与其它OSD的消息。下图是simple通信机制的框架,包括一个OSD server和一个client
对其中的各模块作简介
SimpleMessager
该类实现了Messager的接口。这只是三种实现接口方式中的一种。Messager是整个网络模块的抽象类,可以看成是一个顶层的控制模块,它定义了网络模块的基本API接口,实现发送和接收消息。
Connection
用来连接,发送和接收消息
Pipe
对于每一个连接,都会在Pipe内部创建一个读线程、一个写线程用来处理接收消息和发送消息。它的层次位于Connetion和Dispatcher的中间,其中拥有读写线程pipe:: reader_thread和pipe::writer_thread,他们的入口函数分别为Pipe::reader和Pipe::writer函数。
Dispatcher
是所有订阅者的基类,这个类用于消息的分发,在Pipe中接收、发送队列中有很多请求,他就是负责把Message的请求分发给具体的应用层。
Accecpter
该类用于listen,封装了系统的socket中的多种方法,是建立连接的主要实现模块。
二、simple_server
在server端,需要一个messenger来完成数据的收发,dispatcher来将数据请求分发到具体处理的类,需要accepter来进行监听。server端通信框图为
- Accepter负责listen, 调用 SimpleMessenger::add_accept_pipe() 创建新的 Pipe 到 SimpleMessenger::pipes 来处理该请求。
- Pipe用于消息的读取和发送。该类主要有两个组件,分别在不同线程中,Pipe::Reader,Pipe::Writer用来处理消息读取和发送。
- Messenger作为消息的发布者, 各个 Dispatcher 子类作为消息的订阅者, Messenger 收到消息之后,通过 Pipe 读取消息,然后转给 Dispatcher 处理。
- Dispatcher是订阅者的基类,具体的订阅后端(比如OSD、monitor和MDS等)继承该类,初始化的时候通过 Messenger::add_dispatcher_tail/head 注册到 messenger::dispatchers. 收到消息后,通知该类处理。
- DispatchQueue该类用来缓存收到的消息, 然后唤醒 DispatchQueue::dispatch_thread 线程找到后端的 Dispatch 处理消息。
2.1 server端代码
- 将地址字符串解析为entity_addr_t类的地址
string dest_str = "tcp://";
dest_str += addr;
dest_str += ":";
dest_str += port;
entity_addr_from_url(&bind_addr, dest_str.c_str()); //将字符串地址解析为实体地址
- 创建simplemessenger,并进行magic和policy设置
messenger = Messenger::create(g_ceph_context, g_conf().get_val<std::string>("ms_type"), //type决定是simple方式还是async方式
entity_name_t::MON(-1),
"simple_server",
0 /* nonce */,
0 /* flags */);
// enable timing prints
messenger->set_magic(MSG_MAGIC_TRACE_CTR);
messenger->set_default_policy(Messenger::Policy::stateless_server(0));
create()
函数根据传入的type确定具体返回的messenger类型,messenger类是父类,在simple中是返回SimpleMessenger,其构造函数中调用了
init_local_connection(); //初始化connection,并且一旦有新连接,需告知所有的dispatcher
void SimpleMessenger::init_local_connection()
{
local_connection->peer_addrs = *my_addrs;
local_connection->peer_type = my_name.type();
local_connection->set_features(CEPH_FEATURES_ALL);
ms_deliver_handle_fast_connect(local_connection.get());//告知所有的dispatcher
}
- 为messenger来bind IP
r = messenger->bind(bind_addr);
messenger.h中定义了bind的接口,具体实现在SimpleMessenger中
int SimpleMessenger::bind(const entity_addr_t &bind_addr)
{
lock.Lock();
if (started) {
ldout(cct,10) << "rank.bind already started" << dendl;
lock.Unlock();
return -1;
}
ldout(cct,10) << "rank.bind " << bind_addr << dendl;
lock.Unlock();
// bind to a socket
set<int> avoid_ports;
int r = accepter.bind(bind_addr, avoid_ports); //实际是将地址和端口bind到了accepter上
if (r >= 0)
did_bind = true;
return r;
}
分析可知,最终完成bind的实际是Accepter,该类继承自thread类,是用于在指定的地址和端口上监听即将出现的connection。accepter是封装的Linux的socket中的 方法,包括创建socket,bind,listen,创建pipe,是建立连接实际完成的模块
参考tcp通信流程
int Accepter::bind(const entity_addr_t &bind_addr, const set<int>& avoid_ports)
{
...
listen_sd = socket_cloexec(family, SOCK_STREAM, 0); //创建socket,封装了Linux的socket创建方法
...
...
// 设置地址
entity_addr_t listen_addr = bind_addr;
if (listen_addr.get_type() == entity_addr_t::TYPE_NONE) {
listen_addr.set_type(entity_addr_t::TYPE_LEGACY);
}
listen_addr.set_family(family);
...
...
//指定端口
if (listen_addr.get_port())
...
...
//开始bind,用的还是Linux的socket的bind方法
rc = ::bind(listen_sd, listen_addr.get_sockaddr(), listen_addr.get_sockaddr_len());
...
...
// listen!,Linux的方法,貌似并没有开启线程,只是准备好监听连接
rc = ::listen(listen_sd, msgr->cct->_conf->ms_tcp_listen_backlog);
...
...
//此处又执行了connection初始化,通知所有dispatcer?为啥两次?
//因为不是同一个messenger了,这里是accepter自己的messenger
msgr->init_local_connection();
//创建pipe
rc = create_selfpipe(&shutdown_rd_fd, &shutdown_wr_fd);
}
bind完成,在bind过程的最后一步创建了pipe
int Accepter::create_selfpipe(int *pipe_rd, int *pipe_wr) {
int selfpipe[2]; //接收和发送各一个
if (pipe_cloexec(selfpipe) < 0) { //执行Linux的pipe创建函数
int e = errno;
lderr(msgr->cct) << __func__ << " unable to create the selfpipe: "
<< cpp_strerror(e) << dendl;
return -e;
}
//为pipe的每个文件设置特性
for (size_t i = 0; i < std::size(selfpipe); i++) {
int rc = fcntl(selfpipe[i], F_GETFL);
ceph_assert(rc != -1);
rc = fcntl(selfpipe[i], F_SETFL, rc | O_NONBLOCK);
ceph_assert(rc != -1);
}
*pipe_rd = selfpipe[0];
*pipe_wr = selfpipe[1];
return 0;
}
- 初始化守护进程,貌似会开启log的线程和守护进程
// Set up crypto, daemonize, etc.
//global_init_daemonize(g_ceph_context, 0);
//log和守护进程开始,若已经存在是否就不需要创建?
common_init_finish(g_ceph_context);
- 创建dispatcher并添加到messenger的dispatcher队列头
dispatcher = new SimpleDispatcher(messenger);
messenger->add_dispatcher_head(dispatcher); // should reach ready()
void add_dispatcher_head(Dispatcher *d) {
bool first = dispatchers.empty();
dispatchers.push_front(d);
if (d->ms_can_fast_dispatch_any())
fast_dispatchers.push_front(d);
//需要ready后才能执行之后的程序
if (first)
ready();
}
同样的,ready函数在simplemessenger中实现,继承自messenger中的接口,在ready函数中会开启多个线程
void SimpleMessenger::ready()
{
ldout(cct,10) << "ready " << get_myaddr_legacy() << dendl;
dispatch_queue.start(); //开启了 dispatch_thread和 local_delivery_thread两个线程
lock.Lock();
//bind成功,开启accepter线程
if (did_bind)
accepter.start();
lock.Unlock();
}
- 开始simplemessenger
reaper_started = true; //开启reaper线程,用于回收通信关闭后的pipe资源
reaper_thread.create("ms_reaper");
- 开始工作,阻塞住main函数 的线程,直到messenger shutdown
messenger->wait(); // can't be called until ready()
...
...
void SimpleMessenger::wait()
{
...
if (!stopped)
stop_cond.Wait(lock); //阻塞住主线程,等待messenger的shutdown
...
// 结束开启的线程,没有见到log和守护进程,所以这俩不归messenger管?
accepter.stop();
...
dispatch_queue.shutdown();
...
if (reaper_started) {
ldout(cct,20) << "wait: stopping reaper thread" << dendl;
lock.Lock();
reaper_cond.Signal();
reaper_stop = true;
lock.Unlock();
reaper_thread.join();
reaper_started = false;
ldout(cct,20) << "wait: stopped reaper thread" << dendl;
}
...
// close+reap all pipes
lock.Lock();
{
ldout(cct,10) << "wait: closing pipes" << dendl;
while (!rank_pipe.empty()) {
Pipe *p = rank_pipe.begin()->second;
p->unregister_pipe();
p->pipe_lock.Lock();
p->stop_and_wait();
...
}
reaper(); //利用reaper来删除pipe
ldout(cct,10) << "wait: waiting for pipes " << pipes << " to close" << dendl;
while (!pipes.empty()) {
reaper_cond.Wait(lock);
reaper();
}
}
...
}
用流程图总结上述代码,可以表示为下图所示,此图从上述第五步的ready开始,描述了配置完成之后,simple通信方式的工作流程