RPC框架的簡單實現

一、RPC理論

1.什麼是RPC

① RPC簡介

RPC(Remote Procedure Call Protocol)-遠程過程調用協議。通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。它假定某種傳輸協議的存在,如TCP,UDP,爲通信程序之間攜帶信息數據。在OSI網絡通信模型中,RPC跨越了傳輸層和應用層,因分佈式,微服務等而興起

其實簡單點來理解,就是比如有一個應用1,通過網絡調用了應用2的服務,而不用關心應用2的協議。這就是最簡單的示例。

 

② 爲什麼是遠程過程調用協議?

這裏的過程指的是業務處理,計算任務,更直白一點,就是程序,就像是在調用本地方法一樣

與本地調用的區別可以用異地戀這個場景來理解,本地調用即是,大家同居了,都住在一起,而遠程調用就像異地戀,想要見面就要不男坐車過來要不女坐車過來,而且也說異地戀一般沒有好結果。遠程調用中間需要網絡,所以我們就可以明白

因爲是通過網絡通信的,所以
    響應會慢上幾個數量級
    不像本地如此可靠

 

③ RPC的C/S模式

RPC採用client-server結構,通過request-response消息模式實現,如果服務器的程序進行了更新,那客戶端方面也需要同步更新才能正常使用

 

④ RPC的三個過程


 

 

1.通訊協議:類似於在中國需要聯繫美國的朋友一樣,我們會有多種聯繫他的方式,比如打電話,比如自己去美國等

2.尋址:我們需要知道怎麼聯繫,比如打電話我們需要知道電話號碼,自己去需要知道美國在哪兒這樣的,

也就是,必須要知道一個具體的地址。

3.數據序列化:序列化的作用也很簡單,比如我們已經撥通了這個外國人的電話,或者說已經去到他面前了

可我們說的是中文,他說英語,大家互相都聽不懂大家說話,這就沒法溝通了,所以序列化其實就是一個翻譯作用

把這三個過程都做好之後RPC才能正常工作

 

⑤ 我們爲什麼要使用RPC

微服務,分佈式系統架構的興起,爲了實現服務可重用或者說系統間的交互調用

 

⑥ RPC與其他協議的區別

RPC和RMI的區別,RMI遠程方法調用是OOP領域中RPC的一種具體實現。也就是RPC是父,RMI是子,且RMI僅能存在Java中調用

WebService,restful接口調用其實都是RPC,只是它們的消息組織方式和消息協議不同而已

 

⑦ 與MQ的使用場景對比:

RPC就像打電話,需要一個迅速的迴應(接通或者不接通),而MQ就像發微信QQ的消息,發過去了,不急着等回

 

架構上它們的差異是:MQ有一箇中間節點Queue作爲消息存儲點

 

RPC是同步調用,對於要等待返回結果的實例,RPC處理的比較自然,由於等待結果時Consumer會有線程消耗,

如果以異步的方式使用,可以避免Consumer的線程消耗,

但它不能做到像MQ一樣暫存請求,壓力會堆積在服務Provider那

 

而MQ會把請求的壓力暫緩,讓處理者根據自己的節奏處理,但是由於消息是暫存在了它的隊列中,

所以這個系統的可靠性會受到這個隊列的影響,它是異步單向,發送消息設計爲不需要等待消息處理的完成,

所以如果有需要設計爲同步返回的功能的話,MQ就會變得不好使用

 

所以如果是非常着急需要等待返回結果的,又或者說是希望使用方便簡單,RPC就比較好用,它的使用基於接口,類似於本地調用,異步編程會相對複雜,比如gRPC。而不希望發送端受限於處理端的速度時,就使用MQ。隨着業務增長,也會出現處理端的處理速度不夠,從而進行同步調用到異步消息的改造

 

 

2.RPC的流程及其協議

① RPC的流程

 

 

1.客戶端處理過程中調用client stub(就像調用本地方法一樣),傳入參數

2.Client stub將參數編組爲消息,然後通過系統調用向服務端發送消息

3.客戶端本地操作系統將消息從客戶端機器發送到服務端機器

