【jsonRpc项目】Registry-Discovery模块

目录

一.功能设计

二.服务中心代码编写

2.1.服务注册者管理器

2.2.服务发现者管理器

[2.3.综合管理器 - 整合注册者管理模块和发现者管理模块](#2.3.综合管理器 - 整合注册者管理模块和发现者管理模块)

2.4.封装服务中心类

三.服务注册和服务发现的客户端代码实现

3.1.服务注册者

3.2.封装服务注册客户端

3.3.服务发现者

3.4.封装服务发现客户端


为什么需要这个服务注册-发现模块?

事实上,我们的这个项目是JSON-RPC项目,旨在实现远程调用另一台机器上的函数方法。那么一个核心问题是:RPC服务调用方如何知道哪里有可用的RPC服务?

在传统的RPC架构中,服务调用方通常需要**硬编码(也就是写死IP地址和端口号)**服务提供方的地址信息,这种方式存在诸多问题:

  1. 地址信息静态化:当服务提供方IP地址或端口发生变化时,所有调用方都需要修改配置并重启应用

  2. 负载均衡困难:当多个节点提供相同服务时,调用方难以实现动态负载均衡

  3. 服务健康状态未知:调用方无法感知服务提供方的实时可用性,可能导致请求发送到已下线的服务

  4. 扩展性差:新增服务节点时,需要手动通知所有调用方更新配置

服务注册-发现模块解决了这些问题

核心作用

1. 动态服务发现

  • 调用方无需硬编码服务地址,只需知道服务名称即可找到可用服务

  • 服务提供方的变更对调用方透明,实现了服务地址的解耦

2. 服务健康管理

  • 通过长连接实时监控服务提供者的在线状态

  • 自动识别服务上下线,确保调用方只访问可用的服务

3. 负载均衡支持

  • 一个服务可由多个提供者同时提供

  • 调用方可根据策略(如轮询)选择不同的提供者,实现请求分流

4. 服务动态扩展

  • 新增服务节点只需向注册中心注册,即可被所有调用方发现

  • 无需重启或修改调用方代码,支持无缝扩容

一.功能设计

首先,我们之前的情况是跟下面这张图是一样的

那么引入服务注册和发现模块,我们的RPC调用就变成了下面这样子

首先,我们需要明白下面几件事

  • 一个RPC服务端都会内置有一个服务注册客户端,它们是相互绑定的,是一一对应的。
  • 一个RPC客户端也都内置有一个服务发现客户端,它们是相互绑定的,是一一对应的。
  • 一个RPC服务端不会只提供一种RPC服务
  • 一个RPC客户端也不会只调用一种RPC服务

我们发现,这个服务注册和服务发现模块加入的3个新角色:

  1. 服务注册者客户端
  2. 服务提供者客户端
  3. 服务中心

它们协同工作,实现服务注册与发现的完整流程。

服务注册者客户端 的核心功能是:若某服务节点需要对外提供RPC服务,就通过服务提供者来向注册中心注册服务信息,主要包括:

  • RPC服务名称

  • 与这个服务注册者客户端关联的那个RPC服务器的IP地址+端口号

注册成功后,其他服务发现者即可查询到该服务节点的信息。

服务发现者客户端 的主要职责是:当某个RPC调用方需要调用特定RPC服务时,会先通过服务发现者向服务中心查询提供该服务的主机信息,并返回对应的IP地址和端口号。调用方依据这些信息发起远程调用。

**服务中心:**作为整个服务注册与发现系统的核心协调者,扮演着"服务信息交易所"的角色。它负责维护所有注册服务的元数据,并处理来自服务提供者和发现者的请求。

具体而言:

  • 服务注册者客户端需先与服务中心建立连接,随后发起服务注册请求;

  • 服务发现者客户端同样需先连接服务中心,再发起服务发现请求。

  • 服务中心统一接收并响应这两类请求,确保服务信息的准确同步与高效查询。

需要说明的是:

  • 服务注册者客户端本身不提供RPC服务,仅负责完成服务信息的注册;

  • 服务发现者客户端并不直接发起RPC调用,它只负责从注册中心获取支持某服务的主机列表。

这样的架构实现了服务治理中关注点分离,提升了系统的可维护性与扩展性。

实现思想

首先,我们需要明白下面几件事

  • 一个RPC服务端内置有一个服务注册客户端,它们是相互绑定的,是一一对应的。
  • 一个RPC客户端内置有一个服务发现客户端,它们是相互绑定的,是一一对应的。
  • 一个RPC服务端不会只提供一种RPC服务
  • 一个RPC客户端也不会只调用一种RPC服务

换句话说:

如果说一个RPC服务端需要对外提供RPC服务,那么就需要借助它内置的这个服务注册客户端,将这个RPC服务端的IP地址+端口号 ,提供的RPC方法名称 传递给这个服务注册客户端,服务注册客户端与服务中心建立起通信连接,服务注册客户端就向服务中心里面发起服务注册请求,服务中心就对此进行处理。

如果说一个RPC客户端需要调用某种RPC服务,那么就需要借助它内置的这个服务发现客户端,将需要调用的RPC服务名称,服务发现客户端就与服务中心建立通信连接,向服务中心发送服务发现请求,服务中心向服务发现客户端返回能够提供这种RPC服务的所有RPC服务端的IP地址+端口号,然后服务发现客户端采用RR轮转的思想返回其中的一个RPC服务端的IP地址+端口号给这个RPC客户端,RPC客户端再去通过这个IP地址+端口号向对应RPC服务端发送RPC调用请求,RPC服务端再作出响应。

我们来详细说说

在一个完整的RPC(远程过程调用)分布式架构中,服务注册中心 是协调服务提供方与服务消费方的核心组件。其工作流程可以清晰地分为两个主要部分:服务注册(上线)服务发现与调用(寻址与通信)

第一部分:服务注册(服务端如何对外暴露服务)

  1. 服务端启动与信息准备

    • 当一个RPC服务端应用启动后,它首先会加载配置,明确自身需要对外提供的RPC服务名称 以及具体的方法列表

    • 同时,服务端知晓自身监听的网络地址(也就是对外提供RPC服务的地址) ,即 IP地址端口号

  2. 发起注册请求

    • RPC服务端内置服务注册客户端 会主动与远端的 服务中心建立网络连接。

    • 连接建立后,服务注册客户端会将一个完整的 服务注册信息 封装成请求发送给服务中心。这个信息包通常包含:

      • 需要注册的服务名称

      • 与这个服务注册客户端相关联的RPC服务器的IP地址+端口号

      • 其他元数据:可选的附加信息,如版本号、权重、健康检查路径等。

  3. 中心处理与登记

    • 服务中心接收到注册请求 后,会进行校验(如格式、权限),然后将该 服务实例 的信息存储到其内部的注册表中。

    • 注册表本质上是一个映射关系数据库,其结构类似于:

      cpp 复制代码
      服务名称 -> 多个可用的服务实例地址列表
    • 注册成功后,服务中心通常会向服务端返回一个成功的响应。

  4. 维持心跳与健康检查

    • 为确保注册信息的实时性,服务注册客户端会周期性地向服务中心发送心跳或响应其健康检查探针。

    • 如果服务中心在预定时间内未收到某个实例的心跳,则会判定该实例不健康已下线 ,并将其从对应服务的可用实例列表中移除,从而防止客户端调用到故障节点。

第二部分:服务发现与调用(客户端如何找到并调用服务)

  1. 客户端发起调用意图

    • 当一个RPC客户端需要调用某个RPC远程服务时,它并不事先知道该由哪个具体的服务器来处理。
  2. 查询可用服务列表

    • RPC客户端内置的 服务发现客户端 会向 服务中心 发起查询。它向服务中心发送一个服务发现请求 ,核心内容是目标 服务名称

    • 服务中心在收到请求后,查询内部的注册表,将当前所有注册为该名称且状态为健康的 服务实例地址列表 (例如 [192.168.1.10:8080, 192.168.1.11:8080])返回给发现客户端。

  3. 负载均衡选择实例

    • 服务发现客户端获取到可用实例列表后,并不会直接将整个列表交给业务代码,而是会内置负载均衡策略,从列表中选出一个具体的实例。

    • 轮询 是其中一种常用策略,它按顺序依次选择列表中的实例,以达到均匀分摊请求流量的目的。其他策略还包括随机、加权轮询、基于响应时间的选择等。

    • 选择完成后,服务发现客户端将最终确定的 单个服务实例地址(IP:Port) 返回给RPC客户端的调用器。

  4. 建立连接与发起调用

    • RPC客户端调用器获得目标地址后,会与对应的RPC服务端建立独立的网络连接。

    • 接着,它将本次调用的方法名、参数等数据序列化成网络字节流,通过该连接发送至服务端。

  5. 服务端处理并返回

    • 服务端收到请求后,反序列化数据,通过本地调用执行对应的业务方法,并将执行结果序列化后,通过网络返回给客户端。

    • 客户端接收到响应后,反序列化得到结果,完成一次完整的RPC调用。


接下来来了解一些概念

服务下线

  • 如果说某个服务注册者客户端掉线了,也就是说服务提供者和注册中心的通信连接断开了,那就说明与这个服务注册者客户端相关联的那个RPC服务不会再提供相关RPC服务了,那么我们的服务中心就能通过通信连接来知道是哪个服务提供者下线了
  • 那么我们的服务中心就会根据自己的内部的哈希表(记录了每个服务注册者相关联的RPC服务端注册了什么服务),得知哪些服务下线了。
  • 然后就将这些已经下线的服务记录从自己的哈希表(记录了每个服务注册者相关联的RPC服务端注册了什么服务)里面删除掉,并且去通过服务中心和服务发现者客户端的通信连接去告诉服务发现者客户端,然后服务发现者就更新自己内部缓存的数据(每种RPC服务有哪些RPC服务端提供),将对应的RPC服务端的信息删除掉。

服务上线

  • 如果有另外一个服务注册者客户端往这个服务中心注册了某种服务,
  • 那么我们的服务中心得知哪些服务上线了,然后就将服务提供者所注册的方法和RPC服务器的信息啥的插入自己的哈希表里面,并且去通过服务中心和服务发现者客户端的通信连接去告诉我们的服务发现者客户端哪些服务上线了。
  • 我们的服务发现客户端就更新自己内部维护的缓存信息

1.为什么要服务注册,服务注册是要做什么

  • 服务注册主要是实现分布式的系统,让系统更加健壮。
  • 一个节点主机,将自己所能提供的服务,在服务中心进行登记

2.为什么要服务发现,服务发现是要做什么

  • rpc调用者需要知道哪个节点主机能够为自己提供指定的服务
  • 服务发现其实就是询问服务中心,谁能为自己提供指定的服务,将节点信息给保存起来以待后用

3.服务下线

  • 当前使用长连接进行服务主机是否在线的判断,
  • 一旦服务注册者客户端和服务中心断开连接,服务中心就查询这个服务注册者注册了哪些服务,分析哪些RPC调用者进行过这些服务发现,则对这些RPC调用者进行服务下线通知

4.服务上线

  • 因为服务发现是一锤子买卖(调用方不会进行二次服务发现),因此一旦中途有新的主机可以提供指定的服务,调用方是不知道的。
  • 因此,一旦某个服务上线了,则对发现过这个服务的服务发现者客户端进行一次服务上线通知

现在要做的事情:

  • 1.将服务注册,发现功能集合到客户端中
  • 2.将服务信息管理集合到服务端中

服务端需要如何实现服务信息的管理:

服务端要能够提供服务注册,发现的请求业务处理

  • 1.需要将 哪个服务 能够由 哪些主机提供管理起来 hash<method,vector<provider>>,实现当由caller进行服务发现的时候,告诉caller谁能提供指定的服务
  • 2.需要将 哪个主机 发现过 哪些服务管理起来 hash<method,vector<discoverer>>:当进行服务通知的时候,都是根据谁发现过这个服务,才会给谁通知
  • 3.需要将哪个 连接 对应哪个 服务提供者 管理起来 hash<conn,provider>,当一个连接断开的时候,能够知道哪个主机的哪些服务下线了,然后才能给发现者通知xxx的xxx服务下线了
  • 4.需要将哪个 连接 对应哪个 服务发现者管理起来 hash<conn,discoverer> ,当一个连接断开的时候,如果有服务上线下线,就不需要给他进行通知了。

注意上面的provider和discoverer其实是一个复合的数据结构

provider

  • 1.提供者的主机信息(IP地址和端口号)
  • 2.提供的服务名称(实际上也是有很多个服务)

discoverer:

  • 1.客户端连接(表示一个发现者)
  • 2.发现过的服务名称(实际上也是有很多个服务)

客户端:

客户端的功能比较分离,服务注册端跟服务发现端根本就不在同一个主机上,因此客户端的注册与发现功能是完全分离的

1.作为服务提供者

需要一个能够进行服务注册的接口,用于连接注册中心,进行服务注册

2.作为服务发现者

  • 需要一个能够进行服务发现的接口,需要将获取到的能够提供指定服务的主机信息管理起来 hash<method,vector<host>> 一次发现,多次使用,没有的话再次进行发现。
  • 需要进行服务上线/下线通知请求的处理(需要向dispatcher提供一个请求处理的回调函数)

host是一个复合结构,里面的信息是

  • 主机IP
  • 端口号

二.服务中心代码编写

说实话,我们需要搞清楚一件事情,注册中心,服务提供者,服务发现者的关系:

  • 服务中心是一个服务器
  • 服务注册者客户端和服务发现者客户端都相当于一个客户端,分别连接着我们的这个注册中心

我们现在写的代码就是服务中心里面的东西

因为这个服务注册者和服务发现者都是不止一个的。

我们需要在注册中心里面管理好连接上注册中心的那些服务注册者客户端和服务发现者客户端,能够处理好它们发送过来的请求:

  • 服务注册者客户端------服务注册请求
  • 服务发现者客户端------服务发现请求

在我们的这个注册中心,需要能识别出来这两种请求,并进行处理。

当然了,如果我们的服务发现者和服务提供者下线了,我们的注册中心也需要能够作出相应的处理。具体处理我们看下面。

那么我们就来看看我们的注册中心到底怎么进行设计吧!!

2.1.服务注册者管理器

首先,我们都说这是一个管理器,那么我们管理的这个服务注册者到底是什么样子?我们还没有定义出来吧!!

cpp 复制代码
// 服务注册者结构体
            struct Registrar
            {
                using ptr = std::shared_ptr<Registrar>;
                std::mutex _mutex;                // 方法列表的互斥锁
                BaseConnection::ptr conn;         // 服务注册者客户端的连接
                Address host;                     // 需要注册的服务的IP地址+端口号(本质也就是这个RPC服务端程序所在位置)
                std::vector<std::string> methods; // 注册的服务方法名称列表(一个注册者可以注册多次服务)
                //这里同一个 IP地址+端口 可以提供多种RPC服务,所以使用了vector来保存注册的方法
                //我们这里设计的是 一个IP地址+端口 可以运行一个RPC服务端程序,但是这个RPC服务端程序里面不会只提供一种RPC服务

                Registrar(const BaseConnection::ptr &c, const Address &h) : conn(c), host(h) {}

                // 添加服务方法到服务注册者的方法列表
                void appendMethod(const std::string &method)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    methods.emplace_back(method);
                }
            };

