RPC(簡單實現)



筆者以前僅看過RPC這個單詞,徹底沒有了解過,不想終於仍是碰上了。原由:這邊想提升併發量而去看kafka(最後折中使用了redis),其中kafka須要安裝ZooKeeper,而ZooKeeper又與分佈式相關,再繼續就發現分佈式的基礎是RPC,因而寫下了這篇博文java


1. RPC

RPC(Remote Procedure Call)遠程過程調用,即經過網絡通訊來調用遠程計算機程序上的服務,而這個調用過程就像調用本地方法同樣簡單透明,而且不須要了解底層的網絡技術協議。RPC採用C/S架構,發出請求的程序是Client,提供服務的則是Server,相似於Http請求與響應。簡單總結就是:調用的方法實際在遠程,而要像調用本地方法同樣簡單。redis


1)對於客戶端的我:調用本地的一個方法(存根)就能得到服務。 這個存根是遠程服務的一個代理,其底層如何實現,對於我來講是透明的。服務器

2)對於遠程服務器:監聽是否有鏈接過來,來了就調用對應的方法並返回(服務器端較易理解)網絡


其結構圖以下:架構

  1. 用戶調用一個 「本地」 函數,該函數調用客戶句柄(遠程服務在本地的存根)
  2. 客戶句柄調用網絡通訊來訪問遠程程序
  3. 遠程程序收到網絡通訊及相關信息就調用服務句柄
  4. 服務句柄就調用服務函數,函數結束逆序返回結果完成一次遠程調用








2. 爲何須要RPC

當咱們的業務量愈來愈龐大,垂直增長服務器的數量對提升性能的做用越發微乎,此時不免會採用分佈式的架構以便更好地提升性能。分佈式架構的每一個服務都是獨立的部分,當須要完成某項業務且依賴不一樣的服務式時,這些服務就須要互相調用,此時服務之間的調用就須要一種高效的應用程序之間的通信手段了,這就是PRC出現的緣由併發








3. RPC實現要求


3.1 服務提供方

提供服務:實現所提供的服務負載均衡

服務暴漏:僅僅實現了服務是不夠的,還須要將提供的服務暴漏給外界,讓外界知道有何,如何使用服務框架


3.2 服務調用方

遠程代理對象:在調用本地方法時實際調用的是遠程的方法,那麼勢必本地須要一個遠程代理對象socket


總結:爲了實現RPC須要有:通訊模型(BIO、NIO),服務定位(IP、PORT),遠程代理對象(遠程服務的本地代理),序列化(網絡傳輸轉換成二進制)分佈式





4. 簡單實現

其主要的對象有:服務端接口、服務端接口實現、服務暴漏、客戶端接口(與服務端共享同個接口)、服務的引用


4.1 服務端接口

public interface Service {

    // 提供兩個服務,說hello和整數相加
    public String hello();
    public int sum(int a, int b);
    
}


4.2 服務端接口實現

public class ServiceImpl implements Service {

    @Override
    public String hello() {
        return "Hello World";
    }

    @Override
    public int sum(int a, int b) {
        return  a + b;
    }
}


4.3 服務暴漏

