
Olá a todos! No mundo moderno, a capacidade de dimensionar o aplicativo com o clique de um dedo é extremamente importante, porque a carga no aplicativo pode variar bastante em momentos diferentes. O afluxo de clientes que decidem usar seu serviço pode trazer grandes lucros e perdas. Particionar um aplicativo em serviços separados resolve problemas de dimensionamento; você sempre pode adicionar instâncias de serviços carregados. Isso, sem dúvida, ajudará a lidar com a carga e o serviço não cairá dos clientes em alta. Mas os microsserviços, juntamente com seus benefícios inegáveis, também introduzem uma estrutura de aplicativos mais complexa, além de entrelaçamento em seus relacionamentos. E se, mesmo escalando com êxito meu serviço, os problemas continuarem? O tempo de resposta está aumentando e há mais erros? Como entenderonde exatamente está o problema? Afinal, cada solicitação à API pode gerar uma cadeia de chamadas de diferentes microsserviços, recebendo dados de vários bancos de dados e APIs de terceiros. Talvez isso seja um problema de rede ou a API do seu parceiro não possa lidar com a carga ou talvez seja o culpado desse cache? Neste artigo, tentarei explicar como responder a essas perguntas e encontrar rapidamente o ponto de falha. Bem-vindo ao gato.
, . OpenTracing. , . JavaScript, TypeScript. , , .

OpenTracing Trace, Span, SpanContext, Carrier, Tracer.
- Trace. , Span', traceId. Span' c . ChildOf — . , span'a . FollowsFrom , span span, .
- Span. OpenTracing. Span , . , , , span, . Span OpenTracing, Tracer ( ). ( ), Span timestamp spanId. traceId, span , traceId , spana' , . , span finish. Span timestamp , span ( ). span , key:value. , , , OpenTracing. , , Span', , error: true. , , , . , , , timestamp . span'a . .
- SpanContext. , OpenTracing, , span' . traceId, spanId, key:value, . OpenTracing baggage. , . SpanContext, span', . span.
- Carrier. key:value , SpanContext. Carrier tracer. OpenTracing . — FORMAT_TEXT_MAP, key:value. . FORMAT_BINARY . , tracer. , FORMAT_HTTP_HEADERS, http.
- Tracer. OpenTracing, span', , (distributed tracing system) Jaeger Elastic APM. Tracer , carrier . inject extract

Extract carrier'a, carrier . Inject SpanContext, carrier , , . , span'.
, , typescript . NATS, Jaeger.
NATS
, , golang. Publish-Subscribe Request-Reply . NATS , , , . NATS , , . NATS, Docker.
docker run -d --name nats -p 4222:4222 -p 6222:6222 -p 8222:8222 nats
Jaeger
, opensource Uber. Jaeger , , , . Jaeger Cassandra, Elasticsearch , . Kafka, , span', . , Jaeger, . :
, Jaeger, , Docker
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.8
, . http. get /devices/:regionId json , . .

