Технологія мікросервісів
Мікросервісна архітектура - це підхід до розробки програмного забезпечення, який полягає у створенні набору невеликих, незалежних служб, кожна з яких виконує певну функцію та спілкується з іншими через легко визначені інтерфейси, зазвичай через API. Кожен мікросервіс є самостійним та може бути розроблений, розгорнутий, працювати та масштабуватися незалежно від інших частин системи. Цей підхід може поліпшити модульність, спростити масштабування та сприяти неперервному впровадженню нових функцій.
Для розробки мікросервісів використовується технологія gRPC, яка дозволяє легко та швидко створити легкомаштабовані сервіси, які використовують протокол HTPP/2
для передачі інформації.
GRPC Python
Для розробки мікросервісів використовується мова програмування python
та набір фрейморків для роботи з gRPC
.
gRPC
- це сучасна високопродуктивна платформа Remote Procedure Call (RPC) з відкритим кодом, яка може працювати в будь-якому середовищі.
Дана технологія рохроблена компанією GOOGLE. У якості файлів з описом структур даних, які будуть передаватись, використовується технологія Protocol Buffers
До списку використаних розширень мови програмування входять:
- grpcio - основний фреймворк для створення
gRPC
-сервісів - grpcio-tools - консольна ультиліта. Використовується для генерації описових файлів сервісу з
.proto
-файлів - protobuf - бібліотека роботи з
.proto
-файлами для мови програмуванняpython
Структура проєктів мікросервісів
microservise_name/
├── venv/
├── test_files/
├── server_code.py
├── service_description.proto
├── start_server_script.sh
├── Dockerfile or docker-compose.yml
├── requiremets.txt
├── changelog.md
└── README.md
Опис компонентів проекту:
venv/
- віртуальне середовищеtest_files/
- тека з файлами для тестування (якщо потрібні)server_code.py
- код сервераservice_description.proto
- код опису функціонала сервісуstart_server_script.sh
- скрипт ініціалізації сервісуDockerfile or docker-compose.yml
- опис контейнера для сервісуrequiremets.txt
- опис залежностейpython
changelog.md
- опис змін версійREADME.md
- опис сервісу
Protocol Buffers
Protocol Buffers (Protobuf) - це мовно-нейтральний, платформо-незалежний інструмент для серіалізації структурованих даних, розроблений Google. Він використовується для обміну даними між сервісами та системами. Protobuf пропонує більш ефективний і компактний спосіб, ніж традиційні формати обміну даними, як-от XML чи JSON, забезпечуючи швидше кодування та декодування даних.
Особливості які можна виділити:
- Ефективність -
protobuf
оптимізований для низької витрати ресурсів і швидкого обміну даними - Переносимість - сумісний з багатьма мовами програмування і платформами
- Простота використання - легко інтегрується з різними мовами програмування
- Розширюваність - схеми даних можуть бути модифіковані без порушення сумісності з уже існуючими даними
Приклад опису сервісу за допомогою protobuf
:
syntax = "proto3";
package example;
// Сервіс для керування користувачами
service UserService {
// Додавання нового користувача
rpc AddUser (AddUserRequest) returns (UserResponse);
// Отримання інформації про користувача
rpc GetUser (UserRequest) returns (UserResponse);
}
// Запит для додавання користувача
message AddUserRequest {
string name = 1;
string email = 2;
}
// Запит для отримання інформації про користувача
message UserRequest {
int32 user_id = 1;
}
// Відповідь з інформацією про користувача
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
Компіляція protobuf
Відповідно до документації фреймворку grpcio
, компіляція protobuf
виконується за допомогою виконання команди:
python3 -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. <proto_file_path>
Створення функцій сервісу
Для створення функцій сервісу створюється окремий скрипт, який буде виконувати роль сервера. Всередині цього срипта обов'язково повинні бути наступні сніпети:
- Опис сервера
- Код запуску
- Імпортовані залежності
def server(port=4003):
server_opt = [('grpc.max_send_message_length', 512 * 1024 * 1024),
('grpc.max_receive_message_length', 512 * 1024 * 1024)] # Опис налаштуванб сервера
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), options=server_opt) # Cтворення об'єкта сервера з максимальною кількістю паралельних воркерів 10
convertp_pb2_grpc.add_ConvertServicer_to_server(
Convert(), server) # Додавання описаних функцій до сервісу
server.add_insecure_port(f'[::]:{port}') # Встановлення каналу передачі даних для сервісу (налаштування робочої ip адреси та порта)
server.start() # Запус сервера
print(f'Server is runnig on port {port}')
server.wait_for_termination() # Запус циклу сервера
if __name__ == '__main__':
logging.basicConfig(filename=SERVER_LOG_PATH) # Налаштування системи логування
try:
if len(argv) > 1:
server(int(argv[1]))
else:
server()
except Exception as err:
print(err)
import <sevice_name>_pb2 # Файл згенерований з protobuf
import <sevice_name>_pb2_grpc # Файл згенерований з protobuf
import grpc
from concurrent import futures # Фреймоврк для паралельної обробки запитів
import logging # Бібліотека логування
Приклад опису функції сервісу:
class MyServiceServicer(MyServiceServicer):
def MyFunction(self, request, context):
# Використання request
data = request.some_field
# Перевірка відміни запиту
if context.is_active():
# Обробка даних
pass
else:
# Обробка відміни
context.set_code(grpc.StatusCode.CANCELLED)
context.set_details('Request cancelled by the client')
return MyResponse()
Усі функції описуються всередині відповідного класу, який наслідує клас, який згенерований з опису сервісу. Приклад класу:
class MyServiceServicer(MyServiceServicer):
# Опис функціоналу сервісу
Опис параметрів функцій сервера
1. request
request
- це параметр, який містить дані, відправлені клієнтом до сервера. Властивості та тип request визначаються за допомогою файлів .proto
, де описуються повідомлення та сервіси gRPC
.
Особливості:
- Тип даних: Тип request відповідає типу вхідного повідомлення, яке було визначено у файлі
.proto
для даної RPC-функції. - Використання: request використовується для доступу до даних, які клієнт хоче передати серверу. Наприклад, якщо ви маєте RPC-функцію
GetUser
, яка приймаєGetUserRequest
, то ви можете отримати доступ до даних користувача черезrequest.user_id
або подібні поля.
2. context
context
- це параметр, що надає метадані, контрольні можливості та інші можливості, пов'язані з обробкою поточного RPC-виклику.
Особливості:
- Контроль таймаутів та відмін: За допомогою context сервер може перевіряти, чи було відмінено запит або чи минув час очікування.
- Метадані: Дозволяє отримувати та відправляти метадані, які супроводжують RPC-виклики.
- Авторизація: Може використовуватися для перевірки даних авторизації, наприклад, токенів доступу.
- Відповіді на помилки: Через context можна відправляти специфічні для
gRPC
помилки та повідомлення про статус.
Контейнеризація сервісу
Для стабільної роботи сервісів вони пакуються у Docker
контейнери. Налаштування контейнера описуються у файлі Dockerfile
або docker-compose.yml
.
Документація зі створення контейнерів знаходиться за посиланням
GRPC Node JS
Для роботи з сервісом gRPC
з сторони Node JS
використовуються наступні залежності:
- @grpc/grpc-js - складова пакету grpc для роботи та створення
gRPC
сервісами - @grpc/proto-loader - складова пакету grpc для роботи з
.proto
файлами
Налаштування підключення
Через те, що усі мікросервіси працюють віддалено - то виникає потреба у конфігурації підключення до сервісу.
Усі підключення описуються всередині конфігураційного файлу додатку Node JS
configServer.json
:
{
"<serverName>ServerAddress": "<ip>:<port>"
}
Структура тек з описом сервісу
grpc/
├── service_name.js
└── service_name.proto
Опис компонентів проекту:
service_name.js
- код клієнта сервісуservice_name.proto
- опис сервісу (повна копія файлу з проекту сервісу)
Опис функцій клієнту
Кожен опис клієнту у Node JS
повинен мати наступні сніпети коду:
- Опис сервера
- Завантаження опису
- Імпортовані залежності
const convertClient = convertServerAddress ? new proto.Convert(
convertServerAddress,
grpc.credentials.createInsecure(),
{
'grpc.max_send_message_length': 512 * 1024 * 1024,
'grpc.max_receive_message_length': 512 * 1024 * 1024,
},
) : null;
const proto = grpc.loadPackageDefinition(
protoLoader.loadSync(path.join(__dirname, '<service_name>.proto'), {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
}),
);
const grpc = require('@grpc/grpc-js'); // модуль роботи з grpc сервісами
const protoLoader = require('@grpc/proto-loader'); // модуль роботи з proto файлами
Приклад опису функції:
onst pdfMerge = convertServerAddress ? async (data) => new Promise((res, rej) => {
convertClient.pdfMerge(data, (err, data) => {
if (err) {
return rej(err);
}
res(data);
});
}) : null;
Усі функції клієнта працюють через Promise
та повертають помилку у якості reject
, який емітить throw new Error