gRPC Framework trong Microservices

Thanh Thế
8 min readMay 23, 2020

--

Mình sẽ đưa ra một số khái niệm trong bài viết, các bạn có thể lướt qua các khái niệm nếu đã biết nhé.

Monolithic (kiến trúc một khối)

ví dụ kiến trúc một khối. nguồn ảnh: http://tiny.cc/cowipz

Trong kiến trúc này, các module tổ chức chung trong cùng 1 project, tức là các service, pakage, đối tượng, phương thức, … đều nằm chung một project lớn. Đây là kiến trúc được sử dụng khá phổ biến do tính đơn giản trong việc gọi thực thi giữa các package với nhau.

Ví dụ, các bạn đang làm một phần mềm cung cấp các dịch vụ về thương mại điện tử, trong đó các dịch vụ bán hàng, thanh toán, giao hàng, rao bán,… được chia thành các pakage, và các package đó nằm chung trong 1 project. Bạn có thể gọi phương thức từ một package bất kỳ dễ dàng bằng cách import package đó.

Vấn đề chỉ xảy ra khi hệ thống được sử dụng rộng rãi, số lượng người dùng tăng cao, tính năng thêm mới nhiều hơn, logic phức tạp hơn, dữ liệu lớn,… Để quản lý một project to đùng như thế quả thực là khó khăn.

Microservices

ví dụ kiến trúc Microservice. nguồn ảnh: http://tiny.cc/cowipz

Đúng với tên gọi của nó (tạm dịch là các dịch vụ cực nhỏ), project sẽ chạy trên nhiều server, mỗi server đảm nhận một service khác nhau, các service giao tiếp với nhau thông qua internet. Mỗi service có thể lập trình bằng ngôn ngữ khác nhau tùy vào nhu cầu, không nhất thiết dùng 1 ngôn ngữ như kiến trúc monolithic.

Ví dụ, các bạn có 2 server,

  • Server ở Long An cung cấp dịch vụ bán hàng.
  • Server ở Hà Nội cung cấp dịch vụ thanh toán.

2 dịch vụ này độc lập với nhau, có thể giao tiếp thông qua API. Việc chia nhỏ này giúp mở rộng project dễ dàng hơn, trong trường hợp một service bị lỗi thì hệ thống vẫn có thể hoạt động bình thường.

Các bạn quan tâm có thể xem thêm tại đây.

RPC (Remote Procedure Call)

Tạm dịch là lời gọi thủ tục từ xa

Nếu bạn đang dùng kiến trúc monolithic, việc gọi thủ tục giữa các package cực kỳ đơn giản, import package cần → gọi tên thủ tục muốn thực hiện, thế là xong, và tốc độ chuyển giao giữa các thủ tục không đáng kể. Riêng microservices thì không như vậy. Sẽ có trường hợp các service giao tiếp với nhau liên tục, mà thời gian để gọi và thực thi 1 API là không nhỏ, có khả năng gây ra nghẽn cổ chai. Trường hợp này có thể gọi là chatty (gọi vặt liên tục).

Nhằm mục đích tạo sự tiện lợi cho lập trình viên khi thao tác trên các server khác nhau, và tối ưu tốc độ giống như đang dùng kiến trúc monolithic, RPC được cho ra đời.

RPC cung cấp cách giao tiếp giữa các server thông qua các lời gọi thủ tục tương tự như monolithic.

RPC hoạt động như thế nào? nguồn ảnh: http://tiny.cc/pi7ipz

* Các bước gửi và nhận một request:

  1. Client gọi RPC function.
  2. Lời gọi được chuyển đến client stub để mã hóa.
  3. Các tiến trình hiện tại bị block cho đến khi nhận được phản hồi từ server hoặc bị timout.
  4. Lời gọi được chuyển đến server thông qua internet.
  5. Server stub giải mã lời gọi và yêu cầu server thực thi function tương ứng.
  6. Server gửi output cho server stub để mã hóa và gửi phản hồi về cho client.
  7. Client stub giải mã phản hồi và trả vềoutput cho client. Các tiến trình được unblock.

Protobuf (Protocol buffer)

Protobuf được Google công bố vào năm 2008, (theo định nghĩa của Google) là một cơ chế cho phép bạn định dạng dữ liệu có cấu trúc (có vẻ tương tự JSON hoặc XML), tuy nhiên tốc độ mã hóa nhanh hơn, gọn nhẹ hơn và đơn giản hơn.

so sánh tốc độ mã hóa của JSON, JSON stream, Protobuf. nguồn ảnh: http://tiny.cc/0g9ipz

Điểm đáng chú ý ở protobuf là độ linh hoạt của nó khi nó có thể được build thành các ngôn ngữ khác nhau (language/platform neutral).

Nó rất hữu ích trong việc phát triển các chương trình để giao tiếp với nhau qua một wire hoặc để lưu trữ dữ liệu. Tất cả những gì bạn phải làm là chỉ định một thông báo cho từng cấu trúc dữ liệu mà bạn muốn Serialize (theo định dạng giống như lớp Java) bằng cách sử dụng file đặc tả .proto.

Từ file *.proto compiler của protobuf (protoc) tạo ra code thực hiện encode tự động và phân tích cú pháp dữ liệu protobuf với định dạng Binary hiệu quả, tùy thuộc vào từng ngôn ngữ nó sẽ tạo ra mã tương ứng .

