【Ceph】Ceph源码分析之Async模块

原文:https://blog.csdn.net/hawkerou/article/details/65449957

1、异步通信核心模块EventCenter+Epoll

概述

EventCenter是Async异步消息通信的核心模块,通过事件/回调模型向上提供异步消息通信,每个Async下的worker线程负责处理一个EventCenter的事件集合。EventCenter针对不同类型的事件使用不同的事件监听来处理事件回调。

详细设计

事件类型

目前EventCenter共支持4种类型的事件,每个worker线程在进入事件处理逻辑的时候,会分别对这4种事件类型的事件进行处理

  • file_events:fd类事件
  • time_events/event_map:定时器类事件
  • external_events/external_num_events:外部事件(时间为0的定时事件)
  • pollers:轮询类事件(目前是DPDk模块在使用)

事件操作

event_center对外提供创建/删除:fd、time、external 3种类型的事件,poller事件则需要EventCenter::Poller的派生类自行实现poll方法。下面介绍下4种事件的监听方式:

  • fd类事件

    支持dpdk、epoll、kqueue、select4种监听方式。以epoll为例子,则是封装的epoll_create/epoll_ctl/epoll_wait接口来向EventCenter提供监听操作接口。(定义在EventEpoll.cc/h)

  • time类事件

    通过定时器来监听

  • 外部事件

    外部事件实际上是定时时间为0的时间,也就是立马执行,设计这个事件的主要目的切换线程上下文,来避免死锁问题。

  • pollers
    派生类的poll轮询

事件的处理

每个worker的线程负责处理EventCenter的底层监听事件集合,因此对EventCenter事件的处理主要在每个worker的线程函数,每个事件的处理逻辑如下:

 

  •  
  • 处理fd类事件

 

通过底层epoll_wait取得可以处理的事件集合,然后再根据注册的读写事件分别调用对应的cb(不会主动将已经注册的事件去除掉,需要上层连接调用注销事件才能删除fd类事件)。

  • 处理time类事件
    检测时间是否到达,到达则调用对应cb
  • 处理external_events
    external_events类事件,不需要条件,只要有,就处理,重点在于:会清空外部事件。
  • 处理pollers
    调用每个poolers[i]->poll()方法来轮询
  •  

Pipe管道设计

fd类事件的监听方式,比如epoll,在使用epoll_wait的时候可能存在阻塞(timeout)会使线程睡眠,对于有些需要马上处理的消息,则线程无法及时处理,因此center就设计了一对Pipe管道,通过监听管道的写事件来唤醒睡眠的线程。管道的创建是在EventCenter初始化的时候

    EventCenter::init(...)
    .....
        notify_receive_fd = fds[0]; 
        notify_send_fd = fds[1]; 

在启动worker线程处理的时候,会在set_owner接口里面创建管道的读端监听事件。

   notify_handler = new C_handle_notify(this, cct); 
   int r = create_file_event(notify_receive_fd, EVENT_READABLE, notify_handler); 

管道的写端则是在wakeup接口里面

   int n = write(notify_send_fd, &buf, sizeof(buf)); 
  •  

因此通过这种机制就可以唤醒线程

global_center设计

global_center是为了维护一个全局的EventCenter表,它通过单例模式来实现一个唯一的全局变量。具体实现是:在启动每个worker线程处理的时候,每个worker都会调用

EventCenter::set_owner
  •  

这个接口来设置owner属性和添加EventCenter给global_center表

事件回调的设计

EventCenter封装了事件的回调基类EventCallback/EventCallbackRef,对于上层使用者来说,注册的事件回调必须是这个基类的派生类,封装这个基类最主要的逻辑是调用do_request接口,因此每个事件回调核心实现都在其do_request里面去实现即可。

结束

EventCenter的设计模型还是比较简单,但是是Async模块实现异步通信的核心,下个章节来介绍Async中间通信模型。

涉及源码:${ceph}/src/msg/async/Event*

2、上层通信模型

在博文Ceph源码分析之Async模块:1、异步通信核心模块EventCenter+Epoll 里面介绍了Async实现异步通信的底层核心模块。这次打算从上层应用来看ceph的通信模型模型。

ceph通信模块的源代码定义在msg目录下,消息类型定义在message目录下面。

概述