这个服务注册者类里面的数据结构很多

  • 服务注册者客户端与服务中心的通信连接
  • 与这个服务注册者关联的那个RPC服务器的IP地址和端口号
  • 服务注册者客户端在服务中心注册的RPC服务名

由于我们的服务注册者不止一个,所以我们需要将这些注册者管理起来。

那么我们就来编写这个服务注册者管理器

首先我们这个服务注册者管理器的核心其实是2个数据结构

  1. 通信连接 -> 注册者
  2. 服务方法 -> 注册者集合

那么我们维护这两个数据结构的意义是什么呢?

  • 维护 "通信连接 -> 注册者" 的映射。当服务注册者客户端和服务中心断开连接时,服务中心能通过通信连接来这个数据结构里面知晓其下线是哪一个注册者。

  • 维护 "服务方法 -> 注册者集合" 的映射。这个数据结构其实是判断我们服务器能不能提供这个服务的核心。如果说服务方法里面的注册者集合为空,就说明我们不能提供这个服务。如果有这个服务,我们就通过RR轮转思想来返回指定RPC服务器的地址信息。

  • 当服务注册者客户端往注册中心里面注册了一个服务,那么这个服务名称和他所对应的那个RPC服务器的地址信息 就会被存放到 "服务方法 -> 注册者集合" 里面,并且这个服务提供者的连接和它的服务提供者结构体也会被存放到**"通信连接 -> 注册者"**里面

  • 当注册中心知道是哪一个服务注册者客户端下线了,那么就能通过这个服务注册者结构体里面的methods;成员变量来知道这个注册者注册了哪些服务方法,然后我们就遍历这些服务方法,然后我们就可以通过这个 "服务方法 -> 注册者集合" 里面删除掉对应的注册者。

cpp 复制代码
// 服务提供者管理器 - 负责管理所有服务提供者
        class ProviderManager
        {
......
        private:
            std::mutex _mutex; // 管理器互斥锁
            // 服务方法 -> 注册者集合的映射
            //注意这里使用了set类型,就不会出现同一个服务会重复记录两个相同的注册者的情况
            std::unordered_map<std::string, std::set<Provider::ptr>> _providers;
            // 连接 -> 注册者的映射
            std::unordered_map<BaseConnection::ptr, Provider::ptr> _conns;
        };
  1. addRegistrar 函数

当有一个新的服务注册者与我们的服务中心进行通信连接的建立,我们就需要借助这个函数来对这个服务注册者进行信息的管理。

功能

事实上,这个函数的功能就是完成下面这四种服务

  • 新服务注册者注册新服务
  • 新服务注册者注册已经存在的服务
  • 已经存在的服务注册者注册新服务
  • 已经存在的服务注册者注册已经存在的服务(注意,这里有一种特殊情况)

注意这个特殊情况:我们这里调用这个函数时可能会出现,服务注册者之前注册了服务A,现在又去注册服务A的情况。

但是不要慌张,因为我们的 服务方法-提供者 映射里面,这个提供者集合我们是使用了set类型

cpp 复制代码
// 添加新的服务注册者,这个服务注册者注册的服务名叫method
            //或为已有服务注册者添加新服务method
            //调用时机:有新的服务注册者和这个服务中心建立的连接时,我们就会调用这个
            void addRegistrar(const BaseConnection::ptr &c, const Address &h, const std::string &method)
            {
                //传递进来一个通信连接,RPC服务端的地址信息(IP地址+端口号),注册的服务名称

                Registrar::ptr Registrar;//创建一个服务注册者对象

                // 查找连接是否已经关联了服务注册者
                {
                    std::unique_lock<std::mutex> lock(_mutex);//加锁
                    auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接里面是不是有对应的服务注册者
                    //因为这个注册者可能之前已经注册过什么服务了
                    if (it != _conns.end())//找到了,说明这个服务注册者是已经存在的服务注册者
                    {
                        // 如果已经存在,则获取已有的服务注册者对象
                        Registrar = it->second;
                    }
                    else//没找到,说明这个服务注册者是新的服务注册者,那么我们就需要在 通信连接-服务注册者 映射里面插入新的键值对
                    {
                        // 如果不存在,创建新的服务注册者对象并建立关联
                        Registrar = std::make_shared<Registrar>(c, h);
                        _conns.insert(std::make_pair(c, Registrar));//在 通信连接-服务注册者 映射里面插入对应通信连接,服务注册者
                    }

                    //我们可能会出现,服务注册者之前注册了服务A,现在又去注册服务A的情况,那岂不是会出现重复的情况吗?
                    //别慌,因为我们的 服务方法-服务注册者 映射里面,这个服务注册者集合我们是使用了set类型,这样子同一个服务方法就不会出现两个相同的服务注册者

                    // 在服务方法到服务注册者的映射中,为该服务方法添加服务注册者
                    auto &Registrars = _Registrars[method];//服务方法-服务注册者集合,注意这里是方法method的服务注册者集合
                    Registrars.insert(Registrar);//往method方法的服务注册者集合里面添加一个新的服务注册者
                }

                //我们可能会出现,服务注册者之前注册了服务A,现在又去注册服务A的情况,那岂不是会出现重复的情况吗?

                // 在服务注册者对象中添加该服务方法method
                Registrar->appendMethod(method);
            }
  1. getRegistrar 函数