4.服務端操作系統將接收到的數據包傳遞給client stub

5.server stub解組消息爲參數

6.server stub再調用服務端的過程,過程執行結果以反方向的相同步驟響應給客戶端

 

stub:分佈式計算中的存根是一段代碼,它轉換在遠程過程調用期間Client和server之間傳遞的參數

 

② 整個流程中需要注意處理的問題

1.client stub和server stub的開發問題

2.參數如何編組爲消息,以及如何解組

3.消息如何發送

4.過程結果如何表示,異常情況如何處理

5.如何實現安全的訪問控制

 

③ 核心概念術語

client和server,calls調用,replies響應,services,programs,procedures,version,Marshalling和unmarshalling編組和解組

關於services,programs,procedures,version

一個網絡服務由一個或者多個遠程程序集構成,而一個遠程程序實現一個或多個遠程過程。過程與過程參數,結果在程序協議說明書中定義說明,而爲兼容程序協議變更,一個服務端可能支持多個版本的遠程程序

 

④ RPC協議

RPC調用過程中需要將參數編組爲消息進行發送,接收方需要解組消息爲參數,過程處理結果同樣需要經過編組解組。消息由哪些部分構成及消息的表示形式就構成了消息協議。RPC調用過程中採用的消息協議成爲RPC協議

RPC是規定要做的事,RPC協議規定請求響應消息的格式,在TCP之上我們可以選用或自定義消息協議來完成我們的RPC交互,此時我們可以選用http或者https這種通用的標準協議,也可以根據自身的需要定義自己的消息協議(較多)

 

RPC框架

RPC框架封裝好了參數編組解組,底層網絡通信的RPC程序開發框架,讓我們可以直接在其基礎之上只需要專注於過程代碼編寫

Java領域的比較常見的RPC框架:webService,Apache的CXF,Axis2,Java自帶的jax-ws,微服務常見的dubbo,spring cloud,Apache Thrift,ICE,google的GRPC等

 

① RPC框架的服務暴露

遠程提供者需要以某種形式提供服務調用相關的信息,包括但不限於服務接口定義,數據結構,或者中間態的服務定義文件,例如Thrift的IDL文件,webService的WSDL文件,服務的調用者需要通過一定的途徑或者遠程服務調用的相關信息,其實簡單點說,就是需要告訴別人怎麼調用服務

 

 

② RPC框架的遠程代理對象

代理處理技術:服務調用者用的服務實際是遠程服務的本地代理,其實就是通過動態代理來實現

Java裏至少提供了兩種方式來提供動態代碼生成,一種是jdk動態代理,另一種是字節碼生成,動態代理相比字節碼生成使用起來更方便,但動態代理方式在性能上比字節碼要差,而字節碼生成在代碼可讀性上要差很多,所以我們一般都是犧牲一些性能來獲得代碼可讀性和可維護性的提高

 

③ RPC框架的通信

RPC框架通信和具體的協議是無關的,它可基於HTTP或TCP協議,webService就是基於http協議的RPC,具有更好的跨平臺性,但性能卻不如基於TCP協議的RPC

 

 

NIO其實不一定會比BIO性能要高,NIO只是爲了支持高併發而已,特點就是非阻塞的,適合小數據量高併發的場景,大數據包情況下,BIO會更爲合適

 

④ RPC框架的序列化

兩個方面會直接影響RPC的性能,一是傳輸方式,二是序列化

 

 

二:RPC的流程和任務

1. RPC的流程

其實這個在上一篇的2 - ① 也已經提到過了,如果忘了,沒關係,我再複製過來

stub:分佈式計算中的存根是一段代碼,它轉換在遠程過程調用期間Client和server之間傳遞的參數

 

 

1.客戶端處理過程中調用client stub(就像調用本地方法一樣),傳入參數

2.Client stub將參數編組爲消息,然後通過系統調用向服務端發送消息

3.客戶端本地操作系統將消息從客戶端機器發送到服務端機器

4.服務端操作系統將接收到的數據包傳遞給client stub

5.server stub解組消息爲參數

