Todavía no entiendo la consistencia de datos entre Redis y MySQL en la entrevista, solo lea este artículo

1. ¿Qué es la coherencia de la base de datos y la memoria caché?

La consistencia de los datos se refiere a:

  • Hay datos en la memoria caché y el valor de los datos almacenados en la memoria caché = el valor en la base de datos;

  • Los datos no están en el caché, el valor en la base de datos = último valor.

La caché de inserción inversa es inconsistente con la base de datos:

  • El valor de datos en caché ≠ el valor en la base de datos;

  • Hay datos antiguos en la memoria caché o en la base de datos, lo que hace que el subproceso lea los datos antiguos.

¿Por qué surgen los problemas de coherencia de datos?

Al usar Redis como caché, cuando los datos cambian, debemos escribir dos veces para garantizar que el caché sea coherente con los datos de la base de datos.

Después de todo, la base de datos y el caché son dos sistemas. Si desea garantizar una consistencia fuerte, debe introducir  2PC o  Paxos esperar protocolos de consistencia distribuida, o bloqueos distribuidos, etc. Esto es difícil de implementar y definitivamente afectará el rendimiento. Influencia.

Si los requisitos para la consistencia de los datos son realmente tan altos, ¿es realmente necesario introducir el almacenamiento en caché?

2. Estrategia de uso de caché

Cuando se utiliza la memoria caché, normalmente existen las siguientes estrategias de uso de la memoria caché para mejorar el rendimiento del sistema:

  • Cache-Aside Pattern(Omitir caché, comúnmente utilizado en sistemas comerciales)

  • Read-Through Pattern

  • Write-Through Pattern

  • Write-Behind Pattern

2.1 Caché aparte (omitir caché)

El llamado "caché de derivación" significa que las operaciones de lectura del caché, lectura de la base de datos y actualización del caché se completan en el sistema de la aplicación , que es la estrategia de almacenamiento en caché más utilizada para los sistemas comerciales .

2.1.1 Leer datos

imagen

La lógica de lectura de datos es la siguiente:

  1. Cuando la aplicación necesita leer datos de la base de datos, primero verifica si los datos de la caché coinciden.

  2. Si el caché falla, consulte la base de datos para obtener los datos y escriba los datos en el caché al mismo tiempo, de modo que las lecturas posteriores de los mismos datos lleguen al caché y finalmente devuelvan los datos a la persona que llama.

  3. Si el caché golpea, regresa directamente.

El cronograma es el siguiente:

imagen

Omitir diagrama de tiempo de lectura de caché

ventaja

  • Solo los datos realmente solicitados por la aplicación se incluyen en el caché, lo que ayuda a mantener el tamaño del caché rentable.

  • Es simple de implementar y puede lograr mejoras de rendimiento.

El pseudocódigo de la implementación es el siguiente:

String cacheKey = "公众号:码哥字节";
String cacheValue = redisCache.get(cacheKey);
//缓存命中
if (cacheValue != null) {
  return cacheValue;
} else {
  //缓存缺失, 从数据库获取数据
  cacheValue = getDataFromDB();
  // 将数据写到缓存中
  redisCache.put(cacheValue)
}

defecto

Dado que los datos solo se cargan en la memoria caché después de una falla de la memoria caché, existe cierta sobrecarga en el tiempo de respuesta de la solicitud de datos para la llamada inicial debido al tiempo de consulta de la base de datos y la ocupación adicional de la memoria caché requerida.

2.1.2 Actualizar datos

Cuando se usa  cache-aside el modo para escribir datos, el flujo es el siguiente.

imagen

Omitir datos de escritura de caché

  1. escribir datos en la base de datos;

  2. invalidar los datos en el caché o actualizar los datos almacenados en caché;

Cuando se usa  cache-aside , la estrategia de escritura más común es escribir datos directamente en la base de datos, pero el caché puede volverse inconsistente con la base de datos.

Deberíamos establecer un tiempo de caducidad para el caché, que es una solución para garantizar la coherencia eventual.

Si el tiempo de caducidad es demasiado corto, la aplicación consultará constantemente la base de datos en busca de datos. Del mismo modo, si el tiempo de caducidad es demasiado largo y la memoria caché no se invalida cuando se produce la actualización, es probable que los datos almacenados en la memoria caché estén sucios.

La forma más común es eliminar el caché para invalidar los datos almacenados en caché .

