ceph AsyncMessenger模块源码分析(下)

本文详细解析了AsyncMessenger在处理连接请求时的工作流程,包括如何通过epoll监听连接请求,利用worker负载均衡处理连接,以及AsyncConnection的创建和状态转换过程。深入探讨了数据收发机制,从初始连接到消息的接收、处理与分发,涵盖多种状态下的具体操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

处理连接请求

对于AsyncMessenger,当新的连接请求到达时,epoll会监听到该事件,并调用listenfd对应的回调函数listen_handler(C_processor_accept),在listen_handler中,会获取引用次数最少的worker(为了负载均衡),用改worker去处理这一个连接请求。具体做法为
(1)调用listen_socket.accept(&cli_socket, opts, &addr, w);来对改连接请求执行accept类的操作。
(2)调用msgr->add_accept(w, std::move(cli_socket), addr), add_accept的实现如下

  AsyncConnectionRef conn = new AsyncConnection(cct, this, &dispatch_queue, w);
  conn->accept(std::move(cli_socket), addr);
  accepting_conns.insert(conn);

AsyncConnection的构造函数如下‘

AsyncConnection::AsyncConnection(CephContext *cct, AsyncMessenger *m, DispatchQueue *q,
                                 Worker *w)
  : Connection(cct, m), delay_state(NULL), async_msgr(m), conn_id(q->get_id()),
    logger(w->get_perf_counter()), global_seq(0), connect_seq(0), peer_global_seq(0),
    state(STATE_NONE), state_after_send(STATE_NONE), port(-1),
    dispatch_queue(q), can_write(WriteStatus::NOWRITE),
    keepalive(false), recv_buf(NULL),
    recv_max_prefetch(std::max<int64_t>(msgr->cct->_conf->ms_tcp_prefetch_max_size, TCP_PREFETCH_MIN_SIZE)),
    recv_start(0), recv_end(0),
    last_active(ceph::coarse_mono_clock::now()),
    inactive_timeout_us(cct->_conf->ms_tcp_read_timeout*1000*1000),
    msg_left(0), cur_msg_size(0), got_bad_auth(false), authorizer(NULL), replacing(false),
    is_reset_from_peer(false), once_ready(false), state_buffer(NULL), state_offset(0),
    worker(w), center(&w->center)
{
  read_handler = new C_handle_read(this);
  write_handler = new C_handle_write(this);
  wakeup_handler = new C_time_wakeup(this);
  tick_handler = new C_tick_wakeup(this);
  recv_buf = new char[2*recv_max_prefetch];
  state_buffer = new char[4096];
}

可以看到一个AsyncConnection是和一个worker关联起来的,且也和一个center关联的。同时在AsyncConnection构造函数中会设置一些回调函数。

在add_accept函数中还会调用AsyncConnection::accept来将已经建立连接的描述符通知给worker的线程函数,accept实现如下

  cs = std::move(socket);
  socket_addr = addr;
  state = STATE_ACCEPTING;
  center->dispatch_event_external(read_handler);

dispatch_event_external就会将回调函数read_handler加入到这个center的external_events中,然后唤醒线程处理函数去处理external_events。在read_handler回调函数中最终会调用AsyncConnection::process,此时事件的发生和处理已经在另一个线程中了(由之前接受连接请求的线程,转换到这个处理数据收发的线程)。

数据收发

在AsyncConnection::process中,会根据连接的状态state来选择做哪些处理,
(1)一开始state的状态是STATE_ACCEPTING,在该状态下,调用
center->create_file_event(cs.fd(), EVENT_READABLE, read_handler)来讲已连接描述符加入到epoll的监听范围内。然后调用try_send(bl);来向对方发送CEPH_BANNER(“ceph v027”)字符串。最后设置state为STATE_ACCEPTING_WAIT_BANNER_ADDR。

(2)在STATE_ACCEPTING_WAIT_BANNER_ADDR状态下,会读取对方回应的CEPH_BANNER和对方的地址信息(ceph_entity_addr),如下

read_until(strlen(CEPH_BANNER) + sizeof(ceph_entity_addr), state_buffer);

然后设置state为STATE_ACCEPTING_WAIT_CONNECT_MSG。

(3)在STATE_ACCEPTING_WAIT_CONNECT_MSG状态下,会读取connect_msg信息,如下

read_until(sizeof(connect_msg), state_buffer)

然后将state设置为STATE_ACCEPTING_WAIT_CONNECT_MSG_AUTH

(4)在STATE_ACCEPTING_WAIT_CONNECT_MSG_AUTH状态下首先还是读取消息中的认证信息,然后放到authorizer_bl中,authorizer_bl是一个专门存放标识的bufferlist。然后根据authorizer_bl和authorizer_reply的值调用handle_connect_msg来处理连接。
在AsyncConnection::handle_connect_msg()中首先根据peer_addr判断连接是否存在,如果连接是存在的可以进行后续的操作,对连接进行一些处理,然后调用AsyncConnection::_reply_accept()将回复信息发送给对端,发送信息的时候有一个flag,如果可以接受消息了则将CEPH_MSGR_TAG_SEQ作为flag回复,然后将state的值置为STATE_ACCEPTING_WAIT_SEQ。

(5)在STATE_ACCEPTING_WAIT_SEQ状态下将确认信息读取到state_buffer中,然后根据确认信息对消息进行优先级的设置,如果是高优先级的消息先处理。最后将state的值置为STATE_ACCEPTING_READY,即可以接受消息了。

