【Apollo 6.0】服务发现 cyber rt是如何进行节点间管理的

服务发现

【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(顶层逻辑)
【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(底层逻辑)

在上一章中我们分析了底层通信的流程,其中对于服务发现部分我们没有进行分析,这里我们来分析一下。

上一章中对于reader和writer,在init的时候,都会调用JoinTheTopology加入服务的拓扑:

void Reader<MessageT>::JoinTheTopology() {
  // add listener
  change_conn_ = channel_manager_->AddChangeListener(std::bind(
      &Reader<MessageT>::OnChannelChange, this, std::placeholders::_1));

  // get peer writers
  const std::string& channel_name = this->role_attr_.channel_name();
  std::vector<proto::RoleAttributes> writers;
  channel_manager_->GetWritersOfChannel(channel_name, &writers);
  for (auto& writer : writers) {
    receiver_->Enable(writer);
  }
  channel_manager_->Join(this->role_attr_, proto::RoleType::ROLE_READER,
                         message::HasSerializer<MessageT>::value);
}

JoinTheTopology主要就是使用了channel_manager_,那么这个 channel_manager_作为channel的管理器,管理着节点的接入和退出工作,channel_manager_在Reader::Init()中进行了初始化工作

channel_manager_ =
      service_discovery::TopologyManager::Instance()->channel_manager();

channel_manager是在TopologyManager中被创建的,TopologyManager为了网络拓扑的管理单例类,管理着三个相关的拓扑网络,分析TopologyManager:

TopologyManager::TopologyManager()
    : init_(false),
      node_manager_(nullptr),
      channel_manager_(nullptr),
      service_manager_(nullptr),
      participant_(nullptr),
      participant_listener_(nullptr) {
  Init();
}

bool TopologyManager::Init() {
  if (init_.exchange(true)) {
    return true;
  }
  
  node_manager_ = std::make_shared<NodeManager>();
  channel_manager_ = std::make_shared<ChannelManager>();
  service_manager_ = std::make_shared<ServiceManager>();

  CreateParticipant();

  bool result =
      InitNodeManager() && InitChannelManager() && InitServiceManager();
  if (!result) {
    AERROR << "init manager failed.";
    participant_ = nullptr;
    delete participant_listener_;
    participant_listener_ = nullptr;
    node_manager_ = nullptr;
    channel_manager_ = nullptr;
    service_manager_ = nullptr;
    init_.store(false);
    return false;
  }

  return true;
}

可以看到在构造TopologyManager的时候调用了其Init函数,这里我们逐步的来进行解析:

Manager::Manager()
    : is_shutdown_(false),
      is_discovery_started_(false),
      allowed_role_(0),
      change_type_(proto::ChangeType::CHANGE_PARTICIPANT),
      channel_name_(""),
      publisher_(nullptr),
      subscriber_(nullptr),
      listener_(nullptr) {
  host_name_ = common::GlobalData::Instance()->HostName();
  process_id_ = common::GlobalData::Instance()->ProcessId();
}
node_manager_ = std::make_shared<NodeManager>()

NodeManager::NodeManager() {
  allowed_role_ |= 1 << RoleType::ROLE_NODE;
  change_type_ = ChangeType::CHANGE_NODE;
  channel_name_ = "node_change_broadcast";
}
channel_manager_ = std::make_shared<ChannelManager>();

ChannelManager::ChannelManager() {
  allowed_role_ |= 1 << RoleType::ROLE_WRITER;
  allowed_role_ |= 1 << RoleType::ROLE_READER;
  change_type_ = ChangeType::CHANGE_CHANNEL;
  channel_name_ = "channel_change_broadcast";
  exempted_msg_types_.emplace(message::MessageType<message::RawMessage>());
  exempted_msg_types_.emplace(message::MessageType<message::PyMessageWrap>());
}

service_manager_ = std::make_shared<ServiceManager>();

ServiceManager::ServiceManager() {
  allowed_role_ |= 1 << RoleType::ROLE_SERVER;
  allowed_role_ |= 1 << RoleType::ROLE_CLIENT;
  change_type_ = ChangeType::CHANGE_SERVICE;
  channel_name_ = "service_change_broadcast";
}

可以看到上面三个manager在构造的时候只是做简单的设置,下面继续分析CreateParticipant