当服务注册者客户端和服务中心断开连接时,注册中心能立即通过 "通信连接 -> 注册者" 的映射知晓其下线是哪一个注册者。进而作出更多处理操作

cpp 复制代码
// 通过连接获取服务注册者信息 - 用于连接断开时的清理
            Registrar::ptr getRegistrar(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接c里面是不是有对应的服务注册者
                if (it != _conns.end())//找到了,直接返回对应的服务注册者
                {
                    return it->second;
                }
                return Registrar::ptr(); // 返回空指针
            }
  1. deldelRegistrar 函数

说实话,我们这个函数就是为了完成下面这些事情

当服务中心知道是哪一个提供者下线了,那么就能通过这个服务提供者结构体里面的methods成员变量来知道这个注册者注册了哪些服务方法,然后我们就遍历这些服务方法,然后我们就可以通过这个 "服务方法 -> 注册者集合" 里面删除掉对应的注册者信息。

cpp 复制代码
// 删除服务注册者 - 当服务注册者断开连接时调用
            void delRegistrar(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接c里面是不是有对应的服务注册者
                if (it == _conns.end())//没找到
                {
                    // 当前断开连接的不是一个服务注册者
                    return;
                }

                //找到了对应的服务注册者

                // 遍历该服务注册者注册的所有服务方法
                for (auto &method : it->second->methods)
                {
                    // 从每个服务方法的服务注册者列表中移除当前服务注册者
                    auto &Registrars = _Registrars[method];
                    Registrars.erase(it->second);
                }

                // 删除连接与服务注册者的关联关系
                _conns.erase(it);//在 通信连接-服务注册者 映射里面删除对应通信连接c的服务注册者
            }
  1. methodHosts 函数

功能:获取指定服务方法的所有RPC服务器的IP地址+端口号

事实上,这个函数的目的就是

当服务发现者客户端往服务中心发送服务发现请求过来了,服务中心就需要返回能提供该RPC服务的RPC服务器的IP地址+端口号给服务发现者客户端,然后服务发现者再根据RR轮转思想,返回其中的一个RPC服务器的IP地址+端口号给RPC客户端去调用服务提供者提供的服务。

那么返回能提供该RPC服务的RPC服务器的IP地址+端口号就是由这个函数来做的。

cpp 复制代码
//获取指定服务方法的所有RPC服务器的IP地址+端口号
            std::vector<Address> methodHosts(const std::string &method)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _Registrars.find(method);//在 服务方法 -> 服务注册者集合 映射里面根据对应服务方法 寻找里面是不是有对应的服务注册者集合
                if (it == _Registrars.end())//没找到对应的服务注册者集合
                {
                    return std::vector<Address>(); // 没有服务注册者,返回空列表
                }

                //找到了对应的服务注册者集合
                //遍历集合中所有服务注册者
                std::vector<Address> result;
                for (auto &Registrar : it->second)
                {
                    result.push_back(Registrar->host);//将集合中所有服务注册者的IP地址+端口号填充到返回值里面去
                }
                return result;
            }

服务注册者管理器完整代码

cpp 复制代码
// 服务注册者管理器 - 负责管理所有服务注册者客户端
        class RegistrarManager
        {
        public:
            using ptr = std::shared_ptr<RegistrarManager>;

            // 服务注册者结构体
            struct Registrar
            {
                using ptr = std::shared_ptr<Registrar>;
                std::mutex _mutex;                // 方法列表的互斥锁
                BaseConnection::ptr conn;         // 服务注册者客户端的连接
                Address host;                     // 与这个服务注册者相关联的那个RPC服务器程序的IP地址+端口号
                std::vector<std::string> methods; // 注册的服务方法名称列表(一个注册者可以注册多次服务,我们都需要进行保存)
                //这里一个 RPC服务端 可以提供多种RPC服务,所以使用了vector来保存注册的方法

                Registrar(const BaseConnection::ptr &c, const Address &h) : conn(c), host(h) {}

                // 添加服务方法到服务注册者的方法列表
                void appendMethod(const std::string &method)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    methods.emplace_back(method);
                }
            };

            // 添加新的服务注册者,这个服务注册者注册的服务名叫method
            //或为已有服务注册者添加新服务method
            //调用时机:有新的服务注册者和这个服务中心建立的连接时,我们就会调用这个
            void addRegistrar(const BaseConnection::ptr &c, const Address &h, const std::string &method)
            {
                //传递进来一个通信连接,RPC服务端的地址信息(IP地址+端口号),注册的服务名称

                Registrar::ptr Registrar;//创建一个服务注册者对象

                // 查找连接是否已经关联了服务注册者
                {
                    std::unique_lock<std::mutex> lock(_mutex);//加锁
                    auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接里面是不是有对应的服务注册者
                    //因为这个注册者可能之前已经注册过什么服务了
                    if (it != _conns.end())//找到了,说明这个服务注册者是已经存在的服务注册者
                    {
                        // 如果已经存在,则获取已有的服务注册者对象
                        Registrar = it->second;
                    }
                    else//没找到,说明这个服务注册者是新的服务注册者,那么我们就需要在 通信连接-服务注册者 映射里面插入新的键值对
                    {
                        // 如果不存在,创建新的服务注册者对象并建立关联
                        Registrar = std::make_shared<Registrar>(c, h);
                        _conns.insert(std::make_pair(c, Registrar));//在 通信连接-服务注册者 映射里面插入对应通信连接,服务注册者
                    }

                    //我们可能会出现,服务注册者之前注册了服务A,现在又去注册服务A的情况,那岂不是会出现重复的情况吗?
                    //别慌,因为我们的 服务方法-服务注册者 映射里面,这个服务注册者集合我们是使用了set类型,这样子同一个服务方法就不会出现两个相同的服务注册者

                    // 在服务方法到服务注册者的映射中,为该服务方法添加服务注册者
                    auto &Registrars = _Registrars[method];//服务方法-服务注册者集合,注意这里是方法method的服务注册者集合
                    Registrars.insert(Registrar);//往method方法的服务注册者集合里面添加一个新的服务注册者
                }

                //我们可能会出现,服务注册者之前注册了服务A,现在又去注册服务A的情况,那岂不是会出现重复的情况吗?

                // 在服务注册者对象中添加该服务方法method
                Registrar->appendMethod(method);
            }

            // 通过连接获取服务注册者信息 - 用于连接断开时的清理
            Registrar::ptr getRegistrar(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接c里面是不是有对应的服务注册者
                if (it != _conns.end())//找到了,直接返回对应的服务注册者
                {
                    return it->second;
                }
                return Registrar::ptr(); // 返回空指针
            }

            // 删除服务注册者 - 当服务注册者断开连接时调用
            void delRegistrar(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//在 通信连接-服务注册者 映射里面寻找对应通信连接c里面是不是有对应的服务注册者
                if (it == _conns.end())//没找到
                {
                    // 当前断开连接的不是一个服务注册者
                    return;
                }

                //找到了对应的服务注册者

                // 遍历该服务注册者注册的所有服务方法
                for (auto &method : it->second->methods)
                {
                    // 从每个服务方法的服务注册者列表中移除当前服务注册者
                    auto &Registrars = _Registrars[method];
                    Registrars.erase(it->second);
                }

                // 删除连接与服务注册者的关联关系
                _conns.erase(it);//在 通信连接-服务注册者 映射里面删除对应通信连接c的服务注册者
            }

            //获取指定服务方法的所有RPC服务器的IP地址+端口号
            std::vector<Address> methodHosts(const std::string &method)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _Registrars.find(method);//在 服务方法 -> 服务注册者集合 映射里面根据对应服务方法 寻找里面是不是有对应的服务注册者集合
                if (it == _Registrars.end())//没找到对应的服务注册者集合
                {
                    return std::vector<Address>(); // 没有服务注册者,返回空列表
                }

                //找到了对应的服务注册者集合
                //遍历集合中所有服务注册者
                std::vector<Address> result;
                for (auto &Registrar : it->second)
                {
                    result.push_back(Registrar->host);//将集合中所有服务注册者的IP地址+端口号填充到返回值里面去
                }
                return result;
            }

        private:
            std::mutex _mutex; // 管理器互斥锁
            // 服务方法 -> 服务注册者集合的映射
            std::unordered_map<std::string, std::set<Registrar::ptr>> _Registrars;
            //注意这里使用了set类型,就不会出现同一个服务会重复记录两个相同的服务注册者的情况
            // 连接 -> 服务注册者的映射
            std::unordered_map<BaseConnection::ptr, Registrar::ptr> _conns;
        };

2.2.服务发现者管理器

首先,我们都说这是一个管理器,那么我们管理的这个服务发现者到底是什么样子?我们还没有定义出来吧!!

cpp 复制代码
// 服务发现者结构体
            struct Discoverer
            {
                using ptr = std::shared_ptr<Discoverer>;
                std::mutex _mutex;                // 方法列表的互斥锁
                BaseConnection::ptr conn;         // 发现者关联的客户端连接
                std::vector<std::string> methods; // 发现过的服务名称列表

                Discoverer(const BaseConnection::ptr &c) : conn(c) {}

                // 添加服务方法到发现者的关注列表
                void appendMethod(const std::string &method)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    methods.push_back(method);
                }
            };

这个服务发现者类里面的数据结构很多

  • 发现者的通信连接
  • **发现者发现过的服务列表,**可以理解为这个发现者的服务关注列表,客户端进行服务发现时确实会将自身关注的服务添加到它的服务关注列表中。

由于我们的发现者不止一个,所以我们需要将这些发现者管理起来。

那么我们就来编写这个服务发现者管理器

首先我们这个服务发现者管理器的核心其实是2个数据结构

  1. 服务方法 -> 发现者集合的映射
  2. 连接 -> 发现者的映射
cpp 复制代码
// 服务发现者管理器 - 负责管理所有服务发现者(消费者)
        class DiscovererManager
        {
        public:
            ......
        private:
            std::mutex _mutex; // 管理器互斥锁
            // 服务方法 -> 发现者集合的映射
            //这里的发现者集合使用了set,确保了同一个服务不会出现两个相同的订阅者
            std::unordered_map<std::string, std::set<Discoverer::ptr>> _discoverers;
            // 连接 -> 发现者的映射
            std::unordered_map<BaseConnection::ptr, Discoverer::ptr> _conns;
        };

