简单了解 RPC 实现原理

本文介绍了RPC框架的基本原理,通过一个简洁的实现案例,详细解释了客户端与服务端的交互流程,包括消息的序列化、网络传输及反序列化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前一直在用rpc框架,内部原理今天学习了一下,原作者梁飞,在此记录下他非常简洁的rpc实现思路,通过该思路了解一下原理。

        一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。

  • 客户端(Client),服务的调用方。
  • 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  • 服务端(Server),真正的服务提供者。
  • 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

RPC调用过程:

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;

(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

(3) 客户端通过sockets将消息发送到服务端;

(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);

(5) 服务端存根( server stub)根据解码结果调用本地的服务;

(6) 本地服务执行并将结果返回给服务端存根( server stub);

(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);

(8) 服务端(server)通过sockets将消息发送到客户端;

(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);

(10) 客户端(client)得到最终结果。

RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。

 

一、核心框架类

/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.study.rpc.framework;

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.ServerSocket;
import java.net.Socket;

/**
 * RpcFramework
 *
 * @author william.liangf
 */
public class RpcFramework {

    /**
     * 暴露服务
     *
     * @param service 服务实现
     * @param port 服务端口
     * @throws Exception
     */
    public static void export(final Object service, int port) throws Exception {
        if (service == null)
            throw new IllegalArgumentException("service instance == null");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        System.out.println("Export service " + service.getClass().getName() + " on port " + port);
        ServerSocket server = new ServerSocket(port);
        for(;;) {
            try {
                final Socket socket = server.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            try {
                                ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                                try {
                                    String methodName = input.readUTF();
                                    Class<?>[] parameterTypes = (Class<?>[])input.readObject();
                                    Object[] arguments = (Object[])input.readObject();
                                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                                    try {
                                        Method method = service.getClass().getMethod(methodName, parameterTypes);
                                        Object result = method.invoke(service, arguments);
                                        output.writeObject(result);
                                    } catch (Throwable t) {
                                        output.writeObject(t);
                                    } finally {
                                        output.close();
                                    }
                                } finally {
                                    input.close();
                                }
                            } finally {
                                socket.close();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 引用服务
     *
     * @param <T> 接口泛型
     * @param interfaceClass 接口类型
     * @param host 服务器主机名
     * @param port 服务器端口
     * @return 远程服务
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
        if (interfaceClass == null)
            throw new IllegalArgumentException("Interface class == null");
        if (! interfaceClass.isInterface())
            throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
        if (host == null || host.length() == 0)
            throw new IllegalArgumentException("Host == null!");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
                Socket socket = new Socket(host, port);
                try {
                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                    try {
                        output.writeUTF(method.getName());
                        output.writeObject(method.getParameterTypes());
                        output.writeObject(arguments);
                        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                        try {
                            Object result = input.readObject();
                            if (result instanceof Throwable) {
                                throw (Throwable) result;
                            }
                            return result;
                        } finally {
                            input.close();
                        }
                    } finally {
                        output.close();
                    }
                } finally {
                    socket.close();
                }
            }
        });
    }

}

二、定义服务接口

/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.study.rpc.test;

/**
 * HelloService
 *
 * @author william.liangf
 */
public interface HelloService {

    String hello(String name);

}

三、实现服务

/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.study.rpc.test;

/**
 * HelloServiceImpl
 *
 * @author william.liangf
 */
public class HelloServiceImpl implements HelloService {

    public String hello(String name) {
        return "Hello " + name;
    }

}

四、暴露服务

/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.study.rpc.test;

import com.alibaba.study.rpc.framework.RpcFramework;

/**
 * RpcProvider
 *
 * @author william.liangf
 */
public class RpcProvider {

    public static void main(String[] args) throws Exception {
        HelloService service = new HelloServiceImpl();
        RpcFramework.export(service, 1234);
    }

}

五、引用服务

/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.study.rpc.test;

import com.alibaba.study.rpc.framework.RpcFramework;

/**
 * RpcConsumer
 *
 * @author william.liangf
 */
public class RpcConsumer {

    public static void main(String[] args) throws Exception {
        HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
        for (int i = 0; i < Integer.MAX_VALUE; i ++) {
            String hello = service.hello("World" + i);
            System.out.println(hello);
            Thread.sleep(1000);
        }
    }

}

六、总结

       这个简单的例子的实现思路是使用阻塞的socket IO流来进行server和client的通信,也就是rpc应用中服务提供方和服务消费方。并且是端对端的,用端口号来直接进行通信。方法的远程调用使用的是jdk的动态代理,参数的序列化也是使用的最简单的objectStream。

       真实的rpc框架会对上面的实现方式进行替换,采用更快更稳定,更高可用易扩展,更适宜分布式场景的中间件,技术来替换。例如使用netty的nio特性达到非阻塞的通信,使用zookeeper统一管理服务注册与发现,解决了端对端不灵活的劣势。代理方式有cglib字节码技术。序列化方式有hession2,fastjson等等。不过梁飞大大的博客使用原生的jdk api就展现给各位读者一个生动形象的rpc demo,实在是强。rpc框架解决的不仅仅是技术层面的实现,还考虑到了rpc调用中的诸多问题,重试机制,超时配置…这些就需要去了解成熟的rpc框架是如果考虑这些问题的了。

 

 

介绍RCP的实现原理 目录 1. 前言 2 2. 基本概念 3 2.1. IDL 3 2.2. 代理(Proxy) 3 2.3. 存根(Stub) 4 3. 三要素 4 3.1. 网络通讯 4 3.2. 消息编解码 5 3.3. IDL编译器 5 4. flex和bison 5 4.1. 准备概念 5 4.1.1. 正则表达式(regex/regexp) 6 4.1.2. 符号∈ 6 4.1.3. 终结符/非终结符/产生式 6 4.1.4. 记号(Token) 6 4.1.5. 形式文法 7 4.1.6. 上下文无关文法(CFG) 7 4.1.7. BNF 8 4.1.8. 推导 8 4.1.9. 语法树 8 4.1.10. LL(k) 9 4.1.11. LR(k) 9 4.1.12. LALR(k) 9 4.1.13. GLR 9 4.1.14. 移进/归约 9 4.2. flex和bison文件格式 9 4.2.1. 定义部分 10 4.2.2. 规则部分 10 4.2.3. 用户子例程部分 10 4.3. flex基础 10 4.3.1. flex文件格式 11 4.3.2. 选项 11 4.3.3. 名字定义 11 4.3.4. 词法规则 12 4.3.5. 匹配规则 12 4.3.6. %option 13 4.3.7. 全局变量yytext 13 4.3.8. 全局变量yyval 13 4.3.9. 全局变量yyleng 13 4.3.10. 全局函数yylex 13 4.3.11. 全局函数yywrap 13 4.4. bison基础 14 4.4.1. bison文件格式 14 4.4.2. %union 14 4.4.3. %token 15 4.4.4. 全局函数yyerror() 15 4.4.5. 全局函数yyparse() 15 4.5. 例1:单词计数 15 4.5.1. 目的 15 4.5.2. flex词法文件wc.l 16 4.5.3. Makefile 16 4.6. 例2:表达式 17 4.6.1. 目的 17 4.6.2. flex词法exp.l 17 4.6.3. bison语法exp.y 17 4.6.4. Makefile 19 4.6.5. 代码集成 19 4.7. 例3:函数 20 4.7.1. 目的 20 4.7.2. func.h 20 4.7.3. func.c 21 4.7.4. IDL代码func.idl 22 4.7.5. flex词法func.l 22 4.7.6. bison语法func.y 24 4.7.7. Makefile 27 5. 进阶 27 5.1. 客户端函数实现 27 5.2. 服务端函数实现 28 5.2.1. Stub部分实现 28 5.2.2. 用户部分实现 29 6. 参考资料 29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值