Ví dụ, một file *.proto được tạo ra để định nghĩa một struct, các ngôn ngữ như: Go, C++, Java, Python, Ruby, C#, Objective C, PHP có thể sử dụng struct đó bằng cách dùng protoc compiler để generate ra file tương ứng (*.go, *.cpp, …). Nếu một ngày đẹp trời mình vào thay đổi cấu trúc của file *.proto, thì cũng không cần cập nhật lại code chương trình.

gRPC

Qua các khái niệm đã nêu bên trên, mình có thể tóm gọn gRPC là sự tích hợp giữa RPC, protobuf và HTTP/2. Là một platform mã nguồn mở được Google phát triển, được công bốvào tháng 2 năm 2015.

Tạo gRPC cơ bản với Golang

(Project mình thực hiện trên môi trường Windows)

Ý tưởng: server có một số hàm, client sử dụng các hàm đó bằng lời gọi như trong kiến trúc monolithic.

Setup

Cấu trúc thư mục như sau:

Trong project này mình có sử dụng protoc complier để generate file hello.pb.go (protobuf) từ file hello.proto. Link download protoc cho bạn nào cần, lưu ý chọn file:

protoc-<version>-<he_dieu_hanh>.zip

Giải nén, sau đó thêm path dẫn đến thư mục bin (VD: C:\Program Files\protoc\bin). Các bạn có thể thêm từ giao diện windows hoặc dùng lệnh cmd:

$ set path=%path%;<path đến thư mục bin>ví dụ:
$ set path=%path%;C:\Program Files\protoc\bin

Tiếp đến mình cần protoc-gen-go:

$ go get github.com/golang/protobuf/protoc-gen-go$ set path=%path%;%gopath%\bin

Trong quá trình code nếu thiếu package nào thì các bạn cứ go get về nhé.

hello.proto

(Có thể các bạn sẽ muốn hiểu thêm về proto3 )

Đây sẽ là file trung gian cung cấp các hàm cho client giao tiếp với server, mình sẽ dùng proto3 để định nghĩa các lớp và phương thức mà client có thể sử dụng từ server.

hello.proto

Ý nghĩa: cung cấp 2 struct SayHello{} và HelloReply{}, và một service cung cấp 2 hàm Hello() và HelloAgain() cho các client sử dụng.

2 hàm trên nhận vào tham số là SayHello và trả về HelloReply như nhau.

Tiếp theo, mình sẽ generate ra file hello.pb.go. Các bạn trỏ đến thư mục gốc và dùng lệnh sau:

$ protoc 
-I=<path tới thư mục chứa hello.proto>
--go_out=plugins=grpc: <path muốn lưu hello.pb.go>
<path tới file hello.proto>

Ví dụ trong trường hợp này là:

$ protoc -I=proto --go_out=plugins=grpc:proto proto/hello.proto

Chương trình sẽ thông báo thiếu ‘go_package’, nhưng nó không ảnh hưởng đến project nên mình có thể tạm cho qua. Nếu các bạn nhận được thông tin này tức là đã tạo thành công:

2020/05/23 10:15:09 WARNING: Missing ‘go_package’ option in “hello.proto”,
please specify it with the full Go package path as
a future release of protoc-gen-go will require this be specified.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

Và bây giờ trong thư mục proto đã có thêm 1 file nữa, file này do protoc build tự động, các bạn có thể vào xem nhưng đừng thay đổi nội dung nhé.

server.go

Import vài thứ nào

import (      “context”      pd “source/grpc/proto”      “net”      “google.golang.org/grpc”)

Công việc của mình ở đây là implement 2 hàm Hello và HelloAgain, cả 2 đều trả về struct HelloReply, chỉ khác nội dung Message.

(Nếu quên implement ở server, khi client gọi đến sẽ có error báo chưa implement)

implement các hàm

Thêm vài đoạn code cho việc run server RPC vào main.

client.go

Ở client mình sẽ truy cập vào server RPC và gọi 2 hàm Hello(), HelloAgain()

  • Input: tên
  • Ouput:
“Greeting: Hello: <tên>”“Greeting: Hello Again: <tên>”

Mình đã bỏ qua việc check error để không rườm rà.

Tại dòng số 25, 26 mình truyền vào object có kiểu SayHello với key Name gán bằng “Your name”.

Done! Chạy thử chương trình thôi nào! (nhớ chạy server trước nhé)

$ go run server/server.go$ go run client/client.go

Và chúng ta nhận được kết quả:

Các bạn có thể xem toàn bộ project tại: https://github.com/thanh-the-lozi/grpc

gRPC được dùng bởi nhiều công ty lớn vì nhiều thế mạnh mà nó mang lại: độ trễ thấp, khả năng mở rộng cao khi dùng trong microservices. Đồng thời giúp lập trình viên “dễ thở” hơn trong việc sử dụng các phương thức thông qua cách gọi hàm như thể đang ở cùng 1 server. Nếu bạn đang làm việc với Microservices thì gRPC có khả năng cao là 1 lựa chọn được ưu tiên hàng đầu.

Cảm ơn các bạn đã dành thời gian, thân chào!

--

--