Giao thức truyền siêu văn bản (Hypertext transfer protocol - HTTP)
HTTP là một phương thức để mã hóa và vận chuyển dữ liệu giữa client và server. Đây là giao thức kiểu yêu cầu/phản hồi (request/response): client gửi yêu cầu, server trả phản hồi kèm nội dung liên quan và thông tin trạng thái hoàn tất của yêu cầu. HTTP mang tính tự đóng gói (self-contained), cho phép yêu cầu và phản hồi đi qua nhiều router và server trung gian thực hiện cân bằng tải (load balancing), lưu đệm (caching), mã hóa (encryption) và nén (compression).
Một yêu cầu HTTP cơ bản gồm một động từ (verb, hay method) và một tài nguyên (resource, hay endpoint). Dưới đây là các động từ HTTP thông dụng:
Động từ
Mô tả
Lũy đẳng (idempotent)*
An toàn (safe)
Có thể cache
GET
Đọc một tài nguyên
Có
Có
Có
POST
Tạo một tài nguyên hoặc kích hoạt một tiến trình xử lý dữ liệu
Không
Không
Có, nếu phản hồi chứa thông tin về độ tươi (freshness)
PUT
Tạo hoặc thay thế một tài nguyên
Có
Không
Không
PATCH
Cập nhật một phần tài nguyên
Không
Không
Có, nếu phản hồi chứa thông tin về độ tươi (freshness)
DELETE
Xóa một tài nguyên
Có
Không
Không
*Có thể gọi nhiều lần mà không cho ra kết quả khác nhau.
HTTP là một giao thức tầng ứng dụng (application layer), dựa trên các giao thức tầng thấp hơn như TCP và UDP.
TCP là một giao thức hướng kết nối (connection-oriented) chạy trên mạng IP. Kết nối được thiết lập và kết thúc bằng một quá trình bắt tay (handshake). Mọi gói tin (packet) được gửi đi đều được đảm bảo tới đích đúng thứ tự ban đầu và không bị hỏng, nhờ:
Số thứ tự (sequence number) và trường checksum cho mỗi gói tin
Nếu bên gửi không nhận được phản hồi đúng, nó sẽ gửi lại các gói tin. Nếu hết thời gian chờ (timeout) nhiều lần, kết nối sẽ bị hủy. TCP cũng cài đặt điều khiển luồng (flow control) và điều khiển tắc nghẽn (congestion control). Những đảm bảo này gây ra độ trễ và nhìn chung làm việc truyền dữ liệu kém hiệu quả hơn so với UDP.
TCP bắt tay ba bước (SYN, SYN-ACK, ACK) để thiết lập kết nối trước khi truyền dữ liệu thật.
Để đảm bảo thông lượng cao, web server có thể giữ mở một số lượng lớn kết nối TCP, dẫn tới tiêu tốn nhiều bộ nhớ. Việc duy trì nhiều kết nối mở giữa các luồng (thread) của web server và, chẳng hạn, một server memcached có thể rất tốn kém. Gộp kết nối (connection pooling) có thể giúp ích, bên cạnh việc chuyển sang UDP ở những chỗ phù hợp.
TCP hữu ích cho các ứng dụng đòi hỏi độ tin cậy cao nhưng ít khắt khe về thời gian. Một số ví dụ gồm web server, thông tin cơ sở dữ liệu, SMTP, FTP và SSH.
Dùng TCP thay vì UDP khi:
Bạn cần toàn bộ dữ liệu tới nơi nguyên vẹn
Bạn muốn tự động tận dụng tối đa thông lượng mạng ở mức ước lượng tốt nhất
Giao thức gói dữ liệu người dùng (User datagram protocol - UDP)
UDP là giao thức phi kết nối (connectionless). Các datagram (tương tự gói tin) chỉ được đảm bảo ở mức từng datagram. Datagram có thể tới đích sai thứ tự hoặc không tới được. UDP không hỗ trợ điều khiển tắc nghẽn. Không có những đảm bảo như TCP, UDP nhìn chung hiệu quả hơn.
UDP có thể phát quảng bá (broadcast), gửi datagram tới mọi thiết bị trong mạng con (subnet). Điều này hữu ích với DHCP, vì client khi đó chưa nhận được địa chỉ IP, nên TCP không có cách nào truyền luồng dữ liệu khi chưa có địa chỉ IP.
UDP kém tin cậy hơn nhưng hoạt động tốt trong các trường hợp thời gian thực như VoIP, gọi video, phát trực tuyến (streaming) và game nhiều người chơi thời gian thực.
Dùng UDP thay vì TCP khi:
Bạn cần độ trễ thấp nhất
Dữ liệu tới muộn còn tệ hơn mất dữ liệu
Bạn muốn tự cài đặt cơ chế sửa lỗi riêng
TCP đánh số và xác nhận (ACK) từng gói, gửi lại nếu mất; UDP gửi thẳng không bắt tay, gói có thể mất hoặc tới sai thứ tự.
Trong RPC, client khiến một thủ tục (procedure) được thực thi ở một không gian địa chỉ khác, thường là trên một server từ xa. Thủ tục được viết như thể đó là một lời gọi thủ tục cục bộ, che giấu khỏi chương trình client các chi tiết về cách giao tiếp với server. Lời gọi từ xa thường chậm hơn và kém tin cậy hơn lời gọi cục bộ, nên việc phân biệt lời gọi RPC với lời gọi cục bộ là điều có ích. Các framework RPC phổ biến gồm Protobuf, Thrift và Avro.
RPC là một giao thức yêu cầu - phản hồi:
Chương trình client (client program) - Gọi thủ tục stub phía client. Các tham số được đẩy lên ngăn xếp (stack) giống như một lời gọi thủ tục cục bộ.
Thủ tục stub phía client (client stub procedure) - Đóng gói (marshal) mã định danh thủ tục và các đối số vào một thông điệp yêu cầu.
Mô-đun giao tiếp phía client (client communication module) - Hệ điều hành gửi thông điệp từ client tới server.
Mô-đun giao tiếp phía server (server communication module) - Hệ điều hành chuyển các gói tin đến cho thủ tục stub phía server.
Thủ tục stub phía server (server stub procedure) - Giải gói (unmarshal) kết quả, gọi thủ tục phía server khớp với mã định danh thủ tục và truyền vào các đối số đã cho.
Phản hồi của server lặp lại các bước trên theo thứ tự ngược lại.
Ví dụ các lời gọi RPC:
GET /someoperation?data=anIdPOST /anotheroperation{ "data":"anId"; "anotherdata": "another value"}
RPC tập trung vào việc phơi ra các hành vi (behavior). RPC thường được dùng cho giao tiếp nội bộ vì lý do hiệu năng, vì bạn có thể tự viết các lời gọi native sao cho khớp nhất với trường hợp sử dụng của mình.
Chọn thư viện native (hay SDK) khi:
Bạn biết rõ nền tảng đích.
Bạn muốn kiểm soát cách "logic" của mình được truy cập.
Bạn muốn kiểm soát cách xử lý lỗi diễn ra bên ngoài thư viện của mình.
Hiệu năng và trải nghiệm người dùng cuối là mối quan tâm hàng đầu.
Các HTTP API theo phong cách REST thường được dùng nhiều hơn cho API công khai.
Nhược điểm: RPC
Client RPC bị gắn chặt (tightly coupled) với cách cài đặt của dịch vụ.
Mỗi thao tác hoặc trường hợp sử dụng mới đều phải định nghĩa một API mới.
Gỡ lỗi (debug) RPC có thể khó khăn.
Bạn có thể không tận dụng ngay được các công nghệ sẵn có. Ví dụ, có thể cần thêm công sức để đảm bảo các lời gọi RPC được cache đúng cách trên các caching server như Squid.
Chuyển giao trạng thái biểu diễn (Representational state transfer - REST)
REST là một phong cách kiến trúc áp đặt mô hình client/server, trong đó client thao tác trên một tập tài nguyên do server quản lý. Server cung cấp biểu diễn (representation) của tài nguyên và các hành động có thể thay đổi tài nguyên hoặc lấy về một biểu diễn mới của tài nguyên. Mọi giao tiếp đều phải phi trạng thái (stateless) và có thể cache.
Một giao diện RESTful có bốn đặc tính:
Định danh tài nguyên (URI trong HTTP) - dùng cùng một URI bất kể thao tác nào.
Thay đổi thông qua biểu diễn (động từ trong HTTP) - dùng động từ, header và body.
Thông báo lỗi tự mô tả (mã trạng thái phản hồi trong HTTP) - dùng mã trạng thái (status code), đừng phát minh lại bánh xe.
HATEOAS (giao diện HTML cho HTTP) - dịch vụ web của bạn phải truy cập được đầy đủ từ trình duyệt.
Ví dụ các lời gọi REST:
GET /someresources/anIdPUT /someresources/anId{"anotherdata": "another value"}
REST tập trung vào việc phơi ra dữ liệu. Nó giảm thiểu sự phụ thuộc lẫn nhau (coupling) giữa client và server, và thường được dùng cho các HTTP API công khai. REST dùng một cách thức tổng quát và đồng nhất hơn để phơi ra tài nguyên thông qua URI, biểu diễn thông qua header, và hành động thông qua các động từ như GET, POST, PUT, DELETE và PATCH. Nhờ phi trạng thái, REST rất phù hợp cho mở rộng theo chiều ngang (horizontal scaling) và phân vùng (partitioning).
Nhược điểm: REST
Vì REST tập trung vào phơi ra dữ liệu, nó có thể không phù hợp nếu tài nguyên không được tổ chức hoặc truy cập một cách tự nhiên theo một cấu trúc phân cấp đơn giản. Ví dụ, việc trả về mọi bản ghi được cập nhật trong một giờ qua khớp với một tập sự kiện cụ thể không dễ biểu diễn thành một đường dẫn. Với REST, việc này nhiều khả năng được cài đặt bằng cách kết hợp đường dẫn URI, tham số truy vấn (query parameter), và có thể cả body của yêu cầu.
REST thường dựa vào một vài động từ (GET, POST, PUT, DELETE và PATCH), đôi khi không khớp với trường hợp sử dụng của bạn. Ví dụ, việc chuyển các tài liệu hết hạn vào thư mục lưu trữ có thể không gói gọn được một cách sạch sẽ trong các động từ này.
Việc lấy các tài nguyên phức tạp có cấu trúc phân cấp lồng nhau đòi hỏi nhiều lượt đi - về (round trip) giữa client và server chỉ để hiển thị một màn hình, ví dụ lấy nội dung một bài blog rồi lấy các bình luận của bài đó. Với ứng dụng di động hoạt động trong điều kiện mạng thay đổi thất thường, nhiều lượt đi - về như vậy là rất không mong muốn.
Theo thời gian, nhiều trường có thể được thêm vào phản hồi của API và các client cũ sẽ nhận toàn bộ các trường dữ liệu mới, kể cả những trường chúng không cần. Kết quả là kích thước dữ liệu trả về (payload) phình to và độ trễ tăng lên.
RPC: mỗi hành động có một endpoint riêng (/signup, /resign...). REST: một URI cho mỗi tài nguyên, hành động chọn qua động từ HTTP (GET/PUT/DELETE).
So sánh lời gọi RPC và REST
Thao tác
RPC
REST
Đăng ký
POST /signup
POST /persons
Hủy tài khoản
POST /resign { "personid": "1234" }
DELETE /persons/1234
Đọc thông tin một người
GET /readPerson?personid=1234
GET /persons/1234
Đọc danh sách vật phẩm của một người
GET /readUsersItemsList?personid=1234
GET /persons/1234/items
Thêm một vật phẩm vào danh sách của một người
POST /addItemToUsersItemsList { "personid": "1234"; "itemid": "456" }
POST /persons/1234/items { "itemid": "456" }
Cập nhật một vật phẩm
POST /modifyItem { "itemid": "456"; "key": "value" }