RESTful源碼學習筆記之RPC和Restful深入理解


以下資料搜集自網絡 

 

0x00 RPC

RPC 即遠程過程調用(Remote Procedure Call Protocol,簡稱RPC),像調用本地服務(方法)一樣調用服務器的服務(方法)。通常的實現有 XML-RPC , JSON-RPC , 通信方式基本相同, 所不同的只是傳輸數據的格式.

RPC是分布式架構的核心,按響應方式分如下兩種:

同步調用:客戶端調用服務方方法,等待直到服務方返回結果或者超時,再繼續自己的操作

異步調用:客戶端把消息發送給中間件,不再等待服務端返回,直接繼續自己的操作。

同步調用的實現方式有WebService和RMI。Web Service提供的服務是基於web容器的,底層使用http協議,因而適合不同語言異構系統間的調用。RMI實際上是Java語言的RPC實現,允許方法返回 Java 對象以及基本數據類型,適合用於JAVA語言構建的不同系統間的調用。

異步調用的JAVA實現版就是JMS(Java Message Service),目前開源的的JMS中間件有Apache社區的ActiveMQ、Kafka消息中間件,另外有阿里的RocketMQ。

RPC架構里包含如下4個組件:

1、 客戶端(Client):服務調用方

2、 客戶端存根(Client Stub):存放服務端地址信息,將客戶端的請求參數打包成網絡消息,再通過網絡發送給服務方

3、 服務端存根(Server Stub):接受客戶端發送過來的消息並解包,再調用本地服務

4、服務端(Server):真正的服務提供者。 

具體實現步驟:

1、 服務調用方(client)(客戶端)以本地調用方式調用服務;

2、 client stub接收到調用后負責將方法、參數等組裝成能夠進行網絡傳輸的消息體;在Java里就是序列化的過程

3、 client stub找到服務地址,並將消息通過網絡發送到服務端;

4、 server stub收到消息后進行解碼,在Java里就是反序列化的過程;

5、 server stub根據解碼結果調用本地的服務;

6、 本地服務執行處理邏輯;

7、 本地服務將結果返回給server stub;

8、 server stub將返回結果打包成消息,Java里的序列化;

9、 server stub將打包后的消息通過網絡並發送至消費方

10、 client stub接收到消息,並進行解碼, Java里的反序列化;

11、 服務調用方(client)得到最終結果。

RPC框架的目標就是把2-10步封裝起來,把調用、編碼/解碼的過程封裝起來,讓用戶像調用本地服務一樣的調用遠程服務。要做到對客戶端(調用方)透明化服務, RPC框架需要考慮解決如下問題: 
1、通訊問題 : 主要是通過在客戶端和服務器之間建立TCP連接,遠程過程調用的所有交換的數據都在這個連接里傳輸。連接可以是按需連接,調用結束后就斷掉,也可以是長連接,多個遠程過程調用共享同一個連接。 
2、尋址問題 : A服務器上的應用怎么告訴底層的RPC框架,如何連接到B服務器(如主機或IP地址)以及特定的端口,方法的名稱是什么,這樣才能完成調用。比如基於Web服務協議棧的RPC,就要提供一個endpoint URI,或者是從UDDI服務上查找。如果是RMI調用的話,還需要一個RMI Registry來注冊服務的地址。 
3、序列化與反序列化 : 當A服務器上的應用發起遠程過程調用時,方法的參數需要通過底層的網絡協議如TCP傳遞到B服務器,由於網絡協議是基於二進制的,內存中的參數的值要序列化成二進制的形式,也就是序列化(Serialize)或編組(marshal),通過尋址和傳輸將序列化的二進制發送給B服務器。 
同理,B服務器接收參數要將參數反序列化。B服務器應用調用自己的方法處理后返回的結果也要序列化給A服務器,A服務器接收也要經過反序列化的過程。

 

0x01 REST

  REST即表述性狀態傳遞(Representational State Transfer,簡稱REST),是一種軟件架構風格。REST通過HTTP協議定義的通用動詞方法(GET、PUT、DELETE、POST) ,以URI對網絡資源進行唯一標識,響應端根據請求端的不同需求,通過無狀態通信,對其請求的資源進行表述。 
  Rest架構的主要原則:

1.   網絡上的所有事物都被抽象為資源

2.   每個資源都有一個唯一的資源標識符

3.   同一個資源具有多種表現形式(xml,json等)

4.   對資源的各種操作不會改變資源標識符

5.   所有的操作都是無狀態的

其中表述性狀態,是指(在某個瞬間狀態的)資源數據的快照,包括資源數據的內容、表述格式(XML、JSON)等信息。

其中無狀態通信,是指服務端(響應端)不保存任何與特定HTTP請求相關的資源,應用狀態必須由請求方在請求過程中提供。要求在網絡通信過程中,任意一個Web請求必須與其他請求隔離,當請求端提出請求時,請求本身包含了響應端為響應這一請求所需的全部信息。

REST使用HTTP+URI+XML /JSON 的技術來實現其API要求的架構風格:HTTP協議和URI用於統一接口和定位資源,文本、二進制流、XML、JSON等格式用來作為資源的表述。

舉例: 
在Restful之前的操作: 請求的地址對應具體的業務操作 
http://127.0.0.1/user/query/1 GET 根據用戶id查詢用戶數據 
http://127.0.0.1/user/save POST 新增用戶 
http://127.0.0.1/user/update POST 修改用戶信息 
http://127.0.0.1/user/delete GET/POST 刪除用戶信息

RESTful用法: 請求 
http://127.0.0.1/user/1 GET 根據用戶id查詢用戶數據 
http://127.0.0.1/user POST 新增用戶 
http://127.0.0.1/user PUT 修改用戶信息 
http://127.0.0.1/user DELETE 刪除用戶信息

