快来手写RPC框架 S1

本文介绍了如何从零开始构建一个简单的RPC框架,包括服务定义、服务提供方和消费方的实现,以及使用Java反射和Socket进行远程调用。文中还提供了一个无需复杂序列化和传输协议的简化示例,并给出了项目的代码结构和关键代码解释。
摘要由CSDN通过智能技术生成

RPC概览

所谓RPC,指的是远程服务调用。抽离所有技术细节,其大致流程不外乎如下几个部分:

  1. 接口定义
  2. 服务方实现接口
  3. 指定服务方端口和host,服务方服务器运行
  4. 服务方的服务器通过反射的方式调用指定方法并回参
  5. 消费方调用接口定义
  6. 消费方通过动态代理在运行时把接口方法的调用转变成远程调用服务方,这个过程有自定义传输协议也有HTTP协议(很少)
  7. 消费方拿到回执,结束

背景

今天我们会开一个新坑,手写一个RPC框架。这个坑会分成好几季,这是第一季。本文会协助你开始一个最简单版本的RPC框架,特点有以下几个:

  1. 可以实现远程调用的功能
  2. 简单易实现,几乎不需要任何外部依赖(除了common-lang3的一个反射工具和guava的线程池工具)

学习本文你应当有的前置知识:

  • maven项目管理技术
  • RPC的概念(远程服务调用)
  • Java反射技术
  • Java Socket技术

本系列绝对不是入门RPC的文章,还是有那么点,不太好懂的,如果是刚开始学习Java或者springboot的同学请先去看我写的Springboot入门系列和Thrift、Dubbo等现成Rpc框架的使用,有了初步的概念再来看这一篇会好很多。

设计

一个最简单RPC框架应当包含哪些部分呢?
我们先来看一下经典的RPC框架都有什么:服务接口定义,服务提供者,序列化,stub代理,传输协议,注册中心,服务消费者,反序列化。
如果说其中哪些部分可以暂时放一放,或者用简化方案代替,那必然是序列化、反序列化、传输协议,不是说这些不需要,而是可以用现成的方案,不需要自己造轮子(当然如果你要实现商用闭源的RPC框架那肯定要自己设计协议)。其实服务接口定义也可以不要,如果你只是做一个测试方法比如sayHello,那要不要接口真的不重要。
所以,最终我们知道,服务提供方和服务消费方以及实现socket传输的部分是绝对必要的。那么我们可以这样简化:

Service Api -> Consumer Proxy <---------socket传输---------> Provider Reflect -> Service Impl

实现

POM

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.0-jre</version>
    </dependency>

</dependencies>

服务定义

package service;

public interface HelloService {

    /**
     * 服务接口
     *
     * @param content
     * @return
     */
    public String sayHello(String content);
}

服务提供方

package service;
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String content) {
        return String.format("hello, %s", content);
    }
}

服务提供代理类

package framework;

/**
 * @Desc
 **/
public class ProviderReflect {

    private static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(
            5,
            200,
            0L,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024),
            new ThreadFactoryBuilder().setNameFormat("test-pool-%d").build(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void provider(final Object service, int port) throws Exception {
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            final Socket socket = serverSocket.accept();
            EXECUTOR_SERVICE.execute(() -> {
                try {
                    ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                    try {
                        try {
                            String methodName = input.readUTF();
                            Object[] arguments = (Object[]) input.readObject();
                            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                            try {
                                Object result = MethodUtils.invokeExactMethod(service, methodName, arguments);
                                output.writeObject(result);
                            } catch (Throwable t) {
                                output.writeObject(t);
                            } finally {
                                output.close();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            input.close();
                        }
                    } finally {
                        socket.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

服务启动器

public class RpcProviderMain {

    public static void main(String[] args) throws Exception {
        HelloService service = new HelloServiceImpl();
        ProviderReflect.provider(service, 10020);
    }
}

消费方代理

package framework;

/**
 * @Desc
 **/
public class ConsumerProxy {

    @SuppressWarnings("unchecked")
    public static <T> T consume(final Class<T> interfaceClass, final String host, final int port) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass},
                (proxy, method, args) -> {
                    try (Socket socket = new Socket(host, port)) {
                        try (ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream())) {
                            output.writeUTF(method.getName());
                            output.writeObject(args);
                            try (ObjectInputStream input = new ObjectInputStream(socket.getInputStream())) {
                                Object result = input.readObject();
                                if (result instanceof Throwable) {
                                    throw (Throwable) result;
                                }
                                return result;
                            }
                        }
                    }
                });
    }

}


服务消费方启动器

package invoke;
/**
 * @Desc
 **/
public class RpcConsumerMain {

    public static void main(String[] args) throws Exception {
        HelloService service = ConsumerProxy.consume(HelloService.class, "127.0.0.1", 10020);
        for(int i = 0; i < 1000; ++i) {
            String hello = service.sayHello(String.format("test_%d", i));
            System.out.println(hello);
            Thread.sleep(1000);
        }
    }
}

项目结构

自定义RPC一阶段项目截图

代码重点说明

  1. 使用时先启动服务方,后启动消费方即可
  2. 服务方本质是通过while(true)让服务方一直停在socket.accept的阶段,只要收到新的socket连接就进行相应处理。处理方式是通过MethodUtils工具,以反射的方式调用服务中指定名称的方法
  3. 消费方本质上是通过动态代理HelloService接口,在运行时实际去调用invoke来实现该接口。在本例中就是用socket进行连接,在运行时把方法名、参数值发送到服务方,然后用输入流拿到回参。这样,一个自定义RPC的闭环就完成了

结论

虽然本例中借口定义、数据传输都非常简陋,但实际上已经具备了RPC的基本要素,服务提供,服务消费,和远程调用,这个例子将是我们这个系列的起点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值