Análisis de código fuente Java GRPC

Introducción

GRPC es un marco de trabajo de llamadas remotas de soporte en varios idiomas lanzado por Google . La arquitectura general de su versión de Java se muestra en la siguiente figura. Está dividido en cuatro capas. De arriba a abajo, la primera capa es Códigos de servidor / cliente, y los usuarios de GRPC implementan funciones comerciales; la segunda capa es el código de servidor / cliente (GRPC Protobuf Server / Client Stub), esta capa es la implementación de la interfaz de la aplicación definida por el servidor y el cliente, responsable de la serialización y deserialización de los parámetros del método y la interacción con GRPC (los amigos que han usado Protobuf no deberían ser difíciles de entender su Función); la tercera capa es el marco central de GRPC capa (GRPC Frame), esta capa es el núcleo funcional de GRPC, compuesto por varios paquetes de código fuente clave; la cuarta capa es la capa de implementación de transporte GRPC (Transport Impl), debido al núcleo GRPC La capa de marco abstrae la transmisión de datos subyacente lógica en forma de interfaces, por lo que la transmisión de datos subyacente real puede ser reemplazada por diferentes implementaciones Actualmente, hay dos versiones de la implementación de red basada en Netty y la implementación simplificada del mismo proceso.
Inserte la descripción de la imagen aquí
Este artículo se centra en la implementación del marco central GRPC (GRPC Frame) y utiliza GRPC Netty Impl como la capa de transporte para implementar el escenario (GRPC Inprocess Impl tiene funciones similares). Más adelante, analizaré primero su arquitectura lógica y tiempo de ejecución, y luego interpretaré su implementación de código clave.
Nota 1 La versión GRPC Java de esta interpretación es 1.25.0 y la versión correspondiente de Netty es 4.1.42.
Nota 2 En el proceso de análisis de la arquitectura lógica, usaré el diagrama de paquetes para dividir sus funciones, por lo que el diagrama de paquetes en el diagrama no es un paquete de código fuente, es solo un paquete lógico. En el código fuente real, las clases en estos diferentes paquetes lógicos pueden pertenecer al mismo paquete fuente, y las clases en el mismo paquete lógico también pueden estar dispersas en diferentes paquetes fuente. El paquete lógico puede entenderse como una unidad lógica dividida por función, y se espera que los lectores no confundan el paquete fuente.
Nota 3 El siguiente diagrama de clases está dividido en tres colores, el amarillo es la clase del marco principal de GRPC (GRPC Frame); el negro y el gris es la clase de la implícita de GRPC Netty; el rojo es la clase en la biblioteca de Netty.
Nota 4 Para aquellos que solo quieren comprender los principios o conceptos, definitivamente no les gustan los diagramas de clases lógicas, los diagramas de interacción en ejecución, etc. que dibujé más adelante, por lo que el propósito de este artículo es hacer un análisis en profundidad de toda su lógica y principios operativos. No es una simple introducción a GRPC. Al mismo tiempo, usar diagramas de clases y ejecutar diagramas interactivos también es conveniente para nosotros para aclarar nuestras ideas y hacer un registro de sus detalles generales. Especialmente para el enorme código del proyecto, es imposible que lo memorice de memoria después de haberlo ordenado hoy. Con este diagrama, cuando se necesita mucho tiempo para profundizar o modificar uno de los detalles, puede ayudarnos a verlo rápidamente.

1 servidor

1.1 Arquitectura lógica

1.1.1 Resumen

  • Arquitectura general
    Inserte la descripción de la imagen aquí

La gestión de servicios (Server Pack) es responsable de la construcción y puesta en marcha del servicio lógico ServerImpl y el servicio de monitorización NettyServer. Es la fábrica funcional de toda la lógica del servidor; la
lógica de transporte (Transport Pack) es responsable de construir la transmisión de datos IO subyacente real y el detector de eventos correspondiente; el
flujo de red (Stream Pack) es la encapsulación de la sesión de red del llamada al método, es decir, el método de una sola vez La llamada es un flujo; la
llamada al método (Call Pack) es la lógica de llamada al método real, y finalmente llamará al método correspondiente de la interfaz de servicio que implementamos; el
registro del servicio ( Registry Pack) es responsable de registrar la descripción del servicio, la descripción de la interfaz y otra información, para la consulta del módulo de llamada al método;

  • Lógica central

