之前的文章内容应该要有修订,但是并没有更新到blog里,而是直接写在了libatbus的文档里
目录
前言
好久没写总结啦,最近一段时间比较忙,抽出的空闲时间都在不断完善之前提到的一个进程间通信lib的想法和实现(libatbus)。
因为这个想法从提出来后实现了共享内存通信的实现后,一直没抽出空来继续后面的内容。而且做得过程中发现,这比之前想象的还是要复杂一些,一个人的空闲时间很难做到非常的完善,只能先有个实现,以后再一点点地改善。毕竟人家TX两个人全职做了两年才能做到一个比较完整的解决方案,而且还不跨平台。我这个虽然有一些非核心的部分使用开源组件,能少很多工作量,但是要做到跨平台并且只是业余时间搞的话还是得慢慢来。
这段时间的完善后,所有最初预想的通信方式都已经实现。包括内存,共享内存,tcp/ipv4,tcp/ipv6,tcp/dns,unix sock。这几种方式。并且为以后可能的一些通信方式做了少量预留。同时完成了同时支持使用自定义的简单环形队列缓冲区(省去复杂内存块管理的CPU消耗,内存换CPU)和使用动态缓冲区(mallo/free)。之所以不实现复杂的内存管理是由于tcmalloc和jemalloc已经足够优秀。如果需要复杂高效的动态内存管理,用这两个东西替换掉ptmalloc或其他系统自带的内存管理器就已经足够高效。
更总要的是,这段时间我还对目前已实现的功能都做了比较完整的单元测试。并且我也对自己的单元测试的框架做了少量优化。在单元测试的过程中确实能发现很多低级的细节问题,特别是对重构数据结构和一些流程细节的帮助非常大。另外由于使用的libuv在Windows下只支持MSVC,而且目前最新版本Windows下的pipe类型通信不能正常工作,所以我关闭了Windows版本下的unix sock类型的单元测试。
剩下的最重要的就是实现节点关系相关的逻辑代码了。
节点关系的初步想法
本来想直接开写得,但是实现过程中发现有点混乱。所以还是需要整理并理清下流程和思路。本文最重要的是帮自己理清思路,所以列一个提纲过来,会实时补充(也会优先补在libatbus的文档里),并且语言必定不会严谨。大纲如下:
协议规划
- 数据转发协议请求
- 如果目标自身直接接收,返回成功否则路由信息+1,进入后续流程
- ttl+1,判定跳数过多则返回失败
- 如果目标节点子节点子域
- 不存在连接完成返回错误
- 直接转发子节点
- 如果发送方是子节点,允许子节点直连,选取最优通道通知建立子节点直连通道
- 如果目标是兄弟节点或兄弟节点子域
- 如果直连通道连接建立完成,直接转发
- 否则发给父节点
- 其他情况发给父节点或出错
- 数据转发协议响应
- 反向发回,忽视错误
- 注册协议请求(握手阶段)
- 附带自身pid,监听信息,机器标识
- 注册协议回包(握手阶段)
- 成功则连接加入endpoint
- 错误则移除连接
- 如果错误码ID冲突则node下线
- 节点同步协议
- 定时拉取
- 新连接协议
- 指定子节点A,连接子节点B
- Ping协议请求
- Ping测试次数+1,如果超过容错则节点下线
- Ping协议回包
- 重置ping测试次数0
- 记录延迟
接口和结构规划
- atbus节点(node)
- 状态
- 未初始化
- 初始化完成
- 丢失父节点
- 正在注册到父节点
- 注册完成
- 正在关闭
- API:获取进程ID: getpid
- API:获取主机名: gethostname
- 配置:
- 节点逻辑:ttl
- 节点逻辑:允许子节点直连
- 节点逻辑:是否需要全局路由表(控制是否发送节点同步协议)
- 节点逻辑:消息循环次数限制(防止单一通道繁忙导致其他通道饥饿)
- 节点逻辑:事件管理对象(ev_loop)
- 节点逻辑:子节点掩码
- 网络:backlog
- 网络:第一次发包时间限制
- 节点逻辑:Ping包间隔
- 节点逻辑:错误容忍次数(超出次数视为下线)
- 网络:重试间隔(父节点断线重连间隔)
- 缓冲区:消息体大小
- 缓冲区:内存通道的接收缓冲区
- 缓冲区:每个连接的发送缓冲区
- 缓冲区:静态发送缓冲区的消息个数限制
- API:查找匹配子域节点(map,记录子域,upper_bound)
- API:判定节点间最优通路
- 定时器
- connection超时下线
- 父节点重连
- Ping
- 同步协议
- 回调函数
- 接收到消息
- 错误处理
- 完成注册
- 节点下线
- 节点上线(所有连接进入完成状态)
- 非法连接
- API:监听地址
- 可同时监听点对点IO和共享通道
- 初始接受的连接为命令通道,接收到注册请求后主动发起的连接为数据通道
- API:连接目标
- 连接内存和共享内存通道必须指定正确的目标,因为这些通道是共享的,不存在握手阶段
- 初始发起的连接为命令通道,连上后发起注册协议。收到注册回包后重新发起的连接为数据通道
- 这里建议配置上初始连接走点对点IO流通信
- IO流通信即连接协议为ipv4,ipv6,dns或unix
- 命令变化不多,性能要求相对较低
- 如果初始通道是内存或共享内存通道,可能导致命令通道和数据通道是同一个
- 未完成的连接池(用于防止重复连接和重复发送握手包)
- 主动连接的address=对端监听地址,被动接受的address=对端发起地址
- 状态
- 端点endpoint
- id
- child_mask
- 记录所属node
- 判定是否子域
- add_connection
- 包含connection集合,区分控制命令connection和数据connection
- 销毁释放所有connection
- 选取发送通道(数据转发协议的流程)
- Connection
- address
- 状态:
- 未连接
- 正在连接(内存通道或者共享内存通道没有这个状态)
- 正在握手(检测双方node的id)
- 正在运行
- 各类connection的发送接口,proc接口,free接口
- 记录所属node
- 记录所属endpoint
- 连接断开、连接失败接口
- 如果是父节点加入重试等待队列
- 否则清理节点信息
- 关闭时如果endpoint没有可用控制命令connection或数据connection,endpoint下线(防止部分通道断开,然后数据通道被用作命令通道) 统一发送接口
目前这样的设计中有一个最重要的部分是连接和握手的流程,纪要考虑子节点和父节点之间自动连接的流程和兄弟节点间由父节点通知而自动连接的过程,也要考虑手动连接兄弟节点的流程;然后连接可能会在多台物理机上的问题;还有连接部分丢失的问题。
按照推荐的标准流程的话,如果节点之间如果只用点对点IO流,那么命令通道和数据通道会分别有一个。但是只用共享内存的话,只会有一个通道。混用的情况就更复杂了,不再详细说明。
如果上述设计有问题的话,还需要另外在修订
Written with StackEdit.