Diagnostizieren Sie Probleme in der Microservice-Architektur auf Node.js mit OpenTracing und Jaeger


Hallo alle zusammen! In der modernen Welt ist die Möglichkeit, die Anwendung per Fingerklick zu skalieren, äußerst wichtig, da die Belastung der Anwendung zu unterschiedlichen Zeiten stark variieren kann. Der Zustrom von Kunden, die sich für die Nutzung Ihres Dienstes entscheiden, kann sowohl große Gewinne als auch Verluste bringen. Durch die Partitionierung einer Anwendung in separate Dienste werden Skalierungsprobleme gelöst. Sie können jederzeit Instanzen geladener Dienste hinzufügen. Dies wird zweifellos dazu beitragen, die Belastung zu bewältigen, und der Service wird nicht von den wachsenden Kunden fallen. Microservices führen jedoch neben ihren unbestreitbaren Vorteilen auch eine komplexere Anwendungsstruktur sowie eine Verflechtung in ihren Beziehungen ein. Was ist, wenn die Probleme trotz erfolgreicher Skalierung meines Dienstes weiterhin bestehen? Die Reaktionszeit wächst und es gibt mehr Fehler? Wie man verstehtWo genau liegt das Problem? Schließlich kann jede Anforderung an die API eine Kette von Aufrufen verschiedener Mikrodienste generieren, die Daten aus mehreren Datenbanken und APIs von Drittanbietern empfangen. Möglicherweise handelt es sich um ein Netzwerkproblem, oder die API Ihres Partners kann die Last nicht bewältigen, oder ist dieser Cache schuld? In diesem Artikel werde ich versuchen zu erklären, wie diese Fragen zu beantworten sind, und schnell den Fehlerpunkt finden. Willkommen bei Katze.


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


Das Sammeln und Analysieren von Spuren aus einer verteilten Anwendung ähnelt der Durchführung einer MRT mit Kontrastmittel in der Medizin, mit der Sie nicht nur aktuelle Probleme lösen, sondern auch frühzeitig eine schwerwiegende Krankheit identifizieren können.


All Articles