那么我们维护这两个数据结构的意义是什么呢?

  • 维护 连接 -> 发现者的映射的映射。当发现者和注册中心断开连接时,注册中心能立即通过这个数据结构知晓其下线是哪一个发现者。

  • 维护 服务方法 -> 发现者集合 的映射。这个就大有来头了。我们可以理解为服务方法的关注列表。

  • 进行服务发现 ,所谓的服务发现,其实就是将这个客户端请求的服务名称添加进这个服务方法 -> 发现者集合 数据结构里面去而已。然后等待新的服务提供者上线时(注册服务),系统会立即通知所有关注该服务的发现者,这个时候我们再去调用对应的服务。

  • 当服务提供者下线时(连接断开),系统也会立即通知所有关注该服务的发现者,然后就会进行下一步操作。

嗯?有的人可能就好奇了,这个服务发现者类里面有一个成员变量------发现者发现过的服务列表。它的功能不是和上面这个 服务发现者管理器里面的 服务方法 -> 发现者集合 的映射好像作用有点重复了吧?

实则不然。

当一个发现者和注册中心断开连接的时候,这就代表着当有新的服务到来时,我们就不需要再去提醒这个发现者了,

  • 也就是说我们需要借助这个发现者里面的 发现者发现过的服务列表,来获取到这个客户端关注的所有服务,
  • 然后每一个服务都有一个对应的 服务方法 -> 发现者集合 映射,
  • 然后我们只需要去遍历 这个客户端关注的所有服务,然后在每个方法对应的 服务方法 -> 发现者集合映射里面把这个发现者给去掉。
  • 当然别忘了,还需要在 连接 -> 发现者的映射 里面把当前发现者删除掉

  1. addDiscoverer 函数

功能:添加服务发现者或更新发现者的关注列表

执行流程

  1. 创建发现者对象指针

  2. 加锁保护共享数据结构

  3. 在"连接 -> 发现者"映射中查找指定连接对应的发现者

    • 如果找到:获取已有的发现者对象

    • 如果没找到:创建新的发现者对象,并建立连接与发现者的关联关系

  4. 在"服务方法 -> 发现者集合"映射中,为该服务方法添加发现者到集合中

  5. 解锁

  6. 在发现者对象中添加该服务方法到其关注列表中

  7. 返回发现者对象指针

  8. 函数结束

这个思想和上面的服务注册者管理器差不多

cpp 复制代码
// 添加服务发现者或更新发现者的关注列表
            Discoverer::ptr addDiscoverer(const BaseConnection::ptr &c, const std::string &method)
            {
                //传递进来的是一个连接对象,方法名称

                Discoverer::ptr discoverer;//创建一个发现者对象
                {
                    std::unique_lock<std::mutex> lock(_mutex);//加锁
                    auto it = _conns.find(c);//用连接去 连接-发现者 映射里面找到对应的发现者
                    if (it != _conns.end())//找了对应的发现者,说明这个发现者之前已经发现过某个服务
                    {
                        // 如果已经存在,则获取已有的发现者对象
                        discoverer = it->second;
                    }
                    else//没有找到对应的发现者,说明这个发现者是新的发现者,那么我们就需要往将 连接-发现者 映射里面添加一个新的键值对进去
                    {
                        // 如果不存在,创建新的发现者对象并建立关联
                        discoverer = std::make_shared<Discoverer>(c);
                        _conns.insert(std::make_pair(c, discoverer));
                    }
                    
                    //注意:这里可能会出现同一个发现者发现了服务A,又去发现服务A的情况
                    //但是我们的 服务方法-发现者集合 的映射里面,这个发现者集合我们是使用了set,自带去重功能,所以即使出现了上面这种情况,也不会有问题

                    // 在 服务方法-发现者 的映射中,为该服务方法添加发现者,表明有一个新的发现者来发现服务了
                    auto &discoverers = _discoverers[method];
                    discoverers.insert(discoverer);//自带去重功能
                }

                // 在发现者对象中添加该服务方法
                discoverer->appendMethod(method);//这个发现者对象内部需要记录这个服务方法
                return discoverer;
            }
  1. delDiscoverer 函数

功能:当发现者断开连接时删除对应的服务发现者

执行流程

  1. 加锁保护共享数据结构

  2. 在"连接 -> 发现者"映射中查找指定连接对应的发现者

  3. 如果没找到:直接返回(说明当前断开连接的不是一个服务发现者)

  4. 如果找到:

    • 遍历该发现者关注的所有服务方法

    • 从每个服务方法的发现者集合中移除当前发现者

    • 删除连接与发现者的关联关系

  5. 解锁

  6. 函数结束

这个说白了就是下面这个情况

当一个发现者和注册中心断开连接的时候,这就代表着当有新的服务到来时,我们就不需要再去提醒这个发现者了,

  • 也就是说我们需要借助这个发现者里面的 发现者发现过的服务列表,来获取到这个客户端关注的所有服务,
  • 然后每一个服务都有一个对应的 服务方法 -> 发现者集合 映射,
  • 然后我们只需要去遍历 这个客户端关注的所有服务,然后在每个方法对应的 服务方法 -> 发现者集合映射里面把这个发现者给去掉。
  • 当然别忘了,还需要在 连接 -> 发现者的映射 里面把当前发现者删除掉
cpp 复制代码
// 删除服务发现者 - 当发现者断开连接时调用
            void delDiscoverer(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//通过通信连接去 连接-发现者 映射 里面获取对应的发现者
                if (it == _conns.end())//没有找到对应的发现者
                {
                    // 没有找到连接对应的发现者信息,代表客户端不是一个服务发现者
                    return;
                }

                //找到了对应的发现者

                // 遍历该发现者关注的所有服务方法
                for (auto &method : it->second->methods)
                {
                    // 从每个服务方法的发现者列表中移除当前发现者
                    auto discoverers = _discoverers[method];
                    discoverers.erase(it->second);
                }

                // 删除连接与发现者的关联关系
                _conns.erase(it);//我们还需要去 连接-发现者映射 里面移除掉当前发现者
            }

服务上下线

进行服务发现 ,所谓的服务发现,其实就是将这个客户端请求的服务名称添加进这个服务方法 -> 发现者集合 数据结构里面去而已。然后等待有服务注册者注册了对应方法,系统会立即通知所有关注该服务的发现者,这个时候我们再去调用对应的服务。

当服务注册者客户端下线时(连接断开),系统也会立即通知所有关注该服务的发现者,然后就会进行下一步操作。

而我们的下面这3个函数,就是完成上面这两种情况的

cpp 复制代码
            // 服务上线通知 - 当有服务注册者注册了新的服务时调用
            void onlineNotify(const std::string &method, const Address &host)
            {
                return notify(method, host, ServiceOptype::SERVICE_ONLINE);
            }

            // 服务下线通知 - 当有服务提供者下线时调用
            void offlineNotify(const std::string &method, const Address &host)
            {
                return notify(method, host, ServiceOptype::SERVICE_OFFLINE);
            }

        private:
            // 统一的通知方法 - 向关注某个服务的所有发现者发送通知
            void notify(const std::string &method, const Address &host, ServiceOptype optype)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _discoverers.find(method);//通过服务方法 到 服务方法 -> 发现者集合的映射 里面获取对应的发现者集合
                if (it == _discoverers.end())//没用获取到
                {
                    // 这个服务当前没有发现者关注,无需通知
                    return;
                }
                //获取到了对应的发现者集合,就需要去通知这些发现者

                // 创建服务变更通知消息
                auto msg_req = MessageFactory::create<ServiceRequest>();//通过工厂类创建一个服务请求类
                msg_req->setId(UUID::uuid());
                msg_req->setMType(MType::REQ_SERVICE);
                msg_req->setMethod(method);
                msg_req->setHost(host);
                msg_req->setOptype(optype);

                // 向所有关注该服务的发现者发送通知
                for (auto &discoverer : it->second)//遍历这个服务的发现者集合
                {
                    discoverer->conn->send(msg_req);//遍历
                }
            }

这个的思想还是很简单的。

完整代码