bool TopologyManager::CreateParticipant() {
  std::string participant_name =
      common::GlobalData::Instance()->HostName() + '+' +
      std::to_string(common::GlobalData::Instance()->ProcessId());
  participant_listener_ = new ParticipantListener(std::bind(
      &TopologyManager::OnParticipantChange, this, std::placeholders::_1));
  participant_ = std::make_shared<transport::Participant>(
      participant_name, 11511, participant_listener_);
  return true;
}

Participant::Participant(const std::string& name, int send_port,
                         eprosima::fastrtps::ParticipantListener* listener)
    : shutdown_(false),
      name_(name),
      send_port_(send_port),
      listener_(listener),
      fastrtps_participant_(nullptr) {}

在CreateParticipant中最重要的就是transport::Participant,他主要就是去创建RTSP服务,cyber的服务发现,就是使用RTSP去实现的。

bool TopologyManager::InitNodeManager() {
  return node_manager_->StartDiscovery(participant_->fastrtps_participant());
}

bool TopologyManager::InitChannelManager() {
  return channel_manager_->StartDiscovery(participant_->fastrtps_participant());
}

bool TopologyManager::InitServiceManager() {
  return service_manager_->StartDiscovery(participant_->fastrtps_participant());
}
eprosima::fastrtps::Participant* Participant::fastrtps_participant() {
  if (shutdown_.load()) {
    return nullptr;
  }

  std::lock_guard<std::mutex> lk(mutex_);
  if (fastrtps_participant_ != nullptr) {
    return fastrtps_participant_;
  }

  CreateFastRtpsParticipant(name_, send_port_, listener_);
  return fastrtps_participant_;
}

bool Manager::StartDiscovery(RtpsParticipant* participant) {
  if (participant == nullptr) {
    return false;
  }
  if (is_discovery_started_.exchange(true)) {
    return true;
  }
  if (!CreatePublisher(participant) || !CreateSubscriber(participant)) {
    AERROR << "create publisher or subscriber failed.";
    StopDiscovery();
    return false;
  }
  return true;
}

通过CreateFastRtpsParticipant进行FastRtps的创建与初始化,之后将会使用该对象进行服务发现,且将会使用环境变量CYBER_DOMAIN_ID作为domain_id,因此如果两台在同个局域网的机器,如果想要进行通信的话,则CYBER_DOMAIN_ID需要一致。

并且在StartDiscovery中创建了Publisher和Subscriber,后面就可以通过这两个进行读写。

当创建完Rtsp之后,那么就要等待其他节点的接入/退出,然后服务发现通知原有节点,那么节点是什么时候接入/退出的呢。

对于服务发现来说,当一个节点接入的时候,需要发送接入的消息,因此我们可以查找Publisher::write的相关代码,进行反向查找,Publisher是在Manager中进行创建的,因此进行查找:

Manager::Join
    Manager::Publish
        publisher_->write

可以看到顶层是Join函数,实现了节点加入的消息通知,在代码中搜索Join(这里使用的是VS CODE的转到引用功能)

在这里插入图片描述

可以看到node_manager、service_manager和channel_manager都调用了父类的Join函数,我们这里分析的是Reader和Writer模式,因为Reader和Writer是由node创建的,因此分析node_manager:

 explicit NodeChannelImpl(const std::string& node_name)
      : is_reality_mode_(true), node_name_(node_name) {
    node_attr_.set_host_name(common::GlobalData::Instance()->HostName());
    node_attr_.set_host_ip(common::GlobalData::Instance()->HostIp());
    node_attr_.set_process_id(common::GlobalData::Instance()->ProcessId());
    node_attr_.set_node_name(node_name);
    uint64_t node_id = common::GlobalData::RegisterNode(node_name);
    node_attr_.set_node_id(node_id);

    is_reality_mode_ = common::GlobalData::Instance()->IsRealityMode();

    if (is_reality_mode_) {
      node_manager_ =
          service_discovery::TopologyManager::Instance()->node_manager();
      node_manager_->Join(node_attr_, RoleType::ROLE_NODE);
    }
  }

可以看到在NodeChannelImpl的构造函数当中会调用node_manager_->Join在加入网络,之后node将会调用NodeChannelImpl::CreateReader来创建Reader,根据上一章的分析,在Reader::Init()会调用JoinTheTopology来加入拓扑