NATS. endpoint , , . , http , api NATS x devices. devices (, mongodb), , ( redis) . devices id users . api. , .
,
export interface Location {
lat: number;
lng: number;
}
export interface Device {
id: string;
regionId: string;
userId: string;
connected: boolean;
}
export interface User {
id: string;
name: string;
address: string;
}
export interface ConnectedDevice extends Device {
user: User;
connected: true;
location: Location;
}
devices users
export const UsersMethods = {
getByIds: 'users.getByIds',
};
export const DevicesMethods = {
getByRegion: 'devices.getByRegion',
};
, NATS, publish subscribe. .
import * as Nats from 'nats';
import * as uuid from 'uuid';
export class Transport {
private _client: Nats.Client;
public async connect() {
return new Promise(resolve => {
this._client = Nats.connect({
url: process.env.NATS_URL || 'nats://localhost:4222',
json: true,
});
this._client.on('error', error => {
console.error(error);
process.exit(1);
});
this._client.on('connect', () => {
console.info('Connected to NATS');
resolve();
});
});
}
public async disconnect() {
this._client.close();
}
public async publish<Request = any, Response = any>(subject: string, data: Request): Promise<Response> {
const replyId = uuid.v4();
return new Promise(resolve => {
this._client.publish(subject, data, replyId);
const sid = this._client.subscribe(replyId, (response: Response) => {
resolve(response);
this._client.unsubscribe(sid);
});
});
}
public async subscribe<Request = any, Response = any>(subject: string, handler: (msg: Request) => Promise<Response>) {
this._client.subscribe(subject, async (msg: Request, replyId: string) => {
const result = await handler(msg);
this._client.publish(replyId, result);
});
}
}
http api express. index api Transport, devices.
(async () => {
const transport = new Transport();
const port = 5000;
await transport.connect();
const api = express();
api.get('/devices/:regionId', async (request, response) => {
const result = await transport.publish<GetByRegion, ConnectedDevice[]>(DevicesMethods.getByRegion, {
regionId: request.params.regionId,
});
response.send(result);
return result;
});
api.listen(port, () => {
console.info(`Server started on port ${port}`);
});
})();
devices users. devices, users .
. mongodb, redis
export class DeviceRepository {
private db = 'mongodb';
private devices: Device[] = [...];
public async getByRegion(regionId: string): Promise<Device[]> {
return new Promise(resolve => {
setTimeout(() => resolve(this.devices), 300);
});
}
}
export class LocationRepository {
private db = 'redis';
private locations = new Map<string, Location>([...]);
public async getLocation(deviceId: string): Promise<Location> {
return new Promise(resolve => {
setTimeout(() => resolve(this.locations.get(deviceId)), 40);
});
}
}
— devices, getByRegion. .
export async function getByRegion(request: Msg<GetByRegion>) {
try {
const deviceRepository = new DeviceRepository();
const locationRepository = new LocationRepository();
const regionId = request.regionId;
const devices = await deviceRepository.getByRegion(regionId);
const connectedDevices = await Promise.all(
devices.map(async device => {
const location = await locationRepository.getLocation(device.id);
return { ...device, location };
}),
);
const users: User[] = await transport.publish<GetByIds, User[]>(UsersMethods.getByIds, {
ids: devices.map(device => device.id),
});
return connectedDevices.map(device => {
const user = users.find(user => user.id === device.userId);
return {
...device,
user,
};
});
} catch (error) {
console.error(error);
(this as any).createError(error);
}
}
index devices Transport .
export const transport = new Transport();
(async () => {
try {
await transport.connect();
transport.subscribe(DevicesMethods.getByRegion, getByRegion);
} catch (error) {
console.error(error);
process.exit(1);
}
})();
, endpoint, , json . , , , Tracer. OpenTracing jaeger-client.
import { JaegerTracer, initTracer } from 'jaeger-client';
export class Tracer {
private _client: JaegerTracer;
constructor(private serviceName: string) {
this._client = initTracer(
{
serviceName,
reporter: {
agentHost: process.env.JAEGER_AGENT_HOST || 'localhost',
agentPort: parseInt(process.env.JAEGER_AGENT_PORT || '6832'),
},
sampler: {
type: 'const',
param: 1,
},
},
{},
);
}
get client() {
return this._client;
}
}
Jaeger, . const. span' publish subscribe, . , Transport, , Tracer.
constructor(private tracer?: Tracer) {}
. subscribePerfomance subscribe
export function subscribePerfomance(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const origin = descriptor.value;
descriptor.value = async function() {
if (this.tracer) {
const { client } = this.tracer as Tracer;
const subject: string = arguments[0];
const handler: Handler = arguments[1];
const wrapperHandler = async (msg: Msg) => {
const childOf = client.extract(FORMAT_TEXT_MAP, msg[CARRIER]);
if (childOf) {
const span = client.startSpan(subject, { childOf });
this[CONTEXT] = span;
try {
const result = await handler.apply(this, [msg]);
span.finish();
return result;
} catch (error) {
span.setTag(Tags.ERROR, true);
span.log({
'error.kind': error,
});
span.finish();
throw error;
}
} else {
return handler(msg);
}
};
return origin.apply(this, [subject, wrapperHandler]);
}
return origin.apply(this, arguments);
};
}
- Tracer extract carrier . , .
- span SpanContext
- span this. , .
- span.
, , span , span
publishPerfomance publish
export function publishPerfomance(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const origin = descriptor.value;
let isNewSpan = false;
descriptor.value = async function() {
if (this.tracer) {
const { client } = this.tracer as Tracer;
const subject: string = arguments[0];
let data: Msg = arguments[1];
let context: Span | SpanContext | null = this[CONTEXT] || null;
if (!context) {
context = client.startSpan(subject);
isNewSpan = true;
}
const carrier = {};
client.inject(context, FORMAT_TEXT_MAP, carrier);
data[CARRIER] = carrier;
try {
const result = await origin.apply(this, [subject, data]);
if (isNewSpan) {
(context as Span).finish();
}
return result;
} catch (error) {
if (isNewSpan) {
const span = context as Span;
span.setTag(Tags.ERROR, true);
span.log({
'error.kind': error,
});
span.finish();
}
throw error;
}
}
return origin.apply(this, arguments);
};
}
- this . , , publish , . . api devices, , users.
- , span.
- carrier . context , . traceId.
- , carrier.
Transport, . Jaeger , . .


, getByRegion. 97.22% . , . , , , users, , . ? span' c . .
export function repositoryPerfomance({ client }: Tracer) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = async function() {
if (this.parent[CONTEXT]) {
const span = client.startSpan(propertyKey, {
childOf: this.parent[CONTEXT],
});
span.setTag(Tags.DB_TYPE, this.db);
try {
const result = await original.apply(this, arguments);
span.finish();
return result;
} catch (error) {
span.setTag(Tags.ERROR, true);
span.log({
'error.kind': error,
});
span.finish();
throw error;
}
} else {
return original.apply(this, arguments);
}
};
};
}
- , span'a. span' , .
- span.
- , . OpenTracing . ip .
Jaegere, .


. , . 74.38% devices . , , github.
NATS, , . , OpenTracing, . http . , , . — , , . , , , , . , , , . , Jaeger, , .
A coleta e análise de rastreamentos de um aplicativo distribuído é semelhante à realização de uma ressonância magnética com contraste na medicina, com a qual você pode não apenas solucionar os problemas atuais, mas também identificar uma doença grave em um estágio inicial.