java网络编程实战 - 运用TCP通讯思想,手写设计实现可定制、复用和扩展的RPC服务调用框架

前言

RPC(Remote Procedure Call)远程过程调用是一种思想,也是目前微服务调用领域的主流开发模式。就底层的网络协议来说,TCP、UDP、HTTP等协议均可以设计实现我们需要的RPC。其中基于TCP协议实现的RPC框架在可靠性、性能和可伸缩性等方面都更胜一筹,其中Dubbo、GRPC为代表(虽然其基于Netty通信框架,但其底层均为TCP协议)。

image.png


RPC框架交互流程

image.png

根据以上流程我们可以形成以下开发思路

  1. 服务定义端负责约定通用的业务接口和公共的数据结构;

  2. 服务业务实现端负责实现业务接口,并在启动时,注册到RPC注册中心;

  3. 客户端可使用代理调用业务服务;

  4. 客户端 代码把调用对象、方法和参数等序列化成二进制码传输;

  5. 客户端代理与服务端通过Socket通讯传递数据报文;

  6. 服务端反序列化数据报成对象,解析方法和参数等;

  7. 服务端代理拿到对象和参数后,通过反射得到服务实例,然后调用对应接口方法;


根据以上的大体思路,我们能不能也基于TCP定制设计我们的RPC框架呢 ? 答案是肯定的。

那要手写实现一个RPC框架需要考虑哪些问题 ?

  1. 如何使我们的服务通用的问题 ;

  2. 数据对象序列化与反序列化问题;

  3. 通讯协议和机制问题 ;

  4. 服务的实例化问题 ;

  5. 服务缓存方案 ;

  6. 服务负载方案 ;

    ... ...

这些都是我们在一个RCP框架中需要妥善解决的问题。OK, 那我们就来一步一步实现我们的想法。


手写设计实现

第一部分 服务接口定义

package com.wavebeed.serviceapi;

/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:系列业务接口定义
* */

//这部分比较简单,为了不占用文章篇幅,具体代码先省略...


第二部分 服务生产端部分


/**
* @author andychen https://blog.51cto.com/14815984
* @description:RPC服务端框架部分
* */
public class ProducerFramework {
   //服务监听端口
   private final int port;
   //服务在服务端缓存
   private final static Map<String,Class<?>> cachedService = new ConcurrentHashMap<>();
   //服务处理任务池
   private final static ExecutorService taskPool = Executors.newFixedThreadPool(
           Runtime.getRuntime().availableProcessors()+1);
   public ProducerFramework(int port) {this.port = port;}

   /**
    * 注册服务核心业务
    * @param service 服务接口类
    * @param serviceImpl 服务实现类
    */
   public void register(Class<?> service, Class<?> serviceImpl) {
       InputStream inputStream = null;
       OutputStream outputStream = null;
       Socket socket = new Socket();
       try {
           //连接注册中心
           socket.connect(new InetSocketAddress(Constant.REG_CENTER_HOST, Constant.REG_CENTER_PORT));
           //服务名称
           String serviceName = service.getName();
           outputStream = new ObjectOutputStream(socket.getOutputStream());
           ((ObjectOutputStream) outputStream).writeUTF(serviceName);
           //标记服务注册
           ((ObjectOutputStream) outputStream).writeBoolean(false);
           //服务所在主机
           ((ObjectOutputStream) outputStream).writeUTF(Constant.SERVER_HOST);
           //服务监听端口
           ((ObjectOutputStream) outputStream).writeInt(this.port);
           //刷服务到注册中心
           outputStream.flush();
           //获取注册结果
           inputStream = new ObjectInputStream(socket.getInputStream());
           if (((ObjectInputStream) inputStream).readBoolean()) {
               //缓存在服务端
               cachedService.put(serviceName, serviceImpl);
               System.out.println("服务[" + serviceName + "],注册成功!");
           } else {
               System.out.println("服务[" + serviceName + "],注册失败,请查看日志!");
           }
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           try {
               if (null != inputStream) {
                   inputStream.close();
                   inputStream = null;
               }
               if (!socket.isClosed()) {
                   socket.close();
               }
           } catch (IOException e) {
               e.printStackTrace();
           }
           socket = null;
       }
   }

