Apollo2 + ros1的中间件总结

Ros1

核心通信框架

img
(ps:rpc基于开源的 https://xmlrpc-c.sourceforge.net/ 改的)

  1. Pub进程向Master注册Pub的RpcServer
  2. Sub进程向Master注册Sub的RpcServer
  3. 查看是否存在符合Topic的Sub,符合后立即进行Rpc连接(异步调用Sub进程的publisherUpdate),这个步骤告诉Sub发送这个Topic的Pub进程的RpcServer Uri是多少; 从这步开始也叫服务发现,只不过依赖Master
  4. Sub端通过上面得到的Pub的uri主动发起requestTopic异步函数调用
  5. Pub端告诉Sub,Tcp传输Server的ip(那个listen的)
  6. 常规的socket通信了,Sub主动connect,从而建立通信
  7. Pub通过socket write开始传输数据

缺点分析

  1. 任何新的Sub和Pub都依赖Master来注册Rpc的Uri,一旦Master没了就什么都没了 ---> 备份Master/不依赖Master
  2. 多次拷贝的通信
    上面可以知道本质传递消息走的就是socket通信,那么当一个节点被多个节点订阅,这个传输时间是线性增长的!有几个sub就要拷贝几次
    img
    优化方式:组播,Sub端只需要拷贝一次
  3. 多次拷贝
    Pub --》Serialize --》Copy TcpBuffer
    Sub --》 Copy TcpBuffer --》Deserialize

Apollo1的改进

  1. 加入FastRtps库,替换调Master,我这边直接给出最大差别:
    img
    内部核心还是差不多,但是这个前期Rpc的ip地址发现,改用rtps库来拓扑发现,用百度的图来看下组合:
    img
    再次强调,这个步骤不是具体的数据传输,只是封装了Topic、DataType让其他节点知道这个的存在,然后继续和Ros1一样走RPC的远程调用连接
  2. 使用自定义共享内存来IPC传输(你问为什么不直接用fastrtps的?这个时候fastrtps还不支持Shm,百度就自己搞了一个)
    核心技术为:Boost的Shared Memory(https://theboostcpplibraries.com/boost.interprocess-shared-memory)
    百度基于这个自定义整套的结构,大体共享内存组成为:
    img
    对象的创建就是在内存上,平时我们可能在堆/栈,对于Shm也是一片内存,同样可以直接在这个内存上创建对象!
    这里可以看见Block是是实际传输的最小节点,每一个Block都有一把锁,这也同时减少了锁的颗粒度,不需要一把锁控制整个Segment

缺点:Apollo的Shm实现可能存在跳帧,因为每次读端的Read取的index都是最新的Block_index
看一下整体给出的对比(借用百度的图):
img
说明:依然存在序列化和反序列化,除了这个外没有拷贝。也就是说这种方式不是零拷贝,但是我认为优于普通的Shm,因为一般使用Shm都是先把数据拷贝到共享内存,再进行拷出,只是为了不走内核

  1. Protobuffer替换msg
    这个就是纯纯的技术替换了,考虑数据的兼容性直接引入了这个
    额外想说的是这套框架上很快能加入Protobuffer,原因是对于代码中对于Write的抽象:
    img
    这样对于Protobuffer只需要额外写一个Wrapper就可以了

数据的接收

习惯了Ros的接收方式的同学,再用其他中间件可能会有习惯,主要就是因为Ros目前是支持异步轮训多次和一次的原因,AutoSarap Cm文档也说过好的中间件应该是支持异步会调(只在数据更新的时候触发)和同步轮询(类似于自旋,不让Cpu进行上下文切换)
img
这个时候仅仅是把数据放到全局queue中
如果客户端主动进行一次spin就会将已经存在的数据都进行触发回调
img

提醒:

  1. 默认都是一个GlobaQueue,如果接收多种消息,A消息的回调慢可能对B消息丢帧和及时性有影响 --》可以考虑自定义Queue
  2. Pub接收数据和回调用的锁是一把锁,回调做了超过队列 * 帧率的事情就会导致丢帧 --> 比如GAC目前PC的解码程序

调测工具 : hz echo

  1. 这个工具的最大难点就是怎么通过Topic + DataType来反射到具体类型
  2. ros通过本地环境 + python语法糖 :
    msg通过生成工具会生成python的代码
    然后使用python的__import__('%s.%s' % (package, type_str))导入对应py模块
    然后getattr(getattr(pypkg, type_str), base_type)就可以获得字符串对应的类了
  3. Apollo怎么做的?
    Protobuffer直接支持反射,可以通过proto进行反射出类型
  4. senseloto怎么做的?
    使用Idl包裹 + 动态库加载方式,非常巧妙和适合
  5. mdc怎么做的?
    以上的方式几乎都依赖于对应消息的so,对于华为,自定义了Msg到类的动态加载方式,可以仅仅替换Idl就可以直接序列化对应的消息
    唯一的小缺点:需要作为底软来考虑动态Idl->类的统一方式,否则会有数据不对齐风险
posted @ 2022-10-27 11:00  make_wheels  阅读(571)  评论(2编辑  收藏  举报