RPC的小demo

RPC的小demo

理论

RPC其实就是远程程序调用,即Remote Procedure Call,相对于远程程序调用,其实最常见的调用就是本地函数调用,比如main函数中调用两数相加的函数,实现两数相加,这种形式在单体应用中,当然是很常见的,但是对于在分布式的场景下,就无法满足了,比如主机A总是想要主机B上的某些服务,就可以利用远程调用的方式。

但是很容易想到的是,我们可以直接HTTP请求啊,但是利用httpclient需要对于参数这些的设定,无法将模块抽象出来,而RPC则就是要解决远程调用像在本地调用一样简单。

所以,RPC要解决的问题就是:

1、解决分布式环境下,服务之间调用的问题

2、远程调用用,像本地调用一样方便。

RPC其实就相当于一个远程代理

实践

RPC的小demo_第1张图片

以上就是一个RPC远程调用的示意图,client中的Application通过Client Stub实现远程调用,client Run-Time Library是实现远程调用的工具包,可以是java的socket,也可以是httpclient,因为传输的数据是二进制的,因此需要进行序列化以及反序列化。

服务层

提供两数相加的服务(相当于上图Server中的Application):

AddService:

public interface AddService {
    String add(double a,double b);
}

AddServiceImpl:

public class AddServiceImpl implements AddService {
    @Override
    public String add(double a, double b) {
       return String.valueOf(a+b);
    }
}

service的RPC封装(Server Stub)

Server:

public interface Server {
    public void  stop();
    public void start() throws IOException;
    public void register(Class serviceInterface,Class impl);
    public boolean isRunning();
    public int getPort();
}

ServiceCenter:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServiceCenter implements Server {
    //声明线程池,一个可重用固定个数的线程池
    private static ExecutorService executor= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static final HashMap<String,Class> serviceRegistry=new HashMap<String,Class>();

    private static boolean isRunning=false;

    private static int port;

    public  ServiceCenter(int port){
        this.port=port;
    }
    //客户端的请求服务通过socket发送过来远程调用服务端接口并执行得结果
    private static class ServiceTask implements Runnable{
        Socket client=null;

        public ServiceTask(Socket client){
            this.client=client;
        }

        public void run(){
            ObjectInputStream input=null;
            ObjectOutputStream output=null;
            try{
                //将客户端发送的码流反序列化成对象,反射调用服务实现者,获得调用服务实现者,获得执行结果
                input=new ObjectInputStream(client.getInputStream());
                //服务名称
                String serviceName=input.readUTF();
                //方法名称
                String methodName=input.readUTF();
                //获得各种参数类型
                Class<?>[] parameterTypes=(Class<?>[]) input.readObject();
                //获得各种参数的值
                Object[] arguments=(Object[])input.readObject();
                //用服务名称寻得服务
                Class serviceClass=serviceRegistry.get(serviceName);
                if(serviceClass==null){
                    throw  new ClassNotFoundException(serviceName+"not found");
                }
                //利用方法名和参数类型拿到该服务中的该方法
                Method method=serviceClass.getMethod(methodName,parameterTypes);
                //远程执行方法,实例化类,利用参数值获得执行结果
                Object result=method.invoke(serviceClass.newInstance(),arguments);

                //将执行结果反序列化,通过socket发送给客户端
                output=new ObjectOutputStream(client.getOutputStream());
                output.writeObject(result);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(output!=null){
                    try{
                        output.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if(input!=null){
                    try{
                        input.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if(client!=null){
                    try{
                        client.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }


    @Override
    public void stop() {
        isRunning=false;
        executor.shutdown();
    }

    @Override
    public void start() throws IOException {
        //建立服务端的socket
        ServerSocket server=new ServerSocket();
        //绑定端口
        server.bind(new InetSocketAddress(port));
        System.out.println("start service");
        try{
            while(true){
                executor.execute(new ServiceTask(server.accept()));
            }
        }finally {
            server.close();
        }

    }

    @Override
    public void register(Class serviceInterface, Class impl) {
        serviceRegistry.put(serviceInterface.getName(),impl);
    }

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

    @Override
    public int getPort() {
        return port;
    }
}

客户端

于客户端建立代理

RPCClient:

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

public class RPCClient {
        //第一个表示泛型,第二个表示返回的是T类型的数据
        public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) {

            // 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用

            return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},

                    new InvocationHandler() {

                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                            Socket socket = null;

                            ObjectOutputStream output = null;

                            ObjectInputStream input = null;

                            try {

                                // 2.创建Socket客户端,根据指定地址连接远程服务提供者

                                socket = new Socket();

                                socket.connect(addr);



                                // 3.将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者

                                output = new ObjectOutputStream(socket.getOutputStream());

                                output.writeUTF(serviceInterface.getName());

                                output.writeUTF(method.getName());

                                output.writeObject(method.getParameterTypes());

                                output.writeObject(args);



                                // 4.同步阻塞等待服务器返回应答,获取应答后返回

                                input = new ObjectInputStream(socket.getInputStream());

                                return input.readObject();

                            } finally {

                                if (socket != null) socket.close();

                                if (output != null) output.close();

                                if (input != null) input.close();

                            }

                        }

                    });

        }


}

测试类

@SpringBootApplication
public class RpcApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RpcApplication.class, args);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    //服务端开通一个端口提供对外服务
                    Server serviceServer=new ServiceCenter(8088);
                    //将add服务的相应的服务的名称以及服务类注入(接口类名以及实现类)
                    serviceServer.register(AddService.class, AddServiceImpl.class);
                    //开启对外服务
                    serviceServer.start();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
        //建立客户端代理
        AddService service= RPCClient.getRemoteProxyObj(AddService.class,new InetSocketAddress("localhost",8088));
        //得到结果
        System.out.println(service.add(3,4));
    }

}

扩展

问:当存在多个不同的服务时是什么情况呢?

答:多个不同类型的服务,自然就是封装成不同的服务,对于服务端,将该服务再次注入到开启到对外服务的端口上,再次开启对外服务即可;而对于客户端,就需要制定一个新的动态代理实现该服务的远程调用。对于相同类型的服务,在服务端可以放在一个服务类中,用不同的方法名体现,这样在客户端就还是之前的动态代理,只主要用不同的方法调用即可。说了这么多,就是一个动态代理实则对应一个服务端的服务(一类方法)

演示如下:

AddService服务中存在add1以及add2服务,还存在不同类型的服务SayHiService

@SpringBootApplication
public class RpcApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RpcApplication.class, args);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    //服务端开通一个对外端口提供对外服务
                    Server serviceServer=new ServiceCenter(8088);
                    //将add服务的相应的服务的名称以及服务类注入(接口类名以及实现类)
                    serviceServer.register(AddService.class, AddServiceImpl.class);
                    //将sayHi服务注入
                    serviceServer.register(SayHi.class, SayHiImpl.class);
                    //开启对外服务
                    serviceServer.start();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }).start();
        //建立客户端代理实现远程调用,指定调用远程服务的名称
        AddService service= RPCClient.getRemoteProxyObj(AddService.class,new InetSocketAddress("localhost",8088));
        SayHi sayHiService=RPCClient.getRemoteProxyObj(SayHi.class,new InetSocketAddress("localhost",8088));
        //得到结果
        System.out.println("8088调用两数相加服务:"+service.add1(3,4));//同一类服务的不同方法
        System.out.println("8088调用两数相加服务:"+service.add2(3,4,5));
        System.out.println("8088调用sayHi服务:"+sayHiService.sayHi());//不同类的服务
    }

}

总结

RPC的小demo_第2张图片

总归来说,就是如上图所示,在分布式环境下serviceB想要调用serviceA上的服务的时候,就需要建立一个那个远程服务的动态代理,就相当于本地调用,动态代理其实就将你的请求发给ServiceA的IP以及其开放的对外端口,实现服务的远程调用。一个动态代理对应一个服务,动态代理将想要调用的接口类、方法名以及参数列表进行编码后在调用的时候发送给服务提供者,并通过同步阻塞等待获得应答。而服务端需要把客户端发过来的进行反序列化,明确客户端调用哪个服务中的哪个函数以及相应的参数列表,在获得这些之后执行方法,将结果写入,返回给客户端。(具体的通信方式可以用socket也可以用httpcliient的方式)

问题

rpc是分布式微服务架构中常见的通信方式,但是以上仅仅实现了一个简单的demo,具体怎么通信还是要进行设计,因为还有很多想不到的问题,比如:

1、假设serviceA又存在了一个新的服务,需要发布出去,就是让别人知道我有这个功能了,你们可以调用了。这个过程其实就是服务注册,可以用Zookeeper进行实现,在Dubbo中就是用Zookeeper做注册中心,实现服务的统一注册,可以获得服务的实例列表。

2、假设存在一个serviceB也有AddService服务,那对于serviceB来说,我只想要这个服务,具体是谁给我的并不重要,但是具体调用谁确实负载需要考虑的,也就是说需要存在一个中间件去平衡这个负载,但是对于serviceB是屏蔽的。

3、serviceB需要每次去注册中心查询实例列表吗,是不是可以考虑缓存呢…

4、异步调用,不是阻塞调用?版本控制?服务端关闭?

其实以上问题,对于微服务架构springcloud以及Dubbo都是需要考虑的,我希望我会学习到。。。

了一个新的服务,需要发布出去,就是让别人知道我有这个功能了,你们可以调用了。这个过程其实就是服务注册,可以用Zookeeper进行实现,在Dubbo中就是用Zookeeper做注册中心,实现服务的统一注册,可以获得服务的实例列表。

2、假设存在一个serviceB也有AddService服务,那对于serviceB来说,我只想要这个服务,具体是谁给我的并不重要,但是具体调用谁确实负载需要考虑的,也就是说需要存在一个中间件去平衡这个负载,但是对于serviceB是屏蔽的。

3、serviceB需要每次去注册中心查询实例列表吗,是不是可以考虑缓存呢…

4、异步调用,不是阻塞调用?版本控制?服务端关闭?

其实以上问题,对于微服务架构springcloud以及Dubbo都是需要考虑的,我希望我会学习到。。。

小小菜鸟,一堆问题…

你可能感兴趣的:(rpc,java)