   /**
    * 服务运行
    */
   public void run() throws IOException {
       ServerSocket serverSocket = new ServerSocket();
       try {
           serverSocket.bind(new InetSocketAddress(this.port));
           System.out.println("服务器端口:["+this.port+"]开始运行..");
           for (;;){
               Socket socket = serverSocket.accept();
               taskPool.execute(new ServiceRunTask(socket));
           }
       } catch (IOException e) {
           //停止服务
           this.stop();
           e.printStackTrace();
       }finally {
           if(!serverSocket.isClosed()){
               serverSocket.close();
           }
           serverSocket = null;
       }
   }

   /**
    * 停止服务
    */
   public void stop(){
       try {
           cachedService.clear();
           taskPool.shutdown();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

   /**
    *服务运行池化任务
    * @param consumer 消费端连接
    */
   private static class ServiceRunTask implements Runnable{
       //消费端连接
       private Socket consumer = null;
       public ServiceRunTask(Socket consumer) {this.consumer = consumer;}

       /**
        * 服务运行核心业务
        */
       @Override
       public void run() {
           try(InputStream inputStream = new ObjectInputStream(this.consumer.getInputStream());
               OutputStream outputStream = new ObjectOutputStream(this.consumer.getOutputStream())){
               //服务名
               String name = ((ObjectInputStream) inputStream).readUTF();
               //方法名称
               String methodName = ((ObjectInputStream) inputStream).readUTF();
               //方法参数类型
               Class<?>[] parmTypes = (Class<?>[])((ObjectInputStream) inputStream).readObject();
               //方法参数
               Object[] args = (Object[]) ((ObjectInputStream) inputStream).readObject();
               //本地服务缓存
               Class<?> serviceClass = cachedService.get(name);
               if(null == serviceClass){
                   throw new ClassNotFoundException("服务:["+name+"] 不存在或过期");
               }

               //服务方法
               Method method = serviceClass.getMethod(methodName, parmTypes);
               //调用结果
               Object result = method.invoke(serviceClass.newInstance(), args);
               //回刷处理结果到对端
               ((ObjectOutputStream) outputStream).writeObject(result);
               outputStream.flush();
           }catch (Exception e){
               e.printStackTrace();
           }finally {
               if(null != this.consumer){
                   if(!this.consumer.isClosed()){
                       try {
                           this.consumer.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                   this.consumer = null;
               }
           }
       }
   }
}

package com.wavebeed.producer.service;

/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:系列服务实现部分
* */
//这部分比较简单,为了不占用文章篇幅,具体代码先省略...

/**
* @author andychen https://blog.51cto.com/14815984
* @description:RPC服务启动器
* */
public class ProducerStarter {
   //============业务服务端口配置(推荐放到配置介质中)======================
   //微信服务监听端口
   private static final int WX_SERV_PORT = 9000;
   //报表服务监听端口1
   private static final int RPT_SERV_PORT1  = 9001;
   //报表服务监听端口2
   private static final int RPT_SERV_PORT2  = 9002;
   /**
    * 注册和运行相关服务
    * @param args
    */
   public static void main(String[] args) {
       //(1)微信服务注册和运行
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   ProducerFramework framework = new ProducerFramework(WX_SERV_PORT);
                   framework.register(SendWeiXinService.class, SendWeiXinServiceImpl.class);
                   framework.run();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       //(2)报表服务注册和运行1
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   //报表服务1
                   ProducerFramework framework = new ProducerFramework(RPT_SERV_PORT1);
                   framework.register(ReportingService.class, ReportingServiceImpl.class);
                   framework.run();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       //(3)报表服务注册和运行2
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   //报表服务2
                   ProducerFramework framework2 = new ProducerFramework(RPT_SERV_PORT2);
                   framework2.register(ReportingService.class, ReportingServiceImpl.class);
                   framework2.run();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }).start();
   }
}


第三部分 服务注册中心

/**
* @author andychen https://blog.51cto.com/14815984
* @description:注册中心核心实现
* 服务提供者启动时将服务端的服务注册到注册中心以及服务的管理等
* */
public class RegisterCenterFramework {
   //注册中心监听端口
   private final int port;
   //注册中心服务池,负责维护注册的服务(这里只做简要的实现,提供思路)
   private final static Map<String, Set<ServiceAddress>> servicePool = new ConcurrentHashMap<>();
   //定义服务任务池,负责执行服务
   private final static ExecutorService taskPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1);
   public RegisterCenterFramework(int port) {this.port=port;}

   /**
    * 启动服务
    */
   public void start(){
       try {
           ServerSocket serverSocket = new ServerSocket();
           serverSocket.bind(new InetSocketAddress(this.port));
           System.out.println("注册中心服务开始运行...");
           for (;;){
                Socket socket = serverSocket.accept();
                taskPool.execute(new RegisteTask(socket));
           }
       } catch (IOException e) {
           //停止服务
           this.stop();
           e.printStackTrace();
       }
   }

   /**
    * 停止服务
    */
   public void stop(){
       try {
           servicePool.clear();
           taskPool.shutdown();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

   /**
    * 注册服务
    * */
   private static void registeService(String name, ServiceAddress address){
       Lock lock = new ReentrantLock();
       try{
           lock.lock();
           Set<ServiceAddress> service = servicePool.get(name);
           if(null == service){
               service = new HashSet<>();
               servicePool.put(name, service);
           }
           service.add(address);
           System.out.println("服务:["+name+"]注册完成");
       }finally {
           lock.unlock();
       }
   }
   /**
    * 获取服务
    * */
   private static Set<ServiceAddress> getService(String name){
      return servicePool.get(name);
   }

   /**
    * 处理服务注册请求
    */
   private static class RegisteTask implements Runnable {
       private Socket register = null;

       public RegisteTask(Socket register) {
           this.register = register;
       }

       /**
        * 实现服务注册核心业务
        */
       @Override
       public void run() {
           try (InputStream inputStream = new ObjectInputStream(this.register.getInputStream());
                OutputStream outputStream = new ObjectOutputStream(this.register.getOutputStream())) {
                //服务名称
                String name = ((ObjectInputStream) inputStream).readUTF();
               //检查当前请求是否获取
                boolean isGet = ((ObjectInputStream) inputStream).readBoolean();
                if(isGet){
                    //获取存在服务信息
                    Set<ServiceAddress> service = getService(name);
                    //回写消费端
                    ((ObjectOutputStream) outputStream).writeObject(service);
                    outputStream.flush();
                    System.out.println("服务["+name+"]信息已返回消费端!");
                }
                //服务注册
                else{
                    //服务端主机
                    String serviceHost = ((ObjectInputStream) inputStream).readUTF();
                    //服务端
                    int servicePort = ((ObjectInputStream) inputStream).readInt();
                    //注册
                    registeService(name, new ServiceAddress(serviceHost,servicePort));
                    ((ObjectOutputStream) outputStream).writeBoolean(true);//已注册
                    outputStream.flush();//刷消息到对端
                }
           } catch (Exception e) {
               e.printStackTrace();
           } finally {
               if(null != this.register){
                   if(!this.register.isClosed()){
                       try {
                           this.register.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                   this.register = null;
               }
           }
       }
   }
}

package com.wavebeed.regcenter;

/**
* @author andychen https://blog.51cto.com/14815984
* @description:注册中心启动服务类
*/
public class RegisterCenterStarter {
   //注册中心监听端口, 这里推荐从配置获取
   public static final int REG_CENTER_PORT = 8888;

   public static void main(String[] args) {
       new Thread(new Runnable(){
           public void run() {
               new RegisterCenterFramework(REG_CENTER_PORT).start();
           }
       }).start();
   }
}


第四部分 服务消费端部分

/**
* @author andychen https://blog.51cto.com/14815984
* @description:服务消费端框架部分
* */
public class ConsumerFramework {
   /**
    * 获取远程服务对象
    * @param serviceInterface 服务接口类
    * @param <T> 服务类型
    * @return 服务对象
    */
    public static <T> T getServiceObject(Class<?> serviceInterface){
        InetSocketAddress address = new InetSocketAddress(Constant.REG_CENTER_HOST, Constant.REG_CENTER_PORT);
        T instance = (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
                                           new Class<?>[]{serviceInterface},
                                           new ServiceInvocationHandler(serviceInterface, address));
        return instance;
    }
   
   /**
    * 远程服务代理核心业务
    */
   private static class ServiceInvocationHandler implements InvocationHandler {
         //请求的服务接口类
         private Class<?> serviceInterface = null;
         //注册中心地址
         private InetSocketAddress centerAddress = null;

         //服务地址(同一个服务可在不同端口部署多份)
         private ServiceAddress[] serviceAddresses = null;

        public ServiceInvocationHandler(Class<?> serviceInterface, InetSocketAddress centerAddress) {
            this.serviceInterface = serviceInterface;
            this.centerAddress = centerAddress;
        }

       /**
        * 代理核心业务实现
        * @param proxy 代理对象
        * @param method 调用方法
        * @param args 方法参数
        * @return 方法返回
        * @throws Throwable
        */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Socket socket = null;
            try {
                //从注册中心获取服务
                if (null == this.serviceAddresses) {
                    socket = new Socket();
                    socket.connect(this.centerAddress);
                    if (socket.isConnected()) {
                        try (OutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                             InputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
                            //服务名
                            ((ObjectOutputStream) outputStream).writeUTF(this.serviceInterface.getName());
                            //注册标记
                            ((ObjectOutputStream) outputStream).writeBoolean(true);
                            //刷到注册中心
                            outputStream.flush();

                            //获取注册结果
                            Set<ServiceAddress> result = (Set<ServiceAddress>) ((ObjectInputStream) inputStream).readObject();
                            //缓存结果
                            this.serviceAddresses = new ServiceAddress[result.size()];
                            result.toArray(this.serviceAddresses);
                        } finally {
                            if (!socket.isClosed()) {
                                socket.close();
                            }
                            socket = null;
                        }
                    }
                }
                /**
                 * 访问对应服务
                 * 若同一服务在多个服务器或端口部署,推荐采用负载算法,这里为了演示暂随机获取一台访问
                 */
                int servIndex = new Random().nextInt(this.serviceAddresses.length);
                String servHost = this.serviceAddresses[servIndex].getHost();
                int servPort = this.serviceAddresses[servIndex].getPort();
                InetSocketAddress servAddress = new InetSocketAddress(servHost, servPort);
                socket = new Socket();
                socket.connect(servAddress);
                if (socket.isConnected()) {
                    try (OutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                         InputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
                        if (socket.isConnected()) {
                            //服务名
                            ((ObjectOutputStream) outputStream).writeUTF(this.serviceInterface.getName());
                            //方法名
                            ((ObjectOutputStream) outputStream).writeUTF(method.getName());
                            //方法参数类型
                            ((ObjectOutputStream) outputStream).writeObject(method.getParameterTypes());
                            //方法参数
                            ((ObjectOutputStream) outputStream).writeObject(args);
                            outputStream.flush();

                            //获取返回结果
                            return ((ObjectInputStream) inputStream).readObject();
                        }
                    } finally {
                        if (!socket.isClosed()) {
                            socket.close();
                        }
                        socket = null;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

/**
* @author andychen https://blog.51cto.com/14815984
* @description:服务消费者调用入口
* */
public class ConsumerCaller {
   /**
    * 服务调用入口
    * @param args
    */
   public static void main(String[] args) {
       try {
           //(1)访问发送微信服务
           SendWeiXinService wxService = ConsumerFramework.getServiceObject(SendWeiXinService.class);
           boolean result = wxService.send(new UserInfo(1,"andychen", 99));
           System.out.println("Rpc远程调用发送微信服务返回:"+result+" ...");

           //(2)访问报表服务
           ReportingService rptService = ConsumerFramework.getServiceObject(ReportingService.class);
           ReportingVO vo = new ReportingVO();
           vo.setIp("192.168.3.12");
           vo.setUv(10000);
           vo.setPv(30000);
           vo.setTitle("系统第二季度访问统计");

           //访问接口1
           rptService.setReportInfo(vo);
           System.out.println("Rpc远程调用设置报表信息服务已返回 ...");

           //访问接口2
           rptService.setReportStat(50000, 60000);
           System.out.println("Rpc远程调用报表服务统计报表信息已返回 ...");
       } catch (Exception e) {
           e.printStackTrace();
       }
   };
}


频繁验证后效果

image.png

image.png

image.png


结论和思考

以上基于TCP协议的RPC框架设计实现,在经过细节完善和充分测试后,可作为轻量级远程调用框架,在微服务调用场景中使用。虽基于TCP协议实现,但毕竟采用的是阻塞式IO模型,相比NIO和IO 多路复用模型等,在性能和高并发场景,存在固有的缺陷;可采用后者对其进行改造。接一下来,我们也会在NIO和IO多路复用模型(包括Netty框架)上设计更多的实现,给大家在java网络编程方面提供不同的思路和体验。

猜你喜欢

转载自blog.51cto.com/14815984/2506013