在现在的网络编程实现中,大多数项目都会采用基于事件通知的异步网络 IO 方式来实现。目前无论是 Epoll 还是 Kqueue 都已经成为主流网络编程知识,本文就不介绍这些基本概念和使用了。主要围绕目前 Ceph 的网络层实现来解构和探讨如何重构。
总体设计在 Ceph 项目伊始的 06 年,那时候在网络编程还是线程模型当道的年头,Ceph 采用了简单粗暴的采用了每两个线程对应一个终端的方式,其中一个用于监听和读取该终端的读事件,另一个用于写事件。我们可以简单称这两个线程为一条连接的读线程和写线程。读线程得到请求以后会解析网络流并开始构建消息,然后派发到后面的 Dispatcher。写线程在大部分时候会处于 Sleep 状态,直到有新的消息需要发送才会被唤醒。
Ceph 在目前的网络层面上有三个重要概念,分别是 Messenger,Pipe,Connection。Messenger 实际上可以理解为一个监听地址和多个连接的集合。比如每个 OSD 中会有 cluster_messenger 和 public_messenger,顾名思义 cluster_messenger 负责给 OSD 与其他 OSD 和 Monitor 的通信并提供了一个监听地址,public_messenger 负责与客户端的通信并提供了一个面向客户端的监听地址。因此 cluster_messenger 中负责的连接会全部是面向其他 OSD 或者 Monitor 的连接。Pipe 实际上是一个 Session 的载体,为了解决网络连接不稳定或者临时闪断连接的问题,Pipe 会一直维护面向一个终端地址的会话状态,如类似 TCP 包序号的消息序号和发送队列。Connection 就是一个 socket 的 wrapper,它从属于某一个 Pipe。
一个会话的逻辑上图主要聚焦 OSD 端的网络逻辑,客户端实际上是一模一样的网络实现只是不会拥有一个监听端口。网络上层的业务实际上只需要关注一个逻辑上的持久会话,通过 Dispatcher 得到消息处理,然后通过 Connection 接口把消息放到发送队列发送。
上面这个图主要描述了一个会话建立的流程,其中 banner 类似于一个宣告,然后互相了解对方的地址信息。主要逻辑在于 connection message 中的信息,服务器端会校验这些连接信息并确保面向这个地址的连接只有一条,如果 client 发送了一个已经建立会话的地址,服务器端会考虑是否需要替换或者废弃当前连接。另外在这里会需要确定是否要发送验证信息(图中省略)。在会话成功后,实际上就对双方当前的 Pipe 状态达成了一致。
线程问题实际上我们从中很容易发现这个线程模型是存在重大问题的,也就是随着一个实体(如 OSD)的增加会线性增加一个实体的线程数目。线程的增加会导致严重的 Context Switch 损耗,线程级的 Context Switch 大概在 us 级别,会影响延迟敏感性应用的性能并且对系统造成资源压力。
因此,在去年 Accelio 提交了基于开源的一个 RPC 库的高性能的 Ceph 网络层实现,另外,作者也在上个月完成了一个基于事件通知的异步 Messenger 实现,主要将之前同步的 SimpleMessenger 改造成状态机并且实现了一个简单的事件管理器。相对于 accelio 的实现,本人的实现希望避免引入一个巨大的 RPC 库造成诸多不便,会大大限制 Ceph 在网络层实现上的范围,也可能会引来第三方库使用的问题。而实际上 Ceph 只需要一个简单的事件管理器即可高效的达到目的。
在 CentOS 7.1 上安装分布式存储系统 Ceph
HOWTO Install Ceph On FC12, FC上安装Ceph分布式文件系统
Ubuntu 12.04 Ceph分布式文件系统
Fedora 14上安装 Ceph 0.24