ceph是一个分布式的文件系统。对于一个分布式系统,需要一个稳定的底层网络通信模块(消息模块),用于组件之间的互联互通。
ceph的组件主要包括,OSD,monitor,mgr,osdclient,client等,这些模块内部的通信,以及模块间的通信都使用了,后面我们直接把这些组件称为网络模块的使用者。使用者在使用网络模块的时候,主要涉及到2个角色,一个是messenger,一个是dispatcher。messenger相当于一个消息管理器(网络模块的核心,消息的发送和接收都是messenger通过底层的类实现),dispatcher相当于一个消息的处理器。
-messenger
- ① 将dispatcher移交给它的信息发送给其它节点(节点,这里节点是一个逻辑单元,比如osd.0就算是一个节点,osd.1算一个新的节点)处理
- ② 从其它节点的messenger获取消息移交给本节点的dispatcher。
-dispatcher
- ① 对接收到的消息(有messenger移交给它)进行处理
- ② 把需要发送的消息移交给本节点的messenger
每个ceph组件都会注册多个messenger和多个dispatcher用于处理不同类型的消息。以OSD组件为例,OSD的守护进程启动的时候会注册7个messenger来管理消息,每个messenger的用途不一样。这部分代码在ceph osd守护进程启动中 /src/ceph_osd.cc。 每一个OSD都有一个守护进程(OSD deamon)。这个deamon负责完成OSD的所有逻辑功能,包括与monitor和其他OSD(事实上是其他OSD的deamon)通信以维护更新系 统状态,与其他OSD共同完成数据的存储和维护,与client通信完成各种数据对象操作等等。
例子:一个OSD模块会注册7个messenger和2个dispatcher。
编号 | Messenger实例名称 | 作用 |
1 | *ms_public | 用来处理OSD和Client之间的消息 |
2 | *ms_cluster | 用来处理OSD和集群之间的消息 |
3 | *ms_hb_front_client | 用来向其它OSD发送心跳的消息 |
4 | *ms_hb_back_client | 用来向其它OSD发送心跳的消息 |
5 | *ms_hb_back_server | 用来接收其他OSD的心跳消息 |
6 | *ms_hb_front_server | 用来接收其他OSD的心跳消息 |
7 | *ms_objecter | 用来处理OSD和Objecter之间的消息 |
编号 | Dispatcher实例名称 | 作用 |
1 | *OSD | 可以处理部分osd节点的消息 |
2 | *heartbeat_dispatcher | 处理心跳连接 |
1 消息模块的使用框架
1.1 发送消息
本节描述ceph中的应用(osd、mgr、monitor等)是如何使用网络模块发送消息的。在实际的应用中,有两种常见的发送消息的方式,当然这两种方式只是看起来有些不同,的底层实现都是相同,都是调用AsyncConnection::send_message(Message *m)把消息发送出去。
1)方式一:使用AsyncMessenger::send_message(Message *m, const entity_inst_t& dest))
其中Message *m是要发送的消息,dest是目的地址。
send_message会首先去判断自己和目标地址之前是不是已经存在链接Connection,如果没有就创建一个,conn->send_message(m)发送消息
-----------------------------------------------------------
conn->send_message(m)
-----------------------------------------------------------
2) 方式二:
① 首先要通过Messenger类,获取对应的Connection:
------------------------------------------------------------
conn = messenger->get_connection(dest_server);
------------------------------------------------------------
get_connection过程是这样的,如果dest.addr是my_inst.addr,就直接返回local_connection。
如果链接不存在就新建一个。
② 当获得一个Connection之后,就可以调用Connection的发送函数来发送消息。
-----------------------------------------------------------
conn->send_message(m)
-----------------------------------------------------------
具体发送的实现过程依赖于选择的消息模式,simple、async等实现方式都不同。在另一个文章里我会讲到具体实现过程,这里不多做解释。
1.2 消息的接收
消息的接收过程简言之就是通过监听socket判断是否有消息到来,如果有就接收。这个过程是个很复杂的过程,涉及到了连接建立、错误处理等等。具体的实现依赖于选择的消息模式,比如,SimpleMessenger是使用一个read线程来实现;AsyncMessenger是使用基于事件的机制实现。接收的过程对应用层都是透明的,本章不做解释。
1.3 消息的处理
消息接收完成后,就进入消息的处理。首先判断消息m是否可以fast_dispatch,如果可以,调用注册fast_dispatcher函数处理消息。如果不能fast_dispatch,调用函数in_q->enqueue,将接收到的消息加入到DispatchQueue的mqueue队列中,排队等待处理。
2 ceph OSD心跳检测与网络模块
下面我们具体举一个OSD心跳检测的例子来讲解,通过心跳检测机制来了解网络模块的使用。在ceph中需要通过心跳检测来判断OSD是不是在线,因为这部分的功能比较简单独立。
2.1 Messenger & Dispatcher的注册
在OSD模块注册的7个Messenger和2个Dispatcher中,4个Messenger都和心跳检测相关,一个heartbeat_dispatcher用来处理心跳连接。
编号 | Messenger实例名称 | 作用 |
1 | *ms_public | 用来处理OSD和Client之间的消息 |
2 | *ms_cluster | 用来处理OSD和集群之间的消息 |
3 | *ms_hb_front_client | 用来向其它OSD发送心跳的消息 |
4 | *ms_hb_back_client | 用来向其它OSD发送心跳的消息 |
5 | *ms_hb_back_server | 用来接收其他OSD的心跳消息 |
6 | *ms_hb_front_server | 用来接收其他OSD的心跳消息 |
7 | *ms_objecter | 用来处理OSD和Objecter之间的消息 |
对应代码在ceph_osd.cc中
-------------------------------------------------------------------------------------------------------
-HeartBeatMessenger
在ceph守护进程启动过程中(ceph-osd.cc),创建了4个messenger用于心跳检测。
在ceph守护进程创建osd的时候将这些messenger传给了osd, 注意这些messenger在osd中被重命名了。
重命名为了:
hb_front_client_messenger
hb_back_client_messenger
hb_front_server_messenger
hb_back_server_messenger
---------------------------------------------------------------------------------------------------
编号 | Dispatcher实例名称 | 作用 |
1 | *OSD | 可以处理部分osd节点的消息 |
2 | *heartbeat_dispatcher | 处理心跳连接 |
对应代码在osd.cc中
-------------------------------------------------------------------------------------------------------
hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
-------------------------------------------------------------------------------------------------------
2.2 心跳机制介绍
OSD 中有一个heartbeat_thread,这个heartbeat_thread的作用就是不断的发送ping请求给其他节点。在Ceph中,OSD的地位都是对等的,每一个OSD在向其他OSD发送ping消息的同时,也会收到其他OSD发来ping消息。OSD收到ping消息后会发送一个回复消息reply message。在部署ceph的时候,通常会使用两张网卡front和back,将流量分开。所以OSD使用两对messenger和来分别发送和监听front额back的ping心跳。图中展示了一个3个OSD的ceph集群的心跳检测过程,只画出了front网卡的心跳检测,back雷同。
2.3 心跳检测中网络模块的使用
我们着重看两个osd节点间如何通信。上图展示了OSD A向OSD B发送Ping消息(hb_front_client_messenger(A) ->ms_hb_front_server(B)),然后OSD B收到消息后交给dispatcher进行处理,然后发送回复消息给OSD A (hb_front_client_messenger(A) <-ms_hb_front_server(B))的过程。具体的可以拆分成5个步骤:
- ① 连接(connection)建立
- ② Ping消息发送
- ③ Ping消息接受及处理
- ④ 回复消息
- ⑤ 处理PING_REPLY
下面对这五个步骤进行一一介绍。
① 连接(connection)建立
获取目标 messenger B的链接connection
conn =hb_front_client_messenger(A)->get_connection(dest_server_B),如果没有链接就创建一个。
monitor中的osdmap记录了每一个osd的front地址和back地址,这个是在osd启动的时候就告诉monitor的。
OSDService::get_con_osd_hb(),首先先获取osdmap,获取目标osdB的front地址,然后在A的messenger hb_front_client_messenger中创建一个connection。
pair<ConnectionRef,ConnectionRef> OSDService::get_con_osd_hb(int peer, epoch_t from_epoch)
{
OSDMapRef next_map = get_nextmap_reserved();
// service map is always newer/newest
assert(from_epoch <= next_map->get_epoch());
pair<ConnectionRef,ConnectionRef> ret;
if (next_map->is_down(peer) ||
next_map->get_info(peer).up_from > from_epoch) {
release_map(next_map);
return ret;
}
ret.first = osd->hb_back_client_messenger->get_connection(next_map->get_hb_back_inst(peer));
if (next_map->get_hb_front_addr(peer) != entity_addr_t())
ret.second = osd->hb_front_client_messenger->get_connection(next_map->get_hb_front_inst(peer));
release_map(next_map);
return ret;
}
osd->hb_back_client_messenger->get_connection(next_map->get_hb_back_inst(peer));
if (next_map->get_hb_front_addr(peer) != entity_addr_t())
ret.second = osd->hb_front_client_messenger->get_connection(next_map->get_hb_front_inst(peer));
release_map(next_map);
return ret;
}
② Ping消息发送
在OSD::heartbeat()中,对记录了osd连接信息map进行遍历,每一个heartbeatinfo中记录了一个目标osd的链接信息(connection),通过这些conns把消息发送出去。重点的就是红色部分。
void OSD::heartbeat()
{
......
// send heartbeats
for (map<int,HeartbeatInfo>::iterator i = heartbeat_peers.begin();
i != heartbeat_peers.end();
++i) {
int peer = i->first;
i->second.last_tx = now;
if (i->second.first_tx == utime_t())
i->second.first_tx = now;
dout(30) << "heartbeat sending ping to osd." << peer << dendl;
i->second.con_back->send_message(new MOSDPing(monc->get_fsid(),
service.get_osdmap()->get_epoch(),
MOSDPing::PING, now,
cct->_conf->osd_heartbeat_min_size));
if (i->second.con_front)
i->second.con_front->send_message(new MOSDPing(monc->get_fsid(),
service.get_osdmap()->get_epoch(),
MOSDPing::PING, now,
cct->_conf->osd_heartbeat_min_size));
......
}
}
i->second.con_back->send_message(new MOSDPing(monc->get_fsid(),
service.get_osdmap()->get_epoch(),
MOSDPing::PING, now,
cct->_conf->osd_heartbeat_min_size));
if (i->second.con_front)
i->second.con_front->send_message(new MOSDPing(monc->get_fsid(),
service.get_osdmap()->get_epoch(),
MOSDPing::PING, now,
cct->_conf->osd_heartbeat_min_size));
......
}
}
③ Ping消息接受及处理
OSD B收到消息,Messenger B内部的dispatch线程会调用事先键入的dispatcher对消息进行处理。HeartbeatDispatcher会将message交给osd->heartbeat_dispatch()处理。
struct HeartbeatDispatcher : public Dispatcher {
OSD *osd;
explicit HeartbeatDispatcher(OSD *o) : Dispatcher(o->cct), osd(o) {}
bool ms_dispatch(Message *m) override {
return osd->heartbeat_dispatch(m);
}
......
}heartbeat_dispatcher
bool OSD::heartbeat_dispatch(Message *m)
{
dout(30) << "heartbeat_dispatch " << m << dendl;
switch (m->get_type()) {
case CEPH_MSG_PING:
dout(10) << "ping from " << m->get_source_inst() << dendl;
m->put();
break;
case MSG_OSD_PING:
handle_osd_ping(static_cast<MOSDPing*>(m));
break;
default:
dout(0) << "dropping unexpected message " << *m << " from " << m->get_source_inst() << dendl;
m->put();
}
return true;
}
osd->heartbeat_dispatch(m);
}
......
}heartbeat_dispatcher
bool OSD::heartbeat_dispatch(Message *m)
{
dout(30) << "heartbeat_dispatch " << m << dendl;
switch (m->get_type()) {
case CEPH_MSG_PING:
dout(10) << "ping from " << m->get_source_inst() << dendl;
m->put();
break;
case MSG_OSD_PING:
handle_osd_ping(static_cast<MOSDPing*>(m));
break;
default:
dout(0) << "dropping unexpected message " << *m << " from " << m->get_source_inst() << dendl;
m->put();
}
return true;
}
heartbeat_dispatch()根据消息的type进行处理,因为消息的type是MSG_OSD_PING,调到OSD::handle_osd_ping(MOSDPing *m)进行处理, 进入case MOSDPing::PING:
void OSD::handle_osd_ping(MOSDPing *m)
{
switch (m->op) {
case MOSDPing::PING:
{
//做了一系列处理
...
//发送回复包
Message *r = new MOSDPing(monc->get_fsid(),
curmap->get_epoch(),
MOSDPing::PING_REPLY, m->stamp,
cct->_conf->osd_heartbeat_min_size);
m->get_connection()->send_message(r);
...
}
case MOSDPing::PING_REPLY:
{
// 更新时间戳,避免心跳超时
// osd有专门的tick线程进行周期性的检查,如果发现有心跳超时的,就会上报monitor
}
}
case MOSDPing::PING:
{
//做了一系列处理
...
//发送回复包
Message *r = new MOSDPing(monc->get_fsid(),
curmap->get_epoch(),
MOSDPing::PING_REPLY, m->stamp,
cct->_conf->osd_heartbeat_min_size);
m->get_connection()->send_message(r);
...
}
case MOSDPing::PING_REPLY:
{
// 更新时间戳,避免心跳超时
// osd有专门的tick线程进行周期性的检查,如果发现有心跳超时的,就会上报monitor
}
}
④ 回复消息
第③步中,在OSD B的dispatcher中对消息做一系列处理后,会封装一个回复消息PING_REPLY,然后发送OSD A。
------------------------------------------------------------------------
Message *r = new MOSDPing(monc->get_fsid(),
curmap->get_epoch(),
MOSDPing::PING_REPLY, m->stamp,
cct->_conf->osd_heartbeat_min_size);
m->get_connection()->send_message(r);
------------------------------------------------------------------------
这个过程和前面类似,不同的是这次是OSD B的Messenger去获取connection链接,(这个链接就是之前OSD A建立的链接,通过看Asyncmessenger.cc 可以看到在执行第②步中AsyncConnection::send_message(Message *m)时,通过 m->set_connection(this);将connection赋给了message,所以说A向B发送消息和B向A发送回复消息都是用的同一条链接connection),发送回复消息给OSD A。
⑤ 处理PING_REPLY
OSD A 的Messenger(hb_front_client_messenger)监听到了回复消息,交给自己的Dispatcher处理。
还是先进入osd->heartbeat_dispatch(m)的MSG_OSD_PING,然后使用OSD::handle_osd_ping(MOSDPing *m)根据 消息的type做相应处理,这次是进入ping reply
void OSD::handle_osd_ping(MOSDPing *m)
{
switch (m->op) {
case MOSDPing::PING:
{
...
//发送回复包
Message *r = new MOSDPing(monc->get_fsid(),
curmap->get_epoch(),
MOSDPing::PING_REPLY, m->stamp,
cct->_conf->osd_heartbeat_min_size);
m->get_connection()->send_message(r);
...
}
case MOSDPing::PING_REPLY:
{
// 更新时间戳,避免心跳超时
// osd有专门的tick线程进行周期性的检查,如果发现有心跳超时的,就会上报monitor
}
}
case MOSDPing::PING_REPLY:
{
// 更新时间戳,避免心跳超时
// osd有专门的tick线程进行周期性的检查,如果发现有心跳超时的,就会上报monitor
}
}
可以看出,心跳的发送流程是很简单的,也是很独立的。在设计分布式系统的时候,为了保证集群的内部状态正确,应尽量不要引入过多复杂的因素影响心跳的流程。 毕竟心跳快速正确的处理是确保集群运转正常的最基本条件。
参考文章【reference】:
整理中。。。