手写RPC框架(四)

v1.2

更新事项 以下更新均在非阻塞模块进行更新,阻塞模块可供读者自己尝试

  • 进一步减少用户使用的复杂感 之前分辨方法是通过判断传递的字符串、类型中的方法编号字段来抉择的,这次的话,将更加精简,使用注册中心,目前每个服务提供者每人只提供一个服务,其他的日后再完善

    • 首先我做的是将zookeeper的地址设置为常量这样,之后每次在创建zookeeper连接中地址参数可以直接拿,同时修改起来也方便,把sessionTimeout也设为常量

      package constants;
      
      public class RpcConstants {
          //zookeeper服务器连接地址
          public static String ZOOKEEPER_ADDRESS = "zytCentos:2181";
          //超时时间
          public static int ZOOKEEPER_SESSION_TIMEOUT = 2000;
      }
      
      
    • 实现服务提供端将对应地址注册到zookeeper中

      package zkService;
      
      import constants.RpcConstants;
      import org.apache.zookeeper.*;
      import org.apache.zookeeper.data.Stat;
      
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      
      
      //该类将对应服务端的方法和相应的端口和地址,注册到zooKeeper中
      public class ZkServiceRegistry {
          private static String connectString = RpcConstants.ZOOKEEPER_ADDRESS;
          private static int sessionTimeout = RpcConstants.ZOOKEEPER_SESSION_TIMEOUT;
          private static ZooKeeper zooKeeper;
      
          static void createConnect() throws IOException {
              zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                  @Override
                  public void process(WatchedEvent watchedEvent) {
      
                  }
              });
          }
      
          //创建成功后把方法注册进去
          static void register(String RpcServiceName,String hostname,int port) throws InterruptedException, KeeperException {
              //节点名就是方法名  然后对应的数据就是hostname+":"+port
      
      
              //因为这个地区属于一个临界区 可能会发生线程不安全问题 所以进行上🔒
              synchronized (ZkServiceRegistry.class) {
                  Stat exists = zooKeeper.exists("/service", null);
                  if (exists ==null) {
                      zooKeeper.create("/service",
                              "".getBytes(StandardCharsets.UTF_8),
                              ZooDefs.Ids.OPEN_ACL_UNSAFE,
                              CreateMode.PERSISTENT
                      );
                  }
              }
      
              String date = hostname+":"+port;
      
              //权限目前都设置为全放开   创建方式均为持久化
              zooKeeper.create("/service/"+RpcServiceName,
                      date.getBytes(StandardCharsets.UTF_8),
                      ZooDefs.Ids.OPEN_ACL_UNSAFE,
                      CreateMode.PERSISTENT
                      );
          }
      
          /**
           *
           * @param RpcServiceName 这是对应的服务名
           * @param hostname 和可以调用该服务的ip
           * @param port  还有对应的端口号
           * @throws IOException
           * @throws InterruptedException
           */
          public static void registerMethod(String RpcServiceName,String hostname,int port) throws IOException, InterruptedException, KeeperException {
              //先创建对应的额zooKeeper连接客户端再进行相应的注册
              createConnect();
              register(RpcServiceName,hostname,port);
              System.out.println("服务端:"+hostname+":"+port+"方法注册完毕");
      
          }
      }
      
      

      进行测试

      存在问题 测试出现问题 启动两个服务只有一个服务的方法注册进去了;
      解决方法 因为我每个服务提供方到后面都是循环着监听 所以第一个进去了就出不来立刻 开了多个线程

      存在问题 就是会重复进行创建service节点 应该是引入了线程安全的问题 就是单例模式懒汉模式1中类似的问题
      解决方法 对方法进行加锁 使用了synchronized锁,该锁jdk1.6之后进行了优化可以使用!

      成功解决

    • 服务提供方启动代码如下(遇到了线程安全问题 之后尝试如何进一步优化)

      /*
          以nio为网络编程框架的服务提供端启动类  加入了zk  遇到了线程安全问题 成功解决了
       */
      public class NIOProviderBootStrap12 {
          public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
              //启动
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //因为每个服务提供端内部都是在监听循环阻塞 每个开启一个线程进行监听
                      try {
                          NIONonBlockingServer12hello.start(6666);
                      } catch (IOException e) {
                          e.printStackTrace();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } catch (KeeperException e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
      
              //启动
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          NIONonBlockingServer12bye.start(6667);
                      } catch (IOException e) {
                          e.printStackTrace();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } catch (KeeperException e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
          }
      }
      
      
    • 实现消费者端获取所需服务对应的地址

      //通过方法名 反过来获取服务对应的地址
      public class ZkServiceDiscovery {
          private static String connectString = RpcConstants.ZOOKEEPER_ADDRESS;
          private static int sessionTimeout = RpcConstants.ZOOKEEPER_SESSION_TIMEOUT;
          private static ZooKeeper zooKeeper;
      
          //第一步当然是连接到远端服务器上了
          public static void getConnect() throws IOException {
              zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                  @Override
                  public void process(WatchedEvent watchedEvent) {
      
                  }
              });
          }
      
          // 根据所请求的服务地址 获取对应的远端地址
          public static String getMethodAddress(String methodName) throws RpcException, InterruptedException, KeeperException {
      
              //判断节点中是否存在对应路径  不存在则抛出异常
              if (zooKeeper.exists("/service/"+methodName,null)==null)
              {
                  System.out.println("不存在该方法");
                  throw new RpcException();
              }
      
              //到对应节点中获取地址   stat节点状态信息变量
              byte[] data = zooKeeper.getData("/service/" + methodName, false,null);
              String address = new String(data);
              return address;
          }
      
          public static void getStart(String methodName) throws IOException, RpcException, InterruptedException, KeeperException {
              //先进行连接
              getConnect();
              //获取相应的远端地址
              String methodAddress = getMethodAddress(methodName);
              //进行连接
              String[] strings = methodAddress.split(":");
              //启动
              String address = strings[0];
              int port = Integer.valueOf(strings[1]);
              NIONonBlockingClient12.start(address,port);
          }
      }
      
  • 用代理模式 让用户感觉在调用本地方法一样,调用远端方法。

    • 前面的改动尚不能实现,需要加上现在下面这一部分,代理模式,才能更好的实现用户的远端调用

    • 设计被代理用户类

      //这是之后要被代理的对象 我们会实现它的方法
      public interface Customer {
          String sayBye(String saying);
          String sayHello(String saying);
      }
      
    • 设计代理类 //实现了调用后回传的功能 像本身调用一样

      package consumer.proxy;
      
      import consumer.zkService.ZkServiceDiscovery;
      
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      //代理类的实现
      public class RpcClientProxy {
      
          //获取代理对象 并返回 当前类别
          public static Object getBean(final Class<?> serviceClass){
              /*
                  参数详解
                  1、用哪个类加载器去加载对象
                  2、动态代理类需要实现的接口 class[]{xxxx.class} 得到的就是对应的类别
                  3、动态代理类执行方法的时候需要干的事
               */
              return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                      new Class[]{serviceClass},
                      new InvocationHandler() {
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              //暂时还没有设置回信这个操作
                              String methodName = method.getName();
                              String response = ZkServiceDiscovery.getStart(methodName, (String) args[0]);
                              return response;
                          }
                      }
              );
          }
      }
      
    • 修改了消费者端的方法,和之前的功能不一样了,之前是属于在控制台输入 持续的回复,这次改为一次调用得到回复,像调用自己的方法一样

      package consumer.nio;
      
      import java.io.IOException;
      import java.net.InetSocketAddress;
      import java.nio.ByteBuffer;
      import java.nio.channels.SelectionKey;
      import java.nio.channels.Selector;
      import java.nio.channels.SocketChannel;
      import java.nio.charset.StandardCharsets;
      import java.util.Iterator;
      
      
      //v1.2版本非阻塞nio   真正意义上实现了rpc调用
      public class NIONonBlockingClient12 {
          public static String start(String HostName, int PORT,String msg) throws IOException{
              return start0(HostName,PORT,msg);
          }
      
          //真正启动在这
          private static String start0(String hostName, int port,String msg) throws IOException {
              //得到一个网络通道
              SocketChannel socketChannel = SocketChannel.open();
              System.out.println("-----------服务消费方启动-------------");
              socketChannel.configureBlocking(false);
              //建立链接  非阻塞连接  但我们是要等他连接上
              if (!socketChannel.connect(new InetSocketAddress(hostName,port))) {
                  while (!socketChannel.finishConnect());
              }
              //创建选择器 进行监听读事件
              Selector selector = Selector.open();
              socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
      
              //进行发送 发的太快了 来不及收到
              socketChannel.write(ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)));
      
              //直接进行监听
              while (true)
              {
                  //捕获异常  监听读事件
                  try {
                      if (selector.select(1000)==0)
                      {
                          continue;
                      }
                      Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                      while (keyIterator.hasNext())
                      {
                          SelectionKey key = keyIterator.next();
                          ByteBuffer buffer = (ByteBuffer)key.attachment();
                          SocketChannel channel = (SocketChannel)key.channel();
                          int read = 1;
                          //用这个的原因是怕 多线程出现影响
                          StringBuffer stringBuffer = new StringBuffer();
                          while (read!=0)
                          {
                              buffer.clear();
                              read = channel.read(buffer);
                              stringBuffer.append(new String(buffer.array(),0,read));
                          }
                          return stringBuffer.toString();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
      
    • 启动类代码

      package consumer.bootstrap;
      
      
      import consumer.proxy.RpcClientProxy;
      import method.Customer;
      
      
      import java.io.IOException;
      
      /*
          以nio为网络编程框架的消费者端启动类
       */
      public class NIOConsumerBootStrap12 {
          public static void main(String[] args) throws IOException {
      
              RpcClientProxy clientProxy = new RpcClientProxy();
              Customer customer = (Customer) clientProxy.getBean(Customer.class);
              String response = customer.hello("success");
              System.out.println(response);
              System.out.println(customer.bye("fail"));
          }
      }
      
    • 实现效果

    在这里插入图片描述

    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值