ceph网络通信三种实现方式
ceph的网络通信的源码在src/msg下,有Ceph Messager的三个实现:simple,async,xio。
simple模式:2021:基本不用了,不建议看了)
simple模式相对比较简单,一个链接的每一端都有两个线程,分别负责读和写,当集群规模很大的时候,会有大量线程,占用大量资源,限制了网络性能。(SimpleMessenger是网络层最早的实现,当时的网络编程模型还是线程当道,因此simple采用的就是这种简单粗暴的线程模型)async模式:2021:当前主要模式)
async模式基于Epoll + EventCenter,这也是当前的默认消息机制。(async主要是国内ceph大神麦子迈 王豪迈大神的实现的。随着linux提供epoll这样的系统调用,可以提供基于事件的IO多路服用,越来越多的网络开始使用该系统调用来实现高并发通信。比如libevent。async也是如此。)xio模式(2021:不太了解)
xio模式基于开源网络通信库accelio来实现,目前处于试验阶段。(xio使用了开源网络通信模块accelio,需要依赖第三方的库,目前也在实验阶段。)从Messenger.cc 这个类中可以看出在create Messager实例的时候根据type选择网络框架是simple/async/xio.其中选择xio的话需要的话需要定义HAVE_XIO 这个宏,并且xio支持tcp/ip。infiniband 这两种协议,而前两种支持tcp/ip 协议。
AsyncMessenger

