系统间通信:基于TCP协议的RPC实现范例

              系统间通信:基于TCP协议的RPC实现范例

一、RPC名词解释

         RPC的全称是Remote Process Call,即远程过程调用,它应用广泛,实现方式也很多,拥有RMI、WebService等诸多成熟的方案,在业界得到了广泛的使用。单台服务的处理能力受到硬件成本的限制,不可能无限制地提升。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能,这是系统发展到一定阶段必然性的变革,也是实现分布式计算的基础。

         RPC的实现包括客户端和服务端,即服务的调用方与服务的提供方。服务调用方发送RPC请求到服务提供方,服务提供方根据调用方提供的参数执行请求方法,将执行结果返回给调用方,一次RPC调用完成。这里涉及到网络通信、序列化与反序列化的操作,如果服务提供者是多台或者服务提供者进行了分组,还会涉及到负载均衡与路由。

二、RPC调用过程

         随着业务的发展,服务调用者的规则发展到一定阶段,对服务提供方的压力也日益增加,因此,服务需要进行扩容。而随着服务提供者的增加与业务的发展,不同的服务之间还需要进行分组,以隔离不同的业务,避免相互影响,在这种情况下,服务的路由和负载均衡成为必须考虑的问题。服务消费者通过获取服务提供者的分组信息和地址信息进行路由,如果服务提供者为一个集群而非单台机器,则需要根据相应的负载均衡策略,选取其中一台进行调研,有关服务的路由和负载均衡,后续章节会进行详细介绍RPC调用过程(不包括负载均衡和路由)如下图所示:

系统间通信:基于TCP协议的RPC实现范例_第1张图片

(1)    服务消费方(client)调用以本地调用方式调用服务;;

(2)    clientstub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体,并将消息发送到服务端(对象序列化);

(3)    serverstub收到消息后进行解码(对象反序列化);

(4)    serverstub根据解码结果调用本地的服务;

(5)    本地服务执行并将结果返回给server stub;

(6)    serverstub将返回结果打包成消息并发送至消费方;

(7)    clientstub接收到消息,并进行解码;

(8)    服务消费方得到最终结果。

RPC的目标就是要2~7这些步骤都封装起来,让用户对这些细节透明。(注意:无论是何种类型的数据,最终都需要转换二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。目前成熟的方案有多种,比如Protocol Buffers、Java自带方式、Hessian、JSON、XML等)。各种序列化方式性能对比图如下所示:

系统间通信:基于TCP协议的RPC实现范例_第2张图片

三、范例代码

        基于Java的Socket API,我实现一个简单的RPC调用的范例,在这个范例中,包括服务的接口、接口的远程实现、服务的提供方以及服务的消费方。为了方便大家下载代码运行,我把代码放到了Github(https://github.com/dreamerkr/RPCDemo),其类图如下所示:

系统间通信:基于TCP协议的RPC实现范例_第3张图片

(1)    服务接口SayHelloservice

package com.primeton.RPCDemo;

public interface SayHelloService {
    /**
     * 问候接口
     * @param msg
     * @return
     */
    public String sayHello(String msg);
}

(2)    接口远端实现

package com.primeton.RPCDemo;

public class SayHelloServiceImpl implements SayHelloService {

    @Override
    public String sayHello(String msg) {
        if("hello".equals(msg)){
            return "hello client";
        }else{
            return "bye bye";
        }
    }
}

(3)   服务消费者

package com.primeton.RPCDemo;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;

/**
 * TODO 服务消费者
 *
 * @author wangzhao (mailto:[email protected])
 */
public class Consumer {

    public static void main(String[] args) throws Exception {
        
        //构造需要调用的方法
        String interfaceName = SayHelloService.class.getName();
        Method method = SayHelloService.class.getMethod("sayHello", 
                java.lang.String.class);
        Object[] argments = {"hello"};
        
        //发送调用信息到服务器端,调用相应的服务
        Socket socket = new Socket("127.0.0.1",1234);
        ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
        outputStream.writeUTF(interfaceName);
        outputStream.writeUTF(method.getName());
        outputStream.writeObject(method.getParameterTypes());
        outputStream.writeObject(argments);
        System.out.println("发送信息到服务端,发送的信息为:"+argments[0]);
        
        //服务返回的结果
        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
        Object object = inputStream.readObject();
        System.out.println("服务返回的结果为"+object);
    }
}

(4)    服务提供者

package com.primeton.RPCDemo;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * RPC服务提供者
 *
 * @author wangzhao (mailto:[email protected])
 */
public class Provider {

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(1234);
        Map services = new HashMap();
        services.put(SayHelloService.class, new SayHelloServiceImpl());
        while(true){
            System.out.println("服务提供者启动,等待客户端调用…………");
            Socket socket = serverSocket.accept();
            
            //收到消息后进行解码
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            String interfaceName = objectInputStream.readUTF();
            String methodName = objectInputStream.readUTF();
            Class[] paramterTypes = (Class[])objectInputStream.readObject();
            Object[] argments = (Object[])objectInputStream.readObject();
            System.out.println("客户端调用服务端接口"+interfaceName+"的"+ methodName+"方法");
            
            //根据解码结果调用本地的服务
            Class serviceClass = Class.forName(interfaceName);
            Object serivce = services.get(serviceClass);
            Method method = serviceClass.getMethod(methodName, paramterTypes);
            Object result = method.invoke(serivce, argments);
            
            //服务提供者发送result给服务调用者
            ObjectOutputStream stream = new ObjectOutputStream(socket.getOutputStream());
            stream.writeObject(result);
            System.out.println("服务端返回结果为"+result);
        }
    }
}

     服务提供者端事先将服务实例化好后放在services这个Map中(此处涉及服务的路由被简化处理了,后面章节会详细讲解),通过一个while循环,不断地接收到来的请求,得到所需的参数,包括接口名称、方法名称、参数类型和参数,通过Java的反射取得接口所需调用的方法,执行后将结果返回给服务的消费者。

        在真实的生产环境中,常常是多个客户端同时发送多个请求到服务端,服务端则需要同时接收和处理多个客户端请求,涉及并发处理、路由、负载均衡、监控、日志记录等功能,这个是无法满足的。下一章我将给大家分享基于HTTP协议的RPC实现的范例。

你可能感兴趣的:(微服务,devops,容器云)