(6)
在STATE_ACCEPTING_READY状态下主要操作是打印accept完成的信息,然后将用于连接的数据结构connect_msg清空,最后把state的值置为STATE_OPEN。

(7)在STATE_OPEN状态下首先读出标识信息tag,如果tag是CEPH_MSGR_TAG_MSG,即读取的是消息的标识,将state的值置为STATE_OPEN_MESSAGE_HEADER,否则进行一些其它的处理。

(8)在STATE_OPEN_MESSAGE_HEADER状态下读出消息的头部,然后进行一些类似CRC的校验工作,如果收到的是坏的消息中断当前的操作,返回错误信息,如果没有问题,将state的值置为STATE_OPEN_MESSAGE_THROTTLE_MESSAGE,进行下一步的消息读取操作。

(9)在STATE_OPEN_MESSAGE_THROTTLE_MESSAGE中对消息进行判断,调用get_or_fail函数判断policy的throttler_messages中是否还能容下一个message,如果不能则需要添加一个wakeup_handler回调函数给create_time_event,并设置1000ms后回调,wakeup_handler会最终调用AsyncConnection::process,并根据state的值进入到这里。
如果正常状态则将state的值置为STATE_OPEN_MESSAGE_THROTTLE_BYTES。

(10)在STATE_OPEN_MESSAGE_THROTTLE_BYTES状态下计算一下当前收到的消息头部的操作,然后加上时间戳,同时也会调用

policy.throttler_bytes->get_or_fail(cur_msg_size)

判断throttler_bytes中是否还能容下cur_msg_size大小的字节,最后将state的值置为STATE_OPEN_MESSAGE_THROTTLE_DISPATCH_QUEUE。

(11)在STATE_OPEN_MESSAGE_THROTTLE_DISPATCH_QUEUE下,调用

dispatch_queue->dispatch_throttler.get_or_fail(cur_msg_size)

判断dispatch_queue的dispatch_throttler中是否还能容下cur_msg_size大小的字节。然后把state设置为STATE_OPEN_MESSAGE_READ_FRONT。

(12)在STATE_OPEN_MESSAGE_READ_FRONT状态下调用read_until()函数将消息的头部(不同于之前的头部校验信息,这个是数据的front部分)读到front中,front是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的头部。完成以后将state的值置为STATE_OPEN_MESSAGE_READ_MIDDLE。

(13)在STATE_OPEN_MESSAGE_READ_MIDDLE状态下和读取头部数据一样,调用read_until()函数将消息的中间部分读取到middle中,middle也是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的中间部分。完成以后将state的值置为STATE_OPEN_MESSAGE_READ_DATA_PREPARE。

(14)在STATE_OPEN_MESSAGE_READ_DATA_PREPARE状态下进行的是读取消息数据部分的准备工作,比如判断接收消息中数据部分的数据结构是不是足够容纳数据,如果现有的申请的接收数据的结构的空间大小不能容纳数据,则重新申请空间大小给其使用,如果可以则不用操作,最后将state的值置为STATE_OPEN_MESSAGE_READ_DATA,真正接收消息中的数据部分。

(15)在STATE_OPEN_MESSAGE_READ_DATA状态下用一个while循环来读取消息携带的数据,直到消息中没有数据可读才跳出循环,在循环中将消息读取到data中,data是在AsyncConnection中定义的一个bufferlist的结构,专门用于存放消息的数据部分。如果一次没有读完则终端当前的操作,等待下一次继续读取数据,最后将将state的值置为STATE_OPEN_MESSAGE_READ_FOOTER_AND_DISPATCH。

(16)
在STATE_OPEN_MESSAGE_READ_FOOTER_AND_DISPATCH状态下主要读取消息的尾部,然后对读取到的消息进行处理后分发出去让注册的Dispatcher来处理,如果当前AsyncMessenger实例中可以处理fast_dispatch,就会调用dispatch_queue->fast_dispatch(message);将message进行快速转发,否则就会调用dispatch_queue->enqueue(message, message->get_priority(), conn_id);来进行入队操作。

Ceph中,stripe是一种将数据分片存储的概念。当进行文件读取操作时,需要通过一系列的计算来确定数据所在的具体位置。本文以CephFS的文件读取流程为例进行分析。 首先,在文件读取过程中,Ceph会将文件划分为若干个条带(stripe),每个条带由多个对象分片(stripe unit)组成。条带可以看作是逻辑上连续的一维地址空间。 接下来,通过file_to_extent函数将一维坐标转化为三维坐标(objectset,stripeno,stripepos),来确定具体的位置。其中,objectset表示所在的对象集,stripeno表示条带号,stripepos表示条带内的偏移位置。 具体的计算过程如下:假设需要读取的数据的偏移量为offset,每个对象分片的大小为su(stripe unit),每个条带中包含的对象分片数为stripe_count。 首先,计算块号blockno = offset / su,表示数据所在的分片号。 然后,计算条带号stripeno = blockno / stripe_count,表示数据所在的条带号。 接着,计算条带内偏移stripepos = blockno % stripe_count,表示数据在条带内的偏移位置。 接下来,计算对象集号objectsetno = stripeno / stripes_per_object,表示数据所在的对象集号。 最后,计算对象号objectno = objectsetno * stripe_count + stripepos,表示数据所在的对象号。 通过以上计算,可以确定数据在Ceph中的具体位置,从而完成文件读取操作。 需要注意的是,以上分析是基于Ceph版本10.2.2(jewel)进行的,尽管版本跨度较大,但是该部分代码在12.2.10(luminous)版本中仍然比较稳定,基本的框架没有发生变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值