¿Por qué no actualizar el caché?

problema de rendimiento

Cuando el costo de actualización de la memoria caché es alto y es necesario acceder a varias tablas para el cálculo conjunto, se recomienda eliminar la memoria caché directamente en lugar de actualizar los datos de la memoria caché para garantizar la coherencia.

Pregunta de seguridad

En un escenario de alta concurrencia, puede causar que los datos encontrados en la consulta sean valores antiguos. Analizaré los detalles más adelante, así que no te preocupes.

2.2 Read-Through (lectura directa)

Cuando la memoria caché falla, los datos también se cargan desde la base de datos, se escriben en la memoria caché y se devuelven al sistema de aplicación.

Aunque es  read-through muy  similar  a cache-aside ,  el sistema de aplicación es responsable de obtener datos de la base de datos y llenar el caché.cache-aside

Read-Through, por otro lado, transfiere la responsabilidad de obtener el valor en el almacén de datos al proveedor de caché.

imagen

Leer de parte a parte

Read-Through implementa el principio de separación de preocupaciones. El código solo interactúa con el caché, y el componente del caché administra la sincronización de datos entre él y la base de datos.

2.3 Escritura directa síncrona de escritura simultánea

Similar a Read-Through, cuando ocurre una solicitud de escritura, Write-Through transfiere la responsabilidad de escritura al sistema de caché, y la capa de abstracción de caché completa la actualización de los datos almacenados en caché y de la base de datos. El diagrama de flujo de tiempo es el siguiente :

imagen

Escriba por medio de

Write-Through La principal ventaja es que el sistema de aplicación no necesita considerar el manejo de fallas ni la lógica de reintento, y se transfiere a la capa de abstracción de caché para administrar la implementación.

Ventajas y desventajas

No tiene sentido usar esta estrategia directamente, porque esta estrategia necesita escribir primero en el caché y luego escribir en la base de datos, lo que genera un retraso adicional en la operación de escritura.

Cuando  se usa  Write-Through junto  con , puede aprovechar al máximo  sus ventajas al tiempo que garantiza la coherencia de los datos sin considerar cómo invalidar la configuración de la memoria caché.Read-ThroughRead-Through

imagen

Escriba por medio de

Esta estrategia invierte  Cache-Aside el orden de llenado de la memoria caché. En lugar de retrasar la carga en la memoria caché después de un error de memoria caché, los datos se escriben primero en la memoria caché y luego el componente de la memoria caché escribe los datos en la base de datos .

ventaja

  • Los datos de la caché y la base de datos están siempre actualizados;

  • El rendimiento de las consultas es el mejor, porque es posible que los datos que se consultarán ya se hayan escrito en la memoria caché.

defecto

Los datos solicitados con poca frecuencia también se escriben en la memoria caché, lo que da como resultado una memoria caché más grande y costosa.

2.4 Escritura diferida

Esta imagen parece ser la  Write-Through misma a primera vista, pero no lo es, la diferencia es la flecha de la última flecha: cambia de sólido a línea.

Esto significa que el sistema de caché actualizará los datos de la base de datos de forma asíncrona y el sistema de la aplicación solo interactúa con el sistema de caché .

Las aplicaciones no tienen que esperar a que se completen las actualizaciones de la base de datos, lo que mejora el rendimiento de la aplicación porque las actualizaciones de la base de datos son las operaciones más lentas.

imagen

Escritura posterior

Bajo esta estrategia, la consistencia entre el caché y la base de datos no es fuerte y no se recomienda para sistemas con alta consistencia.

3. Análisis de problemas de consistencia bajo bypass cache

La estrategia (omitir el almacenamiento en caché) se usa más comúnmente en escenarios comerciales  Cache-Aside Bajo esta estrategia, el cliente lee los datos del caché primero y regresa si acierta; si falla, lee de la base de datos y escribe los datos en el caché , por lo que las operaciones de lectura no provocarán incoherencias entre la memoria caché y la base de datos.

La atención se centra en las operaciones de escritura. Tanto la base de datos como la memoria caché deben modificarse, y habrá una secuencia entre los dos, lo que puede hacer que los datos ya no sean coherentes . Para escribir, debemos considerar dos cuestiones:

  • ¿Actualizar el caché primero o actualizar la base de datos?

  • Cuando los datos cambian, ¿elige modificar el caché (actualizar) o eliminar el caché (eliminar)?

Combinando estas dos preguntas, surgen cuatro soluciones:

  1. Primero actualice el caché, luego actualice la base de datos;

  2. Primero actualice la base de datos, luego actualice el caché;

  3. Elimine el caché primero, luego actualice la base de datos;

  4. Primero actualice la base de datos, luego elimine el caché.

En el siguiente análisis, no tienes que memorizarlo de memoria, la clave es que solo debes considerar si los siguientes dos escenarios causarán problemas serios durante el proceso de deducción:

  • Cuando la primera operación tiene éxito y la segunda falla, ¿cuál sería la causa del problema?

  • ¿Causará inconsistencia en la lectura de datos bajo alta concurrencia?

¿Por qué no considerar el primer fracaso y el segundo éxito?

¿adivina?

Dado que el primero falla, no hay necesidad de ejecutar el segundo, solo devuelva 50x y otra información anormal en el primer paso, y no habrá inconsistencia.

Solo cuando el primero tiene éxito, el segundo fracaso es un dolor de cabeza.Para asegurar su atomicidad, involucra el alcance de las transacciones distribuidas.

3.1 Primero actualice el caché, luego actualice la base de datos

imagen

Primero actualice el caché y luego actualice la base de datos

Si la actualización de la memoria caché es exitosa primero, pero falla la escritura en la base de datos, la memoria caché contendrá los datos más recientes y la base de datos contendrá datos antiguos, entonces la memoria caché será datos sucios.

Después de eso, cuando ingresen otras consultas de inmediato, se obtendrán estos datos, pero estos datos no existen en la base de datos.

Para los datos que no existen en la base de datos, no tiene sentido almacenarlos en caché y devolverlos al cliente.

El programa es sencillo  Pass.

3.2 Primero actualice la base de datos, luego actualice el caché

Todo funciona de la siguiente manera:

  • Escriba la base de datos primero, éxito;

  • Luego actualice el caché, éxito.

No se pudo actualizar el caché

En este momento, deduzcamos que si se rompe la atomicidad de estas dos operaciones: ¿ qué problemas surgirán si el primer paso tiene éxito y el segundo falla ?

Hará que la base de datos tenga los datos más recientes y que la memoria caché tenga datos antiguos, lo que generará problemas de coherencia.

No dibujaré esta imagen, es similar a la imagen anterior, solo cambie las posiciones de Redis y MySQL.

Escenario de alta concurrencia

Xie Bage a menudo 996, le duele la espalda y le duele el cuello, y escribe cada vez más errores. Quiere ir a un masaje para mejorar sus habilidades de programación.

Afectados por la epidemia, el pedido es difícil de conseguir, y los técnicos de los clubes de alta gama se apresuran a tomar este pedido, alta concurrencia, hermanos.

Después de ingresar a la tienda, la recepción ingresará la información del cliente en el sistema.El  set xx的服务技师 = 待定valor inicial de la ejecución indica que actualmente no hay recepción y lo guarda en la base de datos y el caché, y luego organiza el servicio de masaje del técnico.

Como se muestra abajo:

imagen

La alta concurrencia actualiza la base de datos primero, luego actualiza el caché

  1. El técnico No. 98 actuó primero y escribió  set 谢霸歌的服务技师 = 98 el comando enviado al sistema en la base de datos.En este momento, la red del sistema fluctuó y se congeló, y los datos no habían tenido tiempo de escribirse en el caché .

  2. Luego, el técnico No. 520 también envió al sistema  set 谢霸哥的服务技师 = 520para escribir en la base de datos y también escribió estos datos en el caché.

  3. En este momento, la solicitud de caché de escritura del técnico No. 98 comenzó a ejecutarse y los datos se  set 谢霸歌的服务技师 = 98 escribieron con éxito en el caché.

Finalmente se encontró que el valor de la base de datos =  set 谢霸哥的服务技师 = 520, y el valor del caché =  set 谢霸歌的服务技师 = 98.

Los últimos datos del técnico 520 en la memoria caché se sobrescriben con los datos antiguos del técnico 98.

Por lo tanto, en un escenario de alta simultaneidad, si varios subprocesos escriben datos al mismo tiempo y luego escriben en la memoria caché, habrá incoherencias en las que la memoria caché es el valor anterior y la base de datos es el valor más reciente.

El programa pasa directamente.

Si el primer paso falla, se devolverá directamente una excepción de 50x y no habrá incoherencia en los datos.