相关的类
message数据格式
见:https://blog.csdn.net/bandaoyu/article/details/121315682
Messenger实例名称
在main()函数中首先创建Messenger,然后对注册的Messenger进行bind,绑定后start消息模块进行工作,不同的角色会启动相应的守护进程,如果OSD在main()函数中注册了6个Messenger的实例,如下表所示。
编号 Messenger实例名称 作用
1 *ms_public 用来处理OSD和Client之间的消息
2 *ms_cluster 用来处理OSD和集群之间的消息
3 *ms_hbclient 用来处理OSD和其它OSD保持心跳的消息
4 *ms_hb_back_server 用来处理OSD接收心跳消息
5 *ms_hb_front_server 用来处理OSD发送心跳消息
6 *ms_objecter 用来处理OSD和Objecter之间的消息
EventCenter
worker 线程 while循环,执行EventCenter::process_events。这里事件有三类:
- time_events:定时事件,比如connection的定时函数AsyncConnection::tick。
- external_events:外部事件,放进去里面立马唤醒线程执行,send_message就是外部事件。
- file事件:epoll监听的事件,与文件(如socket连接fd)绑定,当fd可读or可写时执行。
例子如下:
time_events:定时事件,比如connection的定时函数AsyncConnection::tick。
register_time_events.insert(center->create_time_event(delay_period*1000000, this));
external_events:外部事件,放进去里面立马唤醒线程执行,send_message就是外部事件。
Messenger::send_message(Message *m, dest)
AsyncMessenger::send_message(Message *m, dest)
--|AsyncMessenger::_send_message(Message *m, dest)
----|conn=_lookup_conn(dest.addr)
----|AsyncMessenger::submit_message(Message *m,conn,dest,...)
------|conn->send_message(m) # AsyncConnection::send_message(Message *m)
--------|out_q[priority].emplace_back(std::move(bl),m) #放入队列//回调操作(write_handler= new C_handle_write(this))放入event中心,wakeup线程执行
--------|EventCenter::dispatch_event_external(write_handler)
----------|external_event.push_back(write_handler) //放入事件中,就可以唤醒线程去执行
----------|wakeup()
|
|
w->center.process_events
|
cb = event->read_cb;
cb->do_request()
|C_handle_write
|
|
--|write_handler = new C_handle_write(this)
file事件:epoll监听的事件,与文件(如socket连接fd)绑定,当fd可读or可写时执行,如
int r = create_file_event(notify_receive_fd, EVENT_READABLE, notify_handler);
notify_receive_fd可读时,process_events将执行回调notify_handler
建立连接的过程
Ceph 数据IO会通过AsyncMessenger 收发数据,所以以OSD的是数据收发为切入点,学习AsyncMessenger
ceph版本10.2.5
一、client如何与osd进行建立通信的呢?
- osd是运行在服务器上的进程,进程的主体在
ceph_osd.cc中的main函数开始,并且申请一些用于服务的线程。比如这里用于接收客户端连接的线程Accepter。如果配对连接成功后,会把连接成功的socket交给SimpleMessager来处理。
//ceph_osd.cc
Messenger *ms_public = Messenger::create(g_ceph_context, g_conf->ms_type,
entity_name_t::OSD(whoami), "client",
getpid());
- 在ceph_osd.cc 中main函数中 会申请一个Messager 消息管理的类,该类是一个基类,最终会选择SimpleMessager类实例,SimpleMessager类中会有一个叫做Accepter的成员,会申请该对象,并且初始化。
然后在main中把它绑定一个固定的ip,这个ip根据配置文件来获取。3.这个ip最终会绑定在Accepter中。然后在Accepter->bind函数中,会对这个ip初始化一个socket,并且保存为listen_sd。接着会启动Accepter->start(),这里会启动Accepter的监听线程,这个线程做的事情放在Accepter->entry()函数中。接下来看看这个函数中实现了什么。
//SimpleMessenger.cc
int SimpleMessenger::bind(const entity_addr_t &bind_addr)
{
// bind to a socket
set<int> avoid_ports;
int r = accepter.bind(bind_addr, avoid_ports);
}
//Accepter.cc
int Accepter::bind(const entity_addr_t &bind_addr, const set<int>& avoid_ports)
{
/* socket creation */
listen_sd = ::socket(family, SOCK_STREAM, 0);
...
rc = ::bind(listen_sd, (struct sockaddr *) &listen_addr.ss_addr(), listen_addr.addr_size());
...
// listen!
rc = ::listen(listen_sd, 128);
}
//Accepter.cc
int Accepter::start()
{
// start thread
create("ms_accepter"); //转到线程创建函数,自动调用entry()
}
//Accepter.cc
void *Accepter::entry()
{
while (!done) {//开始一个循环,线程不能结束。直到停止这个线程时。
int r = poll(&pfd, 1, -1);//poll监听一个端口,等待客户端来进行连接。
// accept
......
int sd = ::accept(listen_sd, (sockaddr*)&addr.ss_addr(), &slen);//如果有客户端来连接时,如果连接成功则进行下面的处理
if (sd >= 0) {
/*注意这一句,add_accept_pipe,即创建专门的pipe负责此次通信,而线程继续accept,而不是停下来处理通信请求*/
msgr->add_accept_pipe(sd); //msgr 是具体实现类为SimpleMessager,跟踪到实现的函数中去
}
}
}
//SimpleMessenger.cc
Pipe *SimpleMessenger::add_accept_pipe(int sd)
{
Pipe *p = new Pipe(this, Pipe::STATE_ACCEPTING, NULL);//当接收到一个连接时,这时需要建立一个数据通道pipe,。
p->sd = sd;//告知pipe 处理socket为sd。也就是从这个sd读取数据。
p->pipe_lock.Lock();
p->start_reader();//启动pipe的读线程,从sd中读取message信息
p->pipe_lock.Unlock();
pipes.insert(p);
accepting_pipes.insert(p);//将pipe加入到SimpleMessager中的accepting_pipes,便于管理。
lock.Unlock();
return p;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
二、消息的传递
- 接下来已经建立了pipe的通道,马上就要开始进行message的接收, 目前接收message的工作交给了pipe的reader线程。接下来看看pipe->reader()做了什么事情。
pipe->reader()中会先 read_message(),将传递的消息解析出来,然后把这个消息放入in_q->fast_dispatch(m)中处理,继续发送到OSD::ms_fast_dispatch中处理,自此转到OSD模块处理,在ms_fast_dispatch中会将message 转化为OpRequestRef op,后续直接对这个op进行处理。继续经过dispatch_session_waiting()、dispatch_op_fast()、handle_op()。handle_op()中开始将这个处理由OSD转化为PG的处理。
// Pipe.cc
void Pipe::reader()
{
if (tag == CEPH_MSGR_TAG_ACK) {
...
}
else if (tag == CEPH_MSGR_TAG_MSG) {
ldout(msgr->cct,20) << "reader got MSG" << dendl;
Message *m = 0;
int r = read_message(&m, auth_handler.get());//接收消息
}
m->set_connection(connection_state.get());//创建了一个connection
in_q->fast_preprocess(m);//void OSD::ms_fast_preprocess(Message *m)
if (delay_thread) {
...
}
} else {
if (in_q->can_fast_dispatch(m)) {//判断消息是否可以快速处理
in_q->fast_dispatch(m);// 主调用函数,后依次展开。
}
} else {
in_q->enqueue(m, m->get_priority(), conn_id);// in_q:接收到消息的队列, out_q:准备发送的消息队列
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
备注:
通过调用函数in_q->can_fast_dispatch(m)判断消息是否可以快速处理,如果可以进行fast_dispatch,那么调用in_q->fast_dispatch(m)进行处理,最终调用(*p)->ms_fast_dispatch(m);如果不可以,那么调用函数in_q->enqueue(m, m->get_priority(), conn_id),把接收到的消息加入到DispatchQueue的mequeue队里中,用DispatchQueue的分发线程ms_dispatch进行处理。
注意:ms_dispatch和ms_fast_dispatch两种处理区别在于:ms_dispatch是由DispatchQueue的线程处理的,是一个单线程,阻塞式处理;ms_fast_dispatch是Pipe的接收线程ms_pipe_read直接调用处理的,它的性能高于前者。
// DispatchQueue.cc
void DispatchQueue::fast_dispatch(Message *m)
{
uint64_t msize = pre_dispatch(m);
msgr->ms_fast_dispatch(m);//---
post_dispatch(m, msize);
}
// Messenger.h
void ms_fast_dispatch(Message *m) {
for (list<Dispatcher*>::iterator p = fast_dispatchers.begin();
p != fast_dispatchers.end();
++p) {// 循环fast_dispatchers,下发到osd端
if ((*p)->ms_can_fast_dispatch(m)) {
(*p)->ms_fast_dispatch(m);// 这里调用osd.cc 中的ms_fast_dispatch函数
return;
}
}
}
//OSD类继承Dispatcher类
class OSD : public Dispatcher类, public md_config_obs_t
{
}
//OSD.cc 读写消息的入口函数。客户端读写IO都是从该函数开始,而不是ms_dispatch
void OSD::ms_fast_dispatch(Message *m)
{
OpRequestRef op = op_tracker.create_request<OpRequest, Message*>(m);//message - > OpRequest
OSDMapRef nextmap = service.get_nextmap_reserved();//最新的osdmap
Session *session = static_cast<Session*>(m->get_connection()->get_priv());//conn - > session
if (session) {
{
update_waiting_for_pg(session, nextmap);// 更新session里保存的osdmap信息
session->waiting_on_map.push_back(op);// 把op放入 session 的waiting_on_map list列表中
dispatch_session_waiting(session, nextmap);//处理等待发送的session
}
session->put();
}
}
//OSD.cc
void OSD::dispatch_session_waiting(Session *session, OSDMapRef osdmap)
{
for (list<OpRequestRef>::iterator i = session->waiting_on_map.begin();
i != session->waiting_on_map.end() && dispatch_op_fast(*i, osdmap);
session->waiting_on_map.erase(i++));// 如果session->waiting_on_map list中有op,就调用dispatch_op_fast函数进行处理
......
}
//OSD.cc
bool OSD::dispatch_op_fast(OpRequestRef& op, OSDMapRef& osdmap)
{
switch(op->get_req()->get_type()) {//message::header.type
// client ops
case CEPH_MSG_OSD_OP:
handle_op(op, osdmap);//go---
break;
// for replication etc.
case MSG_OSD_SUBOP:
handle_replica_op<MOSDSubOp, MSG_OSD_SUBOP>(op, osdmap);
break;
case MSG_OSD_REPOP: //如果是副本请求,那么直接调用这里,最终会和handle_op()函数一样,调用enqueue_op,然后和主副本请求流程一致,直到下述ReplicatedPG::do_request中调用函数handle_message。
handle_replica_op<

本文详细解析了Ceph的三种网络通信模式,重点介绍了async模式的Epoll+EventCenter实现和xio模式的加速通信库Accelerio应用,以及它们在处理大规模并发和性能优化中的作用。还涵盖了Messenger实例及其在OSD角色中的分工和EventCenter的工作原理。
最低0.47元/天 解锁文章
5679

被折叠的 条评论
为什么被折叠?



