分布式系列-从无到有实现一个RPC框架

前期提要

RPC即远程过程调用,我们不管用什么语言,比如c,java,都有本地过程或者本地方法,也就是一般来说,都是自己机器上的代码在使用自己机器上的代码,但是如果自己机器受不了,也可以交由远程服务去计算,将我们的远程服务发布出来,然后自己本机调用那个服务即可了。在分布式环境中经常使用,其实也可以将分布式应用看成一台机器,一台机器内本地调用,只是真实的本地机器是通过CPU,内存,磁盘,总线等去加载数据计算的,而分布式应用这种“一台机器”内部是使用网络去加载数据通信计算的。当然你像Redis也可以不使用RPC而是直接轻量的去使用,这里想熟悉熟悉研究研究RPC主要是因为像Hadoop,Spark,Flink,Kafka,Zookeeper等大量的分布式应用底层通信都有用到RPC,所以从这里就开始就先自己一步一步实现一个RPC框架,再去研究一些通信应用,像Netty,Dubbo等等
在了解RPC之前,必须掌握几个基础:
动态代理
反射
序列化与反序列化
网络传输


Demo1:简易版RPC框架-无注册中心

Demo1模型

在这里插入图片描述
我们的RPC先简化成传统的客户端和服务端,客户端就是分布式应用中我们常用的调用服务,而服务端就是我们常常发布了一个服务,等待被调用然后执行。

  • 第一步:我们服务端首先需要发布一个服务,等待客户端的调用。其实我们平常开发SpringBoot,不也是发布一个服务,通过端口转发或者访问,然后等待返回结果,模型道理是一样的,只是实现不一样。
  • 第二步:有了服务端,我们客户端就可以去调用了,在我们客户端,不用知道服务端的那个服务的具体实现逻辑,只需要知道服务端是干什么的就可以了,这样你就可以想到java里面的接口了,接口不就是定义了一系列行为吗,让我们知道是干什么的。所以我们只需要根据这个接口去生成一个代理对象即可了。为什么要生成一个代理对象,这里是动态代理,毕竟如果不使用,客户端每次去调用一个服务,比如sayHello,我就要吧相应的sayHello,参数名,所实现的接口去告诉服务端,这样虽然是我们的代码直接和服务器通信,但是如果又想调用sayInt,那么就会造成我们又要写代码将这个名字传过去,就会出现大量的代码冗余。你有可能会想到,可以将公共的访问服务提取出来,就是统一的利用反射,将method,参数啥的传给服务端,这样服务端就可以根据这个东西去使用了。这样想没错,这个就是动态代理的思想,利用java中的Proxy自动实现一个代理类来实现我们的服务接口,然后调用的时候就将方法和参数发送过去就行了。
  • 第三步:就像第二步说的那样,有了动态代理,将我们用户代码的实现接口,调用方法和参数,送给代理对象,代理对象可能经过包装或者序列化传输给服务端。
  • 第四步:服务端受到了客户端的请求,经过反序列化,将我们的接口,方法名,和参数提取出来,然后通过反射调用那个方法
  • 第五步:服务端将调用结果返回给客户端,客户端操作调用结果

整个逻辑不是很复杂,我们这一节可以根据这个模型实现一个简单的RPC,但是呢,我们后期还需要考虑协议,网络,序列化和反序列化,负载均衡等等,到时候再去研究,先简单看看这个怎么做

Demo1:服务接口

这个就是我们远程服务的接口,就是我们客户端也会存一份,来了解这个接口可以干嘛。

public interface IHelloServie {
    public String sayHello(String name);
    public String sayInt(String info, Integer port);
}
Demo1:服务接口实现

这个保存在服务端,用来发布服务

public class HelloServiceImpl implements IHelloServie {
    @Override
    public String sayHello(String info) {
       return "你好:"+info;
    }

    @Override
    public String sayInt(String info, Integer port) {
       return "info:"+info+"port:"+port;
    }
}
Demo1:RpcRequest

这个是用来在服务器和客户端传输数据的对象,包装了类名,方法名和参数,注意这里简单的实现了序列化接口


/**
 * 用来序列化,告诉服务器端需要做什么
 * 统一传输对象
 * */