3.3 Elimine el caché primero, luego actualice la base de datos

De acuerdo con la rutina mencionada por "Brother Code", asumiendo que la primera operación es exitosa y la segunda falla, ¿qué sucederá? ¿Qué sucede en escenarios de alta concurrencia?

No se pudo escribir en la base de datos en el segundo paso

Supongamos que ahora hay dos solicitudes: la solicitud de escritura A y la solicitud de lectura B.

El primer paso de la solicitud de escritura A es eliminar el caché con éxito, pero no escribir datos en la base de datos, lo que conducirá a la pérdida de los datos escritos y la base de datos guardará el valor anterior .

Luego entra otra solicitud de lectura B y descubre que el caché no existe, lee los datos antiguos de la base de datos y los escribe en el caché.

Problemas bajo alta concurrencia

imagen

Primero elimine el caché, luego escriba la base de datos

  1. Es mejor que el técnico No. 98 actúe primero. El sistema recibe una solicitud para eliminar los datos almacenados en caché. Cuando el sistema está a punto de  set 肖菜鸡的服务技师 = 98escribir en la base de datos, se congela y es demasiado tarde para escribir.

  2. En este momento, el administrador del lobby ejecuta una solicitud de lectura al sistema para verificar si Xiao Caiji tiene una recepción técnica, a fin de organizar el servicio técnico. El sistema encuentra que no hay datos en el caché, por lo que lee los datos antiguos de la base de datos y la escribe en el caché  set 肖菜鸡的服务技师 = 待定.

  3. set 肖菜鸡的服务技师 = 98En este momento, se completa la operación de escritura de datos en la base de datos por parte del Técnico No. 98 en la congelación original  .

De esta forma, los datos antiguos se almacenarán en la memoria caché y los datos más recientes no se podrán leer antes de que caduque la memoria caché. Xiao Caiji ya había sido aceptado por el Técnico No. 98, pero el gerente del vestíbulo pensó que no había nadie allí.

Este esquema pasa, porque el primer paso es exitoso y el segundo falla, la base de datos contendrá datos antiguos y no habrá datos en el caché. Continúe leyendo valores antiguos de la base de datos y escríbalos en el caché. resultando en inconsistencia de datos, y un cahche más.

Ya sea que se trate de una situación anormal o de un escenario de alta concurrencia, dará lugar a incoherencias en los datos. extrañar.

3.4 Primero actualice la base de datos, luego elimine el caché

Después de aprobar los tres planes anteriores, todos fueron aprobados. Analicemos si el plan final funcionará o no.

De acuerdo con la "rutina", juzgue respectivamente qué problemas serán causados ​​por anormalidad y alta concurrencia.

Esta estrategia puede saber que si falla la fase de escritura en la base de datos, se devolverá una excepción al cliente y no es necesario realizar operaciones de almacenamiento en caché.

Por lo tanto, si falla el primer paso, no habrá inconsistencia de datos.

No se pudo eliminar el caché

El punto es que el primer paso es escribir los datos más recientes en la base de datos con éxito, pero ¿qué debo hacer si no se puede eliminar el caché?

Puede poner estas dos operaciones en una transacción y, cuando falla la eliminación de la memoria caché, revertir la escritura en la base de datos.

No es adecuado para escenarios de alta simultaneidad, y es probable que se produzcan grandes transacciones, lo que provoca problemas de interbloqueo.

Si no retrocede, parecerá que la base de datos son datos nuevos, el caché sigue siendo datos antiguos y los datos son inconsistentes. ¿Qué debo hacer?

Por lo tanto, tenemos que encontrar una manera de que la eliminación del caché sea exitosa, de lo contrario, solo podemos esperar hasta que expire el período de validez.

Utilice un mecanismo de reintento.

Por ejemplo, vuelva a intentarlo tres veces, si falla las tres veces, registre el registro en la base de datos y use el componente de programación distribuida xxl-job para implementar el procesamiento posterior.

En escenarios de alta simultaneidad, es mejor usar métodos asíncronos para reintentar , como enviar mensajes al middleware mq para lograr el desacoplamiento asíncrono.

O use el marco de Canal para suscribirse al registro binlog de MySQL, monitorear la solicitud de actualización correspondiente y eliminar la operación de caché correspondiente.

Escenario de alta concurrencia

Analicemos los problemas de alta lectura y escritura concurrente...

imagen

Escriba primero en la base de datos y luego elimine el caché

  1. El técnico No. 98 actuó primero y se hizo cargo del negocio de Xiao Caiji,  set 肖菜鸡的服务技师 = 98y la base de datos lo ejecutó, la red aún estaba atascada y no tuvo tiempo de ejecutar la operación de eliminación de caché .

  2. Candy, la supervisora, ejecuta una solicitud de lectura al sistema, verifica si un técnico recibe a Xiao Caiji, descubre que hay datos en el caché  肖菜鸡的服务技师 = 待定y devuelve directamente la información al cliente. uno para recibirlo.

  3. Originalmente, el Técnico No. 98 tomó la orden, pero el caché no se eliminó debido a la congelación y ahora la eliminación se realizó correctamente.

Se puede leer una pequeña cantidad de datos antiguos en una solicitud de lectura, pero los datos antiguos se eliminarán pronto y las solicitudes posteriores pueden obtener los datos más recientes, lo que no es un gran problema.

También hay una situación más extrema. Cuando el caché se invalida automáticamente, se encuentra con una situación de lectura y escritura concurrente alta. Supongamos que hay dos solicitudes, un subproceso A realiza la operación de consulta y el otro subproceso B realiza la operación de actualización, entonces se producirá la siguiente situación:

imagen

invalidación de caché

  1. El tiempo de caducidad de la memoria caché caduca y la memoria caché deja de ser válida.

  2. El subproceso A lee la solicitud para leer el caché y falla, luego consulta la base de datos para obtener un valor antiguo (porque B escribirá un valor nuevo, en términos relativos, es un valor antiguo), y cuando los datos se escriben en el caché, el problema de la red de envío está atascado .

  3. El subproceso B realiza una operación de escritura, escribiendo el nuevo valor en la base de datos.

  4. El subproceso B ejecuta la eliminación de caché.

  5. El subproceso A continúa, se despierta del bloqueo y escribe el valor antiguo consultado en la memoria caché.

Código hermano, cómo jugar esto, todavía hay una inconsistencia.

No se asuste, la probabilidad de que esto suceda es muy pequeña, las condiciones necesarias para que suceda la situación anterior son:

  1. La operación de escritura de la base de datos del paso (3) lleva menos tiempo y es más rápida que la operación de lectura del paso (2), por lo que el paso (4) puede preceder al paso (5).

  2. El caché acaba de llegar a su fecha de caducidad.

Por lo general, el QPS de MySQL independiente es de aproximadamente 5K, y el TPS es de aproximadamente 1k (ps: el QPS de Tomcat es de aproximadamente 4K, TPS = aproximadamente 1k).

Las operaciones de lectura de la base de datos son mucho más rápidas que las operaciones de escritura (es precisamente por esto que se realiza la separación de lectura y escritura), por lo que es difícil que el paso (3) sea más rápido que el paso (2), y también es necesario cooperar con el fallo de caché.

Por lo tanto, al usar la estrategia de omitir caché, se recomienda usarla para operaciones de escritura: primero actualice la base de datos y luego elimine el caché.

4. ¿Cuáles son las soluciones de consistencia?

Finalmente, para la estrategia Cache-Aside (omitir caché), cuando se usa la operación de escritura para actualizar primero la base de datos y luego eliminar la caché, analicemos cuáles son las soluciones de consistencia de datos.

4.1 Eliminación doble de retraso de caché

¿Cómo evitar datos sucios si primero elimina el caché y luego actualiza la base de datos?

Se adopta una estrategia de doble eliminación retrasada.

  1. Elimine el caché primero.

  2. Escribir en la base de datos.

  3. Dormir durante 500 milisegundos antes de eliminar el caché.

De esta forma, solo habrá un tiempo de lectura de datos sucios de hasta 500 milisegundos. La clave es cómo determinar el tiempo de sueño?

El propósito del tiempo de demora es garantizar que finalice la solicitud de lectura y que la solicitud de escritura pueda eliminar los datos sucios de la memoria caché causados ​​por la solicitud de lectura.

Por lo tanto, debemos evaluar el consumo de tiempo de la lógica comercial de lectura de datos del proyecto por nosotros mismos y agregar unos cientos de milisegundos como tiempo de retraso en función del consumo de tiempo de lectura .

4.2 Eliminar mecanismo de reintento de caché

¿Qué debo hacer si falla la eliminación de caché? Por ejemplo, si la segunda eliminación de la eliminación doble retrasada falla, significa que los datos sucios no se pueden eliminar.

Utilice el mecanismo de reintento para asegurarse de que la memoria caché se elimine correctamente.

Por ejemplo, vuelva a intentarlo tres veces, si falla las tres veces, el registro se registrará en la base de datos y se enviará una advertencia para la intervención manual.

En escenarios de alta simultaneidad, es mejor usar métodos asíncronos para reintentar , como enviar mensajes al middleware mq para lograr el desacoplamiento asíncrono.

imagen

mecanismo de reintento

Paso (5) Si la eliminación falla y no se alcanza el número máximo de reintentos, el mensaje se volverá a poner en cola hasta que la eliminación sea exitosa, de lo contrario, se registrará en la base de datos y se intervendrá manualmente.

Esta solución tiene una desventaja, que es la intrusión en el código comercial, por lo que existe la siguiente solución, que es iniciar un servicio que se suscriba específicamente a la base de datos binlog para leer los datos que deben eliminarse y realizar la operación de eliminación de caché. .

4.3 Leer binlog y eliminar de forma asíncrona

imagen

Binlog elimina de forma asíncrona

  1. actualizar la base de datos;

  2. La base de datos registrará la información de la operación en el registro binlog;

  3. Use el canal para suscribirse a los registros de binlog para obtener datos y claves de destino;

  4. El sistema de eliminación de caché obtiene datos del canal, analiza la clave de destino e intenta eliminar el caché.

  5. Si la eliminación falla, el mensaje se envía a la cola de mensajes;

  6. El sistema de eliminación de caché vuelve a obtener datos de la cola de mensajes y vuelve a realizar la operación de eliminación.

Resumir

La mejor práctica para la estrategia de almacenamiento en caché es  Cache Aside Pattern. Se dividen en mejores prácticas de almacenamiento en caché de lectura y mejores prácticas de almacenamiento en caché de escritura.

La mejor práctica de leer caché : primero lea el caché, regrese si acierta; consulte la base de datos si falla y luego escriba en el caché.

Escriba las mejores prácticas de almacenamiento en caché:

  • Escriba primero en la base de datos, luego opere el caché;

  • Elimine el caché directamente en lugar de modificarlo, ya que cuando el costo de actualización del caché es muy alto y necesita acceder a varias tablas para el cálculo conjunto, se recomienda eliminar el caché directamente en lugar de actualizarlo. borrar el caché es simple, y el efecto secundario es solo aumentar la pérdida de caché. Se recomienda que todos usen esta estrategia.

Según las mejores prácticas anteriores, para garantizar la coherencia de la memoria caché y la base de datos tanto como sea posible, podemos utilizar la eliminación doble retrasada.

Para evitar fallas en la eliminación, usamos un mecanismo de reintento asíncrono para garantizar la eliminación correcta. Con el mecanismo asíncrono, podemos enviar un mensaje de eliminación al middleware de mensajes mq, o usar canal para suscribirnos al registro binlog de MySQL para escuchar solicitudes de escritura y eliminar el caché correspondiente.

Entonces, ¿qué pasa si tengo que asegurar una consistencia absoluta? Permítanme dar una conclusión primero:

No hay forma de lograr una consistencia absoluta, que está determinada por la teoría CAP.El escenario donde se aplica el sistema de caché es el escenario de consistencia no fuerte, por lo que pertenece al AP en CAP.

Por lo tanto, tenemos que comprometernos y podemos lograr la consistencia final mencionada en la teoría BASE .

De hecho, una vez que se usa la memoria caché en la solución, a menudo significa que renunciamos a la gran consistencia de los datos, pero también significa que nuestro sistema puede mejorar un poco el rendimiento.

La llamada compensación es exactamente eso.

Si cree que este artículo es útil para usted, haga clic en Me gusta y sígalo para respaldarlo. Si desea obtener más información sobre el backend de Java, big data y la información más reciente en el campo de los algoritmos, puede seguir mi cuenta pública. [Arquitecto Lao Bi] mensaje privado 666 para obtener más publicaciones de Java Terminal, Big Data, Algorithm PDF + Clasificación de las últimas preguntas de la entrevista de Dachang + conferencia en video

Supongo que te gusta

Origin blog.csdn.net/Javatutouhouduan/article/details/131975559
Recomendado
Clasificación