Diagnostiquer les problèmes dans l'architecture de microservices sur Node.js à l'aide d'OpenTracing et de Jaeger


Bonjour à tous! Dans le monde moderne, la possibilité de mettre à l'échelle l'application d'un simple clic est extrêmement importante, car la charge sur l'application peut varier considérablement à différents moments. L'afflux de clients qui décident d'utiliser votre service peut entraîner des profits et des pertes importants. Le partitionnement d'une application en services séparés résout les problèmes de mise à l'échelle; vous pouvez toujours ajouter des instances de services chargés. Cela aidera sans aucun doute à faire face à la charge et le service ne tombera pas des clients en forte augmentation. Mais les microservices, avec leurs avantages indéniables, introduisent également une structure d'application plus complexe, ainsi qu'un enchevêtrement dans leurs relations. Que se passe-t-il si la mise à l'échelle de mon service réussit, les problèmes persistent? Le temps de réponse augmente et il y a plus d'erreurs? Comment comprendreoù est exactement le problème? Après tout, chaque demande à l'API peut générer une chaîne d'appels de différents microservices, recevant des données de plusieurs bases de données et API tierces. C'est peut-être un problème de réseau, ou l'API de votre partenaire ne peut pas faire face à la charge, ou peut-être que ce cache est à blâmer? Dans cet article, je vais essayer de dire comment répondre à ces questions et trouver rapidement le point d'échec. Bienvenue au chat.


, . 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, . :


  • Const. , ( 1) ( 0)


  • Probabilistic. , Jaeger . . , 0.1 1 10.


  • Rate Limiting. .


  • Remote. , Jaeger'a. , .



, 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]); // 1
        if (childOf) {
          const span = client.startSpan(subject, { childOf }); // 2
          this[CONTEXT] = span; // 3
          try {
            const result = await handler.apply(this, [msg]); // 4
            span.finish(); // 5
            return result;
          } catch (error) {
            span.setTag(Tags.ERROR, true); // 6
            span.log({
              'error.kind': error, 
            });
            span.finish();
            throw error;
          }
        } else {
          return handler(msg);
        }
      };
      return origin.apply(this, [subject, wrapperHandler]);
    }
    return origin.apply(this, arguments);
  };
}

  1. Tracer extract carrier . , .
  2. span SpanContext
  3. span this. , .
  4. span.
  5. , , 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; // 1
      if (!context) {
        context = client.startSpan(subject); // 2
        isNewSpan = true;
      }
    
      const carrier = {};
      client.inject(context, FORMAT_TEXT_MAP, carrier); // 3
      data[CARRIER] = carrier; // 4
      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);
    };
    }

  6. this . , , publish , . . api devices, , users.
  7. , span.
  8. carrier . context , . traceId.
  9. , 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]) { // 1
        const span = client.startSpan(propertyKey, { 
          childOf: this.parent[CONTEXT], // 2
        });
        span.setTag(Tags.DB_TYPE, this.db); // 3
        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);
      }
    };
  };
}

  1. , span'a. span' , .
  2. span.
  3. , . OpenTracing . ip .

Jaegere, .




. , . 74.38% devices . , , github.


NATS, , . , OpenTracing, . http . , , . — , , . , , , , . , , , . , Jaeger, , .


La collecte et l'analyse de traces à partir d'une application distribuée sont similaires à la réalisation d'une IRM avec contraste en médecine, avec laquelle vous pouvez non seulement résoudre les problèmes actuels, mais également identifier une maladie grave à un stade précoce.


All Articles