当node和reader被创建的时候都会调用Join进行通知加入了node和reader,因此我们需要分析下Join,看是如何实现的

bool Manager::Join(const RoleAttributes& attr, RoleType role,
                   bool need_publish) {
  if (is_shutdown_.load()) {
    ADEBUG << "the manager has been shut down.";
    return false;
  }
  RETURN_VAL_IF(!((1 << role) & allowed_role_), false);
  RETURN_VAL_IF(!Check(attr), false);
  ChangeMsg msg;
  Convert(attr, role, OperateType::OPT_JOIN, &msg);
  Dispose(msg);
  if (need_publish) {
    return Publish(msg);
  }
  return true;
}

在Join里面最主要的就是Dispose和Publish这个两个函数,Publish不用说了就是将节点加入网络的消息进行发送,因此需要对Dispose进行分析,Dispose在Manager中是一个纯虚函数,其实现都在三个子类当中:

void NodeManager::Dispose(const ChangeMsg& msg) {
  if (msg.operate_type() == OperateType::OPT_JOIN) {
    DisposeJoin(msg);
  } else {
    DisposeLeave(msg);
  }
  Notify(msg);
}

void ChannelManager::Dispose(const ChangeMsg& msg) {
  if (msg.operate_type() == OperateType::OPT_JOIN) {
    DisposeJoin(msg);
  } else {
    DisposeLeave(msg);
  }
  Notify(msg);
}

继续分析DisposeJoin

void NodeManager::DisposeJoin(const ChangeMsg& msg) {
  auto node = std::make_shared<RoleNode>(msg.role_attr(), msg.timestamp());
  uint64_t key = node->attributes().node_id();
  // duplicated node
  if (!nodes_.Add(key, node, false)) {
    RolePtr existing_node;
    if (!nodes_.Search(key, &existing_node)) {
      nodes_.Add(key, node);
      return;
    }

    RolePtr newer_node = existing_node;
    if (node->IsEarlierThan(*newer_node)) {
      nodes_.Add(key, node);
    } else {
      newer_node = node;
    }

    if (newer_node->attributes().process_id() == process_id_ &&
        newer_node->attributes().host_name() == host_name_) {
      AERROR << "this process will be terminated due to duplicated node["
             << node->attributes().node_name()
             << "], please ensure that each node has a unique name.";
      AsyncShutdown();
    }
  }
}

void ChannelManager::DisposeJoin(const ChangeMsg& msg) {
  ScanMessageType(msg);

  Vertice v(msg.role_attr().node_name());
  Edge e;
  e.set_value(msg.role_attr().channel_name());
  if (msg.role_type() == RoleType::ROLE_WRITER) {
    if (msg.role_attr().has_proto_desc() &&
        msg.role_attr().proto_desc() != "") {
      message::ProtobufFactory::Instance()->RegisterMessage(
          msg.role_attr().proto_desc());
    }
    auto role = std::make_shared<RoleWriter>(msg.role_attr(), msg.timestamp());
    node_writers_.Add(role->attributes().node_id(), role);
    channel_writers_.Add(role->attributes().channel_id(), role);
    e.set_src(v);
  } else {
    auto role = std::make_shared<RoleReader>(msg.role_attr(), msg.timestamp());
    node_readers_.Add(role->attributes().node_id(), role);
    channel_readers_.Add(role->attributes().channel_id(), role);
    e.set_dst(v);
  }
  node_graph_.Insert(e);
}

可以看到对于node来说就是根据node_id加入到nodes_当中,这个nodes_底层就是一个Map。而对于channel来说就是根据node_id和channel_id加入到对的map当中,而且还会将channel的关系加入到图node_graph_当中,可以通过node_graph_来知道channel的流向。

在Dispose当中,还能看到调用了Notify这个函数,这个函数是做什么的呢,其实就是发送信号,在Reader::JoinTheTopology()当中调用了channel_manager_->AddChangeListener来添加信号的槽函数,因此通过这个信号将会执行OnChannelChange

...
    
change_conn_ = channel_manager_->AddChangeListener(std::bind(
      &Reader<MessageT>::OnChannelChange, this, std::placeholders::_1))
     
...
    