El diagrama macro lógico general del servidor es el siguiente. Al ver esta imagen, algunas personas pueden pensar que es demasiado complicado, pero esta es solo la clase principal y la mayoría de las clases no están dibujadas. En este momento, algunas personas pensarán que GRPC está demasiado sobre-diseñado, pero no lo es. Un buen código debe tener su exquisita extracción de conceptos y abstracción lógica para aislar problemas y reducir la complejidad. Esta es también la forma de mantener estable el sistema y escalable La clave es también la diferencia entre la programación orientada a la arquitectura y la programación orientada a las funciones. La creación de conceptos es también el alma del diseño arquitectónico y la clave de la innovación.
Nota 1 : En la figura, se utilizan clases concretas agregadas para mostrar la relación macroscópica completa, pero en la implementación, muchas de ellas son interfaces abstractas agregadas, que se explicarán en detalle en la interpretación posterior de cada submódulo.
Nota 2 : En la figura, el nombre de la clase con $ indica que la última es la clase interna de la primera; {} indica que hay una clase anónima en un método de la clase anterior, y el nombre de la clase base o el nombre de la interfaz de esta clase anónima está en {} nombre.
Inserte la descripción de la imagen aquí
Toda la arquitectura del servidor se divide en seis módulos. La gestión de servicios (Server Pack) es responsable de la construcción y puesta en marcha del servicio lógico ServerImpl y el servicio de monitorización NettyServer; el registro de servicios (Registry Pack) es responsable de registrar información como descripciones de servicios, descripciones de interfaces, etc., por método consulta del módulo de llamada; lógica de transporte (paquete de transporte) Responsable de construir la transmisión de datos de IO subyacente real y los oyentes de eventos correspondientes; el procesamiento de red (Handler Pack (io.grpc.netty)) se basa en el mecanismo de devolución de llamada asíncrono proporcionado por Netty para implementar IO controladores de eventos, y eventualmente se entregarán al oyente de eventos del paquete de transporte o al procesamiento del paquete de flujo; el flujo de red (paquete de flujo) es la encapsulación de la sesión de red de la llamada al método, es decir, una llamada al método es un flujo; el La llamada al método (Call Pack) es la lógica real de la llamada al método, y finalmente se llamará al método correspondiente a la interfaz de servicio que implementamos.

1.1.2 Gestión de servicios (paquete de servidor)

El módulo de gestión de servicios se divide en constructor de servidores lógicos (ServerBuilder), servidor lógico (Server) y servidor de supervisión (InternelServer). ServerBuilder es la fábrica de construcción de Server e InternelServer; Server es un servidor lógico real. Un proceso GRPC puede monitorear múltiples direcciones al mismo tiempo, y cada dirección de escucha corresponde a un InternelServer, por lo que Server puede considerarse como el administrador de InternelServer; InternelServer es un servidor real, un InternelServer solo puede manejar una dirección.
Inserte la descripción de la imagen aquí
Además, la clase de implementación del servidor ServerImpl también contiene una instancia de la interfaz HandlerRegistry (es decir, InternelHandlerRegistry) para administrar la información de registro del servicio; ServerImpl construirá ServerTransportListenerImpl y lo pasará a InternelServer, y la clase de implementación de InternelServer NettyServer lo establecerá en NettyServerTransport al construir Oyente de NettyServerTransport.

1.1.3 Registro de servicios (paquete de registro)

El registro del servicio incluye el administrador de registro (HandlerRegistry), la definición del servicio (ServerServiceDefinition), la definición del método (ServerMethodDefinition), la descripción del servicio (ServiceDescriptor) y la descripción del método (MethodDescriptor). Una clase de implementación HandlerRegistry InternalHandlerRegistry agrega múltiples ServerServiceDefinition y ServerMethodDefinition; una ServerServiceDefinition también agrega múltiples ServerMethodDefinition y un ServiceDescriptor; ServerMethodDefinition agrega una instancia ServerCallHandler (implementada como UnaryServerCallHandler) y un MethodDescriptor de cada atributo y finalmente agrega un MethodDescriptor. MethodType.
Inserte la descripción de la imagen aquí