6.server stub再調用服務端的過程,過程執行結果以反方向的相同步驟響應給客戶端

2. 從使用者的角度開始分析

 

 

1.定義過程接口
2.服務端實現接口的整個過程
3.客戶端使用生成的stub代理對象

 

三:RPC框架的設計及實現

1. 準備一個Student的實體類及基礎接口

客戶端生成過程接口的代理對象,通過設計一個客戶端代理工廠,使用JDK動態代理即可生成接口的代理對象

 

 

① 定義一個StudentService接口

Student類有三個屬性name(String),age(int),sex(String),節省篇幅就不貼代碼了,提供getter,setter和toString方法即可

 

並且提供一個簡單的實現,其實就是打印一個Student的信息出來而已

 

 

 

2.客戶端的搭建

① 從測試類去了解所需

首先,客戶端通過我們的本地代理,獲得我們的StudentService的代理類,此時我們客戶端本地是肯定不存在StudentService的實現的,此時尋址我們是直接給出來了

 

 

此時我們的關注點轉到客戶端是如何幫我們進行代理的

 

② 實現了InvocationHandler接口的RpcClientProxy

 

JDK提供了Proxy類來實現我們的動態代理,可以通過newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2)方法來實例化一個代理對象,此時我們傳入的參數clazz是規定必須爲一個接口的,如果不是接口就不能使用JDK動態代理

 

而第三個參數RpcClientProxy.this則是newProxyInstance()方法雖然幫我們創建好了實例,但是創建實例完成後的具體動作必須由這個InvocationHandler來提供

 

InvocationHandler這個接口裏面僅僅只有一個 Object invoke(Object var1, Method var2, Object[] var3) throws Throwable,這個方法的參數相信不難理解,第一個是代理對象,第二個是執行的方法,第三個是所需的參數集

 

回到我們剛剛的代碼,在我執行System.out.println(service.getInfo())這條語句的時候,我們的邏輯就會跳到invoke()的實現中來,在invoke()方法的註釋中也把過程很詳細的說明了,首先我們需要調用遠程服務了,進行一個參數的封裝,之後就進行一個網絡連接把這些參數發送給我們的服務端,此時我們需要用到RpcClient了

 

③ RpcClient

在start()方法中,我們的RpcRequest request是實現了Serializable接口的,所以此時封裝好的數據會轉換成一個二進制然後被flush()過去,此時我們消息已經發送了,需要等待服務端的響應,響應我們就需要通過我們的服務端ObjectOutputStream來接收一個輸入流

 

 

④ 進行參數封裝的RpcRequest

 

⑤ Rpc服務端響應結果包裝RpcResponse

同時也是實現了JDK默認的序列化Serializable

 

 

3.服務端的搭建

① 服務端的模擬ServerTest

 

給到一個端口號,參數中帶有一個包,功能是掃描某個包下的服務

 

② start()方法的實現

創建一個Map類型的集合services存放掃描到提供rpc服務的類,此時因爲沒有放在註冊中心上所以就不存在尋址了。後面將會把它放入zookeeper的註冊中心

 

getService()下,我們在ServerTest不是提供了一個包名嗎,此時我們先去找到了它們所有的classes(請參考getClasses()方法),getClasses()中我們其實主要是先根據提供的包名往下找,要是目錄都有問題的話就拋出異常,如果沒問題,就開始遍歷此目錄下的所有文件,遍歷出來的結果如果發現這個文件是class文件,就把其實例化,並且進行判斷是否存在一個自定義註解@service,標註了這個註解的類就是RPC服務的實現類。如果存在這個註解,那就是我們需要找的rpc服務,就把它裝到一個結果集classes中,如果目錄下面仍然是目錄,那就自己調用自己,直到看到class文件爲止

 

當我們把所有的class都找到了,回到getService()方法下,就都集中放於一個classList中,然後把它們Map化,就是把接口的名稱作爲key,把實例作爲value(services.put(cla.getAnnotation(Service.class).value().getName(), obj))。

 

最後再回到start(),進行完服務掃描之後還會有一個RpcServerHandler來進行處理