cpp 复制代码
// 服务发现者管理器 - 负责管理所有服务发现者(消费者)
        class DiscovererManager
        {
        public:
            using ptr = std::shared_ptr<DiscovererManager>;

            // 服务发现者结构体
            struct Discoverer
            {
                using ptr = std::shared_ptr<Discoverer>;
                std::mutex _mutex;                // 方法列表的互斥锁
                BaseConnection::ptr conn;         // 发现者关联的客户端连接
                std::vector<std::string> methods; // 发现过的服务名称列表

                Discoverer(const BaseConnection::ptr &c) : conn(c) {}

                // 添加服务方法到发现者的关注列表
                void appendMethod(const std::string &method)
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    methods.push_back(method);
                }
            };

            // 添加服务发现者或更新发现者的关注列表
            Discoverer::ptr addDiscoverer(const BaseConnection::ptr &c, const std::string &method)
            {
                //传递进来的是一个连接对象,方法名称

                Discoverer::ptr discoverer;//创建一个发现者对象
                {
                    std::unique_lock<std::mutex> lock(_mutex);//加锁
                    auto it = _conns.find(c);//用连接去 连接-发现者 映射里面找到对应的发现者
                    if (it != _conns.end())//找了对应的发现者,说明这个发现者之前已经发现过某个服务
                    {
                        // 如果已经存在,则获取已有的发现者对象
                        discoverer = it->second;
                    }
                    else//没有找到对应的发现者,说明这个发现者是新的发现者,那么我们就需要往将 连接-发现者 映射里面添加一个新的键值对进去
                    {
                        // 如果不存在,创建新的发现者对象并建立关联
                        discoverer = std::make_shared<Discoverer>(c);
                        _conns.insert(std::make_pair(c, discoverer));
                    }
                    
                    //注意:这里可能会出现同一个发现者发现了服务A,又去发现服务A的情况
                    //但是我们的 服务方法-发现者集合 的映射里面,这个发现者集合我们是使用了set,自带去重功能,所以即使出现了上面这种情况,也不会有问题

                    // 在 服务方法-发现者 的映射中,为该服务方法添加发现者,表明有一个新的发现者来发现服务了
                    auto &discoverers = _discoverers[method];
                    discoverers.insert(discoverer);//自带去重功能
                }

                // 在发现者对象中添加该服务方法
                discoverer->appendMethod(method);//这个发现者对象内部需要记录这个服务方法
                return discoverer;
            }

            // 删除服务发现者 - 当发现者断开连接时调用
            void delDiscoverer(const BaseConnection::ptr &c)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _conns.find(c);//通过通信连接去 连接-发现者 映射 里面获取对应的发现者
                if (it == _conns.end())//没有找到对应的发现者
                {
                    // 没有找到连接对应的发现者信息,代表客户端不是一个服务发现者
                    return;
                }

                //找到了对应的发现者

                // 遍历该发现者关注的所有服务方法
                for (auto &method : it->second->methods)
                {
                    // 从每个服务方法的发现者列表中移除当前发现者
                    auto discoverers = _discoverers[method];
                    discoverers.erase(it->second);
                }

                // 删除连接与发现者的关联关系
                _conns.erase(it);//我们还需要去 连接-发现者映射 里面移除掉当前发现者
            }

            // 服务上线通知 - 当有新的服务提供者上线时调用
            void onlineNotify(const std::string &method, const Address &host)
            {
                return notify(method, host, ServiceOptype::SERVICE_ONLINE);
            }

            // 服务下线通知 - 当有服务提供者下线时调用
            void offlineNotify(const std::string &method, const Address &host)
            {
                return notify(method, host, ServiceOptype::SERVICE_OFFLINE);
            }

        private:
            // 统一的通知方法 - 向关注某个服务的所有发现者发送通知
            void notify(const std::string &method, const Address &host, ServiceOptype optype)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _discoverers.find(method);//通过服务方法 到 服务方法 -> 发现者集合的映射 里面获取对应的发现者集合
                if (it == _discoverers.end())//没用获取到
                {
                    // 这个服务当前没有发现者关注,无需通知
                    return;
                }
                //获取到了对应的发现者集合,就需要去通知这些发现者

                // 创建服务变更通知消息
                auto msg_req = MessageFactory::create<ServiceRequest>();//通过工厂类创建一个服务请求类
                msg_req->setId(UUID::uuid());
                msg_req->setMType(MType::REQ_SERVICE);
                msg_req->setMethod(method);
                msg_req->setHost(host);
                msg_req->setOptype(optype);

                // 向所有关注该服务的发现者发送通知
                for (auto &discoverer : it->second)//遍历这个服务的发现者集合
                {
                    discoverer->conn->send(msg_req);//遍历
                }
            }

        private:
            std::mutex _mutex; // 管理器互斥锁
            // 服务方法 -> 发现者集合的映射 
            std::unordered_map<std::string, std::set<Discoverer::ptr>> _discoverers;//这里使用了set,确保了同一个服务不会出现两个相同的订阅者
            // 连接 -> 发现者的映射 
            std::unordered_map<BaseConnection::ptr, Discoverer::ptr> _conns;
        };

2.3.综合管理器 - 整合注册者管理模块和发现者管理模块

首先,这个模块是整合了上面两个模块的,所以成员变量就是上面这两个玩意。

cpp 复制代码
 // 综合管理器 - 整合提供者管理和发现者管理
        class RDManager
        {
        public:
           ......

        private:
            RegistrarManager::ptr _Registrars;     // 服务注册者管理器
            DiscovererManager::ptr _discoverers; // 发现者管理器
        };
    }

那么在这个模块,我们就需要提供2个整合的函数给Dispatcher模块去进注册,

  1. onServiceRequest() 函数:处理服务注册请求和服务发现请求,并作出响应
  2. onConnShutdown函数处理服务注册者和服务发现者下线时管理数据的清理

onServiceRequest() 函数

作用:处理客户端发来的服务操作请求,根据请求类型分发到不同的处理逻辑。

具体分支处理

服务注册请求(SERVICE_REGISTRY)

  • 调用者:服务注册者

  • 主要操作

    1. 记录日志:哪个主机注册了哪个服务

    2. 调用服务注册者管理器,将注册者和服务方法注册到系统中

    3. 调用服务发现者管理器,向所有曾关注过这个服务的发现者发送"服务上线"通知

    4. 给提供者返回"注册成功"的响应

服务发现请求(SERVICE_DISCOVERY)

  • 调用者:服务发现者(消费者)

  • 主要操作

    1. 记录日志:哪个客户端要发现哪个服务

    2. 调用发现者管理器,记录这个发现者及其关注的服务(用于后续通知)

    3. 调用服务发现响应函数,返回该服务的所有可用提供者列表

错误请求处理

  • 如果收到无效的操作类型,记录错误日志并返回错误响应
cpp 复制代码
// 处理服务请求(服务注册/服务发现)
            //这个函数就是注册给Dispatcher模块进行注册的,专门处理服务注册-服务发现请求的
            //服务发现请求是服务发现者客户端才会发来的
            //服务注册请求是服务注册者客户端才会发来的
            void onServiceRequest(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                ServiceOptype optype = msg->optype();//获取服务操作类型

                if (optype == ServiceOptype::SERVICE_REGISTRY)//如果操作类型是服务注册,说明发这个请求的是一个服务注册者
                {
                    // 服务注册处理:
                    // 1. 新增服务注册者
                    // 2. 向关注该服务的发现者发送上线通知
                    ILOG("%s:%d 注册服务 %s", msg->host().first.c_str(), msg->host().second, msg->method().c_str());
                    _Registrars->addRegistrar(conn, msg->host(), msg->method());
                    _discoverers->onlineNotify(msg->method(), msg->host());
                    return registryResponse(conn, msg);//返回一个服务注册成功的响应
                }
                else if (optype == ServiceOptype::SERVICE_DISCOVERY)//如果操作类型是服务发现,说明发这个请求的是一个发现者
                {
                    // 服务发现处理:
                    // 1. 记录发现者(用于后续通知)
                    ILOG("客户端要进行 %s 服务发现!", msg->method().c_str());
                    _discoverers->addDiscoverer(conn, msg->method());
                    return discoveryResponse(conn, msg);//返回一个服务发现成功的响应
                }
                else
                {
                    // 无效的操作类型
                    ELOG("收到服务操作请求,但是操作类型错误!");
                    return errorResponse(conn, msg);
                }
            }

**onConnShutdown函数------**发现者/注册者下线的处理

首先,这个发现者和注册者都分别和这个服务中心建立有一个长连接

如果说这个连接断掉了,那么就说明某个发现者和注册者下线了。

那么我们就需要根据发现者/注册者来进行不同的处理。

那么我们这里就提供了 onConnShutdown() 函数 来处理发现者/注册者下线的情况

作用:处理客户端连接断开事件,清理相关资源并发送必要的通知。

处理流程

  1. 检查是否为服务提供者断开

    • 通过连接查找对应的提供者信息

    • 如果是提供者断开:

      • 记录日志:哪个主机的服务下线了

      • 遍历该提供者提供的所有服务方法

      • 对每个方法,通知所有曾关注这个方法的发现者"服务下线"

      • 清理该提供者在系统中的所有注册信息

  2. 检查是否为服务发现者断开

    • 无论断开的是否是发现者,都尝试清理发现者相关数据

    • 因为一个连接可能既是提供者也是发现者(清理发现者数据可以避免后续无效通知)

cpp 复制代码
// 连接断开处理 - 清理相关资源并发送通知
            void onConnShutdown(const BaseConnection::ptr &conn)
            {
                // 检查断开连接的是否是服务注册者
                auto Registrar = _Registrars->getRegistrar(conn);//通过通信连接来获取对应的服务注册者
                if (Registrar.get() != nullptr)//说明断开连接的是服务注册者
                {
                    // 如果是服务注册者,向其所有服务的发现者发送下线通知
                    ILOG("%s:%d 服务下线", Registrar->host.first.c_str(), Registrar->host.second);
                    for (auto &method : Registrar->methods)//遍历这个服务注册者注册的所有方法
                    {
                        _discoverers->offlineNotify(method, Registrar->host);//对于每一种方法,我们都需要去通知那些关注了这个方法的服务发现者
                    }
                    // 清理服务注册者相关数据
                    _Registrars->delRegistrar(conn);//移除这个服务注册者
                }

                // 清理发现者相关数据(无论是不是发现者都尝试清理,这个服务注册者也可能是一个其他服务的发现者)
                _discoverers->delDiscoverer(conn);//这个函数是用于发现者断开连接时调用。
            }

一些响应函数

我们在上面两个函数调用了一些发送响应的函数,那么这些函数其实都很简单,大家看看下面就行了。

errorResponse() 函数

作用:构建并发送错误响应消息。

  • 触发条件:收到无效的服务操作类型

  • 响应内容

    • 错误码:RCODE_INVALID_OPTYPE(无效操作类型)

    • 操作类型:SERVICE_UNKNOW(未知服务操作)

  • 目的:告知客户端请求有误,需要检查请求格式

registryResponse() 函数

作用:构建并发送服务注册成功的响应消息。

  • 触发条件:提供者成功注册服务后

  • 响应内容

    • 错误码:RCODE_OK(操作成功)

    • 操作类型:SERVICE_REGISTRY(服务注册)

  • 目的:告知注册者注册已完成,可以开始提供服务

discoveryResponse() 函数

作用:构建并发送服务发现的响应消息,包含服务提供者列表。

  • 触发条件:发现者请求服务发现时

  • 处理流程

    1. 查询提供指定服务的所有主机地址(IP+端口)

    2. 如果无提供者

      • 返回错误码:RCODE_NOT_FOUND_SERVICE(未找到服务)
    3. 如果有提供者

      • 返回错误码:RCODE_OK(操作成功)

      • 返回服务方法名称

      • 返回所有提供者的主机地址列表

  • 目的:为发现者提供可用的服务提供者信息,支持负载均衡选择

