RPC系列--綜述

RPC原理

其他網址

RPC原理解析 - 牧夢者 - 博客園

什麼是RPC

        RPC(Remote Procedure Call Protocol)——遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP/IP或UDP,爲通信程序之間攜帶信息數據。RPC將原來的本地調用轉變爲調用遠端的服務器上的方法,給系統的處理能力和吞吐量帶來了近似於無限制提升的可能。在OSI網絡通信模型中,RPC跨域了傳輸層和應用層。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這些步驟都封裝起來。

注意:無論是何種類型的數據,最終都需要轉換成二進制流在網絡上進行傳輸,數據的發送方需要將對象轉換爲二進制流,而數據的接收方則需要把二進制流再恢復爲對象。

使用到的技術

1、動態代理
        生成 client stub和server stub需要用到 Java 動態代理技術 ,我們可以使用JDK原生的動態代理機制,可以使用一些開源字節碼工具框架 如:CgLib、Javassist等。

2、序列化
        爲了能在網絡上傳輸和接收 Java對象,我們需要對它進行 序列化和反序列化操作。
* 序列化:將Java對象轉換成byte[]的過程,也就是編碼的過程;
* 反序列化:將byte[]轉換成Java對象的過程;
可以使用Java原生的序列化機制,但是效率非常低,推薦使用一些開源的、成熟的序列化技術,例如:protobuf、Thrift、hessian、Kryo、Msgpack
關於序列化工具性能比較可以參考:jvm-serializers

3、NIO
        當前很多RPC框架都直接基於netty這一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推薦使用Netty 作爲底層通信框架。

4、服務註冊中心
可選技術:
* Redis
* Zookeeper
* Consul
* Etcd

開源的優秀RPC框架

阿里巴巴 Dubbo:https://github.com/alibaba/dubbo
新浪微博 Motan:https://github.com/weibocom/motan
gRPC:https://github.com/grpc/grpc
rpcx :https://github.com/smallnest/rpcx
Apache Thrift :https://thrift.apache.org/ 

實例

RPC原理簡析——三分鐘看完 - 簡書

        本實例使用socket建立連接,JDK的API做動態代理,主要有服務提供方暴露服務、服務消費方獲取代理對象、代理對象與服務提供方建立遠程連接並調用三個方面。忽略所有的參數校驗、異常。

        這個簡易實例中,整個RPC原理很清晰,本實例中最核心的一點,就是當proxy.sayHello執行的時候,實際是在執行RpcHandler的invoke方法,也就是遠程調用。

        如果要真正實現一個企業級RPC框架,僅僅有這個原理還是不夠的。還需要考慮很多東西,例如建立連接的時候,使用NIO從而使得IO效率更高;或者在集羣中,暴露服務的ip和端口都是動態的,而消費者此時也不能將要調用的服務提供方的ip和端口寫死,於是需要一個註冊中心的角色,產生註冊服務、訂閱服務等事件。

暴露服務

暴露服務

        已知的某個服務的實例對象service,建立ServerSocket服務,並監聽指定端口,當有遠程連接建立時,創建一個線程,在線程中從輸入流中依次讀取方法名、參數類型、參數值等信息,並根據方法名和參數執行實例對象service中對應的方法,獲得返回結果。

public class RpcExport {
    private static int port = 1234;
    /**
     * 暴露服務
     * 
     * @param service
     *            服務的實現
     * @param port
     *            服務的端口
     * @throws Exception
     */
    public static void export(final Object service) throws Exception {

        System.out.println("Export service " + service.getClass().getName() + " on port " + port);

        // 創建socket,開始監聽
        ServerSocket server = new ServerSocket(port);
        while (true) {
            final Socket socket = server.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ObjectInputStream input = null;
                    ObjectOutputStream output = null;
                    try {
                        // 從監聽的socket中獲得輸入流
                        input = new ObjectInputStream(socket.getInputStream());
                        String methodName = input.readUTF();
                        Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                        Object[] arguments = (Object[]) input.readObject();
                        // 從監聽的socket中獲得輸出流
                        output = new ObjectOutputStream(socket.getOutputStream());
                        Method method = service.getClass().getMethod(methodName, parameterTypes);
                        Object result = method.invoke(service, arguments);
                        output.writeObject(result);
                    } catch (Exception e) {
                    } finally {
                        try {
                            if (output != null) {
                                output.close();
                            }
                            if (input != null) {
                                input.close();
                            }
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}

代理工廠

        消費方獲取代理對象,使用JDK的動態代理API,傳入接口類名。生成代理對象的時候,需要傳入一個實現了InvocationHandler的對象,也就是下面的RpcHandler

import java.lang.reflect.Proxy;

public class RpcProxyFactory {
    public static <T> T getService(Class<T> interfaceClass) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), 
                        new Class[] { interfaceClass }, new RpcHandler());
    }
}

代理對象建立遠程連接

        這個RpcHandler中重寫了invoke方法,就是代理對象的方法執行的時候真正與服務提供方建立連接並獲得返回結果的地方。

public class RpcHandler implements InvocationHandler {

    private String host = "127.0.0.1";

    private int port = 1234;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 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(args);
                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();
        }
    }
}

使用

服務接口

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

服務實現類

public class HelloServiceImpl implements HelloService{
    @Override
    public String sayHello(String name) {
        return "hello "+name;
    }
}

暴露服務

public class RpcProvider {
    public static void main(String[] args) throws Exception {
        HelloService service = new HelloServiceImpl();
        RpcExport.export(service);
    }
}

消費者獲取代理對象並調用

public class RpcConsumer {
    public static void main(String[] args) throws Exception {
        HelloService proxy = RpcProxyFactory.getService(HelloService.class);
        String result = proxy.sayHello("world");
        System.out.println(result);
    }
}

測試

先運行RpcProvider的main,再運行RpcConsumer的main

運行結果

輸出:hello world

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章