RSocket là gì ?
RSocket là giao thức truyền thông mới, nhằm giải quyết các vấn đề mà giao thức HTTP hiện tại gặp phải. RSocket là 1 giao thức nhị phân sử dụng để transport các byte stream như TCP, WebSocket và Aeron.
Vậy những hạn chế của giao thức HTTP là gì ?
1 2 3 4 | - Nó chỉ support mô hình tương tác request/response - Không hiệu quả - Không reactive |
Một kiến trúc hiện đại phải support được các mô hình giao tiếp khác như streaming và fire-and-forgot. HTTP (hypertext transfer protocol) là 1 giao thức text-base hữu dụng trong việc fetch các document thông qua internet, nhưng đối với data center thì giao thức này không hiệu quả. Và vấn đề chủ yếu là HTTP is not reactive.
Có thể xem qua phần FAQ của HTTP/2
1 2 | HTTP/1.x has a problem called “head-of-line blocking,” where effectively only one request can be outstanding on a connection at a time. |
1 2 | HTTP/1.1 tried to fix this with pipelining, but it didn’t completely address the problem (a large or slow response can still block others behind it). Additionally, pipelining has been found very difficult to deploy, because many intermediaries and servers don’t process it correctly. |
Rsocket là 1 giao thức message-base và require 1 vài giao thức transport lower-level để có thể mang được message. Rsocket define 4 loại giao tiếp :
1 2 3 4 5 | 1. Request / Response 2. Fire And Forget 3. Request / Stream 4. Channel |
Các thuật ngữ trong RSocket:
Frame:
Frame là 1 single message chứa 1 request, response, hoặc protocol processing.
Fragment:
Là 1 phần của message application, đã được part để đưa vào Frame. Xem thêm về Fragmentation và Reassembly ở đây
Transport:
Giao thức để mang giao thức Rscoket, ở đây có thể là Websocket, TCP hoặc Aeron
Stream:
Stream được hiểu như 1 đơn vị tính toán (có thể là request hoặc response hoặc…)
Request:
Là 1 stream request, có thể là 1 trong 4 loại mình đã nêu ở trên
1 2 3 4 5 | 1. Request / Response 2. Fire And Forget 3. Request / Stream 4. Channel |
Payload:
A stream message (upstream or downstream). Contains data associated with a stream created by a previous request. In Reactive Streams and Rx this is the ‘onNext’ event.
Complete:
1 event được sent đến stream để thông báo hoàn thành success. Trong Reactive Streams và Rx thì đâu là sự kiện onComplete.
1 2 | A frame (PAYLOAD or REQUEST_CHANNEL) with the Complete bit set is sometimes referred to as COMPLETE in this document when reference to the frame is semantically about the Complete bit/event. |
Client:
The side initiating a connection.
Server:
The side accepting connections from clients.
Connection:
The instance of a transport session between client and server.
Requester:
The side sending a request. A connection has at most 2 Requesters. One in each direction.
Responder:
The side receiving a request. A connection has at most 2 Responders. One in each direction.
Data And Metadata
RSocket cung cấp cơ chế để ứng dụng có thể phân biệt được payload là data hay metadata.
Những tính năng của Data và MetaData:
- Metadata can be encoded differently than Data.
- Metadata can be “attached” (i.e. correlated) with the following entities:
- Connection via Metadata Push and Stream ID of 0
- Individual Request or Payload (upstream or downstream)
Framing
Transport Protocol:
Giao thức RSocket sử dụng giao thức transport lower level để mang các RSocket frames. 1 giao thức transport phải cung cấp các thông tin sau:
- Unicast Reliable Delivery.
- Connection-Oriented and preservation of frame ordering. Frame A sent before Frame B MUST arrive in source order. i.e. if Frame A is sent by the same source as Frame B, then Frame A will always arrive before Frame B. No assumptions about ordering across sources is assumed.
- FCS is assumed to be in use either at the transport protocol or at each MAC layer hop. But no protection against malicious corruption is assumed.
Framing Format:
Khi sử dụng giao thức transport cung cấp framing thì RSocket frame được đóng gói vào trong message của giao thức transport:
1 2 3 4 5 | +-----------------------------------------------+ | RSocket Frame ... | +-----------------------------------------------+ |
Khi sử dụng giao thức truyền tải không cung cấp khung tương thích, Frame LENGTH phải được thêm vào RSocket frame.
1 2 3 4 5 6 7 8 9 | 0 1 2 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Frame Length | +-----------------------------------------------+ | RSocket Frame ... | +-----------------------------------------------+ |
- Frame Length: (24 bits = max value 16,777,215) Unsigned 24-bit integer representing the length of Frame in bytes. Excluding the Frame Length field.
Frame Header:
Tất cả các RSocket Frames đều bắt đầu với frame header, frame header sẽ bao gồm các thông tin Stream ID, Frame Type, và flags. 2 Flag (I)gnore và (M)etadata sẽ luôn luôn có mặt
1 2 3 4 5 6 7 8 | 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| Stream ID | +-----------+-+-+---------------+-------------------------------+ |Frame Type |I|M| Flags | Depends on Frame Type ... +-------------------------------+ |
- Stream ID: (31 bits = max value 2^31-1 = 2,147,483,647) Unsigned 31-bit integer representing the stream Identifier for this frame or 0 to indicate the entire connection.
Transport protocols that include demultiplexing, such as HTTP/2, MAY omit the Stream ID field if all parties agree. The means of negotiation and agreement is left to the transport protocol. - Frame Type: (6 bits = max value 63) Type of Frame.
- Flags: (10 bits) Any Flag bit not specifically indicated in the frame type should be set to 0 when sent and not interpreted on reception. Flags generally depend on Frame Type, but all frame types MUST provide space for the following flags:
(I)gnore: Ignore frame if not understood
(M)etadata: Metadata present
Connection Setup:
Client phải connect đến server để setup 1 connection, Khi kết nói được thành lập thì nó sẽ gửi SETUP frame.
Giả sử giao thức transport là TCP thì SETUP frame sẽ như bên dưới (nếu không được sử dụng lại):
1 2 3 4 5 6 7 8 | +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 00 00 28 00 00 00 00 04 00 00 01 00 00 00 00 4e |..(............N| |00000010| 20 00 01 5f 90 0a 74 65 78 74 2f 70 6c 61 69 6e | .._..text/plain| |00000020| 0a 74 65 78 74 2f 70 6c 61 69 6e |.text/plain | +--------+-------------------------------------------------+----------------+ |
Request/Response:
Request/Response là model tương tác phổ biến nhất, nhưng đối với RSocket cũng như Reactive Stream thì đây là 1 trường hợp đặc biệt của Request/Stream – trong đó response stream chỉ có 1 element hoặc 1 frame. Requester send 1 request frame và responder reply lại 1 stream of one frame
Request frame mang Stream ID và frame type (trường hợp này là REQUEST_RESPONSE). Nếu client khơi tạo stream ID, nó là số lẻ và bắt đầu bằng 1 cho first stream, Server sử dụng stream IDs chẵn bắt đầu bằng 2.
Dưới đây là ví dụ về request/response sử dụng TCP. Client là requester gửi message “Hello World!” cho server và server gửi lại message đó.
1 2 3 4 5 6 7 | +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 00 00 12 00 00 00 01 10 00 48 65 6c 6c 6f 20 57 |.........Hello W| |00000010| 6f 72 6c 64 21 |orld! | +--------+-------------------------------------------------+----------------+ |
và đây là response từ server:
1 2 3 4 5 6 7 | +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 00 00 12 00 00 00 01 28 60 48 65 6c 6c 6f 20 57 |.......(`Hello W| |00000010| 6f 72 6c 64 21 |orld! | +--------+-------------------------------------------------+----------------+ |
Demo
Phần lý thuyết ở trên có vẻ khá khó hiểu và đau đầu. Ok giờ mình thử demo Request/Response (Dùng Spring boot)
Tạo Project:
Tạo project Spring boot thông thường như bao project khác thôi, chú ý 1 số thông bên dưới :
Dependency
1 2 3 4 5 6 7 8 9 10 11 | <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-rsocket</artifactId> <version>2.3.1.RELEASE</version> </dependency> |
Ở đây mình dùng lombok cho nhanh, và dependency phải có là rsocket
application.properties:
Define port cho rsocket để connet vào
1 2 3 | spring.rsocket.server.port=7000 spring.main.lazy-initialization=true |
Controller:
Ở đây mình tạo 1 controller RsocketController
, endpoint là request-response
nhận 1 String request và return về 1 String Hello
+ request đó
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Controller @Slf4j public class RsocketController { private static final String SERVER = "Serverx"; private static final String RESPONSE = "ResponseX"; @MessageMapping("request-response") String requestResponse(String request) { log.info("Received request-response request: {}", request); return " Hello " + request; } } |
Result:
OK, chỉ thế thôi, để demo request response thì mình start project lên :
./mvnw clean package spring-boot:run -DskipTests
=> Result:
1 2 3 4 5 | 2020-07-22 21:12:30.415 INFO 5274 --- [ main] c.e.rsocketcli.RsocketCliApplication : No active profile set, falling back to default profiles: default 2020-07-22 21:12:31.041 INFO 5274 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2020-07-22 21:12:31.050 INFO 5274 --- [ main] o.s.b.rsocket.netty.NettyRSocketServer : Netty RSocket started on port(s): 7000 2020-07-22 21:12:31.074 INFO 5274 --- [ main] c.e.rsocketcli.RsocketCliApplication : Started RsocketCliApplication in 0.961 seconds (JVM running for 1.208) |
=> Ok, Port 7000 đã start, giờ mình cần gửi 1 command đến port 7000 sử dụng Rsocket CLI:
Để dùng Rsocket CLI thì bạn clone file jar ở đây về nhá :
1 2 | wget -O rsc.jar https://github.com/making/rsc/releases/download/0.4.2/rsc-0.4.2.jar |
Sau đó cd
đến folder vừa download file jar đó về và call đến endpoint request-response
với data request là rsocket client
:
1 2 | java -jar rsc.jar --debug --request --data "rsocket client" --route request-response tcp://localhost:7000 |
=> kết quả sẽ như thế này :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | jav $ java -jar rsc.jar --debug --request --data "rsocket client" --route request-response tcp://localhost:7000 2020-07-22 21:16:34.391 DEBUG --- [actor-tcp-nio-1] i.r.FrameLogger : sending -> Frame => Stream ID: 1 Type: REQUEST_RESPONSE Flags: 0b100000000 Length: 40 Metadata: +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 10 72 65 71 75 65 73 74 2d 72 65 73 70 6f 6e 73 |.request-respons| |00000010| 65 |e | +--------+-------------------------------------------------+----------------+ Data: +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 72 73 6f 63 6b 65 74 20 63 6c 69 65 6e 74 |rsocket client | +--------+-------------------------------------------------+----------------+ 2020-07-22 21:16:34.449 DEBUG --- [actor-tcp-nio-1] i.r.FrameLogger : receiving -> Frame => Stream ID: 1 Type: NEXT_COMPLETE Flags: 0b1100000 Length: 27 Data: +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 20 48 65 6c 6c 6f 20 72 73 6f 63 6b 65 74 20 63 | Hello rsocket c| |00000010| 6c 69 65 6e 74 |lient | +--------+-------------------------------------------------+----------------+ Hello rsocket client |
Nhìn vào phần request bạn sẽ thấy Frame => Stream ID: 1 Type: REQUEST_RESPONSE Flags: 0b100000000 Length: 40
, type ở đây là REQUEST_RESPONSE
Và data request là 1 string rsocket client
được mã hóa :
1 2 3 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | |00000000| 72 73 6f 63 6b 65 74 20 63 6c 69 65 6e 74 | |
Nhận được response có type là Type: NEXT_COMPLETE
, và data là Hello rsocket c
và lient
:
1 2 3 4 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | |00000000| 20 48 65 6c 6c 6f 20 72 73 6f 63 6b 65 74 20 63 | => Hello rsocket c |00000010| 6c 69 65 6e 74 | => lient |
Giải thích
OK, giờ mình giải thích qua 1 chút ở chỗ này:
Rsocket client (rsc.jar download ở trên) sẽ send 1 request message đến RsocketController sử dụng RSocket message protocol. Message được sent thông qua giao thức TCP đến địa chỉ localhost:7000
nơi mà server đã deploy.
Một mesasge routing instruction sẽ được gửi trong message frame đầu tiên (tương ứng với option –route) nó sẽ gửi thông tin request-response
1 2 3 4 5 6 7 | +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 10 72 65 71 75 65 73 74 2d 72 65 73 70 6f 6e 73 |.request-respons| |00000010| 65 |e | +--------+-------------------------------------------------+----------------+ |
Spring sẽ dùng thông tin routing này để select đúng @MessageMapping
endpoint để gọi, trong trường hợp này là method requestResponse(String request)
. Method này sẽ response về 1 chuỗi String, và CLI client in ra toàn bộ tương tác trong terminal, một series message frame
1 2 3 4 5 6 7 8 | +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 20 48 65 6c 6c 6f 20 72 73 6f 63 6b 65 74 20 63 | Hello rsocket c| |00000010| 6c 69 65 6e 74 |lient | +--------+-------------------------------------------------+----------------+ Hello rsocket client |
Tổng kết:
OK, ở đây mình chỉ muốn giới thiệu về RSocket, và 1 demo nhỏ để hiểu được, Có 1 số thuật ngữ cũng như định nghĩa ko biết dịch thế nào cho dễ hiểu nên mình để nguyên bản luôn . Các bạn nên tìm đến các trang nguồn để đọc cho dễ hiểu
Ở bài sau mình sẽ demo các type connect của RSocket trên tầng application thay vì đi sâu vào như thế này
Tham khảo:
https://rsocket.io/docs/Protocol.html
https://dzone.com/articles/an-introduction-to-rsocket