cpp 复制代码
private:
            // 错误响应
            void errorResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                auto msg_rsp = MessageFactory::create<ServiceResponse>();//通过工厂类来生成一个ServiceResponse对象
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setRCode(RCode::RCODE_INVALID_OPTYPE);//无效的操作类型
                msg_rsp->setOptype(ServiceOptype::SERVICE_UNKNOW);//未定义或者无效的操作类型
                conn->send(msg_rsp);
            }

            // 服务注册响应------表示成功的响应
            void registryResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                auto msg_rsp = MessageFactory::create<ServiceResponse>();
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setRCode(RCode::RCODE_OK);//操作成功
                msg_rsp->setOptype(ServiceOptype::SERVICE_REGISTRY);//服务注册
                conn->send(msg_rsp);
            }

            // 服务发现响应------表示成功的响应
            void discoveryResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                //服务发现成功,需要返回该服务的所有服务注册者的IP地址+端口号

                auto msg_rsp = MessageFactory::create<ServiceResponse>();
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setOptype(ServiceOptype::SERVICE_DISCOVERY);//服务发现

                // 查询注册该服务的所有主机的IP地址+端口号
                std::vector<Address> hosts = _Registrars->methodHosts(msg->method());

                if (hosts.empty())
                {
                    // 没有找到服务注册者
                    msg_rsp->setRCode(RCode::RCODE_NOT_FOUND_SERVICE);//未找到请求的服务
                    return conn->send(msg_rsp);
                }

                // 返回服务注册者列表
                msg_rsp->setRCode(RCode::RCODE_OK);
                msg_rsp->setMethod(msg->method());
                msg_rsp->setHost(hosts);
                return conn->send(msg_rsp);
            }

完整代码

cpp 复制代码
// 综合管理器 - 整合服务注册者管理和发现者管理
        class RDManager
        {
        public:
            using ptr = std::shared_ptr<RDManager>;

            RDManager() : _Registrars(std::make_shared<RegistrarManager>()),
                          _discoverers(std::make_shared<DiscovererManager>())
            {
            }

            // 处理服务请求(服务注册/服务发现)
            //这个函数就是注册给Dispatcher模块进行注册的,专门处理服务注册-服务发现请求的
            //服务发现请求是服务发现者客户端才会发来的
            //服务注册请求是服务注册者客户端才会发来的
            void onServiceRequest(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                ServiceOptype optype = msg->optype();//获取服务操作类型

                if (optype == ServiceOptype::SERVICE_REGISTRY)//如果操作类型是服务注册,说明发这个请求的是一个服务注册者
                {
                    // 服务注册处理:
                    // 1. 新增服务注册者
                    // 2. 向关注该服务的发现者发送上线通知
                    ILOG("%s:%d 注册服务 %s", msg->host().first.c_str(), msg->host().second, msg->method().c_str());
                    _Registrars->addRegistrar(conn, msg->host(), msg->method());
                    _discoverers->onlineNotify(msg->method(), msg->host());
                    return registryResponse(conn, msg);//返回一个服务注册成功的响应
                }
                else if (optype == ServiceOptype::SERVICE_DISCOVERY)//如果操作类型是服务发现,说明发这个请求的是一个发现者
                {
                    // 服务发现处理:
                    // 1. 记录发现者(用于后续通知)
                    ILOG("客户端要进行 %s 服务发现!", msg->method().c_str());
                    _discoverers->addDiscoverer(conn, msg->method());
                    return discoveryResponse(conn, msg);//返回一个服务发现成功的响应
                }
                else
                {
                    // 无效的操作类型
                    ELOG("收到服务操作请求,但是操作类型错误!");
                    return errorResponse(conn, msg);
                }
            }

            // 连接断开处理 - 清理相关资源并发送通知
            void onConnShutdown(const BaseConnection::ptr &conn)
            {
                // 检查断开连接的是否是服务注册者
                auto Registrar = _Registrars->getRegistrar(conn);//通过通信连接来获取对应的服务注册者
                if (Registrar.get() != nullptr)//说明断开连接的是服务注册者
                {
                    // 如果是服务注册者,向其所有服务的发现者发送下线通知
                    ILOG("%s:%d 服务下线", Registrar->host.first.c_str(), Registrar->host.second);
                    for (auto &method : Registrar->methods)//遍历这个服务注册者注册的所有方法
                    {
                        _discoverers->offlineNotify(method, Registrar->host);//对于每一种方法,我们都需要去通知那些关注了这个方法的服务发现者
                    }
                    // 清理服务注册者相关数据
                    _Registrars->delRegistrar(conn);//移除这个服务注册者
                }

                // 清理发现者相关数据(无论是不是发现者都尝试清理,这个服务注册者也可能是一个其他服务的发现者)
                _discoverers->delDiscoverer(conn);//这个函数是用于发现者断开连接时调用。
            }

        private:
            // 错误响应
            void errorResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                auto msg_rsp = MessageFactory::create<ServiceResponse>();//通过工厂类来生成一个ServiceResponse对象
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setRCode(RCode::RCODE_INVALID_OPTYPE);//无效的操作类型
                msg_rsp->setOptype(ServiceOptype::SERVICE_UNKNOW);//未定义或者无效的操作类型
                conn->send(msg_rsp);
            }

            // 服务注册响应------表示成功的响应
            void registryResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                auto msg_rsp = MessageFactory::create<ServiceResponse>();
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setRCode(RCode::RCODE_OK);//操作成功
                msg_rsp->setOptype(ServiceOptype::SERVICE_REGISTRY);//服务注册
                conn->send(msg_rsp);
            }

            // 服务发现响应------表示成功的响应
            void discoveryResponse(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                //服务发现成功,需要返回该服务的所有服务注册者的IP地址+端口号

                auto msg_rsp = MessageFactory::create<ServiceResponse>();
                msg_rsp->setId(msg->rid());
                msg_rsp->setMType(MType::RSP_SERVICE);
                msg_rsp->setOptype(ServiceOptype::SERVICE_DISCOVERY);//服务发现

                // 查询注册该服务的所有主机的IP地址+端口号
                std::vector<Address> hosts = _Registrars->methodHosts(msg->method());

                if (hosts.empty())
                {
                    // 没有找到服务注册者
                    msg_rsp->setRCode(RCode::RCODE_NOT_FOUND_SERVICE);//未找到请求的服务
                    return conn->send(msg_rsp);
                }

                // 返回服务注册者列表
                msg_rsp->setRCode(RCode::RCODE_OK);
                msg_rsp->setMethod(msg->method());
                msg_rsp->setHost(hosts);
                return conn->send(msg_rsp);
            }

        private:
            RegistrarManager::ptr _Registrars;     // 服务注册者管理器
            DiscovererManager::ptr _discoverers; // 发现者管理器
        };

这个思路还是非常的清楚的。

2.4.封装服务中心类

我们整个服务这边的服务注册和服务发现模块写好了之后,我们就可以来对我们的服务中心进行封装。

大家注意了,这个服务中心服务端是一个独立的程序。

可以看到:它的核心功能就是

  • 处理服务注册客户端发来的服务注册请求
  • 处理服务发现者客户端发来的服务发现请求
  • 本身就是一个服务器的存在 ,需要处理好与服务注册者客户端,服务发现者的通信连接

这个是思路很简单。

我们主要关注ServiceCenter类的实现思路。它主要由三个部分组成:RDManager(注册者-发现者管理器)、Dispatcher(消息分发器)和BaseServer(网络服务器)。

整个ServiceCenter的设计思路是:网络层接收消息,通过分发器将消息派发给对应的处理器,由PDManager处理服务注册与发现相关的业务逻辑。

下面我们逐步分析:

  1. 构造函数:

    • 创建RDManager和Dispatcher对象。

    • 在Dispatcher中注册一个处理器,用于处理类型为MType::REQ_SERVICE的消息(即服务注册与发现请求)。

    • 使用ServerFactory创建一个网络服务器(BaseServer),监听指定端口。

    • 设置网络服务器的消息回调:当服务器收到消息时,调用Dispatcher的onMessage方法进行分发。

    • 设置网络服务器的连接关闭回调:当连接断开时,调用ServiceCenter的onConnShutdown方法进行清理。

  2. 消息处理流程:

    • 当有客户端连接并发送消息时,BaseServer接收到消息,调用设置的消息回调(message_cb)。

    • 在message_cb中,调用Dispatcher::onMessage,Dispatcher会根据消息类型(MType)找到对应的处理器。

    • 对于REQ_SERVICE类型的消息,会调用RDManager::onServiceRequest进行处理。

  3. 连接关闭处理:

    • 当有连接断开时,BaseServer调用设置的关闭回调(close_cb)。

    • 在close_cb中,调用ServiceCenter::onConnShutdown,然后交给PDManager进行清理(例如,如果断开的是服务提供者,则从注册中心移除,并通知发现者)。

  4. 启动服务器:

    • 调用start方法,启动网络服务器,开始监听端口并处理连接。
cpp 复制代码
// 服务中心类 - 专门处理服务注册与发现请求
        class ServiceCenter
        {
        public:
            using ptr = std::shared_ptr<ServiceCenter>;
            // 构造函数,监听指定端口
            ServiceCenter(int port) : _pd_manager(std::make_shared<RDManager>()),         // 创建注册者-发现者管理器
                                       _dispatcher(std::make_shared<bitrpc::Dispatcher>()) // 创建消息分发器
            {
                // 注册服务请求处理器 - 当收到服务注册/发现请求时,由RDManager处理
                auto service_cb = std::bind(&RDManager::onServiceRequest, _pd_manager.get(),
                                            std::placeholders::_1, std::placeholders::_2);
                _dispatcher->registerHandler<ServiceRequest>(MType::REQ_SERVICE, service_cb);

                // 创建TCP服务器,监听指定端口
                _server = bitrpc::ServerFactory::create(port);

                // 设置消息回调 - 当服务器收到消息时,交给分发器处理
                auto message_cb = std::bind(&jsonRpc::Dispatcher::onMessage, _dispatcher.get(),
                                            std::placeholders::_1, std::placeholders::_2);
                _server->setMessageCallback(message_cb);

                // 设置连接关闭回调 - 当连接断开时,清理相关资源
                auto close_cb = std::bind(&ServiceCenter::onConnShutdown, this, std::placeholders::_1);
                _server->setCloseCallback(close_cb);
                // 注册中心 底层使用的是Muduo网络库,连接注册中心的只有服务注册者和服务发现者,当有连接断开,服务器会自动调用closeCallback函数,
                // 那么我们在closeCallback函数里面调用了ServiceCenter::onConnShutdown函数,自动处理好这个连接断开的情况
            }

            // 启动注册中心服务器
            void start()
            {
                _server->start();
            }

        private:
            // 连接断开时的处理函数
            void onConnShutdown(const BaseConnection::ptr &conn)
            {
                // 交给RDManager处理连接断开,清理注册者/发现者信息
                _rd_manager->onConnShutdown(conn);
            }

        private:
            RDManager::ptr _rd_manager;  // 注册者-发现者管理器
            Dispatcher::ptr _dispatcher; // 消息分发器
            BaseServer::ptr _server;     // 网络服务器
        };