RESTful風格的體現,在你使用了get請求,就是查詢;使用post請求,就是新增的請求;使用put請求,就是修改的請求;使用delete請求,就是刪除的請求。這樣做就完全沒有必要對crud做具體的描述。

滿足REST約束條件和原則的架構,就被稱為是RESTful架構。就像URL都是URI(統一資源標識)的表現形式一樣,RESTful是符合REST原則的表現形式。

 

如何使用:

SpringMVC實現restful服務:

SpringMVC原生態的支持了REST風格的架構設計

所涉及到的注解:

--@RequestMapping

---@PathVariable

---@ResponseBody

package cn.itcast.mybatis.controller;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestMethod;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.ResponseBody;  
  
import cn.itcast.mybatis.pojo.User;  
import cn.itcast.mybatis.service.NewUserService;  
  
@RequestMapping("restful/user")  
@Controller  
public class RestUserController {  
  
    @Autowired  
    private NewUserService newUserService;  
  
    /** 
     * 根據用戶id查詢用戶數據 
     *  
     * @param id 
     * @return 
     */  
    @RequestMapping(value = "{id}", method = RequestMethod.GET)  
    @ResponseBody  
    public ResponseEntity<User> queryUserById(@PathVariable("id") Long id) {  
        try {  
            User user = this.newUserService.queryUserById(id);  
            if (null == user) {  
                // 資源不存在,響應404  
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);  
            }  
            // 200  
            // return ResponseEntity.status(HttpStatus.OK).body(user);  
            return ResponseEntity.ok(user);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        // 500  
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  
    }  
  
    /** 
     * 新增用戶 
     *  
     * @param user 
     * @return 
     */  
    @RequestMapping(method = RequestMethod.POST)  
    public ResponseEntity<Void> saveUser(User user) {  
        try {  
            this.newUserService.saveUser(user);  
            return ResponseEntity.status(HttpStatus.CREATED).build();  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        // 500  
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  
    }  
  
    /** 
     * 更新用戶資源 
     *  
     * @param user 
     * @return 
     */  
    @RequestMapping(method = RequestMethod.PUT)  
    public ResponseEntity<Void> updateUser(User user) {  
        try {  
            this.newUserService.updateUser(user);  
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        // 500  
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  
    }  
  
    /** 
     * 刪除用戶資源 
     *  
     * @param user 
     * @return 
     */  
    @RequestMapping(method = RequestMethod.DELETE)  
    public ResponseEntity<Void> deleteUser(@RequestParam(value = "id", defaultValue = "0") Long id) {  
        try {  
            if (id.intValue() == 0) {  
                // 請求參數有誤  
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();  
            }  
            this.newUserService.deleteUserById(id);  
            // 204  
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        // 500  
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  
    }  
}  

1.           以ApacheThrift為代表的二進制RPC,支持多種語言(但不是所有語言),四層通訊協議,性能高,節省帶寬。相對Restful協議,使用Thrifpt RPC,在同等硬件條件下,帶寬使用率僅為前者的20%,性能卻提升一個數量級。但是這種協議最大的問題在於,無法穿透防火牆。

2.           以Spring Cloud為代表所支持的Restful 協議,優勢在於能夠穿透防火牆,使用方便,語言無關,基本上可以使用各種開發語言實現的系統,都可以接受Restful 的請求。但性能和帶寬占用上有劣勢。

所以,業內對微服務的實現,基本是確定一個組織邊界,在該邊界內,使用RPC; 邊界外,使用Restful。這個邊界,可以是業務、部門,甚至是全公司。

 

使用RPC遠程服務調用方式與傳統http接口直接調用方式的差別在於:

1. 從使用方面看,Http接口只關注服務提供方(服務端),對於客戶端怎么調用,調用方式怎樣並不關心,通常情況下,客戶端使用Http方式進行調用時,只要將內容進行傳輸即可,這樣客戶端在使用時,需要更關注網絡方面的傳輸,比較不適用與業務方面的開發;而RPC服務則需要客戶端接口與服務端保持一致,服務端提供一個方法,客戶端通過接口直接發起調用,業務開發人員僅需要關注業務方法的調用即可,不再關注網絡傳輸的細節,在開發上更為高效。

2. 從性能角度看,使用Http時,Http本身提供了豐富的狀態功能與擴展功能,但也正由於Http提供的功能過多,導致在網絡傳輸時,需要攜帶的信息更多,從性能角度上講,較為低效。而RPC服務網絡傳輸上僅傳輸與業務內容相關的數據,傳輸數據更小,性能更高。

3. 從運維角度看,使用Http接口時,常常使用一個前端代理,來進行Http轉發代理請求的操作,需要進行擴容時,則需要去修改代理服務器的配置,較為繁瑣,也容易出錯。而使用RPC方式的微服務,則只要增加一個服務節點即可,注冊中心可自動感知到節點的變化,通知調用客戶端進行負載的動態控制,更為智能,省去運維的操作。

 

1.   首先要解決尋址的問題,也就是說,A服務器上的應用怎么告訴底層的RPC框架,B服務器的IP,以及應用綁定的端口,還有方法的名稱,這樣才能完成調用

2.   方法的參數需要通過底層的網絡協議如TCP傳遞到B服務器,由於網絡協議是基於二進制的,內存中的參數的值要序列化成二進制的形式

3.   在B服務器上完成尋址后,需要對參數進行反序列化,恢復為內存中的表達方式,然后找到對應的方法進行本地調用,然后得到返回值,

4.   返回值還要發送回服務器A上的應用,也要經過序列化的方式發送,服務器A接到后,再反序列化,恢復為內存中的表達方式,交給應用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM