Estoy confundido... Meituan pide 44 preguntas, después de lo cual es 60W+

decir de frente

En la comunidad de lectores (más de 50) de Nien, un arquitecto de 40 años , a menudo hay pequeños socios que necesitan entrevistar a grandes empresas como Meituan, JD.com, Ali, Baidu y Toutiao.

El siguiente es un pequeño socio que pasó con éxito una entrevista técnica de Meituan. Al final, el pequeño socio pasó varias torturas técnicas y torturas del alma, y ​​finalmente recibió una oferta.

A juzgar por estos temas: las entrevistas de Meituan se centran en principios y conocimientos de bajo nivel, echemos un vistazo.

Ahora coloque las preguntas de la entrevista real y las respuestas de referencia en nuestra colección, echemos un vistazo, ¿ qué necesita aprender para recibir una oferta de Meituan?

Por supuesto, para el desarrollo intermedio y avanzado, estas preguntas de la entrevista también tienen un significado de referencia.

Aquí, las preguntas y las respuestas de referencia se incluyen en nuestra versión V84 de " Nin Java Interview Collection ", para referencia de amigos posteriores, para mejorar los niveles de arquitectura, diseño y desarrollo de 3 niveles altos de todos.

PDF de "Nin Architecture Notes", "Nin High Concurrency Trilogy" y "Nin Java Interview Collection", vaya a la cuenta oficial [Technical Freedom Circle] para obtenerlo

Directorio de artículos

Las 44 preguntas de Meituan

1. Hable sobre el área de memoria de Java y el modelo de memoria

Las regiones de memoria de Java y el modelo de memoria son cosas diferentes.

Área de memoria : cuando se ejecuta la JVM, los datos se almacenan en regiones, lo que enfatiza la división del espacio de memoria.

Modelo de memoria (Java Memory Model, JMM para abreviar) : define la relación abstracta entre los hilos y la memoria principal, es decir, JMM define cómo funciona la JVM en la memoria de la computadora (RAM).

1. Área de memoria

La siguiente figura es la distribución del área de datos del tiempo de ejecución de JVM antes de JDK 1.8:

La siguiente figura es la distribución del área de datos del tiempo de ejecución de JVM después de JDK 1.8:

Al comparar la distribución del área de datos del tiempo de ejecución de JVM antes de JDK 1.8 y después de JDK 1.8, podemos encontrar que la diferencia es que 1.8 tiene un área de método alternativo de metaespacio . A continuación se explica por qué se reemplaza el área de método .元空间章节

A continuación, presentamos lo que hace cada área para el mapa de distribución de memoria JVM después de JDK 1.8.

pila de métodos nativos

Pilas de Métodos Nativos : Sirve al método Nativo utilizado por la máquina virtual.Puede considerarse como una JNIllamada directa a la biblioteca local C/C++ a través de (Interfaz Nativa de Java), que no está controlada por la JVM.

A menudo usamos 获取当前时间毫秒métodos nativos locales, que se nativemodifican con palabras clave.

package java.lang;

public final class System {
    
    
    public static native long currentTimeMillis();
}

De hecho, es para resolver algunos problemas que Java por sí mismo no puede hacer, pero C/C++ sí, expandir el uso de Java a través de JNI e integrar diferentes lenguajes de programación.

contador de programa

Registro de contador de programa : un pequeño espacio de memoria, su función puede verse como el indicador de número de línea del código de bytes ejecutado por el hilo actual. Dado que la JVM puede ejecutar subprocesos simultáneamente, se asigna un contador de programa a cada subproceso, que es el mismo que el ciclo de vida del subproceso. Por lo tanto, habrá cambios entre subprocesos y, en este momento, el contador del programa registrará la ubicación donde se ejecuta el programa actual, de modo que después de que se ejecuten otros subprocesos, la escena se pueda reanudar para continuar con la ejecución.

Si el subproceso está ejecutando un método Java, este contador registra la dirección de la instrucción de código de bytes de la máquina virtual que se está ejecutando; si el subproceso está ejecutando un método nativo, el valor del contador está vacío (indefinido)

Esta área de memoria es un área 唯一一个donde no se especifica nada en la especificación de máquina virtual de Java OutOfMemoryError.

Pila de máquina virtual Java

Pilas de máquinas virtuales de Java : al igual que el contador del programa, la pila de máquinas virtuales de Java también es privada para subprocesos y su ciclo de vida es el mismo que el del subproceso.

La descripción de la pila de la máquina virtual es Java方法执行的内存模型: cuando se ejecuta cada método, se creará un marco de pila (Stack Frame) al mismo tiempo para almacenar 局部变量表, 操作栈, 动态链接y 方法出口otra información. El proceso de cada método que se llama hasta que se completa la ejecución corresponde al proceso de un marco de pila desde que se inserta hasta que se extrae en la pila de la máquina virtual.

La pila es una lista ordenada de FILO-First In Last Out. En el subproceso activo, solo es válido el marco de pila en la parte superior de la pila, llamado marco de pila actual . El método que se ejecuta se denomina método actual y el marco de pila es la estructura básica para que se ejecute el método. Cuando el motor de ejecución está funcionando, todas las instrucciones solo pueden operar en el marco de pila actual.

tabla de variables locales

La tabla de variables locales es un área para almacenar parámetros de métodos y variables locales . Las variables locales no tienen fase de preparación y deben inicializarse explícitamente. Las variables globales se colocan en el montón y hay dos etapas de asignación, una está en la etapa de preparación de la carga de clases y se asigna el valor inicial del sistema; la otra está en la etapa de inicialización de la carga de clases y el valor inicial se asigna el valor definido por el código.

pila de operaciones

La pila de operaciones es una pila de estructura de cubo (primero en entrar, último en salir) con un estado inicial de vacío. Cuando un método recién comienza a ejecutarse, la pila de operandos de este método está vacía.Durante la ejecución del método, varias instrucciones de código de bytes escribirán y extraerán contenido de la pila de operandos, es decir, la operación pop/push.

enlace dinámico

Cada marco de pila contiene una referencia al método actual en el grupo de constantes de tiempo de ejecución, el propósito es admitir la conexión dinámica del proceso de llamada al método. Si es necesario llamar a otros métodos en el método actual, la referencia de símbolo correspondiente se puede encontrar en el conjunto de constantes de tiempo de ejecución, y luego la referencia de símbolo se puede convertir en una referencia directa, y luego se puede llamar directamente al método correspondiente.

No es necesario vincular dinámicamente todas las llamadas a métodos. Algunas referencias simbólicas se convertirán 类加载解析阶段en referencias directas. Esta parte de la operación se llama: 静态解析, lo que significa que la versión de la llamada se puede determinar durante la compilación, lo que incluye: llamar a métodos estáticos, llamar constructor privado de instancias, método privado, método de superclase.

dirección de retorno del método

Hay dos situaciones de salida cuando se ejecuta el método:

  1. Salida normal, es decir, ejecución normal para devolver instrucciones bytecode de cualquier método, como RETURN, IRETURN, ARETURN, etc.;
  2. Salida anormal.

Independientemente de la condición de salida, vuelve al punto en el que se llamó actualmente al método. El proceso de salida del método es equivalente a abrir el marco de pila actual.

montón

¡El 99% de los ajustes de GC/JVM de los que hablamos a menudo se refieren a ambos 调堆! Las pilas de Java, las pilas de métodos nativos, los contadores de programas, etc. generalmente no generan basura.

Montón : la pieza de memoria más grande administrada por la máquina virtual Java. El montón de Java es un área de memoria compartida por todos los subprocesos y creada cuando se inicia la máquina virtual. El único propósito de esta área de memoria es almacenar instancias de objetos y casi todas las instancias de objetos asignan memoria aquí.

El montón es el área principal administrada por el recolector de elementos no utilizados, por lo que a menudo se denomina "montón GC" (Garbage Collected Heap).

metaespacio

JDK 1.8 cambió el área de método a metaspace. La metainformación de la clase se almacena en el metaespacio, que no utiliza la memoria del montón, sino un área de memoria local que no está conectada al montón . Por lo tanto, teóricamente, cuanta memoria pueda usar el sistema, hay tanto metaespacio como sea posible.

área de método

Área de método : al igual que el montón de Java, es un área de memoria compartida por cada subproceso Se utiliza para almacenar datos como información de clase, constantes, variables estáticas y código compilado por el compilador justo a tiempo que ha sido cargado por la maquina virtual

Aunque la especificación de la máquina virtual de Java describe el área del método como una parte lógica del montón, tiene un alias llamado Non-Heap (non-heap), que debe distinguirse del montón de Java.

Cambios en Method Area y Metaspace

La siguiente figura muestra el proceso de transición general del área de métodos de JDK 1.6, JDK 1.7 a JDK 1.8:

En JDK 1.8, HotSpot JVM salió de la generación permanente (PermGen) y usó el metaespacio (Metaspace) al principio. Las principales razones para usar Metaspace en lugar de la implementación de generación permanente son las siguientes:

  1. Evite las excepciones OOM, las cadenas existen en la generación permanente, propensas a problemas de rendimiento y desbordamiento de memoria;
  2. Es difícil determinar el tamaño del espacio de configuración de generación permanente.Si es demasiado pequeño, se producirá fácilmente un desbordamiento de generación permanente, y si es demasiado grande, conducirá fácilmente a un desbordamiento de generación anterior;
  3. Es muy difícil sintonizar la generación permanente;
  4. Combine HotSpot y JRockit en uno;

2. Modelo de memoria

El modelo de memoria es para garantizar la corrección (visibilidad, orden y atomicidad) de la memoria compartida.El modelo de memoria define la especificación del comportamiento de operación de lectura y escritura de programas de subprocesos múltiples en el sistema de memoria compartida.

El modelo de memoria resuelve los problemas de concurrencia de dos maneras principales: limitando la optimización del procesador y utilizando barreras de memoria .

El modelo de memoria de Java (JMM) controla la comunicación entre los subprocesos de Java y determina cuándo las escrituras de un subproceso en las variables compartidas son visibles para otro subproceso.

Caché de computadora y coherencia de caché

Las computadoras usan la memoria caché entre una CPU de alta velocidad y un dispositivo de almacenamiento relativamente lento como un búfer entre la memoria y el procesador. Cuando el programa se está ejecutando, copiará los datos requeridos para la operación desde la memoria principal (la memoria física de la computadora) al caché de la CPU, luego la CPU puede leer directamente los datos de su caché y escribir en ellos cuando realizando calculos Los datos se escriben en él, y una vez que se completa la operación, los datos en el caché se actualizan a la memoria principal.

En un sistema multiprocesador (o un sistema multinúcleo de un solo procesador), cada núcleo del procesador tiene su propio caché y comparten la misma memoria principal (memoria principal). Cuando la CPU quiere leer un dato, primero lo busca desde el caché de primer nivel, si no lo encuentra, lo busca desde el caché de segundo nivel, si aún no está allí, lo busca desde el caché de tercer nivel ( ) 不是所有 CPU 都有三级缓存o memoria.

En una CPU multinúcleo, cada núcleo está en su propio caché y el contenido del caché para los mismos datos puede ser inconsistente. Con este fin, cada procesador debe seguir algunos protocolos al acceder al caché y operar de acuerdo con el protocolo al leer y escribir para mantener la consistencia del caché.

Memoria principal y memoria de trabajo de JVM

El modelo de memoria de Java estipula que todas las variables se almacenan en la memoria principal 主内存, y cada subproceso tiene el suyo 工作内存. Todas las operaciones del subproceso sobre las variables deben realizarse en la memoria de trabajo, y las variables en la memoria principal no se pueden leer ni escribir directamente. .

La memoria de trabajo aquí es un concepto abstracto de JMM, que almacena la memoria de subprocesos 读 / 写共享变量的副本.

reordenar

Para mejorar el rendimiento al ejecutar un programa, los compiladores y procesadores suelen reordenar las instrucciones. Hay tres tipos de reordenamiento:

  1. Reordenación optimizada del compilador : el compilador puede reorganizar el orden de ejecución de las declaraciones sin cambiar la semántica de los programas de un solo subproceso.
  2. Reordenación del paralelismo a nivel de instrucción : los procesadores modernos utilizan el paralelismo a nivel de instrucción (Paralelismo a nivel de instrucción, ILP) para superponer y ejecutar múltiples instrucciones. Si no hay dependencia de datos, el procesador puede cambiar el orden de ejecución de las instrucciones de máquina correspondientes del enunciado.
  3. Reordenación del sistema de memoria : debido al uso de cachés y búferes de lectura/escritura por parte del procesador, es posible que las operaciones de carga y almacenamiento parezcan realizarse fuera de orden.

Desde el código fuente de Java hasta la secuencia final de ejecución real de las instrucciones, se realizarán los siguientes tres reordenamientos, respectivamente:

JMM es un modelo de memoria a nivel de lenguaje que garantiza una visibilidad de memoria consistente para los programadores al prohibir tipos específicos de reordenación de compiladores y reordenaciones de procesadores en diferentes compiladores y plataformas de procesadores.

La prohibición del compilador de Java de reordenar el procesador se 在生成指令序列的适当位置会插入内存屏障logra a través de instrucciones (al reordenar, las siguientes instrucciones no se pueden reordenar a la posición anterior a la barrera de la memoria).

sucede-antes

El modelo de memoria de Java propone el concepto de happening-before, a través del cual se explica la visibilidad de la memoria entre operaciones. La "visibilidad" aquí significa que cuando un subproceso modifica el valor de esta variable, el nuevo valor puede ser conocido inmediatamente por otros subprocesos.

Si el resultado de la ejecución de una operación debe ser visible para otra operación, entonces debe haber una relación antes de que suceda entre las dos operaciones. Las dos operaciones mencionadas aquí pueden estar dentro de un hilo o entre diferentes hilos.

Implementación del modelo de memoria de Java

En Java se proporcionan una serie de palabras clave relacionadas con el procesamiento concurrente, como volatile, synchronized, final, JUC 包etc. De hecho, estas son algunas palabras clave proporcionadas a los programadores después de que el modelo de memoria de Java encapsula la implementación subyacente.

atomicidad

Para garantizar la atomicidad, dos instrucciones de código de bytes de alto nivel monitorentery monitorexitestos dos códigos de bytes son las palabras clave correspondientes en Java synchronized.

Todos estamos synchronizedfamiliarizados con las palabras clave, puede compilar el siguiente código en un archivo de clase, usarlo para javap -v SyncViewByteCode.classver el código de bytes, puede encontrar instrucciones de código de bytes monitorenter.monitorexit

public class SyncViewByteCode {
    
    
  public synchronized void buy() {
    
    
    System.out.println("buy porsche");
  }
}

Bytecode, algunos resultados son los siguientes:

 public com.dolphin.thread.locks.SyncViewByteCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/dolphin/thread/locks/SyncViewByteCode;

  public void test2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
      Exception table:

visibilidad

El modelo de memoria de Java se implementa 变量修改后basándose en la memoria principal 变量读取前como medio de transmisión para sincronizar nuevos valores con la memoria principal y actualizar los valores variables de la memoria principal.

La palabra clave en Java volatileproporciona una función, es decir, la variable modificada por ella se puede sincronizar con la memoria principal inmediatamente después de ser modificada, y la variable modificada por ella se actualiza desde la memoria principal antes de cada uso. Por lo tanto, puede utilizar volatilepara garantizar la visibilidad de las variables en operaciones de subprocesos múltiples.

Además , las palabras clave y dos volatileen Java también pueden lograr visibilidad. Es solo que la implementación es diferente.synchronizedfinal

orden

En Java, puede usar synchronizedy volatilepara garantizar el orden de las operaciones entre varios subprocesos. La implementación es diferente:

  • volatile: La palabra clave prohíbe el reordenamiento de instrucciones.
  • synchronized: La palabra clave garantiza que solo un subproceso puede operar al mismo tiempo.

2. ¿Qué es un sistema distribuido?

Un sistema distribuido es un sistema de red compuesto por varias computadoras que trabajan juntas mediante el paso de mensajes o el almacenamiento compartido para lograr un objetivo común. El objetivo de diseño de un sistema distribuido es distribuir la computación y los datos entre varios nodos para proporcionar mayor rendimiento, escalabilidad y confiabilidad.

En un sistema distribuido, cada nodo puede realizar tareas de forma independiente y comunicarse y coordinarse entre sí a través de protocolos de comunicación. Estos nodos pueden ser equipos distribuidos físicamente en distintas ubicaciones geográficas, o máquinas virtuales, contenedores o instancias de servicios en la nube.

Las características de un sistema distribuido incluyen:

  1. Procesamiento en paralelo : los sistemas distribuidos pueden procesar múltiples tareas al mismo tiempo y mejorar la capacidad de procesamiento del sistema al asignar trabajo a diferentes nodos para la ejecución en paralelo.
  2. Escalabilidad : El sistema distribuido puede aumentar o disminuir nodos según la demanda para adaptarse a cambios de diferentes escalas y cargas. Al agregar más nodos, el sistema puede manejar más solicitudes y brindar un mayor rendimiento.
  3. Tolerancia a fallas : los sistemas distribuidos pueden mejorar la confiabilidad del sistema y la tolerancia a fallas a través de mecanismos de respaldo y redundancia. Cuando falla un nodo, el sistema puede continuar ejecutándose y otros nodos pueden hacerse cargo del trabajo del nodo fallido.
  4. Intercambio y coordinación de datos : los sistemas distribuidos realizan el intercambio y la coordinación de datos entre nodos a través del almacenamiento compartido o el paso de mensajes. Esto permite que diferentes nodos compartan datos, estado y recursos, y colaboren para completar tareas complejas.