这个思路还是很简单的。

三.服务注册和服务发现的客户端代码实现

说实话,我们需要搞清楚一件事情

  • 服务中心是一个服务器
  • 服务注册者客户端和服务发现者客户端都相当于一个客户端,分别连接着我们的这个服务中心

说实话,我们客户端这边的服务注册和服务发现模块是在写这个服务注册者客户端,服务发现者客户端两种客户端的代码。

也就是下面这2个东西

两种客户端都分别和注册中心建立了一个连接,那么我们两种客户端能向注册中心发送不同的注册请求

  • 服务注册者客户端------发送服务注册请求
  • 服务发现者客户端------发送服务发现请求

我们在这两种客户端里面必须实现这两个基础功能。

当然,对于这个服务发现者客户端其实是多了一点东西的。

那就是服务发现者发现过的服务无论是上线还是下线,都会对这个发现者客户端进行提醒,那么我们的服务发现者客户端就必须针对服务上线/下线两种情况作出不同的处理。

3.1.服务注册者

这个东西我们写的就是下面这个

这个服务注册者的功能很简单,它还暂且不需要考虑通信连接的问题。

**它只需要完成一件事:**往服务中心发送服务注册请求

cpp 复制代码
// 服务提供者客户端类 - 用于服务提供者向注册中心注册服务
        class Provider {
            public:
                using ptr = std::shared_ptr<Provider>;
                // 构造函数,初始化请求器
                Provider(const Requestor::ptr &requestor) : _requestor(requestor){}
                
                // 注册服务方法 - 向服务注册中心注册本机提供的服务
                bool registryMethod(const BaseConnection::ptr &conn, const std::string &method, const Address &host) 
                {
                    //传递进来了通信连接,方法名称,主机信息

                    // 创建服务注册请求消息
                    auto msg_req = MessageFactory::create<ServiceRequest>();//通过工厂类来生产一个ServiceRequest对象
                    msg_req->setId(UUID::uuid());
                    msg_req->setMType(MType::REQ_SERVICE);
                    msg_req->setMethod(method);
                    msg_req->setHost(host);
                    msg_req->setOptype(ServiceOptype::SERVICE_REGISTRY);//操作类型是服务注册
                    
                    // 发送请求并等待响应
                    BaseMessage::ptr msg_rsp;
                    bool ret = _requestor->send(conn, msg_req, msg_rsp);//这里是使用了同步请求,会阻塞等待,直到注册中心返回响应
                    if (ret == false) {
                        ELOG("%s 服务注册失败!", method.c_str());
                        return false;
                    }
                    //这个时候msg_rsp里面已经有响应内容了
                    
                    // 将响应转换为服务响应类型
                    auto service_rsp = std::dynamic_pointer_cast<ServiceResponse>(msg_rsp);//将父类指针转换成子类指针,这是一定可以成功的,
                    //因为无论请求还是响应,我们都说通过工厂类来生成一个子类,但是却返回一个父类指针,现在我们在这里只不过是还原到最初的状态而已
                    if (service_rsp.get() == nullptr) 
                    {
                        ELOG("响应类型向下转换失败!");
                        return false;
                    }
                    //转换成子类,就能使用子类的接口rcode()
                    
                    // 检查响应码
                    if (service_rsp->rcode() != RCode::RCODE_OK) {
                        ELOG("服务注册失败,原因:%s", errReason(service_rsp->rcode()).c_str());
                        return false;
                    }
                    
                    return true;
                }
            private:
                Requestor::ptr _requestor; // 请求器,用于发送和接收消息
        };

整个的过程还是很简单的。

3.2.封装服务注册客户端

我们现在是写下面这个模块了。

说实话这个模块其实是对这个服务注册者的二次封装,将它封装成一个客户端。

首先,这个服务注册者客户端其实就是一个客户端程序

  • 能和服务中心建立起通信连接
  • 能往服务中心服务端发起注册请求(提供一个成员函数来完成)
  • 处理服务中心服务端发回来的响应(在构造函数里面完成了)
cpp 复制代码
// 服务注册客户端类,用于向服务中心注册服务
        class RegistryClient
        {
        public:
            // 智能指针类型别名
            using ptr = std::shared_ptr<RegistryClient>;
            // 构造函数,初始化服务注册客户端
            // @param ip: 服务中心的IP地址
            // @param port: 服务中心的端口号
            RegistryClient(const std::string &ip, int port) : _requestor(std::make_shared<Requestor>()),                 // 创建请求器实例
                                                              _Registrar(std::make_shared<client::Registrar>(_requestor)), // 创建服务注册者实例
                                                              _dispatcher(std::make_shared<Dispatcher>())
            { // 创建调度器实例
                //构造函数里面做的东西都是为了处理这个服务中心返回来的响应

                // 设置服务响应回调函数,将服务中心返回的响应交给请求器处理
                auto rsp_cb = std::bind(&client::Requestor::onResponse, _requestor.get(),
                                        std::placeholders::_1, std::placeholders::_2);
                _dispatcher->registerHandler<BaseMessage>(MType::RSP_SERVICE, rsp_cb);//

                // 设置消息回调函数,将收到的消息交给调度器处理
                auto message_cb = std::bind(&Dispatcher::onMessage, _dispatcher.get(),
                                            std::placeholders::_1, std::placeholders::_2);
                _client = ClientFactory::create(ip, port); // 创建客户端连接,传入服务中心的IP地址和端口号
                _client->setMessageCallback(message_cb);   // 设置消息回调
                _client->connect();                        // 连接服务中心服务器
            }

            // 服务注册接口,向服务中心注册一个服务方法
            // @param method: 要注册的方法名称
            // @param host: 服务注册者的地址(IP和端口)
            // @return: 注册成功返回true,失败返回false
            bool registryMethod(const std::string &method, const Address &host)
            {
                return _Registrar->registryMethod(_client->connection(), method, host);//也就是发送一个服务注册请求给服务中心服务端
            }

        private:
            Requestor::ptr _requestor;       // 请求器,管理请求和响应
            client::Registrar::ptr _Registrar; // 服务注册者,处理服务注册逻辑
            Dispatcher::ptr _dispatcher;     // 调度器,分发和处理消息
            BaseClient::ptr _client;         // 客户端连接,与服务中心通信
        };

这个的思路还是很简单的。

3.3.服务发现者

注意:我们现在实现的模块其实是下面这个

它暂且不需要考虑通信连接的问题。

我们的服务发现者其实不进行RPC调用,他只是去服务中心里面获取一下哪些主机能提供这种RPC服务,因为多个主机能提供同一种服务,所以我们还是需要进行管理一下,此外,我们也不是将所有主机信息都返回,我们只是通过RR轮转思想,返回一个主机信息给RPC服务调用者即可。

但是,我们每次进行RPC调用的时候都需要通过服务发现者先去注册中心查询一下数据来,然后再返回吗?这样子效率有点低下。

我们的做法就是将服务发现进行服务发现的时候,将它发现某种服务的所有提供者主机信息都存储起来,方便下次调用。

那么获取到的主机的信息,我们要怎么进行存储呢?

为此,我们专门写了一个类来存储主机信息

cpp 复制代码
// 服务方法主机管理器类 - 管理一个服务方法对应的多个提供者主机
        class MethodHost
        {
        public:
            using ptr = std::shared_ptr<MethodHost>;

            // 默认构造函数
            MethodHost() : _idx(0) {}

            // 带初始主机列表的构造函数
            MethodHost(const std::vector<Address> &hosts) : _hosts(hosts.begin(), hosts.end()), _idx(0) {}

            // 添加主机地址 - 当收到服务上线通知时调用
            void appendHost(const Address &host)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _hosts.push_back(host);
            }

            // 移除主机地址 - 当收到服务下线通知时调用
            void removeHost(const Address &host)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                for (auto it = _hosts.begin(); it != _hosts.end(); ++it)
                {
                    if (*it == host)
                    {
                        _hosts.erase(it);
                        break;
                    }
                }
            }

            // 选择主机 - 使用简单的轮询算法选择提供者主机
            Address chooseHost()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                size_t pos = _idx++ % _hosts.size();
                return _hosts[pos];
            }

            // 检查主机列表是否为空
            bool empty()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                return _hosts.empty();
            }

        private:
            std::mutex _mutex;           // 互斥锁,保护主机列表的线程安全访问
            size_t _idx;                 // 轮询选择索引
            std::vector<Address> _hosts; // 服务提供者主机地址列表
        };

这个的思想还是很简单的。这样子针对一种方法,我们就存储一个MethodHost对象,这样子就方便了。不过,服务发现者不可能只发现一种RPC服务,那么就会产生多个MethodHost对象,我们必须将这些数据管理起来:

因此,我们在这个Discovery类里面搞了一个哈希表来存储这个信息

  • 服务方法名 -> MethodHost对象的映射,缓存服务提供者信息
cpp 复制代码
// 服务发现者客户端类 - 用于服务消费者发现和选择服务提供者
        class Discoverer
        {

        private:
            OfflineCallback _offline_callback; // 服务下线时的回调函数
            std::mutex _mutex;                 // 互斥锁,保护_method_hosts的线程安全访问
            // 服务方法名 -> MethodHost对象的映射,缓存服务提供者信息
            std::unordered_map<std::string, MethodHost::ptr> _method_hosts;
            Requestor::ptr _requestor; // 请求器,用于发送和接收消息
        };

当然,我需要提一嘴下面这个成员变量

  • 服务下线时的回调函数

某个RPC服务提供者下线了,那么会通知那些关注过这个服务调用者方法的服务发现者,那么服务发现者收到这个通知之后,就需要对相关的 MethodHost对象 里面删除对应服务提供者。

服务发现

服务发现者客户端的作用其实很明确:

首先这个图,我们就应该明白有下面这3个功能

  • 需要能和注册中心客户端建立起网络通信
  • 能向注册中心服务端发起服务发现请求
  • 能处理注册中心服务端发回来的响应

服务发现者客户端 的主要职责是:当某个RPC调用方需要调用特定RPC服务时,会先通过服务发现者向注册中心查询提供该服务的主机信息,并返回对应的IP地址和端口号。调用方依据这些信息发起远程调用。

这个服务发现是我们服务发现者客户端的重中之重。

