RestFul 與 RPC

原文地址:https://blog.csdn.net/u014590757/article/details/80233901前端

RPC、REST API深刻理解

一: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個組件:

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

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

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

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

具體實現步驟:

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

二、 client stub接收到調用後負責將方法、參數等組裝成可以進行網絡傳輸的消息體;在Java裏就是序列化的過程

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

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

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

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

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

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

九、 server stub將打包後的消息經過網絡併發送至消費方

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

十一、 服務調用方(client)獲得最終結果。

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

二: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

[java] view plain copy

    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接到後,再反序列化,恢復爲內存中的表達方式,交給應用

 
一、實現技術方案

     下面使用比較原始的方案實現RPC框架,採用Socket通訊、動態代理與反射與Java原生的序列化。

 
二、RPC框架架構

     RPC架構分爲三部分:

1.     服務提供者,運行在服務器端,提供服務接口定義與服務實現類。

2.     服務中心,運行在服務器端,負責將本地服務發佈成遠程服務,管理遠程服務,提供給服務消費者使用。

3.     服務消費者,運行在客戶端,經過遠程代理對象調用遠程服務。



三、 具體實現

 

 

1)服務提供者接口定義與實現,代碼以下:

[java] view plain copy

1.  package services;  

2.    

3.    

4.  public interface HelloService {  

5.    

6.    

7.      String sayHi(String name);  

8.    

9.    

10. }  



2)HelloServices接口實現類:

[java] view plain copy

1.  package services.impl;  

2.    

3.    

4.  import services.HelloService;  

5.    

6.    

7.  public class HelloServiceImpl implements HelloService {  

8.    

9.    

10.     public String sayHi(String name) {  

11.         return "Hi, " + name;  

12.     }  

13.   

14.   

15. }  




3)服務中心代碼實現,代碼以下:

[java] view plain copy

1.  package services;  

2.    

3.    

4.  import java.io.IOException;  

5.    

6.    

7.  public interface Server {  

8.      public void stop();  

9.    

10.   

11.     public void start() throws IOException;  

12.   

13.   

14.     public void register(Class serviceInterface, Class impl);  

15.   

16.   

17.     public boolean isRunning();  

18.   

19.   

20.     public int getPort();  

21. }  



4)服務中心實現類:

[java] view plain copy

1.  package services.impl;  

2.    

3.    

4.    

5.    

6.    

7.    

8.  import java.io.IOException;  

9.  import java.io.ObjectInputStream;  

10. import java.io.ObjectOutputStream;  

11. import java.lang.reflect.Method;  

12. import java.net.InetSocketAddress;  

13. import java.net.ServerSocket;  

14. import java.net.Socket;  

15. import java.util.HashMap;  

16. import java.util.concurrent.ExecutorService;  

17. import java.util.concurrent.Executors;  

18.   

19.   

20. import services.Server;  

21.   

22.   