1.1.4 Lógica de transporte (paquete de transporte)

La lógica de transmisión es relativamente simple, se divide en un canal de transmisión (ServerTransport) y un canal de escucha (ServerTransportListener). La clase de implementación de ServerTransport es NettyServerTransport, que está relacionada con la clase de implementación ServerTransportListenerImpl de ServerTransportListener. ServerTransportListenerImpl es una clase interna de ServerImpl .
Inserte la descripción de la imagen aquí

1.1.5 Procesamiento de red (paquete de controlador (io.grpc.netty))

El procesamiento de red es la lógica de procesamiento de canales de Netty. Hay un total de cuatro controladores, WriteBufferingAndExceptionHandler, WaitUntilActiveHandler, GrpcNegotiationHandler y NettyServerHandler. A su vez, agregan todos los tipos de interfaces abstractos agregados; entre ellos, WaitUntilActiveHandler y GrpcNegotiationHandler son construidos por PlaintextProtocolNegotiator. Cuando NettyServerHandler recibe el encabezado, construye NettyServerStream y llama al método streamCreated de la interfaz ServerTransportListener (implementado como ServerTransportListenerImpl).
Inserte la descripción de la imagen aquí

1.1.5.1 campo netty

En el procesamiento de red, la relación entre las interfaces y las clases abstractas en la biblioteca netty es la siguiente:
Inserte la descripción de la imagen aquí

1.1.6 Paquete de transmisión

El flujo de red se divide en dos partes: objeto de flujo (Stream) y escucha de flujo (StreamListener). Stream aquí es la clase de implementación NettyStream, que agrega StreamListener abstracto (implementado como JumpToApplicationThreadServerStreamListener); la clase de implementación de StreamListener se divide en JumpToApplicationThreadServerStreamListener (es la clase interna de ServerImpl) y ServerStreamListennerImpl (antes es la clase interna de AllImpl). agregados El último tipo de interfaz; ServerStreamListennerImpl agregará un ServerCallImpl y un Listener de interfaz interna ServerCall (se implementa como UnaryServerCallListener, que es un contenedor o entrada para llamar al método de implementación de interfaz real).
Inserte la descripción de la imagen aquí

1.1.7 Llamada de método (paquete de llamadas)

El módulo de llamada al método se divide en controlador de llamada (ServerCallHandler), interfaz de método (UnaryRequestMethod), oyente de llamada (Listener), procesamiento de respuesta (StreamObserver) y objeto de llamada (ServerCall). ServerCallHandler tiene dos subclases, InterceptCallHandler y UnaryServerCallHandler. La primera es el interceptor de llamadas al método establecido por el implementador de la empresa de servicios a pedido, que se utiliza como preprocesamiento de la llamada al método, y el segundo es el ejecutor de llamadas al método real, que llamará al Protobuf. código stub Método de implementación de la interfaz; UnaryRequestMethod es una interfaz provista para llamadas al método de implementación de código stub Protobuf; Listener (su clase de implementación es una clase interna de UnaryServerCallHandler) es principalmente responsable de monitorear las llamadas a métodos y ejecutar el procesamiento de respuestas; StreamObserver proporciona una interfaz para el negocio de servicios implementadores, deje que devuelva el valor de retorno del método; ServerCall es el límite de la llamada al método, responsable de interactuar con el flujo de datos, consultar información de registro del método, etc.
Inserte la descripción de la imagen aquí

1.2 tiempo de ejecución

1.2.1 Construcción del servicio

La función de construcción de servicios se completa principalmente con las cuatro clases de NettyServerBuilder, InternalHandlerRegistry, InternalHandlerRegistry $ Builder y ServerImpl. Por supuesto, hay muchos otros detalles, consulte el código fuente para leer.
Inserte la descripción de la imagen aquí

1.2.2 Inicio del servicio

Proceso de inicio del servicio:

  • Primero, se construye un ServerImpl $ ServerListenerImpl mediante el método de inicio de ServerImpl. Luego use ServerImpl $ ServerListenerImpl como parámetro para llamar a la función de inicio de cada escucha InternalServer;
  • En la función de inicio de NettyServer, se construirá ServerBootstrap (esta clase es la fábrica de inicio de servicio de netty), y luego se construirá un ChannelInitializer parcial como procesador de conexión de servicio;
  • Cuando llega una conexión, el método initChannel del ChannelInitializer local se llamará de forma asincrónica.
    Inserte la descripción de la imagen aquí

