手写rpc框架1


参考视频: 自己动手实现RPC框架,本内容为视频学习笔记。

三篇内容:

  • 理论篇:rpc核心原理、现有框架对比、相关技术

  • 实战篇:代码实现、使用案例

  • 总结篇

理论篇

概念讲解

RPC: Remote Procedure Call,远程过程调用。是分布式系统常见的一种通信方法,从跨进程到跨物理机已经有几十年历史。

函数和过程区别

有返回值的:函数(function)

无返回值:过程(Procedure)

交互形式

跨进程交互形式:HTTP、基于数据库做数据交换、消息队列、RPC

image-20220124153719263

在RPC中:Server: Provider服务提供者,Client: Consumer、服务消费者,Stub: 存根、服务描述。RPC可以像本地方法一样调用远程方法。

stub怎么理解?

image-20220124154253184

客户端存根:存放服务端的地址消息,将客户端请求参数打包成网络消息,然后通过网络远程发送给服务方。

服务端存根:接收客户端发过来的消息,将消息解包,并调用本地方法。

现有框架对比

image-20220124165314869

核心原理

image-20220124165750859

server将需要暴露的服务及地址信息注册到注册中心,client订阅注册中心,如果server地址发生改变会再次注册,注册中心会通知client地址的改变;也可以没有注册中心,地址写死在rpc里面。

过程3如下

image-20220124170130719

问题:网络、序列化、接口怎么调用远程方法?

技术栈

JavaCore、Maven、反射

动态代理,生成client存根实际调用对象

序列化,Java对象和二进制数据互转

网络通信,传输序列化后的数据,jetty、URLConnection

实战篇

步骤:

  • 创建工程、制定协议、通用工具方法
  • 实现序列化模块
  • 实现网络模块
  • 实现Server模块
  • 实现Client模块
  • 使用案例

创建工程

模块

image-20220124175949910

pom.xml配置:

<!--提取出版本号,便于管理-->
<properties>
    <commons.version>2.5</commons.version>
    <jetty.version>9.2.28.v20190418</jetty.version>
    <fastjson.version>1.2.79</fastjson.version>
    <lombok.version>1.18.8</lombok.version>
    <junit.version>4.12</junit.version>
    <slf4j.version>1.7.32</slf4j.version>
    <logback.version>1.2.6</logback.version>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>
<!--子模块可能会用的依赖-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons.version}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<!--公共依赖,所有模块都会依赖的-->
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency>
    <!--全称为Simple Logging Facade for Java。 是对不同日志框架提供的一个门面封装。-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <!--日志框架,实现-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
</dependencies>
<!--工程编译的版本-->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <!--指定maven编译的jdk版本,否则会按默认的
                maven3 jdk1.5, maven2 jdk1.3-->
            <configuration>
                <!--指定源代码使用的JDK版本-->
                <source>${maven.compiler.source}</source>
                <!--指定生成的class文件的编译版本-->
                <target>${maven.compiler.target}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

里面用到了dependenciesdependencyManagement,说下二者区别

dependencyManagement放的是子模块可能要用的依赖,如果全都用的话,直接放到总的dependencies就行了,dependencyManagement不会自动引入依赖,只是声明版本号,如果子模块中写了这个依赖,但是没有指定具体版本,就会从父pom文件继承,如果指定了版本号,就按指定的来,这样做是为了统一管理项目的版本号,确保每个模块的依赖和版本一致,否则每个模块一个版本,不利于管理。

lombok原理

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

Lombok就是一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:

  • javac对源代码进行分析,生成一棵抽象语法树(AST)

  • javac编译过程中调用实现了JSR 269的Lombok程序

  • 此时Lombok就对第一步骤得到的AST进行处理,找到Lombok注解所在类对应的语法树(AST),然后修改该语法树(AST),增加Lombok注解定义的相应树节点

  • javac使用修改后的抽象语法树(AST)生成字节码文件

协议

image-20220124191433604

反射操作类

对反射进行了一层封装

/**
 * 反射工具类
 * @author hqingLau
 **/
public class ReflectUtils {
    /**
     * 根据class创建对象
     * @param clazz 待创建对象的类
     * @param <T> 对象类型
     * @return 创建好的对象
     */
    public static <T> T newInstance(Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 获取某个class的公有方法
     * @param clazz
     * @return
     */
    public static Method[] getPublicMethods(Class clazz) {
        // 返回当前类所有的方法
        Method[] methods = clazz.getDeclaredMethods();
        List<Method> methodList =new ArrayList<>();
        for(Method m:methods) {
            if(Modifier.isPublic(m.getModifiers())) {
                methodList.add(m);
            }
        }
        // new Method[0]就是起一个模板的作用,指定了返回数组的类型,
        // 0是为了节省空间,因为它只是为了说明返回的类型
        // 数组的大小还是list的size决定的
        return methodList.toArray(new Method[0]);
    }

    /**
     * 调用指定对象的指定方法
     * @param obj 被调用的对象
     * @param method 被调用的方法
     * @param args 方法参数
     * @return 返回结果
     */
    public static Object invoke(Object obj,
                                Method method,
                                Object... args) {
        try {
            return method.invoke(obj,args);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

(第一节完)

参考文献

RPC中常见的Stub怎么理解

司马极客视频

Maven中dependencyManagement作用说明

如何用好 IDEA ,Java 撸码效率至少提升 5 倍?