El diseño y la gestión de los sistemas distribuidos deben tener en cuenta los desafíos de la comunicación de red, la consistencia, el control de concurrencia, el manejo de fallas, etc. Al mismo tiempo, los sistemas distribuidos también brindan una mayor flexibilidad y confiabilidad, y se usan ampliamente en varios campos, como la computación en la nube, el procesamiento de big data y el Internet de las cosas.

3. ¿Qué aspectos consideraría en un sistema distribuido?

Al diseñar y administrar un sistema distribuido, se deben considerar los siguientes aspectos:

  1. Fiabilidad y tolerancia a fallos : los sistemas distribuidos deben ser tolerantes a fallos y poder continuar con el funcionamiento normal en caso de fallo de un nodo o interrupción de la red, etc. Esto se puede lograr a través de copias de seguridad redundantes, detección de fallas y mecanismos de recuperación automática.
  2. Escalabilidad : Los sistemas distribuidos deben tener una buena escalabilidad, y pueden aumentar o disminuir nodos según la demanda para adaptarse a cambios en diferentes escalas y cargas. Esto se puede lograr a través de la escala horizontal y la escala vertical.
  3. Coherencia de los datos : en un sistema distribuido, dado que los datos se distribuyen en varios nodos, es necesario garantizar la coherencia de los datos. Esto se puede lograr a través de protocolos de consenso (como Paxos, Raft) y mecanismos de sincronización de réplica.
  4. Comunicación y protocolo : los nodos en un sistema distribuido necesitan comunicarse y coordinar el trabajo, por lo que es necesario elegir un protocolo de comunicación y un mecanismo de entrega de mensajes adecuados. Los protocolos de comunicación comunes incluyen TCP/IP, HTTP, RPC, etc.
  5. Equilibrio de carga : para mejorar el rendimiento y la disponibilidad del sistema, es necesario distribuir la carga de manera uniforme entre los nodos. El equilibrio de carga se puede lograr a través de algoritmos de programación de solicitudes y estrategias de equilibrio de carga dinámicas.
  6. Seguridad : los sistemas distribuidos deben proteger la confidencialidad, la integridad y la disponibilidad de los datos, por lo que se deben considerar medidas de seguridad como la autenticación, el cifrado de datos, el control de acceso, etc.
  7. Monitoreo y diagnóstico : el sistema distribuido debe tener funciones de monitoreo y diagnóstico, que pueden monitorear el estado operativo y los indicadores de rendimiento del sistema en tiempo real, y encontrar y resolver problemas a tiempo.
  8. Implementación y gestión : la implementación y la gestión de sistemas distribuidos deben tener en cuenta cuestiones como la configuración de nodos, la instalación y actualización de software y el control de versiones. Las herramientas de administración e implementación automatizadas pueden mejorar la eficiencia y la confiabilidad.
  9. Copia de seguridad y recuperación de datos : para hacer frente a la falla del nodo o la pérdida de datos, se requieren copias de seguridad y recuperación de datos. Las estrategias comunes de copia de seguridad incluyen copia de seguridad en frío, copia de seguridad en caliente y copia de seguridad incremental.
  10. Optimización del rendimiento : la optimización del rendimiento de los sistemas distribuidos involucra varios aspectos, incluida la optimización de algoritmos, el diseño de la estructura de datos, el control de concurrencia, la estrategia de almacenamiento en caché, etc.

En resumen, diseñar y administrar sistemas distribuidos requiere una consideración integral de confiabilidad, escalabilidad, consistencia de datos, comunicación y protocolos, equilibrio de carga, seguridad, monitoreo y diagnóstico, implementación y administración, copia de seguridad y recuperación de datos, optimización del rendimiento y otros temas.

4. ¿Por qué se dice que el protocolo TCP/IP no es fiable?

El protocolo TCP/IP se considera un protocolo confiable porque proporciona muchos mecanismos para garantizar una transmisión confiable de datos.

Sin embargo, a veces la gente dice que el protocolo TCP/IP no es confiable porque puede no cumplir con los requisitos del usuario o puede tener algunos problemas en ciertas circunstancias.

Estas son algunas situaciones que pueden hacer que el protocolo TCP/IP se considere no confiable:

  1. Pérdida de paquetes : durante la transmisión de la red, los paquetes de datos pueden perderse debido a la congestión de la red, la falla del equipo u otras razones. Aunque el protocolo TCP tiene un mecanismo de retransmisión, en algunos casos, es posible que los paquetes de datos perdidos no se retransmitan a tiempo, lo que da como resultado una transmisión de datos poco fiable.
  2. Latencia : el protocolo TCP/IP utiliza mecanismos de control de congestión para garantizar la estabilidad y equidad de la red. Esto significa que las transferencias de datos pueden retrasarse durante los momentos de congestión de la red. Esta demora puede considerarse poco confiable para ciertas aplicaciones en tiempo real, como los juegos en línea o las videollamadas.
  3. Problema de secuencia : el protocolo TCP garantiza la transmisión en orden de los paquetes de datos, pero en algunos casos, el orden de los paquetes de datos puede verse interrumpido. Por ejemplo, cuando los paquetes de datos viajan por diferentes caminos en una red, pueden llegar a su destino en un orden diferente. Esto puede dar lugar a problemas de reensamblaje y secuenciación de los paquetes de datos, lo que afecta a la fiabilidad de los datos.

Cabe señalar que, aunque el protocolo TCP/IP puede resultar problemático en algunos casos, sigue siendo uno de los protocolos más utilizados en Internet y se usa ampliamente en diversas aplicaciones y servicios. Además, el protocolo TCP/IP también puede configurarse y optimizarse para mejorar su fiabilidad y rendimiento.

5. ¿Cuáles son las siete capas del modelo OSI? ¿Qué modelo de cuatro capas es TCP/IP?

1. ¿Qué es OSI?

El modelo OSI (Open System Interconnection model) es un modelo conceptual propuesto por la Organización Internacional de Normalización, que intenta proporcionar un marco estándar para interconectar varias computadoras y redes en todo el mundo.
Divide la arquitectura de la red informática en siete capas, y cada capa puede proporcionar una interfaz bien resumida.

Se puede dividir en siete capas de arriba a abajo: cada capa realiza funciones específicas, proporciona servicios para la capa superior y utiliza los servicios proporcionados por la capa inferior.

  • Capa física :
    La capa física se encarga finalmente de codificar la información en pulsos de corriente u otras señales para transmisión en línea,
    por ejemplo: RJ45, etc., convertir datos en 0 y 1;
  • Capa de enlace de datos :
    la capa de enlace de datos proporciona transmisión de datos a través de enlaces de red física. Las diferentes capas de enlace de datos definen diferentes características de red y protocolo, incluido el direccionamiento físico, la topología de red, la verificación de errores, la secuencia de tramas de datos y el control de flujo; se puede entender simplemente como: especificar 0 y 1 forma de paquetización, que determina la forma de la
    red paquete;
  • Capa de red :
    La capa de red es la encargada de establecer una conexión entre el origen y el destino,
    se puede entender que aquí se necesita determinar la ubicación de la computadora, ¿cómo determinarla? ¡IPv4, IPv6!
  • Capa de transporte :
    la capa de transporte proporciona servicios confiables de flujo de datos de red de extremo a extremo a las capas superiores.
    Puede entenderse como: cada aplicación registrará un número de puerto en la tarjeta de red, ¡y esta capa es la comunicación entre puertos! Protocolos de uso común (TCP/IP);
  • Capa de sesión :
    La capa de sesión establece, administra y finaliza la sesión de comunicación entre la capa de presentación y la entidad,
    establece una conexión (información automática de teléfonos móviles, direccionamiento automático de red);
  • Capa de presentación :
    la capa de presentación proporciona una variedad de funciones para la codificación y conversión de datos de la capa de aplicación para garantizar que la información enviada por una capa de aplicación del sistema pueda ser reconocida por otra capa de aplicación del sistema; puede entenderse como: resolver la comunicación entre diferentes sistemas, por
    ejemplo : QQ bajo Linux y QQ bajo Windows pueden comunicarse;
  • Capa de aplicación :
    el protocolo de capa de aplicación de OSI incluye el protocolo de transferencia, acceso y gestión de archivos (FTAM), el protocolo de terminal virtual de archivos (VIP) y la información del sistema de gestión pública (CMIP), etc.; especifica el protocolo de transmisión de datos
    ;

2. ¿Qué es el modelo de cuatro capas de TCP/IP?

Capa de aplicación (Aplicación): proporciona a los usuarios varios servicios que necesitan

Capa de transporte (Transport): proporciona funciones de comunicación de extremo a extremo para las entidades de la capa de aplicación, lo que garantiza la transmisión secuencial de los paquetes de datos y la integridad de los datos.

Capa de Internet (Internet): resuelve principalmente el problema de comunicación de host a host

Capa de interfaz de red (Acceso a la red): responsable de monitorear el intercambio de datos entre el host y la red

3. La relación entre el modelo de red de siete capas OSI y el modelo de red de cuatro capas TCP/IP:

OSI introduce los conceptos de servicio, interfaz, protocolo y capas, y TCP/IP extrae lecciones de estos conceptos de OSI para establecer el modelo TCP/IP.

OSI primero tiene modelos, luego protocolos, y primero estándares, y luego prácticas; por el contrario, TCP/IP primero tiene protocolos y aplicaciones, y luego propone modelos, que son los modelos OSI de referencia.

OSI es un modelo teórico, mientras que TCP/IP se ha utilizado ampliamente y se ha convertido en el estándar de facto para la interconexión de redes.

Modelo de red OSI de siete capas Modelo conceptual de cuatro capas de TCP/IP Protocolo de red correspondiente
Capa de aplicación (Aplicación) capa de aplicación HTTP, TFTP, FTP, NFS, WAIS, SMTP
Presentación Telnet, Rlogin, SNMP, Gopher
Capa de sesión (Sesión) SMTP, DNS
Capa de transporte (Transporte) capa de transporte TCP, UDP
Capa de red (Red) Capa de red IP, ICMP, ARP, RARP, AKP, UUCP
Capa de enlace de datos (enlace de datos) Capa de enlace de datos FDDI, Ethernet, Arpanet, PDN, DESLIZAMIENTO, PPP
Capa fisica IEEE 802.1A, IEEE 802.2 a IEEE 802.11

4. La diferencia entre OSI de siete capas y TCP/IP

  • TCP/IP es un grupo de protocolos; OSI (Interconexión de sistemas abiertos) es un modelo, y TCP/IP se desarrolló antes que OSI.
  • TCP/IP es un protocolo en capas hecho de algunos módulos interactivos, cada uno de los cuales proporciona funciones específicas; OSI especifica qué funciones pertenecen a qué capa.
  • TCP/IP es una estructura de cuatro capas, mientras que OSI es una estructura de siete capas. Las tres capas superiores de OSI están representadas por la capa de aplicación en TCP.

El arquitecto Nien, de 40 años, recordó :

TCP/IP no es solo el enfoque absoluto de la entrevista, sino también la dificultad absoluta de la entrevista. Se recomienda que tenga una comprensión profunda y detallada. TCP/IP tiene una introducción sistemática, sistemática y completa.

6. Hable sobre el protocolo de enlace de tres vías y el proceso de onda de cuatro vías del protocolo TCP

La esencia del protocolo de enlace de tres vías y la onda de cuatro vías de TCP es la conexión y desconexión de la comunicación TCP.

Apretón de manos de tres vías: para rastrear y negociar la cantidad de datos enviados cada vez, asegúrese de que el envío y la recepción de segmentos de datos estén sincronizados, confirme el envío de datos de acuerdo con la cantidad de datos recibidos, cuándo cancelar la conexión después de recibir, y establecer una conexión virtual.

Ola cuatro veces: termine la conexión TCP, lo que significa que cuando se desconecta una conexión TCP, el cliente y el servidor deben enviar un total de 4 paquetes para confirmar la desconexión de la conexión.

Diagrama de secuencia del protocolo de enlace de tres vías TCP y onda de cuatro vías

Uno, tres apretones de manos

El protocolo TCP se encuentra en la capa de transporte y su función es proporcionar servicios de flujo de bytes confiables.Para entregar datos con precisión al destino, el protocolo TCP adopta una estrategia de negociación de tres vías.

Principio de apretón de manos de tres vías:

El primer apretón de manos : el cliente envía un paquete con el indicador SYN (sincronizar) al servidor;

El segundo apretón de manos : después de que el servidor lo recibe con éxito, devuelve un paquete con el indicador SYN/ACK para entregar información de confirmación, indicando que lo he recibido;

El tercer apretón de manos : el cliente devuelve un paquete de datos con el indicador ACK, lo que indica que lo sé, y finaliza el apretón de manos.

Entre ellos: el indicador SYN se establece en 1, lo que significa que se ha establecido la conexión TCP; el indicador ACK significa el campo de verificación.

El apretón de manos de tres vías se puede entender a través del siguiente diagrama interesante:

Descripción detallada del proceso de apretón de manos de tres vías:

  1. El cliente envía un mensaje de solicitud para establecer una conexión TCP, que contiene un número de secuencia secuencial, que el remitente genera aleatoriamente, y establece el campo SYN en el mensaje en 1, lo que indica que es necesario establecer una conexión TCP. (SYN=1, seq=x, x es un valor generado aleatoriamente);
  2. El servidor responde al mensaje de solicitud de conexión TCP enviado por el cliente, que contiene el número de secuencia secuencial, que se genera aleatoriamente al final de la respuesta, establece SYN en 1 y genera un campo ACK, cuyo valor se envía desde el cliente Agregue 1 al número de secuencia seq para responder, de modo que cuando el cliente reciba la información, sepa que su solicitud de establecimiento de TCP ha sido verificada. (SYN=1, ACK=x+1, seq=y, y es un valor generado aleatoriamente) El acuse de recibo más 1 aquí puede entenderse como una confirmación de con quién establecer una conexión;
  3. Después de recibir la solicitud de verificación de establecimiento de TCP enviada por el servidor, el cliente agregará 1 a su número de serie, responderá nuevamente a la solicitud de verificación ACK y agregará 1 a la secuencia enviada por el servidor para responder. (SYN=1, ACK=y+1, sec=x+1).

Dos o cuatro olas

Dado que las conexiones TCP son full-duplex, cada dirección debe cerrarse por separado. El principio es que cuando una de las partes completa su tarea de envío de datos, puede enviar un FIN para terminar la conexión en esta dirección. Recibir un FIN solo significa que no hay flujo de datos en esta dirección, y una conexión TCP aún puede enviar datos después de recibir un FIN. La parte que cierra primero realizará un cierre activo, mientras que la otra parte realizará un cierre pasivo.

El principio de las cuatro ondas:

La primera ola : el cliente envía un FIN para cerrar la transmisión de datos del cliente al servidor, y el cliente ingresa al estado FIN_WAIT_1;

La segunda ola : después de recibir el FIN, el servidor envía un ACK al cliente, confirmando que el número de secuencia es el número de secuencia recibido + 1 (igual que SYN, un FIN ocupa un número de secuencia), y el servidor ingresa al estado CLOSE_WAIT ;

La tercera ola : el servidor envía un FIN para cerrar la transmisión de datos del servidor al cliente, y el servidor ingresa al estado LAST_ACK;

La cuarta ola : después de que el cliente recibe el FIN, el cliente t ingresa al estado TIME_WAIT y luego envía un ACK al servidor, confirmando que el número de serie es el número de serie recibido + 1, y el servidor ingresa al estado CERRADO, y completa cuatro manos agitadas.

Entre ellos: el indicador FIN se establece en 1, lo que significa desconectar la conexión TCP.

Las Cuatro Olas se pueden entender con la siguiente ilustración divertida:

El proceso de onda de cuatro vías se detalla:

  1. El cliente envía un paquete solicitando desconectar la conexión TCP, en el que se incluye el número de secuencia en el paquete, que el remitente genera aleatoriamente, y el campo FIN en el paquete se establece en 1, lo que indica que la conexión TCP debe estar desconectado. (FIN=1, seq=x, x es generado aleatoriamente por el cliente);
  2. El servidor responderá al mensaje de solicitud de desconexión de TCP enviado por el cliente, que contiene el número de secuencia secuencial, que se genera aleatoriamente al final de la respuesta, y generará un campo ACK.El valor del campo ACK se basa en la secuencia secuencial. número enviado por el cliente, agregue 1 para responder, de modo que cuando el cliente reciba la información, sepa que su solicitud de desconexión de TCP ha sido verificada. (FIN=1, ACK=x+1, seq=y, y es generado aleatoriamente por el servidor);
  3. Después de que el servidor responda a la solicitud de desconexión TCP del cliente, no desconectará la conexión TCP inmediatamente. El servidor primero se asegurará de que todos los datos transmitidos a A hayan sido transmitidos antes de la desconexión. Una vez que se confirme la transmisión de datos, lo hará. El campo FIN del mensaje de respuesta se establecerá en 1 y se generará un número de secuencia de secuencia aleatoria. (FIN=1, ACK=x+1, seq=z, z es generado aleatoriamente por el servidor);
  4. Después de recibir la solicitud de desconexión de TCP del servidor, el cliente responderá a la solicitud de desconexión del servidor, incluido un campo de secuencia generado aleatoriamente y un campo ACK. El campo ACK agregará 1 a la secuencia de la solicitud de desconexión de TCP del servidor. para completar el servicio La respuesta de autenticación solicitada por el cliente. (FIN=1, ACK=z+1, seq=h, h es generado aleatoriamente por el cliente)
    En este punto, se completa el proceso de 4 ondas de desconexión de TCP.

Tres, análisis de sustantivo estatal

LISTEN:等待从任何远端TCP 和端口的连接请求。
 