23. public class ServiceCenter implements Server {  

24.     private static ExecutorService              executor        = Executors.newFixedThreadPool(Runtime.getRuntime()  

25.                                                                         .availableProcessors());  

26.   

27.   

28.     private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();  

29.   

30.   

31.     private static boolean                      isRunning       = false;  

32.   

33.   

34.     private static int                          port;  

35.   

36.   

37.     public ServiceCenter(int port) {  

38.         this.port = port;  

39.     }  

40.   

41.   

42.     public void stop() {  

43.         isRunning = false;  

44.         executor.shutdown();  

45.     }  

46.   

47.   

48.     public void start() throws IOException {  

49.         ServerSocket server = new ServerSocket();  

50.         server.bind(new InetSocketAddress(port));  

51.         System.out.println("start server");  

52.         try {  

53.             while (true) {  

54.                 // 1.監聽客戶端的TCP鏈接,接到TCP鏈接後將其封裝成task,由線程池執行  

55.                 executor.execute(new ServiceTask(server.accept()));  

56.             }  

57.         } finally {  

58.             server.close();  

59.         }  

60.     }  

61.   

62.   

63.     public void register(Class serviceInterface, Class impl) {  

64.         serviceRegistry.put(serviceInterface.getName(), impl);  

65.     }  

66.   

67.   

68.     public boolean isRunning() {  

69.         return isRunning;  

70.     }  

71.   

72.   

73.     public int getPort() {  

74.         return port;  

75.     }  

76.   

77.   

78.     private static class ServiceTask implements Runnable {  

79.         Socket clent = null;  

80.   

81.   

82.         public ServiceTask(Socket client) {  

83.             this.clent = client;  

84.         }  

85.   

86.   

87.         public void run() {  

88.             ObjectInputStream input = null;  

89.             ObjectOutputStream output = null;  

90.             try {  

91.                 // 2.將客戶端發送的碼流反序列化成對象,反射調用服務實現者,獲取執行結果  

92.                 input = new ObjectInputStream(clent.getInputStream());  

93.                 String serviceName = input.readUTF();  

94.                 String methodName = input.readUTF();  

95.                 Class<?>[] parameterTypes = (Class<?>[]) input.readObject();  

96.                 Object[] arguments = (Object[]) input.readObject();  

97.                 Class serviceClass = serviceRegistry.get(serviceName);  

98.                 if (serviceClass == null) {  

99.                     throw new ClassNotFoundException(serviceName + " not found");  

100.                 }  

101.                 Method method = serviceClass.getMethod(methodName, parameterTypes);  

102.                 Object result = method.invoke(serviceClass.newInstance(), arguments);  

103.   

104.   

105.                 // 3.將執行結果反序列化,經過socket發送給客戶端  

106.                 output = new ObjectOutputStream(clent.getOutputStream());  

107.                 output.writeObject(result);  

108.             } catch (Exception e) {  

109.                 e.printStackTrace();  

110.             } finally {  

111.                 if (output != null) {  

112.                     try {  

113.                         output.close();  

114.                     } catch (IOException e) {  

115.                         e.printStackTrace();  

116.                     }  

117.                 }  

118.                 if (input != null) {  

119.                     try {  

120.                         input.close();  

121.                     } catch (IOException e) {  

122.                         e.printStackTrace();  

123.                     }  

124.                 }  

125.                 if (clent != null) {  

126.                     try {  

127.                         clent.close();  

128.                     } catch (IOException e) {  

129.                         e.printStackTrace();  

130.                     }  

131.                 }  

132.             }  

133.   

134.   

135.         }  

136.     }  

137.   

138.   

139.   

140.   

141.   

142.   

143. }  




5)客戶端的遠程代理對象:

[java] view plain copy

1.  package client;  

2.    

3.    

4.  import java.io.ObjectInputStream;  

5.  import java.io.ObjectOutputStream;  

6.  import java.lang.reflect.InvocationHandler;  

7.  import java.lang.reflect.Proxy;  

8.  import java.net.InetSocketAddress;  

9.  import java.net.Socket;  

10. import java.lang.reflect.Method;  

11.   

12.   

13. public class RPCClient<T> {  

14.     @SuppressWarnings("unchecked")  

15.     public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) {  

16.         // 1.將本地的接口調用轉換成JDK的動態代理,在動態代理中實現接口的遠程調用  

17.         return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[] { serviceInterface },  

18.                 new InvocationHandler() {  

19.                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  

20.                         Socket socket = null;  

21.                         ObjectOutputStream output = null;  

22.                         ObjectInputStream input = null;  

23.                         try {  

24.                             // 2.建立Socket客戶端,根據指定地址鏈接遠程服務提供者  

25.                             socket = new Socket();  

26.                             socket.connect(addr);  

27.   

28.   

29.                             // 3.將遠程服務調用所需的接口類、方法名、參數列表等編碼後發送給服務提供者  

30.                             output = new ObjectOutputStream(socket.getOutputStream());  

31.                             output.writeUTF(serviceInterface.getName());  

32.                             output.writeUTF(method.getName());  

33.                             output.writeObject(method.getParameterTypes());  

34.                             output.writeObject(args);  

35.   

36.   

37.                             // 4.同步阻塞等待服務器返回應答,獲取應答後返回  

38.                             input = new ObjectInputStream(socket.getInputStream());  

39.                             return input.readObject();  

40.                         } finally {  

41.                             if (socket != null)  

42.                                 socket.close();  

43.                             if (output != null)  

44.                                 output.close();  

45.                             if (input != null)  

46.                                 input.close();  

47.                         }  

48.                     }  

49.                 });  

