<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          幾行代碼搞定RPC服務(wù)注冊(cè)和發(fā)現(xiàn)

          共 14407字,需瀏覽 29分鐘

           ·

          2022-06-26 23:18

          前兩篇文章我們把環(huán)境和項(xiàng)目骨干搭建好了之后,從這篇文章開始就進(jìn)入項(xiàng)目編碼階段了。

          在編碼前,需要跟大家說一下,整個(gè)項(xiàng)目是按照一個(gè)一個(gè)功能模塊疊加實(shí)現(xiàn)的,由于文章排版不適合放大塊代碼,文章里我會(huì)截取最關(guān)鍵的代碼給大家講解,想要獲取完整的代碼,可以去 Github 上下載,已經(jīng)正式開源了。

          easy-rpc 開源地址:
          https://github.com/CoderLeixiaoshuai/easy-rpc

          注意:源碼可能會(huì)更新,記得拉取最新的。

          需求分析:服務(wù)注冊(cè)和發(fā)現(xiàn)

          rpc 項(xiàng)目要實(shí)現(xiàn)的第一個(gè)功能模塊就是:服務(wù)注冊(cè)和發(fā)現(xiàn),這個(gè)功能也是整個(gè)框架非常核心和關(guān)鍵的。

          關(guān)于服務(wù)注冊(cè)發(fā)現(xiàn)的介紹和原理,可以看這篇文章:《10 張圖搞懂服務(wù)注冊(cè)發(fā)現(xiàn)機(jī)制

          我們的 rpc 項(xiàng)目不用于生成環(huán)境,造個(gè)輪子嘛,只需要實(shí)現(xiàn)最基礎(chǔ)的功能即可:

          • 服務(wù)實(shí)例注冊(cè)自己的元數(shù)據(jù)到注冊(cè)中心,元數(shù)據(jù)包括:實(shí)例 ip、端口、接口描述等;
          • 客戶端實(shí)例想要調(diào)用服務(wù)端接口會(huì)先連接注冊(cè)中心,發(fā)現(xiàn)待調(diào)用的服務(wù)端實(shí)例;
          • 拿到多個(gè)服務(wù)端實(shí)例后,客戶端會(huì)根據(jù)負(fù)載均衡算法選擇一個(gè)合適的實(shí)例進(jìn)行RPC調(diào)用。

          需求很明確了,下面開始寫代碼。

          引入三方依賴

          市面上靠譜的注冊(cè)中心還是很多的,這次打算同時(shí)兼容兩種注冊(cè)中心:ZookeeperNacos,是不是很良心?!在使用前需要先引入以下依賴。

          與 Zookeeper 交互可以引入對(duì)應(yīng)的 SDK,zkclient 是個(gè)不錯(cuò)的選擇;JSON 序列化和反序列化可以引入 fastjson,雖然經(jīng)常爆漏洞,但是國(guó)產(chǎn)還是得支持下:

          <!-- Zookeeper 客戶端 -->
          <dependency>
              <groupId>com.101tec</groupId>
              <artifactId>zkclient</artifactId>
              <version>0.10</version>
          </dependency>
          <!--Json 序列化反序列-->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>fastjson</artifactId>
              <version>1.2.80</version>
          </dependency>

          至于 Nacos,可以直接引入官方提供的 SDK:nacos-client:

          <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>2.0.3</version>
          </dependency>

          服務(wù)端實(shí)現(xiàn)服務(wù)注冊(cè)

          服務(wù)注冊(cè)和發(fā)現(xiàn)分為兩塊功能:服務(wù)端注冊(cè)和客戶端發(fā)現(xiàn),我們先來實(shí)現(xiàn)服務(wù)端注冊(cè)功能。

          定義服務(wù)注冊(cè)接口

          在日常的工作或者學(xué)習(xí)編碼過程中,我們一定要習(xí)慣面向接口編程,這樣做有助于增強(qiáng)代碼可擴(kuò)展性。

          根據(jù)前面的需求描述,服務(wù)注冊(cè)只需要干一件事情:服務(wù)注冊(cè),我們可以定義一個(gè)接口:ServiceRegistry,接口中定義一個(gè)方法:register,代碼如下:

          public interface ServiceRegistry {
              /**
               * 注冊(cè)服務(wù)信息
               *
               * @param serviceInfo 待注冊(cè)的服務(wù)
               * @throws Exception 異常
               */

              void register(ServiceInfo serviceInfo) throws Exception;

          }

          服務(wù)向注冊(cè)中心注冊(cè),注冊(cè)的內(nèi)容定義一個(gè)類ServiceInfo來封裝。

              /**
               * 服務(wù)名稱
               */

              private String serviceName;

              /**
               * ip 地址
               */

              private String ip;

              /**
               * 端口號(hào)
               */

              private Integer port;

              /**
               * class 對(duì)象
               *
               */

              private Class<?> clazz;

              /**
               * bean 對(duì)象
               */

              private Object obj;
            
              // 省略 get set 方法……
          }

          Zookeeper 實(shí)現(xiàn)服務(wù)注冊(cè)

          我們嘗試用 Zookeeper 來實(shí)現(xiàn)服務(wù)注冊(cè)功能,先新建一個(gè)類實(shí)現(xiàn)前面定義好的服務(wù)注冊(cè)接口:

          public class ZookeeperServiceRegistry implements ServiceRegistry {

          }

          接下來重寫register方法,主要功能包括調(diào)用 Zookeeper 接口創(chuàng)建服務(wù)節(jié)點(diǎn)和實(shí)例節(jié)點(diǎn)。其中服務(wù)節(jié)點(diǎn)是一個(gè)永久節(jié)點(diǎn),只用創(chuàng)建一次;實(shí)例節(jié)點(diǎn)是臨時(shí)節(jié)點(diǎn),如果實(shí)例故障下線,實(shí)例節(jié)點(diǎn)會(huì)自動(dòng)刪除。

          // ZookeeperServiceRegistry.java

              @Override
              public void register(ServiceInfo serviceInfo) throws Exception {
                  logger.info("Registering service: {}", serviceInfo);

                  // 創(chuàng)建 ZK 永久節(jié)點(diǎn)(服務(wù)節(jié)點(diǎn))
                  String servicePath = "/com/leixiaoshuai/easyrpc/" + serviceInfo.getServiceName() + "/service";
                  if (!zkClient.exists(servicePath)) {
                      zkClient.createPersistent(servicePath, true);
                  }

                  // 創(chuàng)建 ZK 臨時(shí)節(jié)點(diǎn)(實(shí)例節(jié)點(diǎn))
                  String uri = JSON.toJSONString(serviceInfo);
                  uri = URLEncoder.encode(uri, "UTF-8");
                  String uriPath = servicePath + "/" + uri;
                  if (zkClient.exists(uriPath)) {
                      zkClient.delete(uriPath);
                  }
                  zkClient.createEphemeral(uriPath);
              }

          代碼非常簡(jiǎn)單,大家看注釋就能懂了。

          Nacos 實(shí)現(xiàn)服務(wù)注冊(cè)

          除了使用 Zookeeper 來實(shí)現(xiàn),我們還可以使用 Nacos,跟上面一樣我們還是先建一個(gè)類:

          public class NacosServiceRegistry implements ServiceRegistry {

          }

          接著編寫構(gòu)造方法,NacosServiceRegistry 類被實(shí)例化之后 Nacos 客戶端也要連接上 Nacos 服務(wù)端。

          // NacosServiceRegistry.java

          public NacosServiceRegistry(String serverList) throws NacosException {
              // 使用工廠類創(chuàng)建注冊(cè)中心對(duì)象,構(gòu)造參數(shù)為 Nacos Server 的 ip 地址,連接 Nacos 服務(wù)器
              naming = NamingFactory.createNamingService(serverList);
              // 打印 Nacos Server 的運(yùn)行狀態(tài)
              logger.info("Nacos server status: {}", naming.getServerStatus());
          }

          獲得NamingService類的實(shí)例對(duì)象后,就可以調(diào)用實(shí)例注冊(cè)接口完成服務(wù)注冊(cè)了。

          // NacosServiceRegistry.java

          @Override
          public void register(ServiceInfo serviceInfo) throws Exception {
              // 注冊(cè)當(dāng)前服務(wù)實(shí)例
              naming.registerInstance(serviceInfo.getServiceName(), buildInstance(serviceInfo));
          }

          private Instance buildInstance(ServiceInfo serviceInfo) {
              // 將實(shí)例信息注冊(cè)到 Nacos 中心
              Instance instance = new Instance();
              instance.setIp(serviceInfo.getIp());
              instance.setPort(serviceInfo.getPort());
              // TODO add more metadata
              return instance;
          }

          注意:NamingService 類提供了很多有用的方法,大家可自行進(jìn)行嘗試。

          客戶端實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)

          定義服務(wù)發(fā)現(xiàn)接口

          前面已經(jīng)將服務(wù)實(shí)例注冊(cè)到Zookeeper 或者 Nacos 服務(wù)端,現(xiàn)在客戶端想要調(diào)用服務(wù)端首先得獲得服務(wù)端實(shí)例列表,這個(gè)過程其實(shí)就是服務(wù)發(fā)現(xiàn)

          我們先定義一個(gè)抽象的接口,這個(gè)接口主要的功能就是定義一個(gè)獲取服務(wù)實(shí)例的接口:

          public interface ServiceDiscovery {

              /**
               * 通過服務(wù)名稱隨機(jī)選擇一個(gè)健康的實(shí)例
               * @param serviceName 服務(wù)名稱
               * @return 實(shí)例對(duì)象
               */

              InstanceInfo selectOneInstance(String serviceName);

          }

          隨機(jī)挑選一個(gè)實(shí)例是為了模擬負(fù)載均衡,盡量使請(qǐng)求均勻分配到各實(shí)例上。

          Zookeeper 實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)

          前面使用 Zookeeper 實(shí)現(xiàn)了服務(wù)注冊(cè)功能,這里我們?cè)儆?Zookeeper 來實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)功能,先定義一個(gè)類實(shí)現(xiàn) ServiceDiscovery 接口:

          public class ZookeeperServiceDiscovery implements ServiceDiscovery {

          }

          下面實(shí)現(xiàn)核心方法:selectOneInstance

          Zookeeper 內(nèi)部是一個(gè)樹形的節(jié)點(diǎn),通過查找一個(gè)指定節(jié)點(diǎn)的所有子節(jié)點(diǎn)即可獲得服務(wù)實(shí)例列表。拿到服務(wù)實(shí)例列表后如何挑選出一個(gè)實(shí)例呢?這里就可以引入負(fù)載均衡算法了。

          // ZookeeperServiceDiscovery.java

          @Override
          public InstanceInfo selectOneInstance(String serviceName) {
              String servicePath = "/com/leixiaoshuai/easyrpc/" + serviceName + "/service";
              final List<String> childrenNodes = zkClient.getChildren(servicePath);

              return Optional.ofNullable(childrenNodes)
                      .orElse(new ArrayList<>())
                      .stream()
                      .map(node -> {
                          try {
                              // 將服務(wù)信息經(jīng)過 URL 解碼后反序列化為對(duì)象
                              String serviceInstanceJson = URLDecoder.decode(node, "UTF-8");
                              return JSON.parseObject(serviceInstanceJson, InstanceInfo.class);
                          } catch (UnsupportedEncodingException e) {
                              logger.error("Fail to decode", e);
                          }
                          return null;
                      }).filter(Objects::nonNull).findAny().get();
          }

          注意:當(dāng)前項(xiàng)目?jī)H僅用于學(xué)習(xí)用,這里沒有引入復(fù)雜的負(fù)載均衡算法,有興趣的同學(xué)可自行補(bǔ)充,歡迎提交 MR 貢獻(xiàn)代碼。

          Nacos 實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)

          最后來到 Nacos 的實(shí)現(xiàn),話不多說先定義一個(gè)類:

          public class NacosServiceDiscovery implements ServiceDiscovery {
          }

          同樣也需要實(shí)現(xiàn)核心方法:selectOneInstance。Nacos 的實(shí)現(xiàn)就比較簡(jiǎn)單了,因?yàn)?Nacos 官方提供的 SDK 功能太強(qiáng)大了,我們直接調(diào)用對(duì)應(yīng)的接口就可以了,Nacos 根據(jù)算法會(huì)隨機(jī)挑選一個(gè)健康的實(shí)例,我們不用關(guān)注細(xì)節(jié)。

          // ZookeeperServiceDiscovery.java

          @Override
          public InstanceInfo selectOneInstance(String serviceName) {
              Instance instance;
              try {
                  // 調(diào)用 nacos 提供的接口,隨機(jī)挑選一個(gè)服務(wù)實(shí)例,負(fù)載均衡的算法依賴 nacos 的實(shí)現(xiàn)
                  instance = namingService.selectOneHealthyInstance(serviceName);
              } catch (NacosException e) {
                  logger.error("Nacos exception", e);
                  return null;
              }

              // 封裝實(shí)例對(duì)象返回
              InstanceInfo instanceInfo = new InstanceInfo();
              instanceInfo.setServiceName(instance.getServiceName());
              instanceInfo.setIp(instance.getIp());
              instanceInfo.setPort(instance.getPort());
              return instanceInfo;
          }

          源碼清單

          服務(wù)注冊(cè)和發(fā)現(xiàn)所用到的源碼清單如下:

          ├── easy-rpc-example
          ├── easy-rpc-spring-boot-starter
          │   ├── pom.xml
          │   ├── src
          │   │   └── main
          │   │       ├── java
          │   │       │   └── com
          │   │       │       └── leixiaoshuai
          │   │       │           └── easyrpc
          │   │       │               ├── client
          │   │       │               │   ├── ClientProxyFactory.java
          │   │       │               │   ├── discovery
          │   │       │               │   │   ├── NacosServiceDiscovery.java
          │   │       │               │   │   ├── ServiceDiscovery.java
          │   │       │               │   │   └── ZookeeperServiceDiscovery.java
          │   │       │               ├── common
          │   │       │               │   └── InstanceInfo.java
          │   │       │               └── server
          │   │       │                   └── registry
          │   │       │                       ├── NacosServiceRegistry.java
          │   │       │                       ├── ServiceRegistry.java
          │   │       │                       └── ZookeeperServiceRegistry.java

          完整的源碼可以自行去 Github 上取:

          https://github.com/CoderLeixiaoshuai/easy-rpc

          小結(jié)

          本文以較少的代碼實(shí)現(xiàn)了 RPC 框架實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn)功能,相信大家對(duì)這個(gè)流程已經(jīng)全面掌握了。

          客戶端與服務(wù)端通信的前提是需要知道對(duì)方的 ip 和端口,服務(wù)注冊(cè)就是將自己的元信息(ip、端口等)注冊(cè)到注冊(cè)中心(Registry),這樣客戶端就可以從注冊(cè)中心(Registry)獲取自己"感興趣"的服務(wù)實(shí)例了。

          服務(wù)注冊(cè)和發(fā)現(xiàn)機(jī)制可以通過一些中間件來輔助實(shí)現(xiàn),如比較流行的:Zookeeper或者 Nacos 等。

          -- END --

          推薦學(xué)習(xí):

          好了,今天的技術(shù)文就到這里了。我是雷小帥,一個(gè)死磕技術(shù)的理工男,如果本文對(duì)你有幫助,麻煩點(diǎn)贊、分享、在看支持一下,非常感謝~ 

          你的支持就是我前進(jìn)的動(dòng)力!

          下期見!

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  超碰凹凸| 国产免费一区二区三区四区午夜视频 | 国产精品一卡2卡3卡4卡5卡免费网站 | 中文无码在线免费播放 | 久色AV射 |