void Reader<MessageT>::OnChannelChange(const proto::ChangeMsg& change_msg) {
  if (change_msg.role_type() != proto::RoleType::ROLE_WRITER) {
    return;
  }

  auto& writer_attr = change_msg.role_attr();
  if (writer_attr.channel_name() != this->role_attr_.channel_name()) {
    return;
  }

  auto operate_type = change_msg.operate_type();
  if (operate_type == proto::OperateType::OPT_JOIN) {
    receiver_->Enable(writer_attr);
  } else {
    receiver_->Disable(writer_attr);
  }
}

因为是reader自己加入,因此change_msg.role_type()为proto::RoleType::ROLE_READER所以不会执行该函数,但是如果有其他的相同channel的writer加入节点的话,那么将会调用到Enable启动接收器。

当执行完DisposeJoin将会调用Publish发送加入节点的消息,那么我们就可分析下Subscriber。在Manager::StartDiscovery中调用了Manager::CreateSubscriber来创建Subscriber,其中注册了一个回调函数Manager::OnRemoteChange

bool Manager::CreateSubscriber(RtpsParticipant* participant) {
  RtpsSubscriberAttr sub_attr;
  RETURN_VAL_IF(
      !AttributesFiller::FillInSubAttr(
          channel_name_, QosProfileConf::QOS_PROFILE_TOPO_CHANGE, &sub_attr),
      false);
  listener_ = new SubscriberListener(
      std::bind(&Manager::OnRemoteChange, this, std::placeholders::_1));

  subscriber_ = eprosima::fastrtps::Domain::createSubscriber(
      participant, sub_attr, listener_);
  return subscriber_ != nullptr;
}

看下Manager::OnRemoteChange代码就知道整个服务发现的流程是怎么样的了:

void Manager::OnRemoteChange(const std::string& msg_str) {
  if (is_shutdown_.load()) {
    ADEBUG << "the manager has been shut down.";
    return;
  }

  ChangeMsg msg;
  RETURN_IF(!message::ParseFromString(msg_str, &msg));
  if (IsFromSameProcess(msg)) {
    return;
  }
  RETURN_IF(!Check(msg.role_attr()));
  Dispose(msg);
}

当收到节点加入的消息之后,这里又调用了一次Dispose,这样就完成了节点的接入通知

当Publish发送消息的时候,不只是执行了Manager::OnRemoteChange的回调,还记得在TopologyManager::CreateParticipant当中创建ParticipantListener时传入的回调TopologyManager::OnParticipantChange吗,这个回调当Participant有数据传输的时候,也会被调用:

void TopologyManager::OnParticipantChange(const PartInfo& info) {
  ChangeMsg msg;
  if (!Convert(info, &msg)) {
    return;
  }

  if (!init_.load()) {
    return;
  }

  if (msg.operate_type() == OperateType::OPT_LEAVE) {
    auto& host_name = msg.role_attr().host_name();
    int process_id = msg.role_attr().process_id();
    node_manager_->OnTopoModuleLeave(host_name, process_id);
    channel_manager_->OnTopoModuleLeave(host_name, process_id);
    service_manager_->OnTopoModuleLeave(host_name, process_id);
  }
  change_signal_(msg);
}

TopologyManager作为三个manager的管理类,当节点退出网络的时候,将会从三个manager当中删除该节点。

这里还有一个疑问,当后面加入的节点,是怎么知道原有节点的呢?cyber这里使用的是rtsp的qos去做的,当设置了KEEP_ALL_HISTORY_QOS跟TRANSIENT_LOCAL_DURABILITY_QOS时,每个新加入的节点会拿到之前的所有节点

到了这里我们可以总结下服务发现的整个流程:

加入节点:

Node::Node —> NodeChannelImpl::NodeChannelImpl —> Manager::Join —> NodeManager::Dispose —> Manager::Publish —> Manager::OnRemoteChange

Node发送完节点接入信息后:

NodeChannelImpl::CreateReader —> Reader::Init() —> Reader::JoinTheTopology() —> Manager::Join —> ChannelManager::Dispose —> Manager::Publish —> Manager::OnRemoteChange

当其他节点收到Node接入和Channel接入的消息之后,将会创建对应的Node和Chaanel用来进行数据通信

大致的流程就是这样,至此cyber服务发现的流程就解析完毕。

参考链接:
百度Apollo系统学习-Cyber RT 通信-服务发现

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值