手打RPC-注册订阅服务

继续上篇内容,本篇讲解注册订阅相关内容。

本例中的RPC注册服务采用应用级服务,一个应用只向注册中心注册一个实例。相比方法级的注册,减少大量的注册信息,便于服务总量能进行估算等。

一. 选择注册中心

这里注册中心选用Nacos,为啥选用nacos呢?

  • nacos 有良好的中文文档
  • 可以当作配置中心和注册中心
  • 较好的后台可视化界面
    客户端选择了2.x版本, 引入依赖,
        <properties>
           <nacos-client.version>2.0.4</nacos-client.version>
        </properties>
   
 		<dependency>
           <groupId>com.alibaba.nacos</groupId>
           <artifactId>nacos-client</artifactId>
           <version>${nacos-client.version}</version>
        </dependency>

二. 注册服务

在上篇末尾提到了ProviderContextStart ,该类实现SmartLifecycle接口,容器启动会执行start方法,停止时会执行stop方法。

整体流程如下:

  1. 获取应用地址
  2. 获取配置信息
  3. 启动一个Netty服务
  4. 获取容器中所有的ProviderBean对象,放入提供者上下文中
  5. 向nacos注册服务
public class ProviderContextStart implements SmartLifecycle, ApplicationContextAware {

   private final Logger log = LoggerFactory.getLogger(getClass());

   private volatile boolean RUNNING = false;

   private ApplicationContext applicationContext;

   private NamingService namingService;

   private RainRpcConfig rpcConfig;


   @Override
   public void start() {
       log.info("准备注册服务");
       try {
           // 获取绑定地址
           String ip = InetAddress.getLocalHost().getHostAddress();
           rpcConfig = applicationContext.getBean(RainRpcConfig.class);
           if (StringUtils.isEmpty(rpcConfig.getApplicationName())) {
               log.info("rpc未配置应用名");
               return;
           }
           // 启动服务类
           new Thread(new NettyServerTask(ip, rpcConfig.getPort())).start();
           // 初始化提供者
           Map<String, ProviderBean> beansOfType = applicationContext.getBeansOfType(ProviderBean.class);
           ProviderContext.PROVIDER_MAP.putAll(beansOfType);
           // 注册应用
           namingService = NamingFactory.createNamingService(rpcConfig.getRegisterAddress());
           namingService.registerInstance(rpcConfig.getApplicationName(), ip, rpcConfig.getPort(), rpcConfig.getCluster());

           RUNNING = true;
       } catch (UnknownHostException | NacosException e) {
           log.error("注册服务异常", e);
       }
       log.info("注册服务成功");
   }


   @Override
   public void stop() {
       log.info("准备注销服务");
       if (namingService != null) {
           try {
               String ip = InetAddress.getLocalHost().getHostAddress();
               namingService.deregisterInstance(rpcConfig.getApplicationName(), ip, rpcConfig.getPort(), rpcConfig.getCluster());

               RUNNING = false;
           } catch (UnknownHostException | NacosException e) {
               log.error("注销服务异常", e);
           }
       }
       log.info("注销服务成功");
   }

   @Override
   public boolean isRunning() {
       return RUNNING;
   }

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
   }
}

ProviderBean 是设置了对接口实现类的引用

public class ProviderBean <T>{

    private Class<T> interfaceClass;

    private T ref;

    private Method[]  methodList;

    /**
     * 获取调用方法
     * @author wu
     * @param simpleMethodName
     * @return {@link Method}
     */
    public  Method getMethod(String simpleMethodName) {
        for (Method method : methodList) {
            if (simpleMethodName.equals(method.getName())) {
                return method;
            }
        }
        return null;
    }

		// ....省略get、set等方法
   
}

三. 引用服务

ConsumerBean 是在上篇自动扫描@RainConsumer时注入容器的。
ConsumerBean 是一个FactoryBean对象,注入容器,会调用getObject()方法,最终调用initRef()。

public class ConsumerBean<T> implements FactoryBean<T>, ApplicationContextAware {

  private final Log logger = LogFactory.getLog(getClass());
  
   /**
   * 服务接口
   */
  private Class<T> interfaceClass;

  private RainConsumer annotation;

  private ApplicationContext applicationContext;

  /**
   * 注册中心地址
   */
  @Value("${rpc.register.address}")
  private String registerAddress;

  private T ref;