1.2.3 Procesamiento de la conexión

Flujo de procesamiento de la conexión:

  • Primero, construya NettyServerTransport y llame al método transportCreated de ServerImpl $ ServerListenerImpl con él como parámetro. En transportCreated, se construirá ServerImpl $ ServerTransportListenerImpl y se devolverá un ServerTransportListener abstracto. Luego, llame al método de inicio del NettyServerTransport recién construido con el ServerTransportListener abstracto como parámetro;
  • En el método de inicio de NettyServerTransport, construirá un NettyServerHandler, llamará al método newHandler de ProtocolNegotiator con él como parámetro, y luego construirá un WriteBufferingAndExceptionHandler con el NettyServerHandler devuelto como parámetro delegado, y establecerá WriteBufferingAndExceptionHandler como el procesador Netty de este canal de conexión;
  • Finalmente, cuando se entrega un encabezado HTTP2.0, el método OnHeadersRead de NettyServerHandler eventualmente se llamará asincrónicamente; de ​​manera similar, cuando se entrega un marco HTTP2.0, el método OnDataRead de NettyServerHandler se llamará de forma asincrónica.
    Inserte la descripción de la imagen aquí

1.2.3.1 Recibir encabezado de mensaje

Cuando se recibe el encabezado del mensaje, se creará un nuevo objeto de flujo de tipo NettyServerStream y se llamará al método streamCreated de ServerImpl $ ServerTransportListenerImpl con el flujo como parámetro. En el método streamCreated, se construirá un objeto jumpListener de tipo ServerImpl $ JumpToApplicationThreadServerStreamListener, y el método setListener del objeto stream se llamará como parámetro. Finalmente, construya un ServerImpl $ StreamCreated, llámelo asincrónicamente y finalmente ejecútelo al método runInternal En runInternal se ejecutarán una serie de funciones sobre la construcción y asociación del objeto que llama y el listener.

Inserte la descripción de la imagen aquí

1.2.3.2 Procesamiento del cuerpo del mensaje

  • Cuando se recibe el cuerpo del mensaje, se llama al método inboundDataRecved del objeto NettyServerStream $ TransportState de la secuencia. En inboundDataRecved, deframe se ejecutará primero para procesar los datos, y luego, cuando el parámetro endOfStream sea verdadero, significa que es el último fotograma, luego se llamará al método closeDeframe.
  • Cuando se llama a closeDeframe, el evento de cierre del marco eventualmente se activará, por lo que se llamará al método deframerClosed de NettyServerStream $ TransportState, y luego se llamará al método halfclose de ServerImpl $ JumpToApplicationThreadServerStreamListener y ServerCalls $ UnaryServerCallHandler $ UnaryServerCall -close, es decir, solo cierra la recepción y el envío Los datos aún están abiertos y se enviarán al cliente después de que regrese la llamada al método). Eventualmente, se ejecutará la implementación del código auxiliar del método de llamada en Protobuf.
  • Además, al final, el evento de finalización de la trama se activará cuando regresen los datos de la red y, finalmente, se cerrará todo el flujo.
    Inserte la descripción de la imagen aquí

1.3 Interpretación del código

El diagrama lógico anterior y el tiempo de ejecución son bastante detallados, por lo que agregaré algo de tiempo a la interpretación del código clave.

2 Cliente

Debido a limitaciones de tiempo, por parte del cliente, publicaré los diagramas lógicos que se han resuelto y no interpretaré el texto. Los amigos que estén interesados ​​pueden verlo en combinación con el código fuente.

2.1 Arquitectura lógica

2.1.1 Resumen

  • Arquitectura general
    Inserte la descripción de la imagen aquí

