Сервисы общаются через Redis Pub/Sub. Канал — это адрес: у каждого сервиса есть прямой канал
с его именем, плюс общие каналы broadcast для рассылки событий и enrich для обогащения.
Сообщения — JSON-RPC 2.0 с двумя дополнительными полями to и from. Всё это реализовано
в rock-me-ts классами RPCAdapterRedis (транспорт) и RPCAgnostic (протокол).
При создании адаптера сервис передаёт список каналов, на которые подписывается:
const channels = [config.rpc.name, BROADCAST]; // слушатель: chwriter, kafkastream
const channels = [ENRICH, config.rpc.name]; // обогатитель: mmgeo
Отправка — это PUBLISH в канал с именем получателя. Чтобы написать сервису chwriter,
достаточно знать его имя: rpc.notify('chwriter', 'method', params).
| Канал | Кто слушает | Что приходит |
|---|---|---|
<имя сервиса> | один сервис | прямые запросы и ответы на его запросы |
broadcast | все слушатели | каждое событие после обработки, уведомлением |
enrich | все обогатители | запросы на обогащение с выбранными полями события |
director | Front | heartbeat __iamalive от сервисов |
Запрос:
{
"jsonrpc": "2.0",
"id": "1k",
"from": "front_1",
"to": "mmgeo_1",
"method": "mmgeo_1:enrich:front_1",
"params": { "ip": "93.184.216.34", "ua": "Mozilla/5.0 ..." }
}
Ответ:
{
"jsonrpc": "2.0",
"id": "1k",
"from": "mmgeo_1",
"to": "front_1",
"service_group": "mmgeo",
"result": { "country_iso": "RU", "city_en": "Moscow" }
}
Особенности:
method кодируется как to:method:from; получатель разбирает эту строку обратно.
В коде вы работаете с чистым именем метода.id — уведомление: получатель выполняет метод и ничего не отвечает.rpc.service_group, она добавляется в ответ. По ней Front группирует
результаты нескольких экземпляров одного обогатителя.error (code, message, data со стеком),
но только для запросов с id.Регистрирует обработчик метода. Один метод — одна функция, повторная регистрация заменяет предыдущую:
this.rpc.register(BROADCAST, this.writer.write);
this.rpc.register(METHOD_STATUS, () => this.status());
Обработчик получает params (или пустой объект, если их нет) и возвращает результат.
Результат уходит отправителю, только если тот ждёт ответа.
Уведомление без ответа. Так Front рассылает события слушателям, а сервисы отправляют heartbeat:
this.rpc.notify(SERVICE_DIRECTOR, METHOD_IAMALIVE, { name: this.name });
Запрос с ожиданием ответа. Возвращает промис:
const status = await this.rpc.request<RPCAppStatus>('mmgeo_1', METHOD_STATUS, {});
const geo = await this.rpc.request('enrich', 'enrich', { ip }, {
timeout: 300,
services: ['mmgeo_1', 'uapc_1']
});
Опции:
timeout — миллисекунды, по умолчанию 200. По истечении промис отклоняется ошибкой
Request timeout.services — список имён сервисов, от которых нужны ответы. Запрос становится групповым:
промис резолвится объектом, где ключ — service_group отвечающего (или его имя, если группы нет),
а значение — его result. Как только ответили все перечисленные, промис завершается; по тайм-ауту
он тоже резолвится, но тем, что успело прийти. Пустой список — запрос не отправляется, результат null.Если result одиночного ответа — объект вида { type__: 'data', data: {...} } (ответ,
собранный через response.data), промис резолвится содержимым data.
request генерирует идентификаторы через TheIds, который берётся из общего контейнера
зависимостей. Перед созданием RPCAgnostic положите его туда, иначе первый же запрос упадёт:
import { getAppDeps, TheIds } from '@rockstat/rock-me-ts';
getAppDeps().setDep('ids', new TheIds());
Слушателям это не нужно: chwriter и kafkastream только принимают уведомления.
Front должен знать, какие сервисы обогащают события и какие обрабатывают ключи. Для этого сервис раз в несколько секунд шлёт heartbeat, а Front в ответ запрашивает его статус:
notify('director', '__iamalive', { name }). Канал director слушает Front.request(name, '__status') в прямой канал сервиса.RPCAppStatus со списком register.import { RPCAppStatus, MethodRegistration, METHOD_STATUS, METHOD_IAMALIVE, SERVICE_DIRECTOR } from '@rockstat/rock-me-ts';
async status(): Promise<RPCAppStatus> {
return {
name: this.name,
app_state: 'running',
app_started: Number(this.appStarted),
app_uptime: Date.now() - Number(this.appStarted),
register: [{
method: 'enrich',
role: 'enricher',
options: { keys: ['in.gen.track'], props: { ip: 'td.ip', ua: 'td.ua' } }
}]
};
}
async setup() {
this.rpc.register(METHOD_STATUS, () => this.status());
const aliver = () => this.rpc.notify(SERVICE_DIRECTOR, METHOD_IAMALIVE, { name: this.name });
setTimeout(aliver, 500);
setInterval(aliver, 9 * 1000);
}
Элемент register:
| Поле | Значение |
|---|---|
method | имя метода, который Front будет вызывать |
role | enricher или handler |
options.keys | для обогатителя: префиксы ключей событий, например in.gen.track |
options.props | для обогатителя: какие поля события прислать, { параметр: 'путь.в.событии' } |
options.alias | для обработчика: имя сервиса в ключе маршрутизации вместо реального |
options.timeout | для обработчика: сколько Front ждёт ответ |
Для обработчика Front строит ключ in.gen.<alias или имя сервиса>.<method> и вызывает метод
запросом с полным событием. Ответ — либо структура response.data, response.redirect,
response.pixel, response.error из rock-me-ts, либо любой объект, который Front обернёт
в response.data. Метод * подписывает обработчик на все события сервиса.
RPCAgnostic сам пишет в Meter время каждого запроса (rpc.request с тегами target и method)
и счётчик ошибок rpc.error. Свои метрики добавляйте через meter.tick('kafka.write.success')
или meter.timenote('ch.flush'), который возвращает функцию-завершитель замера.