这里和上面的服务发现者的情况其实没有太多区别,只是这里多了2步

  • 进行服务发现之前,先在本地缓存里面看看也没有已经有的信息
  • 发现新方法后,需要将这个方法的所有提供者的地址信息存储到本地缓存里面
cpp 复制代码
// 服务发现方法 - 发现指定服务的提供者并选择一个主机
            bool serviceDiscovery(const BaseConnection::ptr &conn, const std::string &method, Address &host)
            {
                //host是输出型参数,用于获取RPC服务提供者主机的信息

                //先去本地看看也没有缓存好的数据
                {
                    // 检查本地是否已缓存该服务的提供者信息
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _method_hosts.find(method);//通过方法名去 服务方法名 -> MethodHost对象的映射 里面寻找对应的MethodHost对象
                    if (it != _method_hosts.end())//找到了,说明我们之前发现过这个服务
                    {
                        if (it->second->empty() == false)//主机信息不为空,说明该服务是有提供者的
                        {
                            // 如果本地有缓存且不为空,直接选择一个主机返回
                            host = it->second->chooseHost();//注意host是输出型参数,专门用于返回可以提供这个RPC服务的一个主机地址信息
                            return true;
                        }
                    }
                }
                //这里有2种情况
                //我们之前没有发现过这个服务
                //我们之前发现过这个服务,但这个服务没有提供者

                //本地没有缓存的数据,我们这个时候就需要向注册中心发起一个服务发现的请求

                // 本地没有缓存或缓存为空,向注册中心发起服务发现请求
                auto msg_req = MessageFactory::create<ServiceRequest>();
                msg_req->setId(UUID::uuid());
                msg_req->setMType(MType::REQ_SERVICE);
                msg_req->setMethod(method);
                msg_req->setOptype(ServiceOptype::SERVICE_DISCOVERY);//服务发现

                BaseMessage::ptr msg_rsp;
                bool ret = _requestor->send(conn, msg_req, msg_rsp);//发送同步请求,这里会阻塞等待
                if (ret == false)
                {
                    ELOG("服务发现失败!");
                    return false;
                }
                //这个时候响应已经在msg_rsp里面了

                // 将响应转换为服务响应类型
                auto service_rsp = std::dynamic_pointer_cast<ServiceResponse>(msg_rsp);
                // 将父类指针转换成子类指针,这是一定可以成功的,
                // 因为无论请求还是响应,我们都说通过工厂类来生成一个子类,但是却返回一个父类指针,现在我们在这里只不过是还原到最初的状态而已
                if (!service_rsp)
                {
                    ELOG("服务发现失败!响应类型转换失败!");
                    return false;
                }
                //这个时候我们就可以去查看响应里面的信息了

                // 检查响应码
                if (service_rsp->rcode() != RCode::RCODE_OK)
                {
                    ELOG("服务发现失败!%s", errReason(service_rsp->rcode()).c_str());
                    return false;
                }

                // 响应成功,进行本地缓存
                // 响应成功,创建MethodHost对象并保存提供者主机列表
                std::unique_lock<std::mutex> lock(_mutex);
                auto method_host = std::make_shared<MethodHost>(service_rsp->hosts());
                if (method_host->empty())
                {
                    ELOG("%s 服务发现失败!没有能够提供服务的主机!", method.c_str());
                    return false;
                }

                // 选择一个主机并返回
                host = method_host->chooseHost();//注意host是输出型参数,专门用于返回可以提供这个RPC服务的一个主机地址信息
                _method_hosts[method] = method_host;
                //我们这次发现了一个新的服务,那么就需要将它的所有提供者的地址信息存储下来,方便下次调用这个方法的时候可以直接从本地缓存中读取
                return true;
            }

服务上下线的请求处理函数

首先,服务上下线,那么

还是需要去我们这个 服务方法名 -> MethodHost对象的映射 里面作出一些修改

  • 服务上线: 去这个 服务方法名 -> MethodHost对象的映射 里面给对应服务方法添加一个提供者的主机地址信息
  • 服务下线:去这个 服务方法名 -> MethodHost对象的映射 里面给对应服务方法删除掉对提供者的主机地址信息
cpp 复制代码
// 服务上/下线请求处理函数 - 注册中心推送通知时的回调
            void onServiceRequest(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                // 获取操作类型和服务方法名
                auto optype = msg->optype();
                std::string method = msg->method();

                std::unique_lock<std::mutex> lock(_mutex);//加锁

                if (optype == ServiceOptype::SERVICE_ONLINE)//如果操作类型是服务上线
                {
                    // 处理服务上线通知:向对应服务的MethodHost中添加新主机
                    auto it = _method_hosts.find(method);//提供方法名去 服务方法名 -> MethodHost对象的映射 里面获取对应的MethodHost对象,也就是所有提供者的地址信息
                    if (it == _method_hosts.end())//这个方法没有提供者,说明我们上次对这个服务进行服务发现的时候没有提供者,那么说明现在这个提供者是这个服务的第一个提供者
                    {
                        // 如果没有该服务的记录,创建新的MethodHost并添加主机
                        auto method_host = std::make_shared<MethodHost>();
                        method_host->appendHost(msg->host());//往MethodHost对象里面添加一个服务提供者的地址信息
                        _method_hosts[method] = method_host;//往 服务方法名 -> MethodHost对象的映射 里面添加一对键值对
                    }
                    else//这个方法有提供者,说明我们上次对这个服务进行服务发现的时候已经有提供者了
                    {
                        // 如果已有该服务的记录,直接添加主机
                        it->second->appendHost(msg->host());
                    }
                }
                else if (optype == ServiceOptype::SERVICE_OFFLINE)//如果操作类型是服务下线
                {
                    // 处理服务下线通知:从对应服务的MethodHost中移除主机
                    auto it = _method_hosts.find(method);//提供方法名去 服务方法名 -> MethodHost对象的映射 里面获取对应的MethodHost对象,也就是所有提供者的地址信息
                    if (it == _method_hosts.end())//没有对应的记录
                    {
                        return; // 没有该服务的记录,直接返回
                    }
                    //走到这里说明有对应的记录,那么我们就需要将这个服务提供者从映射中删除掉

                    // 从MethodHost中移除主机
                    it->second->removeHost(msg->host());

                    // 调用下线回调函数,通知应用程序
                    _offline_callback(msg->host());
                }
            }

这个思路还是很简单的。

3.4.封装服务发现客户端

我们现在这个模块就是下面这个模块

其实也是对服务发现者的二次封装,将它封装成一个客户端

这个客户端的功能

  • 能与服务中心建立起通信连接
  • 能向服务中心发起服务发现请求(通过一个成员函数完成)
  • 能处理服务中心返回的响应(在构造函数里面完成了)

这个其实还是很简单的

cpp 复制代码
// 服务发现客户端类,用于从服务中心发现服务
        class DiscoveryClient
        {
        public:
            // 智能指针类型别名
            using ptr = std::shared_ptr<DiscoveryClient>;
            // 构造函数,初始化服务发现客户端
            // @param ip: 服务中心服务器的IP地址
            // @param port: 服务中心服务器的端口号
            // @param cb: 服务下线时的回调函数
            DiscoveryClient(const std::string &ip, int port, const Discoverer::OfflineCallback &cb) : _requestor(std::make_shared<Requestor>()),                         // 创建请求器实例
                                                                                                      _discoverer(std::make_shared<client::Discoverer>(_requestor, cb)), // 创建服务发现器实例
                                                                                                      _dispatcher(std::make_shared<Dispatcher>())
            { // 创建调度器实例

                // 设置服务响应回调函数,将响应交给请求器处理
                auto rsp_cb = std::bind(&client::Requestor::onResponse, _requestor.get(),
                                        std::placeholders::_1, std::placeholders::_2);
                _dispatcher->registerHandler<BaseMessage>(MType::RSP_SERVICE, rsp_cb);

                // 设置服务请求回调函数,处理服务请求消息
                auto req_cb = std::bind(&client::Discoverer::onServiceRequest, _discoverer.get(),
                                        std::placeholders::_1, std::placeholders::_2);
                _dispatcher->registerHandler<ServiceRequest>(MType::REQ_SERVICE, req_cb);

                // 设置消息回调函数,将收到的消息交给调度器处理
                auto message_cb = std::bind(&Dispatcher::onMessage, _dispatcher.get(),
                                            std::placeholders::_1, std::placeholders::_2);
                _client = ClientFactory::create(ip, port); // 创建客户端连接
                _client->setMessageCallback(message_cb);   // 设置消息回调
                _client->connect();                        // 连接服务中心服务器
            }
            // 服务发现接口,查找指定方法的服务注册者
            // @param method: 要查找的方法名称
            // @param host: 输出参数,返回服务注册者的地址
            // @return: 发现成功返回true,失败返回false
            bool serviceDiscovery(const std::string &method, Address &host)
            {
                return _discoverer->serviceDiscovery(_client->connection(), method, host);
            }

        private:
            Requestor::ptr _requestor;           // 请求器,管理请求和响应
            client::Discoverer::ptr _discoverer; // 服务发现器,处理服务发现逻辑
            Dispatcher::ptr _dispatcher;         // 调度器,分发和处理消息
            BaseClient::ptr _client;             // 客户端连接,与服务中心通信
        };

现在这个思路非常的完美

相关推荐
半熟的皮皮虾1 小时前
Excel2SQL的自动转SQL工具功能升级
数据库·sql·信息可视化
图扑可视化1 小时前
HT 技术实现数字孪生智慧服务器信息安全监控平台
服务器·信息可视化·数字孪生·三维可视化
m0_561359671 小时前
自动化与脚本
jvm·数据库·python
盐真卿1 小时前
python第五部分:文件操作
前端·数据库·python
鸽芷咕1 小时前
无需额外运维!金仓KES V9一站式承接MongoDB全场景需求
运维·数据库·mongodb
Yeats_Liao1 小时前
负载均衡设计:多节点集群下的请求分发与资源调度
运维·人工智能·深度学习·机器学习·华为·负载均衡
j_xxx404_2 小时前
Linux:进程优先级与进程切换与调度
linux·运维·服务器
三水不滴2 小时前
从原理、场景、解决方案深度分析Redis分布式Session
数据库·经验分享·redis·笔记·分布式·后端·性能优化
never_go_away2 小时前
linux Socket限制
linux·运维·服务器