La transmisión lógica (paquete de transporte) es la unidad funcional de transmisión de datos. Dividido en dos niveles Pendiente y Real, Real's Transport realmente interactúa con netty para la transmisión de datos. Transport of Pending es un búfer para el flujo de llamadas;
Stream Pack es una encapsulación de la sesión de red del proceso de llamada al método, que se divide en el flujo lógico PendingStream y el flujo real NettyClientStream. PendingStream primero almacenará en caché las operaciones de la empresa en el flujo y las ejecutará después de que la transmisión lógica esté lista; el
módulo de llamada al método (Paquete de llamadas) es el paquete lógico o la entrada de función para que el cliente llame al servicio remoto; el
canal superior (Canal Pack) es el cliente y el servicio El canal lógico de conexión del terminal no implica una transmisión de datos específica;

  • Lógica central

La macro lógica de toda la parte del cliente GRPC se muestra en la siguiente figura. Está dividido en siete módulos lógicos. El canal superior (paquete de canales) es el canal lógico de conexión entre el cliente y el servidor, y no implica la transmisión de datos específicos; el interceptor (paquete de interceptor) es responsable de agregar el canal superior para crear un paquete lógico o entrada funcional para el cliente real para llamar a servicios remotos; la transmisión lógica (Transport Pack) es la unidad funcional de transmisión de datos, que realmente interactúa con netty para la transmisión de datos; el canal de red (SubChannel Pack) es un canal de red real en lugar de un canal lógico, responsable de crear una capa de transporte lógico y procesar eventos de transmisión lógica; el flujo de red (Stream Pack) es la encapsulación de la sesión de red del proceso de llamada al método, que se divide en flujo lógico PendingStream y flujo real NettyClientStream (PendingStream primero almacenará en caché las operaciones comerciales en el stream y ejecutarlo después de que la transmisión lógica esté lista); el procesamiento de red (Handler Pack) (io.grpc.netty)) se basa en el mecanismo de devolución de llamada asíncrono proporcionado por Netty para implementar el controlador de eventos IO, y eventualmente se entregará al oyente de eventos o Stream Pack en el Transport Pack para su procesamiento; la llamada al método (Call Pack) es un método para el cliente El objeto encapsulado que se llamará.
Inserte la descripción de la imagen aquí

2.1.2 Canal superior (paquete de canales)

Inserte la descripción de la imagen aquí

2.1.3 Paquete interceptor

Inserte la descripción de la imagen aquí

2.1.4 Llamada de método (paquete de llamadas)

Inserte la descripción de la imagen aquí

2.1.5 Capa de transporte lógico (paquete de transporte)

Inserte la descripción de la imagen aquí

2.1.6 Transmisión de red (Steam Pack)

Inserte la descripción de la imagen aquí

2.1.7 Canal de red (paquete subcanal)

Inserte la descripción de la imagen aquí

2.1.8 Procesamiento de red (Handler Pack (io.grpc.netty))

Inserte la descripción de la imagen aquí

2.2 Tiempo de ejecución

2.2.1 Construcción de canales

Inserte la descripción de la imagen aquí

2.2.2 Iniciar una llamada

Inserte la descripción de la imagen aquí

2.2.3 Iniciar DNS

Inserte la descripción de la imagen aquí

2.2.4 La resolución del nombre de dominio está completa

Inserte la descripción de la imagen aquí

2.2.5 Construyendo una transmisión real

Inserte la descripción de la imagen aquí

2.3 Interpretación del código

3 Resumen de experiencia

3.1 Proceso de análisis

El código GRPC completo suma cientos de miles de líneas de código. Pasé una semana analizándolo y luego pasé otra semana para reemplazar la capa de transporte subyacente con la implementación de la capa de transporte de nuestro formato de protocolo personalizado, y no hubo posterior desarrollo y proceso de aplicación ERROR, después de terminar ocupado, me tomé el tiempo para organizar y dibujar sus diagramas lógicos y de ejecución uno tras otro. Nunca antes había usado GRPC, ni había visto ningún código GRPC. Mucha gente puede preguntarse: ¿Por qué puedo ordenar toda su arquitectura tan rápidamente? Al respecto, la experiencia se resume a continuación:

3.1.1 Primero divide un límite razonable

3.1.2 Leer el código fuente desde una perspectiva superior

3.1.3 El análisis y la derivación son muy importantes

Supongo que te gusta

Origin blog.csdn.net/fs3296/article/details/103608383
Recomendado
Clasificación