  public T initRef() {
      Enhancer enhancer = new Enhancer();
      // 设置enhancer对象的父类
      enhancer.setSuperclass(interfaceClass);

      // 设置enhancer的回调对象
      enhancer.setCallback(new ConsumerMethodInterceptor(annotation,applicationContext));

      // 创建代理对象
      ref = (T) enhancer.create();
      try {
          connect();
      } catch (NacosException e) {
          logger.error("获取服务异常", e);
      }
      return ref;
  }


  /**
   * 连接注册中心获取服务
   *
   * @return
   * @author wu
   */
  public void connect() throws NacosException {
      logger.info("注册中心地址:" + registerAddress);
      // 获取注册中心
      NamingService naming = NamingFactory.createNamingService(registerAddress);
      // 查询健康的服务实例
      List<Instance> allInstances = naming.selectInstances(annotation.name(), Collections.singletonList(annotation.cluster()), true);
      // 服务路径包裹
      ProviderDirectory directory = new ProviderDirectory(annotation.name());

      logger.info("已获取到服务提供者实例" + JSON.toJSONString(allInstances));
      for (Instance instance : allInstances) {
          // 开启新服务
          startNewService(instance, directory);
      }
      // 放入消费者上下文
      ConsumerContext.CLIENT_MAP.putIfAbsent(annotation.name(), directory);

      // 添加监听
      naming.subscribe(annotation.name(), Collections.singletonList(annotation.cluster()), this::subscribe);
  }

  /**
   * 订阅回调事件
   *
   * @param event
   */
  public void subscribe(Event event) {
      logger.info("接收到回调事件:" + JSON.toJSONString(event));
      if (event instanceof NamingEvent) {
          NamingEvent namingEvent = (NamingEvent) event;

          ProviderDirectory providerDirectory = ConsumerContext.CLIENT_MAP.get(namingEvent.getServiceName());
          if (providerDirectory == null) {
              logger.error(namingEvent.getServiceName() + "对应的服务目录不存在");
              return;
          }
          // 对应服务全部下线
          if (CollectionUtils.isEmpty(namingEvent.getInstances())) {
              providerDirectory.clearService();
          }
          // 当前的服务key集合
          Set<String> currentKeyList = providerDirectory.getProviderList()
                                                        .stream()
                                                        .map(ClientHolder::getKey)
                                                        .collect(Collectors.toSet());
          // 注册中心服务key集合
          Map<String, Instance> newInstanceMap = namingEvent.getInstances()
                                                            .stream()
                                                            .collect(Collectors.toMap(item -> ProviderDirectory.generateKey(item.getIp(), item.getPort()), v -> v));
          Set<String> newKeyList = newInstanceMap.keySet();

          List<String> commonKeyList = CollectionUtil.getCommon(currentKeyList, newKeyList);
          // 获取移除的服务
          List<String> removeKeyList = CollectionUtil.getDiff(currentKeyList, commonKeyList);
          // 获取新增的服务
          List<String> saveKeyList = CollectionUtil.getDiff(newKeyList, commonKeyList);

          // 移除服务
          providerDirectory.removeService(removeKeyList);
          // 开启新服务
          startNewService(newInstanceMap, saveKeyList, providerDirectory);
      }
  }

  /**
   * 开启新服务
   *
   * @param newInstanceMap
   * @param saveKeyList
   * @param providerDirectory
   * @return
   */
  private void startNewService(Map<String, Instance> newInstanceMap, List<String> saveKeyList, ProviderDirectory providerDirectory) {
      if (CollectionUtils.isEmpty(saveKeyList)) {
          return;
      }
      for (String key : saveKeyList) {
          Instance instance = newInstanceMap.get(key);
          startNewService(instance, providerDirectory);
      }
  }

  /**
   * 开启新服务
   *
   * @param instance
   * @param providerDirectory
   */
  private void startNewService(Instance instance, ProviderDirectory providerDirectory) {
      NettyClientTask client = new NettyClientTask(instance.getIp(), instance.getPort());
      // 启动服务
      new Thread(client).start();

      // 添加服务
      String serviceKey = ProviderDirectory.generateKey(instance.getIp(), instance.getPort());

      providerDirectory.addService(serviceKey, client);
  }

  @Override
  public T getObject() throws Exception {
      if (ref != null) {
          return ref;
      }
      return initRef();
  }

  // ....省略get、set等方法


}

ProviderDirectory对象持有所有同类型应用的所有实例

public class ProviderDirectory {

    private String applicationName;

    private List<ClientHolder> providerList;

    private final AtomicInteger seq=new AtomicInteger(0);