public static void export(Object service, int port) {
    
    if (service == null || port <= 0 || port > 65535) {
        throw new RuntimeException("Arguments error");
    }
    
    System.out.println(service.getClass().getName() + ": " + port + "服務暴露");
    
    new Thread( () -> {
        
        try (ServerSocket server = new ServerSocket(port);) {
            
            while(true){
                try (
                        Socket socket = server.accept();
                        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                        ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
                    ) {
                    
                    // 讀取方法名
                    String methodName = in.readUTF();
                    
                    // 讀取參數類型
                    Class<?>[] parameterTypes = (Class<?>[])in.readObject();
                    
                    // 讀取參數值
                    Object[] arguments = (Object[])in.readObject();
                    
                    // 獲取方法
                    Method method = service.getClass().getMethod(methodName, parameterTypes);
                    
                    // 處理結果
                    Object result = method.invoke(service, arguments);
                    
                    // 寫入結果
                    out.writeObject(result);
                    
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }).start();
}

這個暴露的邏輯是服務端監聽特定端口,等客戶端發起請求後鏈接,而後經過Java的IO流獲取方法名,參數等相關信息,最後經過反射實現方法的調用並將結果響應給客戶端


4.4 客戶端接口

public interface ClientService {

    // 提供兩個服務,說hello和整數相加
    public String hello();
    public int sum(int a, int b);
        
}


4.5 服務引用

public static <T>T refer(Class<T> interfaceClass, String host, int port){
    
    if(interfaceClass == null || !interfaceClass.isInterface() || host == null || port <= 0 || port > 65535){
        throw new RuntimeException("Arguments error");
    }
    
    System.out.println("正在調用遠程服務");
    
    @SuppressWarnings("unchecked")
    T proxy = (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {
                
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            
            Object result = null;
            try (
                    Socket socket = new Socket(host, port);
                    ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
                    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                ) {
                
                out.writeUTF(method.getName());
                
                out.writeObject(method.getParameterTypes());
                
                out.writeObject(args);
                
                result = in.readObject();
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    });
    return proxy;
}

而引用服務的邏輯是:建立Socket套接字鏈接,序列化相關請求信息發送給服務端,而後等待響應結果。其中透明調用是使用了動態代理


4.6 測試

public class Test {
    
    public static void main(String[] args) {
        
        // 暴露服務
        ServiceImpl service = new ServiceImpl();
        RPCFramework.export(service, 8080);
        
        // 調用服務
        Client client = RPCFramework.refer(Client.class, "127.0.0.1", 8080);
        int sum = client.sum(1, 2);
        String rs = client.hello();
        System.out.println("遠程響應:" + sum);
        System.out.println("遠程響應:" + rs);
    }
}
RPC.ServiceImpl:8080----- 服務暴露

正在調用遠程服務
遠程響應:3
遠程響應:Hello World






5. 思考


5.1 爲何不用Http

RPC與具體協議無關,可基於Http、TCP,但由於TCP性能相對較好。Http屬於應用層協議,TCP屬於傳輸層協議,相對在底層少了一層封裝,並且爲了可靠傳輸而選擇TCP不選擇UDP


5.2 經常使用的RPC框架

Dubbo(阿里巴巴)、SpringCloud、RMI(JDK內置)


5.3 爲何要使用動態代理

由於要像本地調用同樣,對於使用者來講是透明的。

Object result = XXX(String method, String host, int port)

上面這樣其實也行,但並不能感受到是調用本地方法同樣,並且若是一個接口有多個方法的話,每調用一次方法就須要發送一次host / port

// 動態代理能夠這樣使用
ProxyObject.方法1
ProxyObject.方法2

// 沒有使用動態代理則不人性化
XXX(String method1, String host, int port)
XXX(String method2, String host, int port)


5.4 爲何參數與參數類型須要分開傳

爲了方便分辨方法的重載,下面獲取方法須要方法名和參數類型

service.getClass().getMethod(methodName, parameterTypes)






6. 優化


6.1 網絡通訊

上面事例中採用BIO形式,阻塞訪問而致使併發量不高,能夠用NIO代替


6.2 序列化

這裏用了JDK原生方法只能序列化實現了Serializable接口的類,可使用第三方的類庫來提升性能


6.3 服務負載

服務的自動發現,客戶端能動態感知服務端的變化,從實現熱部署,可用定時輪詢的方法,eg:ZooKeeper


6.4 Cluster

集羣化,這樣即可以提供負載均衡


6.5 請求與響應

請求與響應能夠進行編碼封裝,而不是這樣單獨一個一個發送






參考Yanyan.He

相關文章
相關標籤/搜索