public class RpcRequest implements Serializable {
    /**
     * 类名,方法名,和参数
     * */
    private String className;
    private String methodName;
    private Object[] parameters;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

Demo1:RpcServer和ProcessHandler

RpcServer用来统一管理,实现的是一个线程池,用来发布服务,当然这里线程池使用不合理,只是随便想的,不用太在意,生产环境肯定不会这样用

public class RpcServer {
    /**线程池*/
    private static final ExecutorService executor = Executors.newCachedThreadPool();
    public void publisher(final Object service,int port){
        /**启动服务监听*/
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while(true){
                Socket socket = serverSocket.accept();
                executor.execute(new ProcessHandler(socket,service));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

ProcessHandler用来真正的服务调用,这里很简单,不就是一个线程嘛,我们的RpcServer在监听,如果来了一个,就启动一个线程,这里异步的等待监听了。我们线程里来接受那个RpcRequest,它是序列化过的。我们这里读出来就会反序列化。然后解析这个类,将其中的类名,方法名,和参数解析出来了,然后去找当前这个服务的方法和参数对应的方法即可。通过反射调用返回去。

public class ProcessHandler implements Runnable {
    private Socket socket;

    /***
     * 服务端发布的服务
     * */
    private Object service;
    /**
     * 构造方法,顺便保存socket和服务service
     * */
    public ProcessHandler(Socket socket,Object service){
        this.socket = socket;
        this.service = service;
    }

    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            /**反序列化,将网络输入流的序列化对象反序列化成RpcRequest*/
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
            Object result = invoke(rpcRequest);
            /**将结果返回给客户端*/
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(result);
            objectOutputStream.flush();
            objectOutputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            if(objectInputStream!=null){
                try {
                    /**关闭输入流*/
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    /***
     * 反射调用
     * */
    private Object invoke(RpcRequest rpcRequest) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("服务端开始调用了......");
        System.out.println("调用的为:"+rpcRequest.getClassName()+" "+rpcRequest.getMethodName()+" ");
        Object[] parameters = rpcRequest.getParameters();
        /**
         * 两种参数类型的选择方式,可以随便选择,第二种要使用jdk1.8以上
         * */
        Class[] parameterTypes = new Class[parameters.length];
        Class<?>[] parameTypes = Arrays.stream(parameters).map(Object::getClass).toArray(Class<?>[]::new);
        for(int i=0,length = parameters.length;i<length;i++){
            parameterTypes[i] = parameters[i].getClass();
            System.out.println(parameterTypes[i].getName());
        }
//        for(int i=0;i<parameTypes.length;i++) System.out.println(parameTypes[i]);
        /**开始调用方法*/
        Method method = service.getClass().getMethod(rpcRequest.getMethodName(),parameterTypes);
        return method.invoke(service,parameters);
    }
}
Demo1:RpcClientProxy客户端动态代理生成代理对象

这里使用了Proxy的newProxyInstance方法来动态生成一个服务接口的代理实现类,用来传输数据。


/**
 * 客户端获取代理对象
 * 客户端代理
 * */
public class RpcClientProxy {
    public <T> T clientProxy(final Class<T> interfaceClass,final String host,final int port){
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new RemoteInvocationHandler(host,port));
    }
}

上面使用的RemoteInvocationHandler在这里,主要是来统一管理调用,当我们的用户代码使用代理对象时,就会统一使用这个invoke方法。


public class RemoteInvocationHandler implements InvocationHandler {
    private String host;
    private int port;

    /**发起客户端和服务器的远程调用,调用客户端的信息进行传输*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //构建一个Rpc传输对象RpcRequest,里面包括了类名,方法名和参数
        System.out.println("proxy:"+proxy.getClass().getName());

        System.out.println("method:"+method.getName());
        System.out.println(args[0]);
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameters(args);
        System.out.println(args[0].getClass());

        /**将这个Rpc调用对象通过tcp传输过去*/
        TcpTransport tcpTransport = new TcpTransport(host,port);
        return tcpTransport.send(rpcRequest);
    }

    /**构造方法*/
    public RemoteInvocationHandler(String host,int port){
        this.host = host;
        this.port = port;
    }
}

Demo1:TcpTransport

这里简单模拟了Socket传输而已,没多大意义


/**
 * Socket传输
 * */
public class TcpTransport {
    private String host;
    private int port;
    public TcpTransport(String host,int port){
        this.host = host;
        this.port = port;
    }
    private Socket newSocket(){
        DatePrint.printdate();
        System.out.println("准备创建Socket连接,host:"+host+"  Port:"+port);
        Socket socket = null;
        try {
            socket = new Socket(host,port);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return socket;
    }
    public Object send(RpcRequest rpcRequest){
        /**建立Socket连接*/
        Socket socket = null;
        socket = newSocket();

        /**向这个socket的输出流写入序列化的Rpc请求对象*/
        ObjectOutputStream outputStream = null;
        try {
            outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(rpcRequest);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        /**输入流,接受socket返回的结果*/
        ObjectInputStream inputStream = null;
        try {
            inputStream = new ObjectInputStream(socket.getInputStream());
            Object result = inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
       return null;

    }
}

Demo1测试:服务器发布服务
public class ServerDemo {
    public static void main(String[] args) {
        RpcServer rpcServer = new RpcServer();
        rpcServer.publisher(new HelloServiceImpl(),12345);
    }
}
Demo1测试:客户端发送数据
public class ClientDemo {
    public static void main(String[] args) {
        RpcClientProxy proxy = new RpcClientProxy();
        IHelloServie helloServie = proxy.clientProxy(IHelloServie.class,"localhost",12345);

        String name = helloServie.sayHello("ongbo");
        System.out.println(name);
        helloServie.sayInt("ongboinfo",1234);
    }
}

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小满锅lock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值