ceph各个组件直接的通信以及和客户端直接的通信都依赖与ceph的通信模块,捋清楚通信模块对阅读源代码会有帮助。
先看一张简单的交互图
这里写图片描述

ceph通信模块的角色主要分为Messenger和Dispatcher两大角色

  • Messenger

    消息管理器,对上负责将消息转给Dispatcher和提供发送消息的接口:send_message,对下负责将应用发送的消息转发给Connection(封装的socket连接实例)

  • Dispatcher

    消息分法器,根据消息类型将消息分发给应用层的具体操作接口

其它涉及到的两个重要的角色

  • Message

    消息格式基类,可以根据需求派生出不同类型的消息,不同的消息类型定义在message目录下面,最大的不同就是msg_type(消息类型)

  • Connection

    连接实例,负责维护同客户端建立的socket连接以及ceph的协议栈操作接口(在Async中间层协议栈的时候会深入介绍),向上提供发送消息接口及转发底层消息给DispathcQueue或者消息管理器,向下发送和介绍消息。

模块设计

使用ceph通信模块来收发消息
* 发送消息

发消息比较简单,应用只需将消息内容按照需要的消息类型(定义在messaging/*或自定义)进行封装后调用Messenger的send_message即可。

  •  
  • 接收消息

Messenger是如何将消息转给应用的或者说是如何管理?Messenger设计了两个分发器管理成员:dispatchers和fast_dispatchers,用来处理不同类型的请求处理。应用层则按需求将不同的分发器注册给Messenger,进而Messenger接收到底层来的消息时,会将消息分发给已经注册的两个dispatchers。
设计fast_dispatchers的目的就是为了让有些消息能够省去底层的一层流程(比如跳过入队列),直接到达应用。Dispatcher类是一个基类,里面设计封装了应用同Messenger交互的接口(每个具体的Dispatcher派生类自行去实现更具体的消息处理,一般都是再根据消息类型来分开处理消息)。Dispatcher并不是所有的接口封装都是为了转发消息,它更深层次的含义是提供一个应用层和底层的通信接口,而这个接口的桥梁是Messenger消息管理器。
当底层有消息到来时,Messenger会将消息转给dispatcher对于的ms_*系列的接口,最常用的是ms_dispatch接口,因此你可以看到像monitor,osd这些应用的核心消息处理都在ms_dispatch接口里面实现。

Dispatcher的使用

  • 最简单的方式就是应用本身作为Dispatcher的派生类,如此,Messenger便是直接通过应用关联,比如Monitor、osd、mgr都是应用组件本身作为Dispatcher的派生类。

  • 申请一个Dispatcher的派生类实例,做为应用的模块注册给Messenger,比如RadosClient里面会注册各个Client给Messenger,而这些Client都是Dispatcher的派生类。

消息类型

ceph的消息基类是:Message,Message里面设计了一个type成员,用来区分不同的消息类型,不同的消息模块可以通过type来构造,而这些type定义在Message.h中

技巧:比如你想要看某个消息是谁发的,那么你只需要去查看这个消息类型对应的消息模块有哪些,然后再查到谁在使用这个消息模块来封装消息,进而就可以找到发送这个消息的地方。

例子

以ceph-mon为例子,Monitor类继承自Dispatcher

class Monitor : public Dispatcher,
实现它的ms_dispatcher方法,这个方法里面实现了mon的消息处理
注册给Messenger,add_dispatcher_tail方法就是将当前应用添加到dispatcher列表中

messenger->add_dispatcher_tail(this); 
  •  

Messenger收到消息转给dipatcher

  void ms_deliver_dispatch(Message *m) {
     m->set_dispatch_stamp(ceph_clock_now());
     for (list<Dispatcher*>::iterator p = dispatchers.begin();
          p != dispatchers.end();
          ++p) {
       if ((*p)->ms_dispatch(m))
         return;
     }
     lsubdout(cct, ms, 0) << "ms_deliver_dispatch: unhandled message " << m << " " << *m << " from "
                          << m->get_source_inst() << dendl;
     assert(!cct->_conf->ms_die_on_unhandled_msg);
     m->put();
   }

ceph monitor处理消息

  bool ms_dispatch(Message *m) override {
    lock.Lock();
    _ms_dispatch(m);
    lock.Unlock();
    return true;
  }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值