    public ProviderDirectory(String applicationName) {
        this.applicationName = applicationName;
        this.providerList = new ArrayList<>();
    }

    /**
     * 生成服务key
     * @param ip
     * @param port
     * @return
     */
    public static String generateKey(String ip,int port) {
        return ip + ":" + port;
    }


    /**
     * 获取客户端实例
     * @return
     */
    public NettyClientTask getClient() {
        if (providerList.isEmpty()) {
            return null;
        }
        List<ClientHolder> temp = this.providerList;
        int index = seq.getAndIncrement();
        return temp.get(index % temp.size()).getClient();
    }


    /**
     * 添加服务实例
     * @param serviceKey
     * @param client
     */
    public void addService(String serviceKey,NettyClientTask client) {
        ClientHolder clientHolder = new ClientHolder(serviceKey, client);
        providerList.add(clientHolder);
    }

    /**
     * 清除所有服务
     */
    public void clearService() {
        providerList.clear();
    }

    /**
     * 删除指定服务
     * @param keysList
     */
    public void removeService(List<String> keysList) {
        providerList.removeIf(holder -> keysList.contains(holder.getKey()));
    }

}
public class ClientHolder {

    private String key;

    private NettyClientTask client;

    public ClientHolder(String key,NettyClientTask client) {
        this.key = key;
        this.client = client;
    }

	// 省略get/set
}

ConsumerBean 代理对象调用方法时,会执行ConsumerMethodInterceptor 的intercept方法,此方法用于构造请求对象,发送请求。

public class ConsumerMethodInterceptor implements MethodInterceptor {

    private RainConsumer annotation;

    private Object degradationService;

    private ApplicationContext applicationContext;

    public ConsumerMethodInterceptor(RainConsumer annotation,ApplicationContext applicationContext) {
        this.annotation = annotation;
        this.applicationContext = applicationContext;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Request request = Request.buildRequest(method.getDeclaringClass().getName(), method.getName(), objects);
        return ConsumerContext.invoke(request, annotation.name(),annotation.timeout());
    }


}

四. 上下文对象

ProviderContext 作为服务端上下文,存放了已加载的服务类。
Netty处理完数据流之后,调用exec方法,根据对应的类执行对应的方法。

public class ProviderContext  {

   /**
    * beanName和 bean的对应关系,beanName 为全路径类名
    */
   public static final Map<String, ProviderBean> PROVIDER_MAP = new ConcurrentHashMap<>();


   public static Object exec(String className, String methodName, Object params) throws InvocationTargetException, IllegalAccessException {
       ProviderBean providerBean = PROVIDER_MAP.get(className);
       if (providerBean == null) {
           throw new RuntimeException("服务提供者不存在,调用类:" + className + ",调用方法" + methodName);
       }
       Method method = providerBean.getMethod(methodName);
       // 无参数
       if (method.getParameterTypes().length == 0) {
           return method.invoke(providerBean.getRef());
       }
        // 单个参数
       if (method.getParameterTypes().length == 1) {
           return method.invoke(providerBean.getRef(), params);
       }
       Object[] paramsArr = (Object[]) params;
       // 多个参数
       return method.invoke(providerBean.getRef(), paramsArr);
   }
}

ConsumerContext 是客户端上下文,此处用于发起以及接收rpc的调用结果。

public class ConsumerContext {


    public static final Map<Long, RainFuture> RESULT_MAP = new ConcurrentHashMap<>();

    public static final Map<String, ProviderDirectory> CLIENT_MAP = new ConcurrentHashMap<>();


    public static Object invoke(Request request, String applicationName,Long requestTimeout) throws Exception {
        RainFuture rainFuture = new RainFuture();
        RESULT_MAP.put(request.getId(), rainFuture);

        getChannelFuture(applicationName).channel().writeAndFlush(request);

        Response response = rainFuture.get(requestTimeout, TimeUnit.SECONDS);
        RESULT_MAP.remove(request.getId());
        // 如果出现异常
        if (ResultEnum.ERROR.getCode() == response.getCode()) {
            throw response.getCause();
        }

        return response.getData();
    }


    public static ChannelFuture getChannelFuture(String applicationName) {
        ProviderDirectory providerDirectory = CLIENT_MAP.get(applicationName);
        NettyClientTask client = providerDirectory.getClient();
        if (client == null) {
            throw new RuntimeException("client 客户端不存在");
        }
        while (true) {
            //获取future,线程有等待处理时间
            if (null == client.channelFuture) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            return client.channelFuture;
        }
    }
}

项目详细代码参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值