/**
 * RpcServer
 * Rpc服務提供者
 */
public class RpcServer {

    /**
     * 啓動指定的網絡端口號服務,並監聽端口上的請求數據。獲得請求數據以後將請求信息委派給服務處理器,放入線程池中執行。
     * @param port 監聽端口
     * @param clazz 服務類所在包名,多個用英文逗號隔開
     */
    public void start(int port, String clazz) {
        ServerSocket server = null;
        try {
            // 1. 創建服務端指定端口的socket連接
            server = new ServerSocket(port);
            // 2. 獲取所有rpc服務類
            Map<String, Object> services = getService(clazz);
            // 3. 創建線程池
            Executor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
            while(true){
                // 4. 獲取客戶端連接
                Socket client = server.accept();
                // 5. 放入線程池中執行
                RpcServerHandler service = new RpcServerHandler(client, services);
                executor.execute(service);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //關閉監聽
            if(server != null)
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

    /**
     * 實例化所有rpc服務類,也可用於暴露服務信息到註冊中心。
     * @param clazz 服務類所在包名,多個用英文逗號隔開
     * @return
     */
    public Map<String,Object> getService(String clazz){
        try {
            Map<String, Object> services = new HashMap<String, Object>();
            // 獲取所有服務類
            String[] clazzes = clazz.split(",");
            List<Class<?>> classes = new ArrayList<Class<?>>();
            for(String cl : clazzes){
                List<Class<?>> classList = getClasses(cl);
                classes.addAll(classList);
            }
            // 循環實例化
            for(Class<?> cla:classes){
                Object obj = cla.newInstance();
                services.put(cla.getAnnotation(Service.class).value().getName(), obj);
            }
            return services;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 獲取包下所有有@Sercive註解的類
     * @param pckgname
     * @return
     * @throws ClassNotFoundException
     */
    public static List<Class<?>> getClasses(String pckgname) throws ClassNotFoundException {
        // 需要查找的結果
        List<Class<?>> classes = new ArrayList<Class<?>>();
        // 找到指定的包目錄
        File directory = null;
        try {
            ClassLoader cld = Thread.currentThread().getContextClassLoader();
            if (cld == null)
                throw new ClassNotFoundException("無法獲取到ClassLoader");
            String path = pckgname.replace('.', '/');
            URL resource = cld.getResource(path);
            if (resource == null)
                throw new ClassNotFoundException("沒有這樣的資源:" + path);
            directory = new File(resource.getFile());
        } catch (NullPointerException x) {
            throw new ClassNotFoundException(pckgname + " (" + directory + ") 不是一個有效的資源");
        }
        if (directory.exists()) {
            // 獲取包目錄下的所有文件
            String[] files = directory.list();
            File[] fileList = directory.listFiles();
            // 獲取包目錄下的所有文件
            for (int i = 0; fileList != null && i < fileList.length; i++) {
                File file = fileList[i];
                //判斷是否是Class文件
                if (file.isFile() && file.getName().endsWith(".class")) {
                    Class<?> clazz = Class.forName(pckgname + '.' + files[i].substring(0, files[i].length() - 6));
                    if(clazz.getAnnotation(Service.class) != null){
                        classes.add(clazz);
                    }
                }else if(file.isDirectory()){ //如果是目錄,遞歸查找
                    List<Class<?>> result = getClasses(pckgname+"."+file.getName());
                    if(result != null && result.size() != 0){
                        classes.addAll(result);
                    }
                }
            }
        } else{
            throw new ClassNotFoundException(pckgname + "不是一個有效的包名");
        }
        return classes;
    }
}

 

 

③ 進行處理的RpcServerHandler

和剛剛的RpcClient非常類似,都是序列化和反序列化的過程,主要是第三步中獲得了實例和方法及其參數後,再調用invoke()方法然後把結果放入response的過程

/**
 * RpcServerHandler
 * 服務端請求處理,處理來自網絡IO的服務請求,並響應結果給網絡IO。
 */
public class RpcServerHandler implements Runnable {

    // 客戶端網絡請求socket,可以從中獲得網絡請求信息
    private Socket clientSocket;

    // 服務端提供處理請求的類集合
    private Map<String, Object> serviceMap;

    /**
     * @param client 客戶端socket
     * @param services 所有服務
     */
    public RpcServerHandler(Socket client, Map<String, Object> services) {
        this.clientSocket = client;
        this.serviceMap = services;
    }


    /**
     * 讀取網絡中客戶端請求的信息,找到請求的方法,執行本地方法獲得結果,寫入網絡IO輸出中。
     * 
     */
    public void run() {
        ObjectInputStream oin = null;
        ObjectOutputStream oout = null;
        RpcResponse response = new RpcResponse();
        try {
            // 1. 獲取流以待操作
            oin = new ObjectInputStream(clientSocket.getInputStream());
            oout = new ObjectOutputStream(clientSocket.getOutputStream());

            // 2. 從網絡IO輸入流中請求數據,強轉參數類型
            Object param = oin.readObject();
            RpcRequest  request = null;
            if(!(param instanceof RpcRequest)){
                response.setError(new Exception("參數錯誤"));
                oout.writeObject(response);
                oout.flush();
                return;
            }else{
                // 反序列化RpcRequest
                request = (RpcRequest) param;
            }

            // 3. 查找並執行服務方法
            Object service = serviceMap.get(request.getClassName());
            Class<?> clazz= service.getClass();
            Method method = clazz.getMethod(request.getMethodName(), request.getParamTypes());
            Object result = method.invoke(service, request.getParams());

            // 4. 返回RPC響應,序列化RpcResponse
            response.setResult(result);
            // 序列化結果
            oout.writeObject(response);
            oout.flush();
            return;
        } catch (Exception e) {
            try {   //異常處理
                if(oout != null){
                    response.setError(e);
                    oout.writeObject(response);
                    oout.flush();
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return;
        }finally{
            try {   // 回收資源,關閉流
                if(oin != null) oin.close();
                if(oout != null) oout.close();
                if(clientSocket != null) clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

 

4.運行結果

先開啓ServerTest再開啓ClientTest,簡單快捷,注意別去右鍵跑main方法即可

 

 

四:優化客戶端的舉措

1.發現者的引入

設計客戶端的時候,在ClientStubInvocationHandler中需要完成的兩件事爲編組消息和發送網絡請求,而將請求的內容編組爲消息這件事就交由客戶端的stub代理,它除了消息協議和網絡層的事務以外,可能還存在一個服務信息發現,此外消息協議可能也是會存在變化的,我們也需要去支持多種協議,這個其實是和框架對協議的支持廣度有關的。比如dubbo相對於spring cloud而言對協議的支持就相對靈活一些

 

此時我們需要得知某服務用的是什麼協議,所以我們需要引入一個服務發現者

 

 

2.協議層

我們想要做到支持多種協議,類該如何設計(面向接口,策略模式,組合)

 

 

此時我們的協議需要抽象出來,對於協議的內容需要進行編組和解組,比如我們上面提供的JSON和HTTP兩種不同的實現,而此時客戶端的存根裏面就不僅僅只是需要服務發現者,還需要我們對於這個協議的支持

 

① 補充:如何從zookeeper中獲取註冊信息

主要看regist()方法,我們在註冊的時候把服務信息進行了拼接,並創建成臨時節點,父節點爲持久節點。servicePath是類似於dubbo的一個目錄結構,一個根目錄/rpc+服務名稱serviceName+service,獲取服務的方法loadServiceResouces()也不難,根據這些地址獲取它們下面的子節點,把所有的url加載出來給到調用者

public class RegistCenter {
    ZkClient client = new ZkClient("localhost:2181");

    private String centerRootPath = "/rpc";

    public RegistCenter() {
        client.setZkSerializer(new MyZkSerializer());
    }

    public void regist(ServiceResource serviceResource) {
        String serviceName = serviceResource.getServiceName();
        String uri = JsonMapper.toJsonString(serviceResource);
        try {
            uri = URLEncoder.encode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String servicePath = centerRootPath + "/"+serviceName+"/service";
        if(! client.exists(servicePath)) {
            client.createPersistent(servicePath, true);
        }
        String uriPath = servicePath+"/"+uri;
        client.createEphemeral(uriPath);
    }

    /**
     * 加載配置中心中服務資源信息
     * @param serviceName
     * @return
     */
    public List<ServiceResource> loadServiceResouces(String serviceName) {
        String servicePath = centerRootPath + "/"+serviceName+"/service";
        List<String> children = client.getChildren(servicePath);
        List<ServiceResource> resources = new ArrayList<ServiceResource>();
        for(String ch : children) {
            try {
                String deCh = URLDecoder.decode(ch, "UTF-8");
                ServiceResource r = JsonMapper.fromJsonString(deCh, ServiceResource.class);
                resources.add(r);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return resources;
    }

    private void sub(String serviceName, ChangeHandler handler) {
        /*
        String path = centerRootPath + "/"+serviceName+"/service";
        client.subscribeChildChanges(path, new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                handler();
            }
        });
        client.subscribeDataChanges(path, new IZkDataListener() {
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                handler();
            }

            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {
                handler();
            }
        });
        */
    }

    interface ChangeHandler {
        /**
         * 發生變化後給一個完整的屬性對象
         * @param resource
         */
        void itemChange(ServiceResource resource);
    }
}

 

② ClientStubProxyFactory

/**
 * ClientStubProxyFactory
  *   客戶端存根代理工廠
 */
public class ClientStubProxyFactory {

    private ServiceInfoDiscoverer sid;

    private Map<String, MessageProtocol> supportMessageProtocols;

    private NetClient netClient;

    private Map<Class<?>, Object> objectCache = new HashMap<>();

    /**
     * 
     * 
     * @param <T>
     * @param interf
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> interf) {
        T obj = (T) this.objectCache.get(interf);
        if (obj == null) {
            obj = (T) Proxy.newProxyInstance(interf.getClassLoader(), new Class<?>[] { interf },
                    new ClientStubInvocationHandler(interf));
            this.objectCache.put(interf, obj);
        }

        return obj;
    }

    public ServiceInfoDiscoverer getSid() {
        return sid;
    }

    public void setSid(ServiceInfoDiscoverer sid) {
        this.sid = sid;
    }

    public Map<String, MessageProtocol> getSupportMessageProtocols() {
        return supportMessageProtocols;
    }

    public void setSupportMessageProtocols(Map<String, MessageProtocol> supportMessageProtocols) {
        this.supportMessageProtocols = supportMessageProtocols;
    }

    public NetClient getNetClient() {
        return netClient;
    }

    public void setNetClient(NetClient netClient) {
        this.netClient = netClient;
    }

    /**
     * ClientStubInvocationHandler
     * 客戶端存根代理調用實現
     * @date 2019年4月12日 下午2:38:30
     */
    private class ClientStubInvocationHandler implements InvocationHandler {
        private Class<?> interf;

        public ClientStubInvocationHandler(Class<?> interf) {
            super();
            this.interf = interf;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            // 1、獲得服務信息
            String serviceName = this.interf.getName();
            ServiceInfo sinfo = sid.getServiceInfo(serviceName);

            if (sinfo == null) {
                throw new Exception("遠程服務不存在!");
            }

            // 2、構造request對象
            Request req = new Request();
            req.setServiceName(sinfo.getName());
            req.setMethod(method.getName());
            req.setPrameterTypes(method.getParameterTypes());
            req.setParameters(args);

            // 3、協議層編組
            // 獲得該方法對應的協議
            MessageProtocol protocol = supportMessageProtocols.get(sinfo.getProtocol());
            // 編組請求
            byte[] data = protocol.marshallingRequest(req);

            // 4、調用網絡層發送請求
            byte[] repData = netClient.sendRequest(data, sinfo);

            // 5解組響應消息
            Response rsp = protocol.unmarshallingResponse(repData);

            // 6、結果處理
            if (rsp.getException() != null) {
                throw rsp.getException();
            }

            return rsp.getReturnValue();
        }
    }
}

 

ClientStub中有兩個引用,一個是服務發現接口ServiceInfoDiscoverer,作用爲根據服務名獲得遠程服務信息,提供一個ServiceInfo getServiceInfo(String name)方法,還有就是對於不同協議的支持supportMessageProtocols,MessageProtocol我們也是定義了一個接口,這個接口就需要比較詳細了,編碼成二級制,和解碼成Request等,對於response也是同樣這麼個過程

 

 

此時又存在一些問題,單純依靠編組和解組的方法是不夠的,編組和解組的操作對象是請求,響應,但是它們的內容是不同的,此時我們又需要定義框架標準的請求響應類

 

 

request有具體的服務名,服務方法,消息頭,參數類型和參數,同樣的response也有狀態(通過枚舉),消息頭,返回值及類型以及是否存在異常。

此時協議層擴展爲4個方法

 

 

將消息協議獨立爲一層,客戶端和服務端都需要使用

 

3. 網絡層

 

 

網絡層的工作主要是發送請求和獲得響應,此時我們如果需要發起網絡請求必定先要知道服務地址,此時我們利用下圖中serviceInfo對象作爲必須依賴,setRequest()方法裏面會存在發送數據,還有發送給誰,此時給出了BIO和Netty兩種實現

所以我們需要的三個依賴就都出來了,一個是服務發現者,一個是協議支持,再然後就是我們網絡層的NetClient

 

4. 總圖

紫色代表客戶端代理部分,淺綠色屬於服務發現,淺藍色屬於協議部分

 

 

5.代碼部分(可直接無視)

因爲這些代碼和主要的思路已經沒有瓜葛了,只是一些功能代碼,所以可以直接忽略了。如果實在是想自己跑一下,也可以問我要一個小樣。

 

① 依舊是回到我們的ClientStubProxyFactory

可以和內容二的RpcClientProxy做一個對比,在原有的基礎上加上了三個依賴ServiceInfoDiscoverer,supportMessageProtocols,netClient

 

 

在ClientStubProxyFactory中對Object做了一個緩存,如果已經存在這個緩存就直接返回,沒有的話加入到緩存中然後new出來,只是一個小小的不同。

 

② invoke()方法的改變

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    // 1、獲得服務信息
    String serviceName = this.interf.getName();
    ServiceInfo sinfo = sid.getServiceInfo(serviceName);

    if (sinfo == null) {
        throw new Exception("遠程服務不存在!");
    }

    // 2、構造request對象
    Request req = new Request();
    req.setServiceName(sinfo.getName());
    req.setMethod(method.getName());
    req.setPrameterTypes(method.getParameterTypes());
    req.setParameters(args);

    // 3、協議層編組
    // 獲得該方法對應的協議
    MessageProtocol protocol = supportMessageProtocols.get(sinfo.getProtocol());
    // 編組請求
    byte[] data = protocol.marshallingRequest(req);

    // 4、調用網絡層發送請求
    byte[] repData = netClient.sendRequest(data, sinfo);

    // 5、解組響應消息
    Response rsp = protocol.unmarshallingResponse(repData);

    // 6、結果處理
    if (rsp.getException() != null) {
        throw rsp.getException();
    }

    return rsp.getReturnValue();
}

 

首先是服務發現,在我們執行 ① 中提到的getProxy()方法時,此時代理的接口已經直接告訴我們了,所以我們就直接獲得了接口信息interf,然後調用getName()方法獲取接口的名稱,通過接口名,調用服務發現者ServiceInfo提供的getServiceInfo()方法就能獲取服務的具體信息,然後放入請求參數request裏面,接下來給request的各個屬性賦值

 

之後我們就開始尋找這個服務所對應的協議,獲得協議之後可以獲取協議支持對象,之後進行編組請求,轉換成二進制,通過netClient發送過去,順帶連同服務端信息給出去。獲取結果repData進行解組(二進制回到response),之後進行結果處理。

 

③ 服務發現者的實現

之前也提到了,服務發現者ServiceInfoDiscoverer是作爲一個接口提供了getServiceInfo()方法的

 

 

有兩種不同的實現,本地實現我們可以自己搞一個配置文件加載進來,把相關的服務信息弄進去得了

 

zookeeper的服務發現實現如下,類似於我們一開始在2 - ① 中補充的zookeeper的內容

public class ZookeeperServiceInfoDiscoverer implements ServiceInfoDiscoverer {
    ZkClient client = new ZkClient("localhost:2181");

    private String centerRootPath = "/rpc";

    public ZookeeperServiceInfoDiscoverer() {
        client.setZkSerializer(new MyZkSerializer());
    }

    public void regist(ServiceInfo serviceResource) {
        String serviceName = serviceResource.getName();
        String uri = JSON.toJSONString(serviceResource);
        try {
            uri = URLEncoder.encode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String servicePath = centerRootPath + "/"+serviceName+"/service";
        if(! client.exists(servicePath)) {
            client.createPersistent(servicePath, true);
        }
        String uriPath = servicePath+"/"+uri;
        client.createEphemeral(uriPath);
    }

    /**
     * 加載配置中心中服務資源信息
     * @param serviceName
     * @return
     */
    public List<ServiceInfo> loadServiceResouces(String serviceName) {
        String servicePath = centerRootPath + "/"+serviceName+"/service";
        List<String> children = client.getChildren(servicePath);
        List<ServiceInfo> resources = new ArrayList<ServiceInfo>();
        for(String ch : children) {
            try {
                String deCh = URLDecoder.decode(ch, "UTF-8");
                ServiceInfo r = JSON.parseObject(deCh, ServiceInfo.class);
                resources.add(r);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return resources;
    }

    @Override
    public ServiceInfo getServiceInfo(String name) {
        List<ServiceInfo> list = loadServiceResouces(name);
        ServiceInfo info = list.get(0);
        list.forEach((e)->{
            if(e != info) {
                info.addAddress(e.getAddress().get(0));
            }
        });
        return info;
    }

}

 

④ 協議支持相關

 

 

這裏只實現了JSON的,通過fastJSON來實現

public class JSONMessageProtocol implements MessageProtocol {

    @Override
    public byte[] marshallingRequest(Request req) {
        Request temp = new Request();
        temp.setServiceName(req.getServiceName());
        temp.setMethod(req.getMethod());
        temp.setHeaders(req.getHeaders());
        temp.setPrameterTypes(req.getPrameterTypes());

        if (req.getParameters() != null) {
            Object[] params = req.getParameters();
            Object[] serizeParmas = new Object[params.length];
            for (int i = 0; i < params.length; i++) {
                serizeParmas[i] = JSON.toJSONString(params[i]);
            }

            temp.setParameters(serizeParmas);
        }

        return JSON.toJSONBytes(temp);
    }

    @Override
    public Request unmarshallingRequest(byte[] data) {
        Request req = JSON.parseObject(data, Request.class);
        if(req.getParameters() != null) {
            Object[] serizeParmas = req.getParameters();
            Object[] params = new Object[serizeParmas.length];
            for(int i = 0; i < serizeParmas.length; i++) {
                Object param = JSON.parseObject(serizeParmas[i].toString(), Object.class);
                params[i] = param;
            }
            req.setParameters(params);
        }
        return req;
    }

    @Override
    public byte[] marshallingResponse(Response rsp) {
        Response resp = new Response();
        resp.setHeaders(rsp.getHeaders());
        resp.setException(rsp.getException());
        resp.setReturnValue(rsp.getReturnValue());
        resp.setStatus(rsp.getStatus());
        return JSON.toJSONBytes(resp);
    }

    @Override
    public Response unmarshallingResponse(byte[] data) {
        return JSON.parseObject(data, Response.class);
    }

}

 

⑤ NetClient相關

 

分爲BIO和Netty兩種模式,netty中使用了EventLoopGroup

 

BIO:

 

 

netty模式:

 

 

⑥ 運行結果

可以自行模擬一個消費者和一個生產者進行測試,這裏就不貼出來了

 

 

更多學習資料下載

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