以下資料搜集自網絡
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接到后,再反序列化,恢復為內存中的表達方式,交給應用