RPC

本文最后更新于:9 个月前

RPC框架的实现

RPC是什么?

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,用于在分布式系统中使一个程序调用另一个程序的过程看起来像本地过程调用一样。它允许在不同的计算机或网络上运行的程序之间进行通信和交互。

RPC的基本工作原理如下:

  1. 客户端(调用方)调用一个远程过程,就像调用本地函数一样。客户端不需要关心远程过程的实际位置或运行在哪台计算机上。
  2. 客户端的请求被封装成一个消息,包括远程过程的标识符、参数等信息。
  3. 这个消息通过网络传输到远程计算机上的服务器端。
  4. 服务器端接收到消息后,解析消息,执行相应的远程过程,然后将结果封装成响应消息。
  5. 服务器端将响应消息发送回客户端。
  6. 客户端接收到响应消息后,解析响应并获取远程过程的结果。

RPC的目标是隐藏底层的通信细节,使远程过程调用的过程对程序员来说尽可能透明和简单。这使得构建分布式系统和客户端-服务器应用程序变得更加容易,因为开发者可以将远程服务看作是本地的函数调用,而不必担心网络通信的细节。

在代码上展示就是如下形式:

服务提供方提供的接口

public interface HelloService {
    String sayHello(String msg);
}

客户端进行远程调用

public static void main(String[] args) {
        HelloService service = ?   				// 获取接口的代理对象
        service.sayHello("Hello world!");
}

其中HelloService就是服务提供方提供的服务,但是在客户端不存在该接口的实现类,所以此时需要进行远程过程调用。既然要远程,客户端与服务端就需要进行网络通信,以传输必要的的调用信息,包括调用服务的接口名,方法名,方法的参数类型,参数,就是如下的类

@Data
@AllArgsConstructor
public class Invocation implements Serializable {
    private String interfaceName;

    private String methodName;

    private Class[] parameterTypes;

    private String version;

    private Object[] parameters;
}

下面就是将信息进行传输

Socket实现网络通信

首先服务端

public class Server {

    private static final RpcRequestHandler rpcRequestHandler = new RpcRequestHandler();

    public void start(int port) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            Socket socket;
            while((socket = serverSocket.accept()) != null) {
                System.out.println("Connecting to " + socket.getRemoteSocketAddress());
                try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                  	// 获取客户端的请求信息
                    Invocation invocation= (Invocation) objectInputStream.readObject();
                    System.out.println("Invocation info: " + invocation.toString());
                    // 调用目标方法
                    Object result = rpcRequestHandler.handle(invocation);
                    objectOutputStream.writeObject(result);
                    objectOutputStream.flush();
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

客户端

public class Client {

    public Object send(Invocation invocation, String host, int port) {
        try(Socket socket = new Socket(host, port)) {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            // 通过输出流向服务端发送消息
            objectOutputStream.writeObject(invocation);
            // 获取服务端的消息
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            return objectInputStream.readObject();

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

socket实现通信的过程比较简单,最重要的是RpcRequestHandler的实现,其功能就是从注册中心获取到客户端调用的服务,通过反射机制调用目标方法,并返回执行结果

public class RpcRequestHandler {

    public Object handle(Invocation invocation) {
         // 从本地注册中心获取服务
        Class service = LocalRegister.getClass(invocation.getInterfaceName(), invocation.getVersion());
        return invokeTargetMethod(invocation, service);
    }

    private Object invokeTargetMethod(Invocation rpcRequest, Class service) {
        Object result = null;

        try {
            // 获取service中目标方法,通过传递方法名和参数类型来获取特定的方法
            Method method = service.getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
            result = method.invoke(service.newInstance(), rpcRequest.getParameters());
            System.out.printf("service:[%s] successful invoke method:[%s]", rpcRequest.getInterfaceName(), rpcRequest.getMethodName());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }

        return result;

    }
}

服务注册中心

服务注册中心的作用是在分布式系统中提供服务的发现和注册功能。它在分布式架构中起到以下重要作用:

  1. 服务注册:RPC服务注册中心允许各个服务提供者将其提供的服务注册到中心服务器上。这样,客户端可以通过查询注册中心来获取服务的信息,而不必事先知道服务提供者的网络地址或位置。
  2. 服务发现:客户端可以向RPC服务注册中心查询需要的服务,然后获取服务的相关信息,如IP地址、端口号、协议等。这样,客户端可以动态地发现可用的服务并进行调用。
  3. 负载均衡:服务注册中心通常支持负载均衡策略,可以将客户端的请求分发给多个服务提供者,以实现负载均衡。这有助于提高系统的性能和可伸缩性。
  4. 故障恢复:如果某个服务提供者发生故障或不可用,服务注册中心可以帮助客户端检测到这种情况,并自动切换到另一个可用的服务提供者,从而提高系统的可用性和容错性。
  5. 服务元数据管理:服务注册中心通常还允许服务提供者添加一些元数据信息,如服务版本、描述、标签等。这有助于更好地管理和理解服务。
  6. 动态配置:RPC服务注册中心支持动态配置服务的相关信息,包括服务提供者的数量和位置,以适应系统的变化和需求。

常见的RPC服务注册中心包括ZooKeeper、Consul、etcd、Eureka(Spring Cloud的一部分)、Nacos等。这些注册中心可以根据具体的需求选择使用,并与不同的RPC框架(如gRPC、Dubbo、Spring Cloud等)集成,以实现分布式系统的服务发现和管理。

本地注册中心

这里以本地注册演示注册中兴的作用

public class LocalRegister {

    private static Map<String, Class> map = new HashMap<String, Class>();


    public static void register(String interfaceName, String version, Class implClass) {
        map.put(interfaceName + version, implClass);
    }

    public static Class getClass(String interfaceName, String version) {
        return map.get(interfaceName + version);
    }
}

服务注册

服务提供者在启动时,需要将需要暴露的服务注册到本地注册中心

public class Provider {

    private static final int PORT = 10001;
    public static void main(String[] args) throws IOException {

        // 将服务放入到本地注册
        LocalRegister.register(HelloService.class.getName(), "1.0", HelloServiceImpl.class);
        LocalRegister.register(HelloService.class.getName(), "2.0", HelloServiceImpl2.class);

        // 启动socket服务
        Server server = new Server();
        server.start(url.getPort());
    }
}

远程调用

客户端需要通过socket连接发送请求信息,实现远程调用

Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Class[]{String.class}, "2.0", new Object[]{"sunzy"});
Client client = new Client();
String result = (String) client.send(invocation, "127.0.0.1", 10001);

System.out.println(result);

服务端控制台输出

image-20230904151231084

客户端控制台输出

从上面的执行结果可以看出,已经完成远程过程调用

使用代理对象调用目标服务

从上面客户端的调用过程不难看出,每次想要调用一个方法都需要先创建一个Invocation对象,再发送请求,这么做不仅效率很低,而且浪费网络带宽

public class ProxyFactory {

    private final String version;


    private LoadBalance loadBalance = new RandomLoadBalance();

    public ProxyFactory(String version) {
        this.version = version;
    }


    public  <T> T getProxy(Class interfaceClass) {
        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 3.使用代理对象调用原始方法
                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(), method.getParameterTypes(),
                        version, args);

                // 服务发现
                List<URL> services = MapRemoteRegister.getClass(interfaceClass.getName());
                // 负载均衡
                URL url = null;

                // 服务调用
                Client client = new Client();
                String result = null;

                ArrayList<URL> invokeURLs = new ArrayList<>();
                // 出错重试机制
                int max = 3;
                while(max > 0) {

                    try {
                        services.remove(services);
                        invokeURLs.add(url);
                        url = loadBalance.loadBalance(services);

                    } catch (Exception e) {
                        return "负载均衡出错!";
                    }

                    try {
                        result = (String) client.send(invocation, "127.0.0.1", url.getPort());
                        System.out.println("代理对象执行结束!");
                        return result; // 4.代理对象返回执行结果
                    } catch (Exception e) {
                        if(max != 0) {
                            max--;
                            continue;
                        }
                        return "服务调用出错!";
                    }
                }

                return null;
            }
        });
        return (T) proxyInstance;
    }
}

此时客户端的调用方式就有所改变

ProxyFactory proxyFactory = new ProxyFactory("1.0");
HelloService service = proxyFactory.getProxy(HelloService.class); // 1.获取代理对象
String result = service.sayHello("Hello world!111111");// 2.通过代理对象调用sayHello方法
System.out.println(service.sayHello2("Hello world!111111"));
System.out.println(result);

从上述代码可以看出,当获得代理对象后,客户端就可以像使用本地对象一样,随意调用该类中的函数,大大简便了RPC的调用

通过以上的代码我们实现了最简单的RPC框架

文件结构

├── Consumer
│   ├── pom.xml
│   └── src
│       └── main
│           ├── java
│           │   └── org
│           │       └── example
│           │           └── Consumer
│           │               ├── Computer.java
│           │               ├── Hello.java
│           │               ├── Hello2.java
│           │               └── NodeCluster.java
│           └── resources
├── Provider
│   ├── pom.xml
│   ├── src
│       └── main
│           ├── java
│           │   └── org
│           │       └── example
│           │           ├── handler
│           │           │   └── RpcRequestHandler.java
│           │           ├── loadbalance
│           │           │   ├── Impl
│           │           │   │   └── RandomLoadBalance.java
│           │           │   └── LoadBalance.java
│           │           ├── provider
│           │           │   └── Provider.java
│           │           ├── proxy
│           │           │   └── ProxyFactory.java
│           │           ├── service
│           │           │   ├── HelloServiceImpl.java
│           │           │   └── HelloServiceImpl2.java
│           │           └── socket
│           │               ├── Client.java
│           │               └── Server.java
│           └── resources
│               └── META-INF
├── Provider-common
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── org
│   │   │   │       └── example
│   │   │   │           ├── common
│   │   │   │           │   ├── Invocation.java
│   │   │   │           │   ├── RpcServiceConfig.java
│   │   │   │           │   └── URL.java
│   │   │   │           ├── register
│   │   │   │           │   ├── LocalRegister.java
│   │   │   │           │   └── MapRemoteRegister.java
│   │   │   │           └── service
│   │   │   │               └── HelloService.java
│   │   │   └── resources
│   │   │       └── map.txt
│   │   └── test
│   │       └── java
│   │           └── org
│   │               └── example
└── pom.xml

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录

Copyright © 2020 my blog
载入天数... 载入时分秒...