目录
现在ceph的网络是async,simple不用,建议只做了解不不必花太多时间在上面。
ceph的网络通信
ceph 网络层代码分析(1)_shuningzhang的专栏-CSDN博客
Ceph通信框架设计模式
设计模式:订阅发布模式(Subscribe/Publish),又名观察者模式,它意图是“定义对象间的一种一对多的依赖关系,
当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。
Ceph通信框架流程图
步骤:
- Accepter监听peer的请求, 调用 SimpleMessenger::add_accept_pipe() 创建新的 Pipe 到 SimpleMessenger::pipes 来处理该请求。
- Pipe用于消息的读取和发送。该类主要有两个组件,Pipe::Reader,Pipe::Writer用来处理消息读取和发送。
- Messenger作为消息的发布者, 各个 Dispatcher 子类作为消息的订阅者, Messenger 收到消息之后, 通过 Pipe 读取消息,然后转给 Dispatcher 处理。
- Dispatcher是订阅者的基类,具体的订阅后端继承该类,初始化的时候通过 Messenger::add_dispatcher_tail/head 注册到 Messenger::dispatchers. 收到消息后,通知该类处理。
- DispatchQueue该类用来缓存收到的消息, 然后唤醒 DispatchQueue::dispatch_thread 线程找到后端的 Dispatch 处理消息。
ceph_message_2
Ceph通信框架类图
架构上采用 Publish/subscribe(发布/订阅) 的设计模式.
simple框架
src/common/config_opts.h
OPTION(ms_type, OPT_STR, "simple")
SimpleMessenger 是simple框架具体的实现。
从这个构造函数中可以看到SimpleMessenger 会在构造函数中分别建立结束accepter和发送dispatch_queue 两个线程
SimpleMessenger::SimpleMessenger(CephContext *cct, entity_name_t name, |
accepter和发送dispatch_queue 线程 建立代码
//这里以accepter为例看看是如何建立接受线程的
class Accepter : public Thread
{
SimpleMessenger *msgr;
bool done;
int listen_sd;
uint64_t nonce;
int shutdown_rd_fd;
int shutdown_wr_fd;
int create_selfpipe(int *pipe_rd, int *pipe_wr);
public:
Accepter(SimpleMessenger *r, uint64_t n)
: msgr(r), done(false), listen_sd(-1), nonce(n),
shutdown_rd_fd(-1), shutdown_wr_fd(-1)
{}
void *entry() override;
void stop();
int bind(const entity_addr_t &bind_addr, const set<int> &avoid_ports);
int rebind(const set<int> &avoid_port);
int start();
};
//可以看到accepter 是thread的子类
//所以我们先看看其start函数
int Accepter::start()
{
ldout(msgr->cct, 1) << __func__ << dendl;
// start thread
create("ms_accepter");
return 0;
}
//在start中通过create来新建一个接收线程,其name是ms_accepter
//其次我们在看看这个线程只要执行的工作,其在entry中实现
void *Accepter::entry()
{
ldout(msgr->cct, 1) << __func__ << " start" << dendl;
int errors = 0;
int ch;
struct pollfd pfd[2];
memset(pfd, 0, sizeof(pfd));
pfd[0].fd = listen_sd;
pfd[0].events = POLLIN | POLLERR | POLLNVAL | POLLHUP;
pfd[1].fd = shutdown_rd_fd;
pfd[1].events = POLLIN | POLLERR | POLLNVAL | POLLHUP;
#开始polling
while (!done)
{
ldout(msgr->cct, 20) << __func__ << " calling poll for sd:" << listen_sd << dendl;
int r = poll(pfd, 2, -1);
if (r < 0)
{
if (errno == EINTR)
{
continue;
}
ldout(msgr->cct, 1) << __func__ << " poll got error"
<< " errno " << errno << " " << cpp_strerror(errno) << dendl;
break;
}
#检查是否polling返回error
if (pfd[0].revents & (POLLERR | POLLNVAL | POLLHUP))
{
ldout(msgr->cct, 1) << __func__ << " poll got errors in revents "
<< pfd[0].revents << dendl;
break;
}
if (pfd[1].revents & (POLLIN | POLLERR | POLLNVAL | POLLHUP))
{
// We got "signaled" to exit the poll
// clean the selfpipe
#检查是否要退出polling
if (::read(shutdown_rd_fd, &ch, 1) == -1)
{
if (errno != EAGAIN)
ldout(msgr->cct, 1) << __func__ << " Cannot read selfpipe: "
<< " errno " << errno << " " << cpp_strerror(errno) << dendl;
}
break;
}
if (done) break;
// accept
#走到这里polling函数就正常返回了,通过accept函数开始接收
sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int sd = ::accept(listen_sd, (sockaddr *)&ss, &slen);
if (sd >= 0)
{
int r = set_close_on_exec(sd);
if (r)
{
ldout(msgr->cct, 1) << __func__ << " set_close_on_exec() failed "
<< cpp_strerror(r) << dendl;
}
errors = 0;
ldout(msgr->cct, 10) << __func__ << " incoming on sd " << sd << dendl;
#实际从pipe中读入msg
msgr->add_accept_pipe(sd);
}
else
{
ldout(msgr->cct, 0) << __func__ << " no incoming connection? sd = " << sd
<< " errno " << errno << " " << cpp_strerror(errno) << dendl;
if (++errors > 4)
break;
}
}
ldout(msgr->cct, 20) << __func__ << " closing" << dendl;
// socket is closed right after the thread has joined.
// closing it here might race
if (shutdown_rd_fd >= 0)
{
::close(shutdown_rd_fd);
shutdown_rd_fd = -1;
}
ldout(msgr->cct, 10) << __func__ << " stopping" << dendl;
return 0;
}
message数据格式
通信的双方需要约定数据格式,这是很明显的。否则收到对方发送的数据,不知道如何解析。这应该是通信的首先要解决的问题。
class Message : public RefCountedObject {
protected:
ceph_msg_header header; // headerelope
ceph_msg_footer footer;
bufferlist payload; // "front" unaligned blob
bufferlist middle; // "middle" unaligned blob
bufferlist data; // data payload (page-alignment will be preserved where possible)
...
};
在消息内容可以分成3个部分
- header
- user data
- footer。
user data 当中可以分成3个部分
- payload
- middle
- data
payload 一般是ceph操作的元数据 , middle是预留字段目前没有使用。 data是一般为读写的数据。
接下来先介绍header:
struct ceph_msg_header {
__le64 seq; /* message seq# for this session */
__le64 tid; /* transaction id */
__le16 type; /* message type */
__le16 priority; /* priority. higher value == higher priority */
__le16 version; /* version of message encoding */
__le32 front_len; /* bytes in main payload */
__le32 middle_len;/* bytes in middle payload */
__le32 data_len; /* bytes of data payload *