SYN_SENT:发送完一个连接请求后等待一个匹配的连接请求。
 
SYN_RECEIVED:发送连接请求并且接收到匹配的连接请求以后等待连接请求确认。
 
ESTABLISHED:表示一个打开的连接,接收到的数据可以被投递给用户。连接的数据传输阶段的正常状态。
 
FIN_WAIT_1:等待远端TCP 的连接终止请求,或者等待之前发送的连接终止请求的确认。
 
FIN_WAIT_2:等待远端TCP 的连接终止请求。
 
CLOSE_WAIT:等待本地用户的连接终止请求。
 
CLOSING:等待远端TCP 的连接终止请求确认。
 
LAST_ACK:等待先前发送给远端TCP 的连接终止请求的确认(包括它字节的连接终止请求的确认)
 
TIME_WAIT:等待足够的时间过去以确保远端TCP 接收到它的连接终止请求的确认。
TIME_WAIT 两个存在的理由:
          1.可靠的实现tcp全双工连接的终止;
          2.允许老的重复分节在网络中消逝。
 
CLOSED:不在连接状态(这是为方便描述假想的状态,实际不存在)

7. ¿Por qué TCP establece un protocolo de conexión con un protocolo de enlace de tres vías, pero cierra la conexión con un protocolo de enlace de cuatro vías? ¿Por qué no puedo conectarme con dos apretones de manos?

TCP utiliza un protocolo de enlace de tres vías para establecer una conexión y utiliza un protocolo de enlace de cuatro vías para cerrar la conexión, principalmente para garantizar la sincronización del estado y la confiabilidad de las partes que se comunican. Aquí hay una explicación detallada:

1. ¿Por qué se requiere un apretón de manos de tres vías para establecer una conexión?

  • El primer protocolo de enlace : el cliente envía un paquete con el indicador SYN (síncrono) al servidor, solicita establecer una conexión y entra en el estado SYN_SENT.
  • El segundo protocolo de enlace : después de recibir la solicitud del cliente, el servidor responde con un paquete de datos con un indicador SYN/ACK (sincronización/confirmación), lo que indica que acepta establecer una conexión y entra en el estado SYN_RCVD.
  • El tercer apretón de manos : después de recibir la respuesta del servidor, el cliente envía un paquete de datos con un indicador ACK (confirmación), lo que indica que la conexión se estableció con éxito, y las dos partes pueden iniciar la comunicación, y tanto el cliente como el servidor ingresan el estado ESTABLECIDO.

El propósito del protocolo de enlace de tres vías es garantizar que ambas partes puedan recibir mensajes de confirmación entre sí para establecer una conexión confiable. Si solo hay dos apretones de manos, entonces son posibles los siguientes escenarios:

  • El cliente envía una solicitud de conexión, pero debido a demoras en la red y otras razones, la solicitud se pierde durante la transmisión y el servidor no puede conocer la solicitud del cliente.
  • Después de que el servidor recibe la solicitud de conexión del cliente, envía una confirmación, pero debido a demoras en la red y otras razones, la confirmación se pierde durante la transmisión y el cliente no puede conocer la confirmación del servidor.

Si solo hay dos apretones de manos, ni el cliente ni el servidor pueden confirmar si la otra parte ha recibido su propia solicitud o confirmación en los casos anteriores, por lo que no se puede establecer una conexión confiable. Por lo tanto, el protocolo de enlace de tres vías puede garantizar que ambas partes puedan confirmar el establecimiento de la conexión.

2. ¿Por qué cerrar una conexión requiere un apretón de manos de cuatro vías?

  • El primer apretón de manos : cuando una de las partes decide cerrar la conexión, envía un paquete de datos con un indicador FIN (fin), lo que indica que no se enviarán más datos, pero aún se pueden recibir datos y entrar en el estado FIN_WAIT_1.
  • El segundo protocolo de enlace : después de que la otra parte recibe el FIN, envía un paquete de datos con el indicador ACK como confirmación, lo que indica que ha recibido la solicitud de cierre y entra en el estado CLOSE_WAIT.
  • El tercer protocolo de enlace : la otra parte envía un paquete con la bandera FIN, acordando cerrar la conexión e ingresar al estado LAST_ACK.
  • El cuarto protocolo de enlace : después de recibir la confirmación, la parte que solicita cerrar la conexión envía un paquete de datos con el indicador ACK, lo que indica que la conexión está cerrada y entra en el estado TIME_WAIT.

El propósito del protocolo de enlace de cuatro vías es garantizar que ambas partes puedan completar la transmisión y confirmación de datos para evitar la pérdida o confusión de datos. Al cerrar una conexión, ambas partes deben intercambiar mensajes de confirmación para asegurarse de que la otra parte sepa que la conexión está cerrada y que no se enviarán más datos.

Si solo hay un apretón de manos de tres vías, pueden ocurrir las siguientes situaciones:

  • Una de las partes envió una solicitud de cierre, pero la otra parte no la recibió, lo que provocó que la conexión permaneciera en un estado semicerrado.
  • Después de que una parte envía una solicitud de cierre, la otra parte cierra directamente la conexión sin esperar a que se complete la transmisión de datos, lo que provoca la pérdida de datos.

A través del apretón de manos de cuatro vías, puede garantizar que ambas partes puedan completar la transmisión y confirmación de datos, para cerrar la conexión de manera segura.

Cabe señalar que el último apretón de manos (cuarto) en el apretón de manos de cuatro vías es para garantizar el cierre confiable de la conexión y esperar la llegada de paquetes de datos potencialmente retrasados ​​​​durante un período de tiempo después del cierre para evitar problemas en la conexión multiplexación

Nien, un arquitecto de 40 años, recordó : TCP/IP no es solo el enfoque absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para contenido específico, consulte el PDF de "Colección de entrevistas de Nin Java - Tema 10: Protocolo TCP/IP". Este tema tiene una introducción sistemática, sistemática y completa a TCP/IP.

8. En el encabezado de la solicitud http, el significado de los campos de caducidad y control de caché, habla sobre el código de estado HTTP

En las solicitudes HTTP, los encabezados Expiresy Cache-Controllos campos se utilizan para controlar el comportamiento de la memoria caché.

ExpiresEl campo especifica un tiempo de caducidad absoluto después del cual la copia en caché se considerará obsoleta. Cuando el servidor devuelva una respuesta, incluirá Expiresun campo en el encabezado de la respuesta para informar al cliente del período de validez de la memoria caché. Después de que el cliente recibe la respuesta, la almacena en caché y usa la copia almacenada en caché hasta el momento de vencimiento. Sin embargo, Expiresexisten algunos problemas con los campos, como que los relojes del servidor y del cliente no estén sincronizados, lo que puede provocar la invalidación de la memoria caché.

Para resolver Expiresel problema de los campos, se introdujeron los campos Cache-Control. Cache-ControlLos campos proporcionan un mecanismo de control de caché más flexible y confiable. Puede contener varias directivas, separadas por comas, cada una con un significado y parámetros específicos. Los comandos comunes incluyen:

  • max-age=<seconds>: especifica el período máximo de validez de la memoria caché, en segundos.
  • no-cache: indica que la copia en caché debe revalidarse y no se puede usar directamente.
  • no-store: indica que no se almacena ninguna copia en caché y que es necesario volver a adquirir recursos para cada solicitud.
  • public: Indica que la respuesta puede ser almacenada por cualquier caché.
  • private: indica que la respuesta solo puede ser almacenada en caché por un solo usuario, generalmente para datos privados.

El código de estado HTTP se utiliza para indicar el estado de respuesta del servidor a la solicitud. Los códigos de estado HTTP comunes incluyen:

  • 1xx: Código de estado informativo, que indica que la solicitud ha sido recibida y el procesamiento continúa.
  • 2xx: código de estado de éxito, que indica que la solicitud se ha recibido, comprendido y procesado correctamente.
  • 3xx: código de estado de redirección, que indica que se requieren más acciones para completar la solicitud.
  • 4xx: código de estado de error del cliente, que indica que la solicitud contiene errores o no se puede completar.
  • 5xx: código de estado de error del servidor, que indica que se produjo un error cuando el servidor procesó la solicitud.

Algunos códigos de estado comunes incluyen:

  • 200 OK: la solicitud se realizó correctamente y el servidor la procesó correctamente.
  • 301 Movido permanentemente: redirigido permanentemente, el recurso solicitado se ha movido permanentemente a una nueva ubicación.
  • 400 Bad Request: La solicitud del cliente tiene un error de sintaxis que el servidor no puede entender.
  • 404 No encontrado: el recurso solicitado no existe.
  • 500 Error interno del servidor: el servidor tiene un error interno y no puede completar la solicitud.

Los códigos de estado proporcionan una forma estandarizada de permitir que los clientes y servidores comprendan con precisión los resultados del procesamiento de las solicitudes y tomen las medidas adecuadas.

9. Dime por qué Redis es rápido

La razón por la que Redis es rápido se debe principalmente a las siguientes razones:

  1. Almacenamiento en memoria : Redis almacena datos en la memoria, no en el disco, lo que acelera la lectura y escritura de datos. En comparación con las bases de datos de almacenamiento en disco tradicionales, como MySQL, Redis puede proporcionar una latencia de acceso más baja.
  2. Modelo de subproceso único : Redis utiliza un modelo de subproceso único para procesar las solicitudes de los clientes. Si bien esto puede sonar como un cuello de botella en el rendimiento, de hecho, este diseño permite a Redis evitar la sobrecarga de la competencia de bloqueos y el cambio de contexto entre varios subprocesos. Además, el modelo de subproceso único también simplifica la implementación y el mantenimiento de Redis.
  3. Estructura de datos eficiente : Redis admite una variedad de estructuras de datos, como cadenas, tablas hash, listas, conjuntos y conjuntos ordenados. Estas estructuras de datos están altamente optimizadas cuando se almacenan y manipulan datos, lo que permite que Redis realice de manera eficiente varias operaciones, como lectura, escritura, actualización y eliminación.
  4. Operación asíncrona : Redis admite la operación asíncrona, es decir, el cliente puede entregar algunas operaciones que consumen mucho tiempo al subproceso en segundo plano de Redis para su procesamiento sin esperar a que se complete la operación. Esto permite que Redis maneje mejor las solicitudes simultáneas y las situaciones de alta carga.
  5. Modelo de red : Redis utiliza un modelo de red basado en eventos para procesar solicitudes de red a través de E/S sin bloqueo y mecanismos de notificación de eventos. Este modelo permite que Redis maneje de manera eficiente una gran cantidad de conexiones y solicitudes simultáneas.

En general, Redis logra un alto rendimiento y una baja latencia a través de diversos medios técnicos, como el almacenamiento en memoria, el modelo de subproceso único, la estructura de datos eficiente, la operación asíncrona y el modelo de red optimizado. Esto convierte a Redis en una base de datos de almacenamiento y caché de clave-valor rápida y confiable.

Nien, un arquitecto de 40 años, recordó : Redis no es solo el foco absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para contenido específico, consulte el PDF "Colección de entrevistas de Nin Java - Tema 14: Preguntas de la entrevista de Redis". Este tema tiene una introducción sistemática, sistemática y completa a Redis .

Si desea incluir la práctica de alta simultaneidad de Redis en su currículum, puede pedirle orientación a Nien.

10. Redis tiene varios métodos de persistencia

Redis proporciona dos métodos de persistencia principales: RDB (Base de datos de Redis) y AOF (Archivo de solo agregar).

1. Persistencia de RDB:

La persistencia de RDB consiste en guardar datos de Redis en el disco en forma de archivos binarios. Lo hace mediante la realización periódica de operaciones de instantáneas, que se pueden activar de forma manual o automática en función de las reglas configuradas. En el proceso de persistencia de RDB, Redis guardará la instantánea de datos en la memoria actual en un archivo RDB y luego escribirá el archivo en el disco. Cuando se reinicia Redis, los datos se pueden recuperar cargando el archivo RDB.

La ventaja de la persistencia de RDB es que es rápida y compacta porque se realiza escribiendo directamente los datos de la memoria en el disco sin realizar operaciones de E/S adicionales. Es adecuado para la copia de seguridad de datos y la recuperación ante desastres.

2. Persistencia de AOF:

La persistencia de AOF se logra agregando comandos de escritura de Redis a un archivo de registro. Redis agregará cada comando de escritura al final del archivo AOF para registrar los cambios de datos. Cuando Redis se reinicia, los comandos del archivo AOF se volverán a ejecutar para restaurar los datos.

La ventaja de la persistencia de AOF es una mejor persistencia de datos, porque registra cada operación de escritura, lo que puede garantizar la integridad de los datos. Además, los archivos AOF se guardan en formato de texto, que es fácil de leer y comprender.

Hay dos modos de persistencia AOF: modo predeterminado y modo de reescritura. En el modo predeterminado, Redis agregará el comando de escritura al final del archivo AOF; en el modo de reescritura, Redis generará un nuevo archivo AOF basado en los datos de la memoria actual para reemplazar el archivo AOF antiguo. El modo de reescritura puede reducir el tamaño del archivo AOF y mejorar la eficiencia de lectura.

En general, la persistencia RDB es adecuada para copias de seguridad y recuperación de datos rápidas, mientras que la persistencia AOF es adecuada para escenarios con altos requisitos de integridad y persistencia de datos. Puede elegir el método de persistencia apropiado de acuerdo con las necesidades reales, o usar ambos métodos al mismo tiempo para brindar mejores capacidades de recuperación y protección de datos.

11. ¿Qué debo hacer si Redis cuelga?

Cuando Redis se bloquea, puede tomar las siguientes medidas para resolver el problema:

  1. Verifique el proceso de Redis : primero, asegúrese de que el proceso de Redis esté inactivo. Puede comprobar el estado del proceso de Redis mediante la línea de comandos o una herramienta administrativa.
  2. Verifique el archivo de registro : si Redis se bloquea, puede verificar el archivo de registro de Redis, generalmente el archivo redis-server.log, para obtener más mensajes de error y excepciones. Los archivos de registro pueden ayudarlo a comprender por qué Redis se bloquea.
  3. Reinicie Redis : si Redis se cuelga, puede intentar reiniciar Redis. Utilice la línea de comandos o las herramientas de administración para iniciar el proceso del servidor Redis.
  4. Restauración de datos : si Redis se bloquea y provoca la pérdida de datos, puede intentar restaurar los datos desde la copia de seguridad. Si utiliza el mecanismo de persistencia de Redis (como RDB o AOF), puede restaurar el archivo de copia de seguridad en el servidor de Redis.
  5. Verifique los recursos del servidor : si Redis se bloquea con frecuencia, debe verificar el uso de recursos del servidor, incluida la memoria, la CPU, el disco, etc. Asegúrese de que el servidor tenga suficientes recursos para admitir el funcionamiento normal de Redis.
  6. Optimizar la configuración : según sus necesidades y las condiciones de los recursos del servidor, puede considerar optimizar el archivo de configuración de Redis. Por ejemplo, aumente el límite máximo de memoria, ajuste el mecanismo de persistencia, ajuste los parámetros de red, etc.
  7. Monitoreo y alerta temprana : para evitar que Redis se cuelgue, puede usar herramientas de monitoreo para monitorear el estado de ejecución de Redis en tiempo real y configurar un mecanismo de alerta temprana para detectar y resolver problemas potenciales a tiempo.
  8. Busca soporte profesional : Si no puedes solucionar el problema que cuelga Redis, puedes buscar soporte técnico profesional o consulta, y ellos podrán ayudarte a analizar y solucionar el problema.

Tenga en cuenta que los pasos anteriores solo brindan una solución general, y los pasos de operación específicos pueden ser diferentes según su entorno y situación. Cuando se trata del problema del bloqueo de Redis, se recomienda consultar la documentación oficial de Redis y los recursos técnicos relacionados para obtener una guía más precisa y detallada.

12. En el caso de subprocesos múltiples, ¿cómo garantizar la seguridad de los subprocesos?

1. Nivel de seguridad del hilo

El tema de la "seguridad de subprocesos" se ha mencionado en blogs anteriores. Generalmente, a menudo decimos que cierta clase es segura para subprocesos y que cierta clase no es segura para subprocesos. De hecho, la seguridad de subprocesos no es una pregunta de opción única "en blanco y negro". De acuerdo con el grado de seguridad de "seguridad de subprocesos" de fuerte a débil, podemos dividir los datos compartidos por varias operaciones en el lenguaje Java en las siguientes cinco categorías: inmutable, seguridad absoluta de subprocesos, seguridad relativa de subprocesos, compatibilidad de subprocesos y oposición de subprocesos.

1. Inmutable

En el lenguaje Java, los objetos inmutables deben ser seguros para subprocesos, y ni la implementación del método del objeto ni la persona que llama al método necesitan tomar ninguna medida de seguridad de subprocesos. Por ejemplo, los datos modificados por la palabra clave final no se pueden modificar y tienen la mayor confiabilidad.

2. Seguridad absoluta del hilo

La seguridad absoluta de subprocesos satisface completamente la definición de seguridad de subprocesos dada por Brian GoetZ. Esta definición es realmente muy estricta. Por lo general, se necesita mucho esfuerzo para que una clase logre "independientemente del entorno de tiempo de ejecución, la persona que llama no necesita ninguna medida de sincronización adicional . "Gran precio.

3. Relativamente seguro para subprocesos

La seguridad relativa de subprocesos significa que una clase es "segura para subprocesos" en el sentido habitual.
Necesita asegurarse de que las operaciones individuales en este objeto sean seguras para subprocesos. No necesitamos tomar medidas de seguridad adicionales al llamar, pero para algunas llamadas continuas en un orden específico, puede ser necesario usar métodos de sincronización adicionales en el extremo que llama. para garantizar la corrección de la llamada.
En el lenguaje Java, la mayoría de las clases seguras para subprocesos son relativamente seguras para subprocesos, como la colección garantizada por el métodosynchronedCollection() de Vector, HashTable y Collections.

4. Compatibilidad de subprocesos

La compatibilidad de subprocesos es lo que solemos llamar una clase que no es segura para subprocesos.
La compatibilidad de subprocesos significa que el objeto en sí no es seguro para subprocesos, pero puede asegurarse de que el objeto se puede usar de manera segura en un entorno simultáneo mediante el uso correcto de métodos de sincronización en el lado de la llamada. La mayoría de las clases en la API de Java son compatibles con subprocesos. Como las clases de colección ArrayList y HashMap correspondientes a las anteriores Vector y HashTable.

5. Oposición de hilos

La oposición de subprocesos se refiere al código que no se puede usar simultáneamente en un entorno de subprocesos múltiples, independientemente de si la persona que llama ha tenido un error de sincronización. Dado que el lenguaje Java es inherentemente de subprocesos múltiples, los códigos que se oponen a los subprocesos múltiples rara vez aparecen.
Un ejemplo de oposición de subprocesos son los métodos suend() y resume() de la clase Thread. Si hay dos subprocesos que contienen un objeto de subproceso al mismo tiempo, uno intenta interrumpir el subproceso y el otro intenta reanudar el subproceso, si se realiza al mismo tiempo, sin importar si la llamada está sincronizada o no, el subproceso de destino tiene un riesgo de estancamiento. Como tal, estos dos métodos han quedado obsoletos.

En segundo lugar, el método de implementación de seguridad de subprocesos

Garantizar la seguridad de subprocesos se clasifica según si se requieren medios de sincronización y se puede dividir en esquemas de sincronización y esquemas de no sincronización.

1. Sincronización mutex

La sincronización de exclusión mutua es el medio más común para garantizar la corrección de la concurrencia. La sincronización significa que cuando varios subprocesos acceden a datos compartidos al mismo tiempo, se garantiza que los datos compartidos solo los utiliza un subproceso al mismo tiempo (al mismo tiempo, solo un subproceso está operando los datos compartidos). La exclusión mutua es un medio para lograr la sincronización, y las secciones críticas, los mutex y los semáforos son las principales formas de implementar la exclusión mutua. Por lo tanto, en estas cuatro palabras, la exclusión mutua es la causa y la sincronización es el efecto; la exclusión mutua es el método y la sincronización es el propósito.
En Java, el método de sincronización de exclusión mutua más básico es la palabra clave sincronizada. Después de compilar la palabra clave sincronizada, se formarán dos códigos de byte, monitorenter y monitorexit, antes y después del bloque de sincronización. Estas dos instrucciones de código de byte necesitan un parámetro de tipo de referencia para especificar el objeto para bloquear y desbloquear.
Además, ReentrantLock también logra la sincronización a través de la exclusión mutua. En el uso básico, ReentrantLock es muy similar a sincronizado, ambos tienen la misma función de reentrada de subprocesos.
El principal problema de la sincronización de exclusión mutua es el problema de rendimiento causado por el bloqueo y activación de subprocesos, por lo que este tipo de sincronización también se denomina sincronización de bloqueo. Desde la forma de abordar el problema, la sincronización de exclusión mutua es una estrategia de concurrencia pesimista. Siempre se cree que mientras no se tomen las medidas de sincronización correctas (como el bloqueo), definitivamente habrá problemas, sin importar si el los datos compartidos son realmente compartidos o no.Si hay competencia, debe bloquearse.

2. Sincronización sin bloqueo

Con el desarrollo de conjuntos de instrucciones de hardware, ha surgido una estrategia de concurrencia optimista basada en la detección de conflictos. En términos sencillos, la operación se realiza primero. Si ningún otro subproceso compite por los datos compartidos, la operación tiene éxito; si hay contienda por el datos compartidos, a Si hay un conflicto, utilice otras medidas compensatorias. (El error de compensación más común es seguir reintentando hasta que tenga éxito.) Muchas implementaciones de esta estrategia de concurrencia optimista no necesitan suspender subprocesos, por lo que esta operación de sincronización se denomina sincronización sin bloqueo.

Implementación sin bloqueo de CAS (compareandswap): la instrucción CAS debe tener 3 operandos, que son la dirección de memoria (entendido como la dirección de memoria de la variable en Java, denotado por V), el valor esperado anterior (denotado por A) y el nuevo valor (indicado por B). Cuando se ejecuta la instrucción CAS, si y solo si el valor en V cumple con el antiguo valor esperado A, el procesador actualiza el valor en V con B; de lo contrario, no realiza la actualización, pero independientemente de si el valor en V se actualiza , devolverá el valor anterior de V, y el procesamiento anterior es una operación atómica.

Desventajas de CAS:

  1. Problema de ABA: debido a que CAS necesita verificar si el valor ha cambiado al operar el valor, si no hay cambios, se actualizará, pero un valor era originalmente A, cambió a B y luego cambió a A, luego al usar CAS para comprobar encontrará que su valor no ha cambiado, pero en realidad ha cambiado.
  2. La solución al problema de ABA es utilizar el número de versión. Agregue el número de versión delante de la variable y agregue uno al número de versión cada vez que se actualice la variable, luego ABA se convierte en 1A-2B-3C. El paquete atómico de JDK proporciona una clase AtomicStampedReference para resolver el problema de ABA. La función del método compareAndSet de esta clase es verificar primero si la referencia actual es igual a la referencia esperada, y si el indicador actual es igual al indicador esperado, y si todos son iguales, establecer el valor de la referencia y el marca al valor de actualización dado de forma atómica.
3. No se requiere esquema de sincronización

Para garantizar la seguridad de los subprocesos, no es necesario sincronizar y no existe una relación causal entre los dos. La sincronización es solo un medio para garantizar la corrección de la contención de datos compartidos. Si un método no involucra datos compartidos, naturalmente no necesita ninguna operación de sincronización para garantizar la corrección, por lo que parte del código es intrínsecamente seguro para subprocesos.

1) Código de reentrada

El código reentrante (ReentrantCode), también conocido como código puro (Pure Code), puede interrumpirlo en cualquier momento de la ejecución del código y luego ejecutar otro fragmento de código, y después de que el control regrese, el programa original no tendrá ningún error. Todo el código reentrante es seguro para subprocesos, pero no todo el código seguro para subprocesos es reentrante.
Las características del código reentrante son que no depende de los datos almacenados en el montón y los recursos comunes del sistema, las cantidades de estado utilizadas se pasan por parámetros y no se llama a los métodos no reentrantes.
(Analógico: sincronizado tiene la función de reentrada de bloqueo, es decir, cuando se usa sincronizado, cuando un subproceso obtiene un bloqueo de objeto, puede obtener el bloqueo del objeto nuevamente cuando solicita el bloqueo del objeto nuevamente)

2) Subproceso de almacenamiento local

Si los datos requeridos en un fragmento de código deben compartirse con otro código, entonces vea si se puede garantizar que el código que comparte datos se ejecute en el mismo hilo. Si se puede garantizar, podemos limitar la visibilidad de los datos compartidos al mismo hilo. De esta forma, no hay necesidad de sincronización para garantizar que no haya problemas de contención de datos entre hilos.
Las aplicaciones que cumplen con esta característica no son infrecuentes. La mayoría de los patrones arquitectónicos que usan colas de consumo (como el patrón "productor-consumidor") consumirán el proceso de consumo del producto en un subproceso tanto como sea posible. Uno de los ejemplos de aplicación más importantes es el método de procesamiento de "una solicitud corresponde a un subproceso de servidor (Subproceso por solicitud)” en el modelo clásico de interacción web. La amplia aplicación de este método de procesamiento permite que muchas aplicaciones de servidor web utilicen subprocesos. Almacenamiento local para resolver problemas de seguridad de subprocesos.

13. ¿Alguna vez has usado volátiles? ¿Cómo asegura la visibilidad y cuál es el principio?

En Java, volatilelas palabras clave se utilizan para modificar variables para garantizar la visibilidad y el orden de las operaciones de lectura y escritura en la variable.

volatileEl principio de la palabra clave es leer y escribir directamente el valor de la variable desde la memoria principal al prohibir la operación de caché de la variable modificada por el hilo. Cuando un subproceso modifica volatileel valor de una variable, inmediatamente vacía el nuevo valor a la memoria principal en lugar de simplemente actualizar su propio caché local. Cuando otros subprocesos leen la variable, obtienen el valor más reciente de la memoria principal en lugar de utilizar su propia memoria caché.

Este mecanismo asegura volatilela visibilidad de la variable, es decir, la modificación de la variable por parte de un hilo es visible para otros hilos. Cuando un subproceso modifica volatileel valor de una variable, otros subprocesos verán el último valor cuando lean la variable.

Además, volatilelas palabras clave también pueden garantizar el orden de las operaciones. Específicamente, volatiletodas las operaciones que preceden a la escritura en la variable se completan antes de la escritura y todas las operaciones posteriores a la escritura comienzan después de la escritura. Esto asegura que volatilelas operaciones sobre las variables se realicen en el orden esperado.

Cabe señalar que volatilelas palabras clave solo pueden garantizar la visibilidad y el orden de una sola variable, pero no pueden garantizar operaciones atómicas entre múltiples variables. Si necesita garantizar la operación atómica de múltiples variables, puede considerar usar synchronizedpalabras clave o java.util.concurrentclases atómicas en el paquete.

14. Hable sobre su comprensión del papel y el principio de la palabra clave volátil

Las dos funciones de la palabra clave voliate :

1. Garantizar la visibilidad de las variables : cuando un subproceso modifica una variable modificada por la palabra clave volatile, otros subprocesos pueden obtener inmediatamente el resultado modificado. Cuando un subproceso escribe datos en una variable modificada por la palabra clave volatile, la máquina virtual obliga a que se vacíe a la memoria principal. Cuando un subproceso utiliza un valor modificado por la palabra clave volátil, la máquina virtual lo obliga a leer desde la memoria principal.

2. Escudo de reordenamiento de instrucciones : el reordenamiento de instrucciones es un medio para que los compiladores y procesadores optimicen los programas de manera eficiente. Solo puede garantizar que los resultados de la ejecución del programa sean correctos, pero no puede garantizar que la secuencia de operación del programa sea consistente con la secuencia del código. . Esto no plantea un problema en un solo subproceso, pero sí en uno multiproceso. Un ejemplo muy clásico es agregar voliate al campo al mismo tiempo en el método singleton, solo para evitar el reordenamiento de las instrucciones.

1. Visibilidad

En pocas palabras : cuando una variable compartida es modificada por volátil, se asegurará de que el valor modificado se actualice en la memoria principal de inmediato, y cuando otros subprocesos necesiten leerlo, irá a la memoria para leer el nuevo valor. .

Protocolo MESI : en las primeras CPU, se implementaba agregando bloqueos LOCK# al bus , pero este método era demasiado costoso, por lo que Intel desarrolló el protocolo de coherencia de caché, que es el protocolo MESI.

Idea de consistencia de caché : cuando la CPU escribe datos, si encuentra que la variable que se está operando es una variable compartida, es decir, la variable también existe en la memoria de trabajo de otros subprocesos, enviará una señal para notificar a otras CPU que la memoria la dirección de la variable no es válida. Cuando otros subprocesos necesitan usar esta variable, si la dirección de la memoria deja de ser válida, volverán a leer el valor en la memoria principal.

Proceso detallado :

diagrama de flujo:

Mecanismo de detección de autobuses :

El mecanismo de rastreo es en realidad un oyente. Volviendo a nuestro proceso anterior, si es después de agregar el protocolo de consistencia de caché MESI y el mecanismo de rastreo de bus:

(1) CPU1 lee datos a=1, y hay una copia de datos a en el caché de CPU1, y la línea de caché se establece en el estado (E)

(2) La CPU 2 también ejecuta la operación de lectura. De manera similar, la CPU 2 también tiene una copia de los datos a = 1. En este momento, el bus detecta que la CPU 1 también tiene los datos, y ambas líneas de caché de la CPU 1 y la CPU 2 están configuradas para (S) estado

(3) CPU1 modifica los datos a=2, el caché de CPU1 y la memoria principal a=2, al mismo tiempo, la línea de caché de CPU1 se establece en el estado (S), el bus envía una notificación y la línea de caché de CPU2 se establece en (I) estado

(4) La CPU2 vuelve a leer a. Aunque la CPU2 encuentra los datos a=1 en la memoria caché, encuentra que el estado es (I), por lo que descarta directamente los datos y va a la memoria principal para obtener los datos más recientes.

2. Prohibición de reordenar

Volatile prohíbe el reordenamiento mediante el uso de barreras de memoria para garantizar el orden.
Una barrera de memoria es un conjunto de instrucciones de la CPU que se utilizan para implementar restricciones secuenciales en las operaciones de memoria.
El compilador de Java, cuando genera una serie de instrucciones, inserta barreras de memoria en las posiciones apropiadas para impedir que el procesador reordene las instrucciones.

(1) volátil agregará dos barreras de memoria antes y después de la operación de escritura variable para garantizar que las instrucciones de escritura anteriores y las instrucciones de lectura posteriores estén en orden.

(2) volatile inserta dos instrucciones después de la operación de lectura de la variable y prohíbe el reordenamiento de las instrucciones de lectura y escritura subsiguientes.

Resumir

Volatile en realidad se puede considerar como ligero sincronizado. Aunque volatile no puede garantizar la atomicidad , si la operación en sí es atómica bajo subprocesos múltiples (como la operación de asignación), entonces el uso de volatile se debe a sincronizado.

Volatile se puede aplicar a una determinada bandera , una vez que se modifica, debe ser inmediatamente visible para otros subprocesos. También es posible modificar la variable utilizada como disparador , una vez que la variable es modificada por cualquier hilo, disparará la ejecución de una operación.

El escenario más adecuado para volatile es que un hilo modifica una variable modificada por volatile, y otros hilos obtienen el valor de esta variable.
Cuando varios subprocesos modifican el valor de una variable al mismo tiempo, se debe usar sincronizado para la sincronización de exclusión mutua.

El rendimiento de volatile :
si una variable se modifica con volatile, entonces cada vez que se lee y escribe la variable, la CPU necesita leer de la memoria principal, y el rendimiento definitivamente se verá afectado hasta cierto punto.
Es decir: las variables volátiles están alejadas de la caché de la CPU, por lo que no son tan eficientes.

15. Hablemos de la subbase de datos y la subtabla. ¿Por qué las subtablas deben detener el servicio? ¿Qué puedo hacer si no detengo el servicio?

1. Subbase de datos y subtabla

La subbase de datos y la subtabla es una estrategia de división horizontal de la base de datos, que se utiliza para resolver el problema del cuello de botella en el rendimiento de una sola base de datos cuando aumenta el volumen de datos o aumenta la presión de acceso. Divide una base de datos en varias subbases de datos (subbases de datos) y divide aún más las tablas de cada subbase de datos en varias subtablas (subtablas), para realizar el almacenamiento descentralizado de datos y la distribución de la carga de consultas.

2. ¿Por qué deberían descontinuarse los submedidores?

Suele ser necesario parar el servidor cuando se realizan operaciones de fraccionamiento de contadores, por dos razones principales:

  1. Migración de datos : la división de tablas implica migrar los datos de la tabla original a una nueva tabla dividida. Este proceso requiere copiar datos de la tabla original a la nueva tabla y puede requerir la conversión y reasignación de datos. En este proceso, para garantizar la consistencia e integridad de los datos, es necesario detener la operación de escritura en la base de datos para evitar la pérdida o inconsistencia de los datos.
  2. Cambios en la estructura de la base de datos : las operaciones de división de tablas generalmente requieren la modificación de la estructura de la base de datos, incluida la creación de nuevas tablas divididas, el ajuste de índices y la actualización de las relaciones de clave externa. Estas operaciones pueden hacer que cambien los metadatos de la base de datos y, durante el proceso de cambio, es posible que la base de datos no pueda procesar las solicitudes de consulta con normalidad, por lo que es necesario detener el servicio.

3. Qué hacer si no dejas de tomarlo

Si no desea detener el servidor y realizar operaciones de división de tablas, puede considerar los siguientes métodos:

  1. Migración gradual : primero puede crear una nueva subtabla y escribir nuevos datos en la nueva tabla mientras mantiene la operación de lectura en la tabla anterior. Con el tiempo, los datos de la nueva tabla aumentarán gradualmente, mientras que los datos de la tabla anterior disminuirán gradualmente. Cuando haya suficientes datos en la tabla nueva, los datos de la tabla anterior se pueden migrar a la tabla nueva y, finalmente, se detendrá la operación de lectura en la tabla anterior.
  2. Replicación de datos : los datos de la tabla original se pueden copiar a la nueva tabla sin detener el servidor, y la coherencia de los datos de las dos tablas se puede mantener mediante la sincronización de datos. Durante la replicación, se debe tener cuidado para manejar escrituras simultáneas para evitar conflictos de datos.
  3. Procesamiento de descarga : el procesamiento de descarga de solicitudes se puede realizar mediante la introducción de una capa de proxy o middleware. Durante el proceso de división de tablas, algunas solicitudes se pueden enrutar a la nueva tabla, mientras que otras solicitudes aún se enrutan a la tabla anterior para lograr una transición gradual de división de tablas.

Cabe señalar que el funcionamiento de la subtabla sin detener el servidor puede aumentar la complejidad y el riesgo del sistema, lo que requiere una evaluación y prueba cuidadosas. Antes de dividir la tabla, debe hacer una copia de seguridad de los datos, hacer un plan detallado y asegurarse de que haya suficientes estrategias de prueba y reversión.

16. En la máquina virtual Java, ¿en qué tipos de tipos de datos se pueden dividir?

En la máquina virtual Java, los tipos de datos se pueden dividir en las siguientes categorías:

  1. Tipos de datos básicos (tipos primitivos) : Java proporciona 8 tipos de datos básicos, a saber boolean, , byte, short, int, long, floaty double. charEstos tipos almacenan directamente el valor de los datos, en lugar de una referencia a un objeto.
  2. Tipos de datos de referencia (Tipos de referencia) : los tipos de datos de referencia se refieren a todos los tipos, excepto a los tipos de datos básicos, incluidas las clases (Clase), las interfaces (Interfaz), las matrices (Array) y las enumeraciones (Enumeración). Un tipo de datos de referencia almacena una referencia a un objeto en lugar del valor del objeto en sí.
  3. Tipos de matrices (Array Types) : las matrices son un tipo de datos de referencia especial que puede almacenar varios elementos del mismo tipo. Los arreglos pueden ser unidimensionales, bidimensionales o incluso multidimensionales.
  4. Tipos de clase (Class Types) : los tipos de clase se refieren a classclases definidas por palabras clave. Una clase es una plantilla para un objeto, que describe sus propiedades y comportamiento.
  5. Tipos de interfaz : los tipos de interfaz se refieren a interfacelas interfaces definidas por palabras clave. Una interfaz define la especificación de un conjunto de métodos que debe implementar una clase que implementa la interfaz.
  6. Tipos de enumeración (tipos de enumeración) : los tipos de enumeración se refieren a enumlas clases de enumeración definidas por palabras clave. Una clase de enumeración representa un conjunto de constantes con nombre, que pueden tener sus propios métodos y propiedades.
  7. Tipos de anotaciones : los tipos de anotaciones se refieren a @interfacelas anotaciones definidas por palabras clave. Las anotaciones se utilizan para agregar metadatos adicionales a los elementos del programa (clases, métodos, variables, etc.).

Estos tipos de datos tienen métodos de operación y representación de memoria correspondientes en la máquina virtual Java, y los desarrolladores pueden elegir el tipo de datos apropiado para almacenar y operar datos de acuerdo con sus necesidades.

17. ¿Cómo entender pila y montón? ¿Qué se almacena en el montón? ¿Qué se almacena en la pila?

En informática, la pila (Stack) y el montón (Heap) son dos áreas de memoria importantes que se utilizan para almacenar datos cuando el programa se está ejecutando. Tienen diferentes características y usos.

1. Pila (Pila):

  • La pila es una estructura de datos lineal que adopta el principio de último en entrar, primero en salir (LIFO), es decir, se accede primero a los datos ingresados ​​en último lugar.
  • La pila almacena variables locales, parámetros de métodos, llamadas a métodos y estado de retorno.
  • El tamaño de la pila se determina durante la etapa de compilación del programa y el compilador lo asigna y libera automáticamente. La velocidad de operación de la pila es rápida y la asignación y recuperación de memoria son muy eficientes.
  • Se determina el tamaño y el ciclo de vida de los datos en la pila.Cuando se ejecuta un método, los datos en la pila se liberarán inmediatamente.

En segundo lugar, el montón (Heap):

  • El montón es una forma de asignar memoria dinámicamente para almacenar objetos y estructuras de datos.
  • newLos objetos, matrices y otros datos asignados dinámicamente creados por palabras clave se almacenan en el montón .
  • El tamaño del montón se puede ajustar dinámicamente y el recolector de basura es responsable de administrar la asignación y liberación de memoria.
  • El tamaño y el ciclo de vida de los datos en el montón son inciertos. El ciclo de vida de un objeto está determinado por la lógica del programa. Cuando no hay referencia a un objeto, el recolector de basura reclamará el objeto.

Resumir:

  • La pila se utiliza para almacenar información de devolución y llamada de método, así como datos como variables locales, y se determina su tamaño y ciclo de vida.
  • El montón se utiliza para almacenar objetos y datos asignados dinámicamente, con un tamaño y una duración indeterminados.

Cabe señalar que el valor del tipo de datos básico en Java se puede almacenar directamente en la pila, mientras que el objeto del tipo de datos de referencia se almacena en el montón y la referencia del objeto se almacena en la pila.

18. ¿Por qué se debe distinguir el montón y la pila? ¿No es posible almacenar datos en la pila?

El propósito de distinguir el montón de la pila es administrar mejor la memoria y admitir la ejecución del programa.

En primer lugar, la pila se utiliza para almacenar llamadas a métodos y devolver información, así como datos como variables locales. La pila se caracteriza por una rápida velocidad de asignación y liberación, y funciona según el principio de "primero en entrar, último en salir". Cuando se llama a un método, la pila asigna un espacio de memoria para el método, llamado marco de pila. El marco de pila contiene información como parámetros de método, variables locales y direcciones de retorno. Cuando el método termine de ejecutarse, su marco de pila correspondiente se liberará para que lo utilicen otros métodos. Debido a que el método de administración de pilas es simple y eficiente, es adecuado para almacenar datos pequeños y variables temporales.

Mientras que el montón se usa para almacenar objetos y datos asignados dinámicamente. El montón se caracteriza por métodos flexibles de asignación y liberación, y puede ajustar dinámicamente el tamaño del espacio de memoria. Los objetos asignados en el montón se pueden compartir y acceder a ellos en diferentes partes del programa. Debido a que el método de gestión del almacenamiento dinámico es relativamente complicado, debe tener en cuenta el ciclo de vida de los objetos, la recolección de elementos no utilizados y otros problemas, por lo que es adecuado para almacenar datos de gran tamaño y objetos a largo plazo.

En resumen, la distinción entre pila y montón es principalmente para cumplir con los requisitos de almacenamiento de diferentes tipos de datos y las necesidades de administración de memoria. La pila es adecuada para almacenar datos temporales, como llamadas a métodos y variables locales, mientras que el montón es adecuado para almacenar objetos asignados dinámicamente y datos de larga duración.

19. ¿Por qué no poner los tipos básicos en el montón?

La colocación de tipos primitivos en el montón genera una sobrecarga de memoria adicional y una penalización de rendimiento. Aquí hay algunas razones:

  1. Sobrecarga de memoria : la colocación de tipos primitivos en el montón hace que cada variable requiera espacio de memoria adicional para almacenar encabezados de objetos y otra información de administración. Por el contrario, almacenar tipos primitivos en la pila requiere solo una asignación de memoria de tamaño fijo, sin sobrecarga adicional.
  2. Velocidad de acceso : la velocidad de acceso de la pila es más rápida que la del montón. Debido a que los datos en la pila funcionan de acuerdo con el principio de "primero en entrar, último en salir", se puede acceder a los datos en la pila y liberarlos a través de simples operaciones de puntero. Es necesario acceder a los datos en el montón por referencia, lo que requiere operaciones adicionales de direccionamiento y eliminación de referencias, por lo que la velocidad de acceso es relativamente lenta.
  3. Complejidad de la gestión : la colocación de tipos primitivos en el montón añade complejidad a la gestión de la memoria. Los objetos en el montón necesitan realizar operaciones como la recolección de basura y la liberación de memoria, mientras que los tipos básicos almacenados en la pila no necesitan realizar estas operaciones, lo que hace que la administración de la memoria sea más simple y eficiente.
  4. Pasar por valor : los tipos básicos se pasan por valor cuando se almacenan en la pila, mientras que los tipos de referencia se pasan por referencia cuando se almacenan en el montón. Pasar por valor puede evitar el uso compartido y los efectos secundarios de los objetos, mientras que pasar por referencia puede realizar el intercambio y el paso de objetos.

En resumen, colocar tipos básicos en la pila puede reducir la sobrecarga de memoria, mejorar la velocidad de acceso y simplificar la administración de la memoria, por lo que los tipos básicos generalmente se almacenan en la pila. El almacenamiento de tipos de referencia en el montón permite la asignación dinámica y el uso compartido de objetos.

20. ¿Qué pasa con el paso de valores al pasar parámetros en Java? O pasar por referencia?

En Java, el paso de parámetros se realiza mediante el paso de valores (paso por valor). Esto significa que cuando se llama al método, en realidad se pasa una copia del valor del parámetro al método, no al parámetro en sí.

Al pasar tipos primitivos como int, float, boolean, etc., en realidad estás pasando una copia del valor al método. Modificar el parámetro dentro del método no afectará el valor original, porque es solo una operación sobre la copia.

Al pasar un tipo de referencia (como objeto, matriz, etc.), en realidad está pasando una copia de la referencia al método. Una referencia en sí misma es un valor de dirección que apunta a dónde se almacena el objeto real en el montón. La modificación de la referencia dentro del método no afectará la referencia original, pero se puede acceder y modificar las propiedades y el estado del objeto a través de la referencia.

Cabe señalar que aunque se pasa una copia de la referencia al pasar un tipo de referencia, la copia aún apunta al mismo objeto. Por lo tanto, las modificaciones al objeto dentro del método afectarán el estado del objeto original.

Además, las cadenas en Java son objetos inmutables, y cuando pasa una cadena, en realidad pasa una copia de la cadena al método. Cuando se modifica una cadena dentro de un método, se crea un nuevo objeto de cadena sin modificar el objeto de cadena original.

En resumen, el paso de parámetros en Java es por valor, ya sea un tipo primitivo o un tipo de referencia. Para los tipos primitivos, se pasa una copia del valor; para los tipos de referencia, se pasa una copia de la referencia, pero sigue apuntando al mismo objeto.

21. ¿Existe un concepto de punteros en Java?

En Java, existe el concepto de punteros, pero los punteros en Java están ocultos en la parte inferior y no se permite el acceso directo ni la manipulación de punteros. En cambio, Java usa referencias para implementar operaciones en objetos.

En Java, una referencia es una variable que apunta a un objeto, que almacena la dirección del objeto en la memoria. A través de referencias, podemos acceder y manipular objetos indirectamente. A diferencia de los punteros, las referencias de Java no permiten la aritmética de punteros y no pueden acceder directamente a la dirección de memoria de un objeto.

La referencia de Java tiene la función de gestión de memoria automática, es decir, el mecanismo de recolección de basura. En Java, cuando ya no se hace referencia a un objeto, el mecanismo de recolección de basura recuperará automáticamente el espacio de memoria ocupado por el objeto, evitando así los problemas de fugas de memoria y punteros colgantes.

Por lo tanto, aunque no existe el concepto de manipulación directa de punteros en Java, Java realiza la manipulación indirecta de objetos a través de referencias y proporciona un mecanismo automático de administración de memoria, lo que hace que la programación sea más conveniente y segura para los desarrolladores.

22. En Java, ¿qué parámetros se utilizan para establecer el tamaño de la pila?

En Java, el tamaño de la pila se puede configurar a través de los parámetros de la máquina virtual. Específicamente, los siguientes parámetros se pueden usar para ajustar el tamaño de la pila:

-Xss: Este parámetro se utiliza para establecer el tamaño de la pila de subprocesos.

Por ejemplo, -Xss1msignifica establecer el tamaño de la pila de subprocesos en 1 MB.

Tenga en cuenta que el tamaño de la pila se establece de forma independiente para cada subproceso, por lo que al ajustar el tamaño de la pila de subprocesos, puede controlar el espacio de pila ocupado por cada subproceso.

Cabe señalar que si el tamaño de la pila se configura demasiado pequeño, puede provocar un desbordamiento de la pila, y si se configura demasiado grande, puede consumir demasiados recursos de memoria. Por lo tanto, debe tener cuidado al ajustar el tamaño de la pila y realizar configuraciones razonables de acuerdo con los escenarios y requisitos de la aplicación específica.

Además, el tamaño de la pila también está limitado por el sistema operativo y el hardware, más allá de cierto límite, no será posible configurar un espacio de pila más grande. Por lo tanto, al establecer el tamaño de la pila, es necesario tener en cuenta las limitaciones del sistema y realizar los ajustes adecuados.

23. ¿Cuánto espacio ocupa un Objeto vacío?

ObjectEn Java, el espacio ocupado por un objeto vacío está determinado principalmente por el espacio ocupado por el encabezado del objeto y el relleno de alineación.

El encabezado del objeto contiene información de metadatos, como el código hash del objeto, el estado de bloqueo, la bandera de GC, etc. En una JVM de 64 bits, el tamaño del encabezado del objeto suele ser de 12 bytes.

Además, debido a los requisitos de alineación y asignación de memoria de la JVM, el tamaño del objeto debe estar alineado por bytes. En la mayoría de los casos, los objetos de Java se redimensionan automáticamente a múltiplos de 8 bytes.

Por lo tanto, un objeto vacío Objectnormalmente ocupa un espacio de 16 bytes en una JVM de 64 bits. Esto incluye 12 bytes para el encabezado del objeto y 4 bytes para el relleno de alineación.

Cabe señalar que las diferentes implementaciones de JVM pueden variar, por lo que el tamaño del objeto vacío real puede variar. Además, si Objectse agregan variables de instancia al objeto, el tamaño del objeto vacío aumentará, según la cantidad y el tipo de variables de instancia agregadas.

24. ¿Cuáles son los tipos de referencias a objetos?

En Java, los tipos de referencia de objetos se pueden dividir en las siguientes categorías:

  1. Referencia fuerte : una referencia fuerte es el tipo de referencia más común. Cuando se hace referencia fuerte a un objeto, el recolector de elementos no utilizados no lo reclamará incluso si se queda sin memoria. Solo cuando el objeto no tenga referencias sólidas se considerará un objeto reciclable.
  2. Referencia blanda (Soft Reference) : las referencias blandas se utilizan para describir algunos objetos útiles pero no necesarios. Cuando la memoria es baja, el recolector de elementos no utilizados puede reclamar objetos con referencias suaves. En Java, puede usar SoftReferenceclases para crear referencias suaves.
  3. Referencia débil (Weak Reference) : las referencias débiles son más débiles que las referencias blandas y se utilizan para describir objetos no esenciales. Cuando se ejecuta el recolector de elementos no utilizados, los objetos a los que se hace referencia débilmente se reclaman independientemente de si hay suficiente memoria. En Java, WeakReferencelas clases se pueden usar para crear referencias débiles.
  4. Phantom Reference : Phantom Reference es el tipo de referencia más débil, casi sin valor de acceso directo. La función principal de las referencias fantasmas es rastrear el estado de los objetos que reclama el recolector de elementos no utilizados. En Java, PhantomReferencelas clases se pueden usar para crear referencias fantasma.

La principal diferencia entre estos tipos de referencia es cómo el recolector de basura los trata de manera diferente. Las referencias seguras no se reciclarán cuando la memoria sea insuficiente, mientras que las referencias blandas, las referencias débiles y las referencias fantasma se pueden reciclar cuando la memoria sea insuficiente.

25. Habla sobre el algoritmo de recolección de basura

El algoritmo de recolección de elementos no utilizados es la parte central de la gestión automática de memoria y es responsable de recuperar automáticamente los objetos no utilizados en tiempo de ejecución y liberar el espacio de memoria que ocupan. Los algoritmos de recolección de basura incluyen principalmente lo siguiente:

  1. Algoritmo de recuento de referencias (recuento de referencias) : este algoritmo registra el número de veces que se hace referencia a un objeto manteniendo un contador de referencias para cada objeto. Cuando el contador de referencia es 0, significa que el objeto ya no está referenciado y se puede reciclar. Sin embargo, el algoritmo de conteo de referencias no puede resolver el problema de las referencias circulares, es decir, dos o más objetos se refieren entre sí, pero no son referenciados por otros objetos. Por lo tanto, el algoritmo de conteo de referencias no se usa como el principal algoritmo de recolección de basura en Java.
  2. Mark and Sweep : Mark and Sweep es uno de los algoritmos de recolección de basura más utilizados en Java. Se divide en dos fases: fase de marcado y fase de limpieza. En la fase de marcado, el recolector de basura recorre todos los objetos accesibles a partir del objeto raíz y los marca. Durante la fase de limpieza, el recolector de elementos no utilizados elimina los objetos no marcados y recupera el espacio de memoria que ocupan. El algoritmo de barrido de marcas puede manejar eficazmente la situación de las referencias circulares, pero producirá una fragmentación de la memoria.
  3. Algoritmo de copia (Copying) : El algoritmo de copia divide el espacio de memoria disponible en dos áreas, generalmente denominadas espacio "Desde" y espacio "Hasta". Durante la recolección de elementos no utilizados, todos los objetos supervivientes se copian primero del espacio "Desde" al espacio "Hasta" y luego se borran todos los objetos del espacio "Desde". El algoritmo de copia es simple y eficiente, y no generará fragmentación de memoria, pero desperdiciará parte del espacio de memoria.
  4. Algoritmo de compresión de marcas (Mark y Compact) : El algoritmo de compresión de marcas es un algoritmo de recolección de basura que combina el algoritmo de barrido de marcas y el algoritmo de copia. Primero marca todos los objetos sobrevivientes a través de la fase de marcado, luego los mueve a un extremo, los compacta y finalmente limpia el espacio de memoria fuera del límite. El algoritmo de compresión de marcas puede manejar referencias circulares y reducir la fragmentación de la memoria.

El recolector de basura generalmente selecciona el algoritmo de recolección de basura apropiado de acuerdo con diferentes situaciones. Por ejemplo, la generación joven usa un algoritmo de copia y la generación anterior usa un algoritmo de barrido de marca o compacto de marca. Además, Java también proporciona diferentes implementaciones de recolector de basura, como Serial, Parallel, CMS, G1, etc., cada recolector de basura tiene diferentes características y escenarios aplicables.

26. ¿Cómo solucionar el problema de la fragmentación de la memoria?

La fragmentación de la memoria significa que cuando se usa la asignación de memoria dinámica, el espacio de la memoria se divide en varios bloques pequeños y hay algunos espacios sin usar entre estos bloques pequeños. La existencia de fragmentación de la memoria puede conducir a una menor utilización de la memoria e incluso causar fallas en la asignación de memoria.

Para resolver el problema de la fragmentación de la memoria, se pueden adoptar los siguientes métodos:

  1. Grupo de memoria : el uso de la tecnología de grupo de memoria puede evitar operaciones frecuentes de asignación y liberación de memoria, lo que reduce la fragmentación de la memoria. El grupo de memoria asigna previamente un espacio de memoria contiguo cuando se inicia el programa y lo divide en varios bloques de memoria de tamaño fijo. Cuando se necesita memoria, se obtiene un bloque de memoria disponible directamente del grupo de memoria en lugar de la asignación de memoria dinámica cada vez. Esto puede reducir la fragmentación y mejorar la utilización de la memoria.
  2. Clasificación de memoria : la clasificación de memoria se refiere a la clasificación de bloques de memoria dispersos para que los bloques de memoria libres se organicen de forma continua. Los bloques de memoria asignados se pueden mover juntos por medio de una copia de memoria, eliminando así la fragmentación. Este proceso necesita suspender la ejecución del programa, por lo que generalmente se realiza durante el tiempo de inactividad.
  3. Optimización del algoritmo de asignación : el algoritmo de asignación de memoria también puede optimizar la fragmentación de la memoria. Los algoritmos comunes incluyen algoritmos de primer ajuste, mejor ajuste y peor ajuste. El algoritmo de primer ajuste selecciona el primer bloque libre que cumple con los requisitos de asignación, el algoritmo de mejor ajuste selecciona el bloque libre más pequeño que cumple con los requisitos de asignación y el algoritmo de peor ajuste selecciona el bloque libre más grande que cumple con los requisitos de asignación. asignación. Diferentes algoritmos tienen diferentes efectos sobre la fragmentación de la memoria, y se puede seleccionar un algoritmo apropiado de acuerdo con la situación real.
  4. Algoritmo de compresión : el algoritmo de compresión es un método de comprimir bloques de memoria asignados para que los bloques de memoria libres se organicen de forma continua. Al mover bloques de memoria hacia un extremo, los bloques libres se pueden fusionar, lo que reduce la fragmentación. Este proceso necesita suspender la ejecución del programa, por lo que generalmente se realiza durante el tiempo de inactividad.

Los anteriores son algunos métodos comunes para resolver el problema de la fragmentación de la memoria, y la elección específica se puede determinar de acuerdo con la situación y las necesidades reales.

27. Hable sobre los principios subyacentes de JVM y los comandos de solución de problemas

JVM (Java Virtual Machine) es la abreviatura de Java Virtual Machine, que es la plataforma básica para ejecutar programas Java. Es una computadora virtual que simula ejecutar Java bytecode en una máquina física y es responsable de interpretar y ejecutar programas Java.

El principio subyacente de JVM:

  1. Carga de clases : la JVM compila el código fuente de Java en un archivo de código de bytes y luego carga el archivo de código de bytes en la memoria a través de un cargador de clases. Los cargadores de clases son responsables de buscar, cargar y validar archivos de clases.
  2. Administración de memoria : JVM usa el administrador de memoria para administrar la memoria, incluido el montón, la pila y el área de método. El montón se usa para almacenar instancias de objetos, la pila se usa para almacenar variables locales y llamadas a métodos, y el área de métodos se usa para almacenar información de clases y conjuntos de constantes.
  3. Recolección de basura : JVM administra automáticamente la memoria a través del mecanismo de recolección de basura. Comprueba periódicamente los objetos a los que ya no se hace referencia y recupera el espacio de memoria que ocupan.
  4. Compilación justo a tiempo : JVM utiliza un compilador justo a tiempo para convertir el código de bytes en código de máquina local para mejorar la eficiencia de ejecución del programa.
  5. Manejo de excepciones : JVM proporciona un mecanismo de manejo de excepciones que puede capturar y manejar excepciones en el programa.

Comando de solución de problemas de JVM:

  1. jps: se utiliza para enumerar los procesos de Java que se están ejecutando actualmente, mostrando el ID del proceso y el nombre de la clase.
  2. jstat: se utiliza para monitorear información como la memoria JVM, la recolección de elementos no utilizados y la carga de clases.
  3. jmap: se utiliza para generar una instantánea de volcado de almacenamiento dinámico y ver el uso de la memoria de almacenamiento dinámico.
  4. jstack: se utiliza para generar instantáneas de volcado de subprocesos, ver el estado del subproceso e información de la pila de llamadas.
  5. jinfo: Se utiliza para ver y modificar los parámetros de configuración de la JVM.
  6. jconsole: Herramienta gráfica para monitorear y administrar JVM.
  7. jcmd: se utiliza para enviar comandos de diagnóstico a un proceso Java en ejecución.

Estos comandos ayudan a los desarrolladores a monitorear y depurar aplicaciones Java, localizar problemas y optimizar el rendimiento. Para usar estos comandos, debe ingresar los comandos y parámetros correspondientes en la línea de comando. Para conocer los métodos de uso específicos, consulte la documentación de cada comando o use el comando de ayuda (por ejemplo: jps -help).

El arquitecto Nien, de 40 años, recordó : JVM no es solo el foco absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para contenido específico, consulte el PDF "Colección de entrevistas de Nin Java - Tema 01: Preguntas de la entrevista de JVM". Este tema tiene una introducción sistemática, sistemática y completa a JVM. .

Si desea escribir la práctica de ajuste de JVM en su currículum , puede pedirle orientación a Nien.

28. Hable sobre el principio de consistencia ZK

ZooKeeper es un servicio de coordinación distribuido que proporciona un mecanismo de acceso y almacenamiento de datos altamente disponible, consistente y confiable. El principio de consistencia de ZooKeeper se implementa principalmente en base al protocolo ZAB (ZooKeeper Atomic Broadcast). La siguiente es una descripción detallada del principio de consistencia de ZooKeeper:

  1. Transmisión atómica : ZooKeeper utiliza el protocolo de transmisión atómica para garantizar la coherencia de los datos. Este protocolo garantiza la entrega secuencial y el envío único de mensajes en un grupo de servidores de ZooKeeper. El protocolo ZAB se divide en dos fases: transmisión y confirmación.
  2. Elección de líder (Leader Election) : los servidores en el clúster de ZooKeeper seleccionan un líder (Líder) a través de un mecanismo de elección. El líder es responsable de procesar las solicitudes de lectura y escritura de los clientes y transmitir los resultados a otros servidores. Durante el proceso de elección, los servidores se comunican enviándose mensajes entre sí y, finalmente, se elige como líder al servidor con el número más alto.
  3. Consistencia de las operaciones de escritura : cuando un cliente envía una solicitud de escritura al líder, el líder reenvía la solicitud a otros servidores y espera la confirmación de la mayoría de los servidores. Una vez que la mayoría de los servidores confirmen la recepción de la solicitud de escritura y escriban los datos con éxito, el líder devolverá el resultado de la operación de escritura al cliente. Esto asegura la consistencia de las operaciones de escritura.
  4. Coherencia de las operaciones de lectura : cuando un cliente envía una solicitud de lectura al líder, el líder reenvía la solicitud a otros servidores y espera respuestas de la mayoría de los servidores. Una vez que la mayoría de los servidores devuelven el mismo resultado, el líder devuelve el resultado al cliente. Esto asegura la coherencia de las operaciones de lectura.
  5. Sincronización de datos : ZooKeeper utiliza el principio de Mayoría para garantizar la coherencia de los datos. Cuando un servidor recibe una solicitud de escritura, escribe los cambios de datos en el almacenamiento local y transmite los cambios a otros servidores. Después de que otros servidores reciben los cambios, los aplican al almacenamiento local. Solo cuando la mayoría de los servidores hayan completado el cambio de datos, la operación de escritura se considerará exitosa.

En resumen, ZooKeeper garantiza la consistencia de los datos a través de la elección del líder, el protocolo de transmisión atómica y el principio de mayoría. Este mecanismo garantiza que los datos de todos los servidores del clúster de ZooKeeper sean coherentes y puedan proporcionar un servicio de coordinación distribuida confiable y de alta disponibilidad.

Nien, un arquitecto de 40 años, recordó : ZooKeeper no es solo el foco absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para obtener contenido específico, consulte el PDF "Programación central de alta concurrencia de Java, volumen 1, edición mejorada: NIO, Netty, Redis, ZooKeeper". y enfoque integral para la introducción de ZooKeeper.

29. Hable sobre la estructura de datos de Redis, persistencia, centinela, reglas de fragmentación de datos de clúster

Redis es una base de datos en memoria que admite múltiples estructuras de datos y métodos de persistencia, y proporciona funciones centinela y de clúster. La siguiente es una descripción detallada de las estructuras de datos, la persistencia, los centinelas y los clústeres de Redis:

1. Estructura de datos:

  • String (String) : La estructura de datos más básica que puede almacenar cadenas, enteros y números de punto flotante.
  • List (Lista) : una lista ordenada de cadenas, los elementos se pueden agregar o eliminar en la cabeza o la cola.
  • Conjunto (Conjunto) : una colección desordenada de cadenas únicas que admite operaciones de conjunto como intersección, unión y diferencia.
  • Hash : una tabla hash desordenada de pares clave-valor, adecuada para almacenar objetos.
  • Conjunto ordenado : una colección ordenada de cadenas, cada elemento está asociado con una puntuación y se puede ordenar de acuerdo con la puntuación.

2. Persistencia:

  • Persistencia RDB (Redis Database) : guarde los datos en la memoria en el disco en formato binario, puede guardar instantáneas periódicamente a través de la configuración o ejecutar manualmente los comandos SAVE y BGSAVE.
  • Persistencia AOF (Append-Only File) : guarde el registro de operación de escritura en el disco de forma adjunta. Puede configurar el vaciado periódico o ejecutar manualmente el comando BGREWRITEAOF.

3. Centinela:

  • Sentinel es un proceso independiente que se utiliza para monitorear el estado de los nodos maestros y esclavos de Redis.
  • Cuando falla el nodo maestro, Sentinel puede actualizar automáticamente un nodo esclavo al nodo maestro para garantizar una alta disponibilidad.
  • Los centinelas también son responsables de monitorear el estado de salud de los nodos maestro y esclavo, y de realizar conmutación por error y conmutación por recuperación cuando sea necesario.

4. Clúster (Clúster):

  • El clúster de Redis es una solución distribuida que puede dispersar y almacenar datos en varios nodos, lo que brinda alta disponibilidad y escalabilidad.
  • El clúster utiliza hash slots (Hash Slot) para fragmentar los datos, y cada nodo se encarga de procesar una parte de los datos en el hash slot.
  • Los nodos del clúster se comunican a través del protocolo Gossip para detectar fallas y transmitir información entre nodos.
  • El cliente puede acceder al clúster a través del proxy de clúster (Cluster Proxy) o la biblioteca del cliente de Redis, y el proxy de clúster reenviará la solicitud al nodo correcto.

En resumen, Redis proporciona una variedad de estructuras de datos y métodos de persistencia, y puede elegir un método de almacenamiento adecuado según las diferentes necesidades. Las funciones de centinela y clúster pueden proporcionar alta disponibilidad y escalabilidad, lo que hace que Redis sea más estable y confiable en un entorno distribuido.

Nien, un arquitecto de 40 años, recordó : Redis no es solo el foco absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para contenido específico, consulte el PDF "Colección de entrevistas de Nin Java - Tema 14: Preguntas de la entrevista de Redis". Este tema tiene una introducción sistemática, sistemática y completa a Redis .

Si desea escribir la alta concurrencia de Redis en su currículum, puede pedirle orientación a Nien.

30. El principio de consistencia de Kafka, ¿cómo solucionar la pérdida y duplicidad de mensajes durante el consumo?

Kafka es una plataforma de procesamiento de flujo distribuido, uno de sus objetivos de diseño es proporcionar una entrega de mensajes altamente confiable. El principio de coherencia de Kafka se basa principalmente en el mecanismo de replicación distribuida y envío de registros.

Kafka logra una alta confiabilidad al dividir los mensajes en múltiples particiones y realizar una replicación distribuida en múltiples Brokers. Cada partición tiene un Broker principal y varios Brokers de réplica. El Broker primario es responsable de recibir y escribir mensajes, mientras que el Broker de réplica es responsable de replicar mensajes en el Broker primario. Cuando el Broker principal falla, el Broker de réplica puede asumir como el nuevo Broker primario para garantizar la persistencia y disponibilidad de los mensajes.

En Kafka, los consumidores pueden consumir mensajes a través de grupos de consumidores. Los consumidores de cada grupo de consumidores pueden consumir diferentes particiones en paralelo, lo que puede mejorar el rendimiento del consumo. Kafka usa compensaciones para rastrear dónde consumen los consumidores en cada partición. Los consumidores pueden enviar compensaciones periódicamente para garantizar la confiabilidad del progreso del consumo.

En cuanto a la solución a la pérdida y duplicación de mensajes, Kafka proporciona los siguientes mecanismos:

  1. Almacenamiento persistente : Kafka utiliza registros persistentes para almacenar mensajes y garantizar su confiabilidad. Incluso en caso de falla, Kafka puede recuperar mensajes del registro.
  2. Copia redundante : Kafka copia los mensajes de cada partición en copias de varios agentes para garantizar que, incluso si un agente falla, los mensajes aún se pueden obtener de otras copias.
  3. Presentación de compensaciones : los consumidores envían compensaciones regularmente para garantizar la confiabilidad del progreso del consumo. Si un consumidor falla, puede continuar consumiendo desde el último desplazamiento comprometido, evitando el consumo repetido de mensajes.
  4. Semántica Exactamente una vez : Kafka proporciona soporte para la semántica Exactamente una vez para garantizar que los mensajes se entreguen exactamente una vez entre productores y consumidores. Esto se logra a través de mecanismos de transacción y garantías de idempotencia.

A través de estos mecanismos, Kafka puede proporcionar una entrega de mensajes altamente confiable y resolver de manera efectiva los problemas de pérdida y duplicación de mensajes.

31. Habla sobre las ventajas y desventajas de los microservicios

Una arquitectura de microservicios es un enfoque para el desarrollo de software que divide una aplicación en un conjunto de pequeños servicios que se pueden implementar de forma independiente. Tiene las siguientes ventajas y desventajas:

ventaja:

  1. Acoplamiento flexible : la arquitectura de microservicios divide la aplicación en varios servicios pequeños, cada uno de los cuales es independiente y se puede desarrollar, implementar y escalar de forma independiente. Este diseño débilmente acoplado permite a los equipos desarrollar e implementar diferentes servicios de forma más independiente, mejorando la eficiencia y la flexibilidad del desarrollo.
  2. Escalabilidad : dado que los servicios de la arquitectura de microservicios son independientes, cada servicio se puede expandir de forma independiente según la demanda. Esta escalabilidad facilita el manejo de alta concurrencia y tráfico a gran escala.
  3. Diversidad tecnológica : la arquitectura de microservicios permite que cada servicio use diferentes pilas de tecnología y lenguajes de programación porque cada servicio es independiente. Esto permite a los equipos elegir la tecnología que mejor se adapta a cada necesidad de servicio, aumentando la flexibilidad de desarrollo y la innovación.
  4. Alta disponibilidad : cada servicio en la arquitectura de microservicio se puede implementar de forma independiente y escalar horizontalmente, lo que hace que el sistema esté más disponible. Incluso si un servicio falla, otros servicios pueden seguir funcionando con normalidad.

defecto:

  1. Complejidad del sistema : la arquitectura de microservicios divide la aplicación en varios servicios, lo que aumenta la complejidad del sistema. Los problemas como la comunicación, la consistencia de los datos y el descubrimiento de servicios entre múltiples servicios deben gestionarse y coordinarse.
  2. Desafíos de los sistemas distribuidos : cada servicio en una arquitectura de microservicio es independiente y se comunica entre sí a través de una red. Esto trae consigo desafíos para los sistemas distribuidos, como la latencia de la red, las transacciones distribuidas y la consistencia de los datos.
  3. Complejidad operativa : debido al aumento en la cantidad de servicios en una arquitectura de microservicios, la complejidad operativa se vuelve más compleja. Es necesario gestionar tareas como la implementación, la supervisión, el registro y la solución de problemas de varios servicios.
  4. Costo de colaboración del equipo : la arquitectura de microservicio requiere que los equipos tengan conocimiento de los sistemas distribuidos y la gobernanza del servicio. Los equipos deben coordinarse y colaborar para garantizar que el desarrollo y la implementación de cada servicio se realicen sin problemas.

En resumen, la arquitectura de microservicios tiene las ventajas de la flexibilidad, la escalabilidad y la alta disponibilidad, pero también debe lidiar con las desventajas de la complejidad, los desafíos del sistema distribuido y la complejidad de la operación y el mantenimiento. Al elegir una arquitectura de microservicio, debe sopesar estos pros y contras y tomar decisiones caso por caso.

32. Hable sobre la implementación subyacente de bloqueo sincronizado

Tanto sincronizado como Lock son mecanismos que se utilizan para implementar la sincronización de subprocesos en Java, y sus implementaciones subyacentes son diferentes.

1. La implementación subyacente de sincronizada:

  • Cada objeto en Java tiene un bloqueo de monitor (también conocido como bloqueo incorporado o bloqueo de objeto), que se puede adquirir y liberar a través de la palabra clave sincronizada.
  • Cuando un subproceso se ejecuta en un bloque de código sincronizado, intenta adquirir el bloqueo del monitor del objeto.
  • Si el bloqueo no está ocupado por otros subprocesos, el subproceso adquirirá el bloqueo y continuará ejecutando el contenido del bloque de código sincronizado.
  • Si el bloqueo ya está ocupado por otro subproceso, el subproceso se bloqueará hasta que se adquiera el bloqueo.
  • Cuando el subproceso termine de ejecutar el bloque de código sincronizado o se produzca una excepción, se liberará el bloqueo.

2. La implementación subyacente de Lock:

  • Lock es una interfaz provista en el paquete java.util.concurrent, que define las operaciones básicas de los bloqueos.
  • La clase de implementación común de la interfaz Lock es ReentrantLock, que utiliza un sincronizador llamado AQS (AbstractQueuedSynchronizer) para implementar la función de bloqueo.
  • AQS es un marco para construir bloqueos y sincronizadores. Proporciona una cola para administrar subprocesos que esperan bloqueos y utiliza operaciones CAS (Comparar e intercambiar) para implementar un cambio seguro de subprocesos.
  • Cuando un subproceso llama al método lock() de Lock, intenta adquirir el bloqueo. Si el bloqueo ya está ocupado por otro subproceso, el subproceso se bloqueará hasta que se adquiera el bloqueo.
  • A diferencia de sincronizado, Lock proporciona una forma más flexible de adquirir y liberar bloqueos, por ejemplo, puede establecer el período de tiempo de espera para adquirir bloqueos, y puede adquirir y liberar bloqueos por separado en diferentes bloques de código.

En general, tanto sincronizado como Lock son mecanismos para implementar la sincronización de subprocesos, y todas sus implementaciones subyacentes dependen del mecanismo de bloqueo subyacente. Synchronized usa el bloqueo del monitor del objeto, mientras que Lock usa el sincronizador AQS. Lock proporciona más flexibilidad y funciones que el sincronizado, pero también es más complicado de usar. En el desarrollo real, qué mecanismo elegir depende de las necesidades y escenarios específicos.

33. Hable sobre la implementación subyacente de hashmap

HashMap es una estructura de datos de uso común en Java, que se implementa en base a una tabla hash (Hash Table). La siguiente es una descripción detallada de la implementación subyacente de HashMap:

1. Estructura de datos:

  • HashMap se compone de matrices y listas enlazadas (o árboles rojo-negro).
  • Array es el cuerpo principal de HashMap, utilizado para almacenar elementos.
  • Una lista enlazada (o árbol rojo-negro) resuelve el problema de las colisiones hash. Cuando varios elementos se asignan a la misma posición de matriz, se almacenarán en esta posición en forma de lista enlazada (o árbol rojo-negro).

2. Algoritmo hash:

  • Al insertar un elemento en HashMap, la posición del elemento en la matriz se determinará de acuerdo con el código hash del elemento (calculado por el método hashCode()).
  • HashMap utiliza un algoritmo hash para asignar el código hash del elemento a la posición de índice de la matriz.
  • El objetivo del algoritmo hash es hacer que los elementos se distribuyan uniformemente en la matriz y reducir la probabilidad de colisiones hash.

3. Resolver conflictos hash:

  • Cuando varios elementos se asignan a la misma ubicación de matriz, se almacenarán en esta ubicación en forma de lista enlazada (o árbol rojo-negro).
  • En JDK 8 y anteriores, se usaba una lista enlazada para resolver el problema de los conflictos de hash.
  • Después de JDK 8, cuando la longitud de la lista enlazada supere cierto umbral (8 de forma predeterminada), la lista enlazada se convertirá automáticamente en un árbol rojo-negro para mejorar la eficiencia de la búsqueda.

4. Expansión:

  • Cuando la cantidad de elementos en HashMap supere el 75 % de la capacidad del arreglo, se activará la operación de expansión.
  • La operación de expansión creará una nueva matriz y reasignará los elementos de la matriz original a la nueva matriz.
  • La operación de expansión hará que los elementos de la matriz original vuelvan a calcular el código hash y la posición para garantizar que los elementos se distribuyan uniformemente en la nueva matriz.

En general, la implementación subyacente de HashMap se implementa a través de una combinación de matrices y listas enlazadas (o árboles rojo-negro). Utiliza un algoritmo hash para asignar el código hash del elemento a la posición de índice de la matriz y utiliza una lista enlazada (o árbol rojo-negro) para resolver el problema de las colisiones hash. Al insertar, buscar y eliminar elementos, HashMap calculará la posición de la matriz de acuerdo con el código hash del elemento y operará en esta posición. Cuando la cantidad de elementos supera un cierto umbral, HashMap se expandirá automáticamente para garantizar que los elementos se distribuyan uniformemente en la matriz. Esto puede mejorar la eficiencia de las operaciones de inserción, búsqueda y eliminación de HashMap.

34. Hable sobre la implementación subyacente de la serialización de Java

La serialización de Java es un proceso de convertir un objeto en un flujo de bytes para que el objeto pueda transmitirse a través de la red o persistir en el disco. Java proporciona dos métodos de serialización: serialización predeterminada y serialización personalizada.

La serialización predeterminada significa que cuando una clase implementa una java.io.Serializableinterfaz, Java realizará automáticamente operaciones de serialización y deserialización. En el proceso de serialización predeterminado, Java escribe el estado del objeto en el flujo de salida en forma de flujo de bytes, mientras que la deserialización es el proceso de convertir el flujo de bytes nuevamente en un objeto.

La implementación subyacente de la serialización predeterminada de Java implica principalmente las siguientes clases e interfaces:

  1. java.io.ObjectOutputStream: esta clase es un flujo de salida de objetos, que se utiliza para serializar un objeto en un flujo de bytes. writeObject()Escribe el estado del objeto en el flujo de salida llamando a los métodos del objeto .
  2. java.io.ObjectInputStream: esta clase es un flujo de entrada de objetos, que se utiliza para deserializar un flujo de bytes en un objeto. readObject()Convierte el flujo de bytes en el estado del objeto llamando a los métodos del objeto .
  3. java.io.SerializableInterfaz: esta interfaz es una interfaz de marcador que se utiliza para identificar una clase que se puede serializar. Las clases que implementan esta interfaz deben proporcionar un constructor sin argumentos y todos los campos no transitorios se serializarán.

Durante la serialización, Java procesa recursivamente cada campo de un objeto. Para campos de tipos primitivos y tipos de cadena, Java los escribirá directamente en el flujo de bytes. Para los campos de tipo de referencia, Java serializa recursivamente el objeto al que se refiere.

La serialización personalizada se refiere a java.io.Externalizablepersonalizar el proceso de serialización y deserialización de objetos mediante la implementación de interfaces. A diferencia de la serialización predeterminada, la serialización personalizada requiere implementación manual writeExternal()y readExternal()métodos para controlar el proceso de serialización y deserialización de objetos.

En general, la implementación subyacente de la serialización de Java depende principalmente del flujo de salida del objeto y del flujo de entrada del objeto, convirtiendo el estado del objeto en un flujo de bytes para la serialización y convirtiendo el flujo de bytes en el estado de un objeto para la deserialización.

35. Hable sobre la implementación subyacente de MySQL

MySQL es un sistema de administración de bases de datos relacionales y su implementación subyacente involucra múltiples componentes y tecnologías.

  1. Motor de almacenamiento : MySQL admite múltiples motores de almacenamiento, como InnoDB, MyISAM, Memory, etc. El motor de almacenamiento es responsable del almacenamiento y recuperación de datos. Entre ellos, InnoDB es el motor de almacenamiento predeterminado de MySQL. Admite funciones como transacciones, bloqueos de nivel de fila y recuperación de fallas, y es adecuado para escenarios de alta concurrencia y alta confiabilidad.
  2. Sistema de archivos : MySQL utiliza un sistema de archivos para administrar archivos de datos. Cada base de datos corresponde a una carpeta y cada tabla corresponde a uno o más archivos. Los archivos de datos incluyen estructuras de tablas, índices, datos y registros, etc.
  3. Optimizador de consultas : el optimizador de consultas de MySQL es responsable de analizar las declaraciones SQL y generar planes de consulta óptimos. Elegirá la mejor ruta de ejecución en función de las estadísticas y los índices de la tabla para mejorar el rendimiento de las consultas.
  4. Motor de ejecución de consultas : el motor de ejecución de consultas de MySQL es responsable de ejecutar planes de consulta y devolver resultados. Los diferentes motores de almacenamiento tienen diferentes motores de ejecución de consultas. Por ejemplo, InnoDB usa el índice de árbol B+ para la recuperación de datos y MyISAM usa el índice hash y el índice de texto completo.
  5. Bloqueos y control de concurrencia : MySQL utiliza bloqueos y mecanismos de control de concurrencia para garantizar la consistencia de los datos y la corrección del acceso concurrente. InnoDB usa bloqueos de nivel de fila para implementar operaciones de lectura y escritura altamente simultáneas, y proporciona control de concurrencia de múltiples versiones (MVCC) para resolver conflictos de lectura y escritura.
  6. Sistema de registro : el sistema de registro de MySQL incluye registros de transacciones (redo log) y registros binarios (binlog). Los registros de transacciones se utilizan para la recuperación de fallas y la persistencia de transacciones, y los registros binarios se utilizan para la replicación maestro-esclavo y la recuperación de datos.
  7. Gestión de caché : MySQL utiliza el almacenamiento en caché para mejorar el rendimiento de las consultas. Entre ellos, el caché de consultas se utiliza para almacenar en caché los resultados de las consultas para mejorar la velocidad de respuesta de la misma consulta. Sin embargo, en un entorno de alta concurrencia, el almacenamiento en caché de consultas puede causar una degradación del rendimiento, por lo que se abandonó en la última versión de MySQL.

En general, la implementación subyacente de MySQL incluye componentes y tecnologías como motor de almacenamiento, sistema de archivos, optimizador de consultas, motor de ejecución de consultas, control de bloqueo y concurrencia, sistema de registro y administración de caché. Estos componentes y tecnologías trabajan juntos para permitir que MySQL proporcione servicios de base de datos de alto rendimiento, confiables y escalables.

Nien recordó que si desea comprender a fondo la implementación subyacente de mysql, puede seguir el video "Comenzando desde 0, escribiendo a mano mysql paso a paso" del equipo de arquitectura de Nien, y escriba su propio mysql a mano para obtener una comprensión más profunda. comprensión de mysql.

36. Hable sobre la lógica general de la implementación subyacente de Spring IOC, AOP y MVC

Spring Framework es un marco de desarrollo de aplicaciones empresariales Java de código abierto que proporciona una solución ligera y no intrusiva para crear aplicaciones empresariales. El núcleo del marco Spring es el contenedor Spring IOC (Inversion of Control, inversión de control), Spring AOP (Programación orientada a aspectos, programación orientada a aspectos) y Spring MVC (Modelo-Vista-Controlador, modelo-vista-controlador) .

1. La lógica de implementación subyacente de Spring IOC:

  • El contenedor Spring IOC es responsable de administrar y organizar la creación y el ciclo de vida de los objetos (también conocidos como beans) en la aplicación. Utiliza el método de inversión de control para entregar la creación de objetos y la gestión de dependencias al contenedor.
  • La lógica de implementación subyacente de Spring IOC incluye: definición de metadatos de configuración de beans (generalmente usando XML o anotaciones), análisis de metadatos de configuración, creación de instancias de beans, manejo de dependencias entre beans y administración de ciclos de vida de beans.
  • Cuando se inicia la aplicación, el contenedor Spring IOC leerá la información del bean definida en el archivo de configuración o la anotación, creará la instancia del bean correspondiente de acuerdo con la información de configuración y la almacenará en el contenedor. Cuando otros componentes necesiten usar estos beans, el contenedor inyectará los beans relevantes donde se necesiten a través de la inyección de dependencia.

2. La lógica de implementación subyacente de Spring AOP:

  • Spring AOP es una tecnología basada en la programación orientada a aspectos, que realiza la modularización y reutilización del código al separar las preocupaciones transversales (como registros, transacciones, seguridad, etc.) de la lógica comercial.
  • La lógica de implementación subyacente de Spring AOP incluye: definir puntos de corte (especificar en qué métodos aplicar la lógica de aspecto), definir aspectos (códigos que contienen lógica de aspecto), tejer aspectos en objetos de destino, etc.
  • En tiempo de ejecución, Spring AOP teje la lógica de aspecto en la llamada al método del objeto de destino a través de la tecnología de proxy dinámico. Cuando se llama al método del objeto de destino, la lógica de aspecto se ejecutará antes y después del método para realizar la función de las preocupaciones transversales.

3. La lógica de implementación subyacente de Spring MVC:

  • Spring MVC es un marco de aplicación web basado en el patrón de diseño MVC. Realiza el desacoplamiento de la lógica empresarial y la visualización de la interfaz al separar las diferentes capas de la aplicación (modelo, vista, controlador).
  • La lógica de implementación subyacente de Spring MVC incluye: definir reglas de mapeo de solicitudes, procesar solicitudes y respuestas, invocar procesadores de lógica empresarial, renderizar vistas, etc.
  • Cuando se inicia la aplicación, el contenedor Spring MVC se inicializa y carga la información de configuración, incluidas las reglas de asignación de solicitudes, los solucionadores de vistas y más. Al recibir una solicitud de cliente, el contenedor encontrará el procesador correspondiente de acuerdo con las reglas de asignación de solicitudes y llamará al método correspondiente para procesar la solicitud. El procesador devuelve el resultado del procesamiento al contenedor, y el contenedor presenta el resultado en una vista final de acuerdo con el sistema de resolución de vista y finalmente lo devuelve al cliente.

En resumen, la lógica de implementación subyacente del marco Spring incluye el análisis de metadatos de configuración, la creación y gestión de objetos, la inyección de dependencia y la aplicación de tecnologías de proxy dinámico. A través de estas tecnologías, Spring realiza la inversión de control, la programación orientada a aspectos y el desarrollo de aplicaciones web basadas en MVC. Estas lógicas de implementación subyacentes permiten a los desarrolladores centrarse más en la implementación de la lógica empresarial, mejorando la capacidad de mantenimiento y la escalabilidad del código.

37. Hable brevemente sobre los patrones de diseño utilizados en los marcos con los que está familiarizado

En el desarrollo de Java, se utilizan muchos patrones de diseño en marcos de trabajo de uso común. Estos son algunos ejemplos de marcos Java comunes y los patrones de diseño que utilizan:

1. Marco de primavera:

  • Inyección de dependencia (Inyección de dependencia) : el marco Spring utiliza el patrón de diseño de inyección de dependencia para lograr el desacoplamiento y la flexibilidad mediante la inyección de objetos.
  • Modo singleton (Singleton) : el Bean en el marco Spring es un singleton por defecto, y el modo singleton garantiza que solo haya una instancia en toda la aplicación.

Dos, Apache Kafka:

  • Modo de publicación-suscripción (Publish-Subscribe) : Kafka utiliza el modo de publicación-suscripción para realizar la entrega y el procesamiento de mensajes. El productor publica el mensaje en el tema (Tema), y el consumidor se suscribe al tema y recibe el mensaje.
  • Modo de adaptador (Adapter) : Kafka proporciona varios adaptadores para interactuar con diferentes fuentes de datos, como Kafka Connect para el intercambio de datos con sistemas externos.

3. Marco MyBatis:

  • Objeto de acceso a datos (DAO) : el marco MyBatis utiliza el patrón de diseño DAO para proporcionar una interfaz de acceso a datos simplificada al encapsular las operaciones de la base de datos.

Cuatro, marco Spring Boot:

  • Modo constructor (Builder) : Spring Boot utiliza el modo constructor para construir la configuración y el entorno de la aplicación, creando y configurando objetos a través de llamadas en cadena.
  • Fachada : el marco Spring Boot proporciona una configuración simplificada y funciones automatizadas, lo que oculta la complejidad subyacente y facilita a los desarrolladores el uso y la administración de aplicaciones.

Estos son solo algunos ejemplos comunes. De hecho, hay muchos patrones de diseño utilizados en los marcos de Java. Diferentes marcos pueden usar diferentes patrones de diseño para resolver problemas específicos. El uso de patrones de diseño puede mejorar la capacidad de mantenimiento, la flexibilidad y la escalabilidad del código, lo que hace que el proceso de desarrollo sea más eficiente y estandarizado.

38. Hable sobre los patrones de diseño utilizados en el proyecto.

En proyectos comunes de Java, a menudo usamos los siguientes patrones de diseño:

  1. Patrón Singleton : se utiliza para garantizar que una clase tenga solo una instancia y proporcione un punto de acceso global. Los escenarios de aplicaciones comunes incluyen grupos de conexiones de bases de datos, grupos de subprocesos, etc.
  2. Patrón de fábrica : una interfaz para crear objetos, pero aplazando la lógica de instanciación concreta a las subclases. Los escenarios de aplicación comunes incluyen biblioteca de registro, controlador de base de datos, etc.
  3. Patrón de observador : define una relación de dependencia de uno a muchos entre objetos, de modo que cuando un objeto cambia de estado, todos los objetos que dependen de él serán notificados y actualizados automáticamente. Los escenarios de aplicaciones comunes incluyen detectores de eventos, colas de mensajes, etc.
  4. Adapter Pattern (Patrón de adaptador) : Convierte la interfaz de una clase en otra interfaz esperada por el cliente, para que las clases que no pudieron funcionar juntas debido a interfaces incompatibles puedan trabajar juntas. Los escenarios de aplicaciones comunes incluyen la adaptación de diferentes versiones de las API a una interfaz unificada.
  5. Patrón de estrategia (Strategy Pattern) : define una serie de algoritmos y encapsula cada algoritmo para que puedan reemplazarse entre sí. Los escenarios de aplicación comunes incluyen algoritmos de clasificación, métodos de pago, etc.
  6. Patrón de método de plantilla : define el esqueleto de un algoritmo, delegando la implementación de algunos pasos a las subclases. Los escenarios de aplicación comunes incluyen métodos de ciclo de vida en el marco, flujo de algoritmo, etc.
  7. Patrón de decorador : agregue dinámicamente responsabilidades adicionales a un objeto sin cambiar su interfaz. Los escenarios de aplicaciones comunes incluyen clases contenedoras para flujos de E/S, registro, etc.
  8. Builder Pattern : Separar el proceso de construcción de un objeto complejo de su representación, de forma que un mismo proceso de construcción pueda crear diferentes representaciones. Los escenarios de aplicaciones comunes incluyen la creación de objetos de datos complejos, objetos de configuración, etc.
  9. Patrón de iterador : proporciona una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación interna. Los escenarios de aplicación comunes incluyen el recorrido y la búsqueda de clases de colección.
  10. Patrón de proxy : proporcione un proxy para que otros objetos controlen el acceso a este objeto. Los escenarios de aplicación comunes incluyen agentes remotos, agentes virtuales, etc.

Estos patrones de diseño se proponen para resolver problemas específicos y son ampliamente utilizados en proyectos prácticos. Se pueden seleccionar diferentes patrones de diseño según las necesidades específicas para mejorar la legibilidad, el mantenimiento y la escalabilidad del código.

39. Habla sobre los componentes principales de Netty

Netty es un marco de programación de red de alto rendimiento que proporciona un conjunto de herramientas y componentes potentes y fáciles de usar para crear aplicaciones de red escalables y de alto rendimiento. Los componentes principales de Netty incluyen:

  1. Canal (channel) : El canal es la abstracción central de Netty, que representa una conexión de red y se puede usar para leer y escribir datos. El canal proporciona operaciones de E/S asincrónicas y basadas en eventos.
  2. EventLoop (bucle de eventos) : EventLoop es el mecanismo de procesamiento de eventos de Netty, que es responsable de manejar todos los eventos de E/S y ejecutar tareas. Cada canal está vinculado a un EventLoop para manejar todos los eventos en el canal.
  3. ChannelHandler (procesador de canales) : ChannelHandler es el procesador de eventos de Netty, que se encarga de procesar varios eventos en el Canal, como el establecimiento de la conexión, la lectura y escritura de datos, etc. Los desarrolladores pueden manejar una lógica empresarial específica mediante la implementación de un ChannelHandler personalizado.
  4. ChannelPipeline (channel pipeline) : ChannelPipeline es el contenedor de ChannelHandler, que se encarga de gestionar y programar el orden de ejecución de ChannelHandler. Cuando se activa un evento, comenzará desde el encabezado de ChannelPipeline y llamará al método de procesamiento de eventos de cada ChannelHandler a su vez.
  5. Codec (códec) : Codec es el códec de Netty, que se encarga de convertir los datos entre la red y la aplicación. Netty proporciona una serie de códecs, incluidos decodificadores basados ​​en longitud, códecs de cadena, códecs de serialización de objetos y más.
  6. Bootstrap (clase de orientación) : Bootstrap es la clase de arranque de Netty para configurar e iniciar aplicaciones de Netty. Proporciona un conjunto de métodos para establecer varios parámetros, como el grupo de bucles de eventos, el tipo de canal, ChannelHandler, etc.
  7. ChannelFuture (Futuro del canal) : ChannelFuture es el resultado de la operación asíncrona de Netty, que representa una operación que aún no se ha completado. A través de ChannelFuture, puede obtener el resultado de la operación, agregar oyentes, etc.

Estos componentes juntos constituyen la arquitectura central de Netty y, a través de su trabajo colaborativo, puede crear fácilmente aplicaciones de red escalables y de alto rendimiento.

Nien, un arquitecto de 40 años, recordó : Netty no es solo el foco absoluto de la entrevista, sino también la dificultad absoluta de la entrevista.

Se recomienda que tenga una comprensión profunda y detallada. Para contenido específico, consulte el PDF de "Colección de entrevistas de Nin Java - Tema 25: Preguntas de la entrevista de Netty". Este tema tiene una introducción sistemática, sistemática y completa a Neto.

Si desea incluir el combate de Netty en su currículum, puede pedirle orientación a Nien.

40. Al usar dubbo para hacer llamadas remotas, el consumidor necesita varios hilos

Al usar Dubbo para llamadas remotas, el consumidor necesita usar varios subprocesos para manejar diferentes tareas. Específicamente, el consumidor necesita usar los siguientes hilos:

  1. Hilo principal : El hilo principal se encarga de recibir las solicitudes de los consumidores y enviarlas a los proveedores. El subproceso principal también es responsable de recibir la respuesta del proveedor y devolver la respuesta a la persona que llama al consumidor.
  2. Subproceso de E/S : el subproceso de E/S es responsable de procesar las operaciones de E/S de la red, incluido el envío de solicitudes y la recepción de respuestas. Estos subprocesos suelen utilizar la tecnología NIO (IO sin bloqueo), que puede manejar de manera eficiente una gran cantidad de solicitudes simultáneas.
  3. Grupo de subprocesos : el consumidor también puede configurar un grupo de subprocesos para procesar la lógica comercial del consumidor. Cuando el consumidor recibe la respuesta del proveedor, puede entregar la respuesta a los subprocesos en el grupo de subprocesos para su procesamiento. Esto puede evitar el bloqueo del subproceso principal y mejorar la capacidad general de procesamiento simultáneo.

Cabe señalar que la cantidad de hilos se puede configurar de acuerdo con las necesidades específicas. Por lo general, el tamaño del grupo de subprocesos se puede determinar de acuerdo con los requisitos de carga y rendimiento del consumidor. Los grupos de subprocesos más grandes pueden manejar más solicitudes simultáneas, pero también consumen más recursos del sistema. Por lo tanto, debe pesarse y ajustarse de acuerdo con la situación real.

En resumen, cuando se usa Dubbo para llamadas remotas, el consumidor necesita el subproceso principal, el subproceso IO y un grupo de subprocesos para procesar solicitudes y respuestas, y la capacidad de procesamiento concurrente se puede configurar de acuerdo con las necesidades reales.

41. Hable sobre la asignación y optimización de la memoria

La asignación de memoria es un vínculo importante en un sistema informático, implica cómo asignar y administrar los recursos de memoria para los programas. La siguiente es una descripción detallada de la asignación y optimización de la memoria:

1. Método de asignación de memoria:

  • Pila : La pila es un área de memoria utilizada para almacenar variables locales e información de llamadas a funciones. Su asignación y liberación la realiza automáticamente el compilador, que tiene una mayor velocidad de asignación y liberación, pero una menor capacidad.
  • Montón : el montón es un área utilizada para almacenar objetos de memoria asignados dinámicamente. El desarrollador controla manualmente su asignación y liberación, y tiene una gran capacidad, pero la velocidad de asignación y liberación es lenta.
  • Área de almacenamiento estático (Static Storage) : El área de almacenamiento estático se utiliza para almacenar variables globales y variables estáticas.Se asigna cuando se inicia el programa y no se libera hasta el final del programa.

2. Optimización de asignación de memoria:

  • Utilice una estructura de datos adecuada : elegir una estructura de datos adecuada para su problema puede reducir el uso de la memoria. Por ejemplo, el uso de una matriz en lugar de una lista vinculada puede reducir la sobrecarga de los punteros.
  • Evite la fragmentación de la memoria : la fragmentación de la memoria se refiere a la existencia de algún espacio disperso sin usar en la memoria. La fragmentación de la memoria se puede reducir mediante el uso de grupos de memoria o algoritmos de asignación de memoria (como los asignadores).
  • Liberar la memoria no utilizada a tiempo : cuando un objeto ya no es necesario, la memoria ocupada por él se libera a tiempo para que otros objetos puedan usar la memoria.
  • Utilice la agrupación de objetos : la agrupación de objetos es una técnica que crea previamente y almacena varios objetos en la memoria. Al reutilizar estos objetos, se puede reducir la sobrecarga de asignación y desasignación de memoria.
  • Memoria comprimida : algunos algoritmos de compresión pueden comprimir datos en la memoria, lo que reduce el uso de la memoria. Por ejemplo, utilizando algoritmos de compresión de diccionario o codificación Huffman.

3. Notas sobre la asignación de memoria:

  • Evite las fugas de memoria : una fuga de memoria se refiere a un programa que no libera memoria correctamente después de usarlo, lo que hace que otros objetos ya no puedan utilizarla. Es necesario prestar atención a la liberación de la memoria no utilizada a tiempo para evitar fugas de memoria.
  • Evite el desbordamiento de memoria : El desbordamiento de memoria significa que la memoria solicitada por el programa excede los recursos de memoria que el sistema puede proporcionar. Es necesario estimar razonablemente el uso de memoria del programa y hacer un buen trabajo en la gestión y optimización de la memoria para evitar problemas de desbordamiento de memoria.

En resumen, la asignación y optimización de la memoria es un proceso que considera integralmente el rendimiento y la utilización de los recursos. Mediante la selección de estructuras de datos apropiadas, la liberación de memoria a tiempo y el uso de grupos de objetos y otros medios técnicos, se puede mejorar la eficiencia del uso de la memoria y el rendimiento del programa. Al mismo tiempo, debe prestar atención para evitar problemas como pérdidas de memoria y desbordamientos de memoria para garantizar la estabilidad y confiabilidad del programa.

42. ¿Cómo evita que las personas deslicen cupones repetidamente?

Para evitar que los cupones se deslicen repetidamente, se pueden considerar los siguientes métodos:

  1. Limitar el número de veces de uso : En el diseño del cupón se puede establecer un límite en el número de veces de uso. Cada vez que se usa un cupón, el sistema verifica si el cupón se ha usado el límite y lo rechaza si se ha usado.
  2. Vincular la información del usuario : Vincule el cupón con el usuario para asegurarse de que cada usuario solo pueda usarlo una vez. Cuando un usuario utiliza un cupón, el sistema verificará si el usuario ya ha utilizado el cupón y, si se ha utilizado, se negará a utilizarlo de nuevo.
  3. Período de validez del diseño : establezca un período de validez para los cupones para asegurarse de que solo se puedan usar dentro del período de validez. Cuando un usuario utiliza un cupón, el sistema verificará si la hora actual está dentro del período de validez del cupón y, de no ser así, se negará a utilizarlo.
  4. Usa identificadores únicos : genera un identificador único para cada cupón, guárdalo en una base de datos o caché. Cuando el usuario utilice el cupón, el sistema comprobará si se ha utilizado el identificador y, en caso de que se haya utilizado, se negará a volver a utilizarlo.
  5. Evite el deslizamiento malicioso de cupones : supervise los registros del sistema para detectar comportamientos anormales. Por ejemplo, detectar el uso frecuente de cupones por parte de un mismo usuario en un corto período de tiempo, o detectar el uso simultáneo de cupones por varios usuarios bajo la misma dirección IP. Si se encuentra un comportamiento anormal, se pueden tomar las medidas correspondientes, como prohibir temporalmente que el usuario use cupones.

Es necesario seleccionar el método de cupón de cupón anti-repetición apropiado de acuerdo con el escenario comercial específico y la arquitectura del sistema, y ​​usarlo en una combinación razonable.

Para obtener más información, consulte el artículo especial de Nien: Meituan es demasiado despiadado: la interfaz muestra 10Wqps maliciosamente, ¿cómo evitarlo?

43. Hay una matriz de enteros, los elementos de la matriz no se repiten y los elementos de la matriz están primero en orden ascendente

Para implementar una matriz de enteros en la que los elementos de la matriz no se repiten y se organizan en orden ascendente, se pueden utilizar los siguientes métodos y principios:

  1. Cree una matriz de enteros vacía para almacenar el resultado final.
  2. Itera sobre la matriz de enteros dada.
  3. Para cada elemento, verifique si ya existe en la matriz resultante.
  4. Si no está presente, el elemento se inserta en la posición correcta en la matriz resultante, manteniendo el orden ascendente.
  5. Devuelve la matriz resultante como resultado final.

Aquí hay un código de muestra implementado en Java:

import java.util.Arrays;

public class SortedArray {
    
    
    public static int[] createSortedArray(int[] arr) {
    
    
        int[] result = new int[arr.length];
        int index = 0;

        for (int i = 0; i < arr.length; i++) {
    
    
            if (index == 0 || arr[i] > result[index - 1]) {
    
    
                result[index++] = arr[i];
            }
        }

        return Arrays.copyOf(result, index);
    }

    public static void main(String[] args) {
    
    
        int[] arr = {
    
    1, 3, 2, 5, 4};
        int[] sortedArr = createSortedArray(arr);
        System.out.println(Arrays.toString(sortedArr));
    }
}

En el código anterior, usamos createSortedArrayel método para crear una matriz de números enteros ordenados en orden ascendente. Usamos resultuna matriz para almacenar el resultado final y indexuna variable para realizar un seguimiento de la posición del índice actual de la matriz de resultados. Iteramos a través de la matriz dada, y si el elemento actual es mayor que el último elemento en la matriz de resultados, lo insertamos en la posición correcta en la matriz de resultados, incrementándolo index. Finalmente, usamos Arrays.copyOfel método para truncar la matriz resultante al tamaño real y devolverla como el resultado final.

En el código de ejemplo, la matriz dada es {1, 3, 2, 5, 4}y el resultado final es {1, 2, 3, 4, 5}.

44. En orden descendente, encuentra el valor máximo

Para implementar el orden descendente y encontrar el valor máximo, se pueden usar los siguientes pasos y métodos:

  1. Crear una matriz de enteros.
  2. Almacene un conjunto de enteros en una matriz mediante un bucle.
  3. Ordena una matriz en orden descendente mediante un algoritmo de ordenación, como la ordenación por burbujas, la ordenación por selección o la ordenación rápida.
  4. Muestra el primer elemento de la matriz ordenada, que es el valor máximo.

Aquí hay un código de muestra implementado en Java:

import java.util.Arrays;

public class DescendingOrder {
    
    
    public static void main(String[] args) {
    
    
        int[] numbers = {
    
    5, 2, 9, 1, 7}; // 示例整数数组

        // 使用Arrays.sort()方法对数组进行降序排序
        Arrays.sort(numbers);
        for (int i = 0; i < numbers.length / 2; i++) {
    
    
            int temp = numbers[i];
            numbers[i] = numbers[numbers.length - 1 - i];
            numbers[numbers.length - 1 - i] = temp;
        }

        // 输出排序后的数组
        System.out.println("降序排序后的数组:");
        for (int number : numbers) {
    
    
            System.out.print(number + " ");
        }

        // 输出最大值
        System.out.println("\n最大值:" + numbers[0]);
    }
}

El código de muestra usa el método sort() de la clase Arrays para ordenar la matriz en orden descendente. Luego, busque el primer elemento ordenado, que es el valor máximo, recorriendo la matriz. Finalmente, genere la matriz ordenada y el valor máximo.

Tenga en cuenta que esta es solo una forma de hacerlo, existen otros algoritmos y técnicas de clasificación para lograr un orden descendente y encontrar el valor máximo.

nian dijo al final

En la comunidad de lectores de Nien (más de 50), muchos pequeños socios necesitan ingresar a una gran fábrica y obtener un salario alto.

El equipo de Nien continuará combinando las preguntas de entrevistas reales de algunas de las principales empresas para resolver el camino de aprendizaje para usted y ver qué necesita aprender.

En el artículo anterior, utilicé muchos artículos para presentar las preguntas reales de Ali, Baidu, Byte y Didi:

" Explotó... Jingdong pidió 40 preguntas y 50W+ después de aprobar "

" Las preguntas están entumecidas... Ali hizo 27 preguntas al mismo tiempo, y 60W+ después de pasar "

" Baidu pidió locamente 3 horas, Dachang recibió una oferta, ¡el tipo es tan despiadado!" "

" Eres demasiado despiadado: enfréntate a un Java avanzado, qué duro y despiadado es "

" Una hora de locura de bytes, el tipo recibió la oferta, ¡es demasiado despiadado!" "

" Acepta una oferta de Didi: de las tres experiencias del chico, ¿qué necesitas aprender? "

Estas preguntas reales se incluirán en el libro electrónico en PDF más completo y continuamente actualizado " Nin's Java Interview Collection " de la historia.

Este artículo está incluido en la edición V84 de "Nin's Java Interview Collection".

Básicamente, si comprende a fondo la "Colección de entrevistas de Ninan Java" de Nien, es fácil obtener ofertas de grandes empresas. Además, si tiene alguna necesidad en el próximo número de Dachang Interview, puede enviar un mensaje a Nien.

Lectura relacionada recomendada

" A partir de 0, Handwriting Redis "

" A partir de 0, Handwriting MySQL Transaction ManagerTM "

" A partir de 0, escritura a mano MySQL Data Manager DM "

" Tencent es demasiado despiadado: 4 mil millones de cuentas QQ, dada la memoria 1G, ¿cómo deduplicar? "

"Notas de arquitectura de Nin", "Trilogía de alta concurrencia de Nin", "Colección de entrevistas de Java de Nin" PDF, vaya a la siguiente cuenta oficial [Technical Freedom Circle] para tomarlo↓↓↓

Supongo que te gusta

Origin blog.csdn.net/crazymakercircle/article/details/131678859
Recomendado
Clasificación