50.     }  

51. }  




6)最後爲測試類:

[java] view plain copy

1.  package client;  

2.    

3.    

4.  import java.io.IOException;  

5.  import java.net.InetSocketAddress;  

6.    

7.    

8.  import services.HelloService;  

9.  import services.Server;  

10. import services.impl.HelloServiceImpl;  

11. import services.impl.ServiceCenter;  

12.   

13.   

14. public class RPCTest {  

15.     public static void main(String[] args) throws IOException {  

16.         new Thread(new Runnable() {  

17.             public void run() {  

18.                 try {  

19.                     Server serviceServer = new ServiceCenter(8088);  

20.                     serviceServer.register(HelloService.class, HelloServiceImpl.class);  

21.                     serviceServer.start();  

22.                 } catch (IOException e) {  

23.                     e.printStackTrace();  

24.                 }  

25.             }  

26.         }).start();  

27.         HelloService service = RPCClient  

28.                 .getRemoteProxyObj(HelloService.class, new InetSocketAddress("localhost", 8088));  

29.         System.out.println(service.sayHi("test"));  

30.     }  

31.   

32.   

33. }  

 

Restful裏面的:(微服務裏的)都要同時註冊到服務的註冊中內心面去。

FeignClient

 除了上面的方式,咱們還能夠用FeignClient。

1

2

3

4

5

6

7
    

@FeignClient(value = "users", path = "/users")

public interface UserCompositeService {

@RequestMapping(value = "/getUserDetail/{id}",

        method = RequestMethod.GET,

        produces = MediaType.APPLICATION_JSON_VALUE)

UserDTO getUserById(@PathVariable Long id);

}

  咱們只須要使用@FeignClient定義一個接口,Spring Cloud Feign會幫咱們生成一個它的實現,從相應的users服務獲取數據。

  其中,@FeignClient(value = 「users」, path = 「/users/getUserDetail」)裏面的value是服務ID,path是這一組接口的path前綴。在下面的方法定義裏,就好像設置Spring MVC的接口同樣,對於這個方法,它對應的URL是/users/getUserDetail/{id}。而後,在使用它的時候,就像注入一個通常的服務同樣注入後使用便可:

1

2

3

4

5

6

7

8

9
    

public class SomeOtherServiceClass {

@Autowired

private UserCompositeService userService;

public void doSomething() {

    // .....

    UserDTO results = userService.getUserById(userId);

    // other operation...

}

}

 

 

從使用方面看,Http接口只關注服務提供方(服務端),對於客戶端怎麼調用,調用方式怎樣並不關心,一般狀況下,客戶端使用Http方式進行調用時,只要將內容進行傳輸便可,這樣客戶端在使用時,須要更關注網絡方面的傳輸,比較不適用與業務方面的開發;(restful是服務端把方法寫好,客戶端經過http方式調用,直接定位到方法上面去。)

而RPC服務則須要客戶端接口與服務端保持一致,服務端提供一個方法,客戶端經過接口直接發起調用,業務開發人員僅須要關注業務方法的調用便可,再也不關注網絡傳輸的細節,在開發上更爲高效。(PRC是服務端提供好方法給客戶端調用。定位到類,而後經過類去調用方法。)

好比這種,本身要了一個計算服務,拿到計算服務類後,本身調用服務類裏的加法去得到結果

若是是restful,就根據Calculate方法對應的url去傳參(people),從而得到結果。


RPC:所謂的遠程過程調用 (面向方法)

SOA:所謂的面向服務的架構(面向消息)

REST:所謂的 Representational state transfer (面向資源)

RPC 即遠程過程調用, 很簡單的概念, 像調用本地服務(方法)同樣調用服務器的服務(方法).

一般的實現有 XML-RPC , JSON-RPC , 通訊方式基本相同, 所不一樣的只是傳輸數據的格式.

REST 的三個要素是 惟一的資源標識, 簡單的方法 (此處的方法是個抽象的概念),必定的表達方式.

重要的特性:無狀態
---------------------
做者:鄭學煒
來源:CSDN
原文:https://blog.csdn.net/u014590757/article/details/80233901
版權聲明:本文爲博主原創文章,轉載請附上博文連接!java

相關文章
相關標籤/搜索