Red de conocimiento informático - Material del sitio web - ¿Cómo garantizar la coherencia final del caché de la base de datos?

¿Cómo garantizar la coherencia final del caché de la base de datos?

Para las empresas de Internet, la forma tradicional de acceder directamente a la base de datos es principalmente mediante fragmentación de datos, un maestro y varios esclavos, etc., para transportar tráfico de lectura y escritura. Sin embargo, con la acumulación de volumen de datos y el. aumento del tráfico, depender únicamente de la base de datos para soportar todo el tráfico no solo es costoso e ineficiente, sino que también conlleva el riesgo de una estabilidad reducida.

Dado que la mayoría de las empresas suelen leer más y escribir menos (la frecuencia de lecturas es mucho mayor que la frecuencia de actualizaciones), incluso el número de operaciones de lectura es muchos órdenes de magnitud mayor que el número de operaciones de escritura. . Por lo tanto, en el diseño de arquitectura, generalmente se usa agregar una capa de caché para mejorar la velocidad de respuesta del sistema, mejorar el rendimiento de lectura y escritura de datos y reducir la presión del acceso a la base de datos, mejorando así la estabilidad comercial y la experiencia de acceso.

Según el principio CAP, es imposible que un sistema distribuido tenga disponibilidad, consistencia y tolerancia a fallas de partición al mismo tiempo, y debido a que la tolerancia a fallas de partición no se puede evitar, la consistencia y disponibilidad a menudo son difíciles. establecer al mismo tiempo. Para los sistemas de almacenamiento en caché, cómo garantizar la coherencia de los datos es un problema que debe resolverse al aplicar el almacenamiento en caché.

Para decirlo sin rodeos, la coherencia de los datos en un sistema de caché generalmente incluye la coherencia entre la capa de persistencia y la capa de caché, así como la coherencia entre las cachés de varios niveles, pero aquí solo analizamos la primera. El problema de coherencia entre la capa de persistencia y la capa de caché también se suele denominar problema de coherencia de doble escritura, donde "doble escritura" significa que los datos se almacenan en la base de datos y en la caché al mismo tiempo.

En términos de consistencia, existen consistencia fuerte y consistencia débil. Una coherencia fuerte garantiza que los datos escritos se puedan leer inmediatamente, mientras que la coherencia débil no garantiza que los datos escritos se puedan leer inmediatamente, pero que los datos escritos se puedan leer después de un cierto período de tiempo. En la mayoría de los escenarios de aplicaciones en los que se utiliza caché, se busca una coherencia final, mientras que en algunos escenarios de aplicaciones que requieren coherencia de datos, se busca una coherencia sólida.

Para lograr una eventual coherencia, hemos desarrollado las siguientes estrategias de almacenamiento en caché de aplicaciones para diferentes escenarios de aplicaciones.

- 1 -

Caché aparte

Caché aparte significa omitir el caché y es la estrategia de almacenamiento en caché más utilizada. La siguiente figura muestra su proceso de lectura y escritura para comprender cómo garantiza la coherencia final. En una solicitud de lectura, primero se solicita el caché y, si el caché llega, se devuelven los datos del caché; si falla el caché, se consulta la base de datos y los resultados de la consulta se actualizan en el caché, y luego se solicitan los datos consultados; devuelto (búsqueda de omisión llena de demanda). En una solicitud de escritura, la base de datos se actualiza y luego se elimina el caché (escritura invalidada).

1. ¿Por qué eliminar el caché en lugar de actualizarlo?

En Cache-Aside, el manejo de las solicitudes de lectura es relativamente fácil de entender, pero en el caso de las solicitudes de escritura, algunos lectores pueden preguntar por qué se elimina el caché en lugar de actualizarlo. Si bien actualizar el caché es una solución intuitiva y fácil de entender, actualizar el caché puede tener algunas consecuencias no deseadas desde una perspectiva de rendimiento y seguridad.

Primero, en términos de rendimiento, si los resultados almacenados en caché requieren muchos cálculos, como acceder a múltiples tablas de bases de datos y realizar cálculos conjuntos, actualizar el caché durante una operación de escritura supondrá una gran sobrecarga. Al mismo tiempo, cuando hay muchas operaciones de escritura, puede suceder que el caché simplemente se actualice y luego se actualice nuevamente antes de leer (esto a menudo se denomina perturbación del caché), lo que obviamente consumirá el rendimiento de la máquina y conducirá a una baja utilización del caché.

Esperar hasta que la solicitud de lectura pierda el caché antes de actualizar el caché también es consistente con el concepto de carga diferida, es decir, los cálculos se realizan cuando es necesario. La operación de eliminar el caché no solo es diferida y se puede volver a intentar en caso de excepciones, sino que la operación de eliminación de escritura y actualización de lectura son semánticamente más simétricas.

El segundo es la seguridad. En escenarios de aplicaciones concurrentes, la actualización del caché en una solicitud de escritura puede causar inconsistencia en los datos.

Con referencia al ejemplo siguiente, si hay dos solicitudes de escritura de subprocesos diferentes, primero la solicitud de escritura del subproceso 1 actualiza la base de datos (paso 1) y luego la solicitud de escritura del subproceso 2 actualiza la base de datos nuevamente (paso 3), pero debido a la red Debido a retrasos y otras razones, el subproceso 1 puede actualizar el caché más tarde que el subproceso 2 (el paso 4 es posterior al paso 3), lo que hace que el subproceso 2 escriba datos en la base de datos. El resultado de escribir en la base de datos es el nuevo valor del subproceso 2, mientras que el resultado de escribir en el caché es el valor anterior del subproceso 1, es decir, el caché va por detrás de la base de datos y luego la solicitud de lectura llega al caché ( paso 5) y se lee el valor anterior.

2. ¿Por qué actualizar primero la base de datos en lugar de eliminar el caché?

Además, algunos lectores pueden tener preguntas sobre el momento de actualizar la base de datos y eliminar el caché, entonces, ¿por qué no eliminar el caché primero y luego actualizar la base de datos? En el caso de un solo subproceso, esta suposición parece razonable, y el hecho de que el caché se elimine con éxito también refleja esta razonabilidad.

Sin embargo, en comparación con la situación en Cache-Aside donde la actualización de la base de datos se realiza correctamente pero la eliminación del caché falla, la situación en la que la actualización de la base de datos falla y la siguiente operación de lectura escribe los datos correctos en el caché parece ser ser una primera situación más razonable de eliminación de caché. Entonces, ¿cuál es el problema de borrar primero el caché?

El problema aún ocurre en el escenario concurrente, es decir, la solicitud de escritura del subproceso 1 elimina el caché (paso 1), y luego la solicitud de lectura del subproceso 2 pierde el caché debido a la eliminación del caché. De acuerdo con el patrón Cache-Aside, el subproceso 2 consultará la base de datos (paso 2), pero dado que las solicitudes de escritura suelen ser más lentas que las solicitudes de lectura, las actualizaciones del subproceso 1 a la base de datos pueden ser posteriores a las consultas del subproceso 2 a la base de datos. Sin embargo, dado que las solicitudes de escritura suelen ser más lentas que las solicitudes de lectura, el subproceso 1 actualiza la base de datos más tarde que el subproceso 2 consulta la base de datos y actualiza el caché (el paso 4 es posterior al paso 3), lo que hace que el subproceso 2 finalmente escriba en el caché. Se consulta el valor anterior y el subproceso 1 escribe el nuevo valor en la base de datos, lo que significa que el caché está retrasado con respecto a la base de datos, y luego hay una solicitud de lectura que llega al caché (paso 5) y lee el valor anterior.

Además, debido a la falta de datos en el caché, eliminar el caché primero puede aumentar la probabilidad de penetración del caché, lo que aumenta la presión sobre la base de datos.

3. Si elige eliminar el caché primero y luego actualizar la base de datos, ¿cómo resuelve el problema de coherencia?

Para evitar los datos sucios del caché que pueden ser causados ​​por la concurrencia de lectura y escritura en la solución "eliminar el caché primero, luego actualizar la base de datos", la industria ha propuesto una estrategia de doble eliminación retrasada, es decir, después Actualizar la base de datos, retrasar un período de tiempo antes de eliminarla. Para garantizar que la segunda eliminación del caché se produzca después de que la solicitud de lectura actualice el caché, el valor empírico de este retraso suele ser ligeramente mayor que el tiempo empleado. en la solicitud de lectura en el negocio.

El retraso se puede implementar mediante suspensión o cola de retraso en el código. Es obvio que no importa cómo se prediga este valor, difícilmente coincidirá con el punto de finalización de la solicitud de lectura, que es la razón principal por la que se ha criticado la doble eliminación retrasada.

4. Entonces, ¿existe la posibilidad de que haya inconsistencia de datos en Cache-Aside?

En Cache-Aside, también existe la posibilidad de que haya inconsistencia en los datos. En el siguiente escenario de simultaneidad de lectura/escritura, la solicitud de lectura del subproceso 1 consulta la base de datos sin llegar a la caché (paso 1) y la solicitud de escritura del subproceso 2 actualiza la base de datos (paso 2), pero por algunas razones extremas, el subproceso 1 La solicitud de lectura del subproceso 2 actualiza el caché más tarde que la solicitud de escritura del subproceso 2 elimina el caché (el paso 4 es posterior al paso 3). Esto hace que el valor antiguo del subproceso 1 se escriba en la caché y el nuevo valor del subproceso 2 se escriba en la base de datos, lo que significa que la caché está retrasada con respecto a la base de datos y luego la solicitud de lectura llega a la caché (paso 5) y lee el valor antiguo.

Esta situación no solo requiere la invalidación de la caché y la ejecución simultánea de lecturas y escrituras, sino que también requiere que las solicitudes de lectura consulten la base de datos antes de que las solicitudes de escritura actualicen la base de datos, y que las solicitudes de lectura se completen más tarde que las solicitudes de escritura. Como puede ver, las condiciones para esta inconsistencia son muy estrictas y es menos probable que ocurra en la producción real.

Además, en un entorno concurrente, la solicitud de lectura Cache-Aside llegará a la caché después de que la solicitud de escritura actualice la base de datos y antes de que se elimine la caché, lo que también hará que la solicitud de lectura consulte la caché. quedarse atrás de la base de datos.

Aunque el caché se actualizará en la siguiente solicitud de lectura, si el nivel empresarial tiene una tolerancia baja para esto, se puede usar un bloqueo para garantizar que la cadena "actualizar base de datos y eliminar caché" en la escritura La ejecución de la fila de solicitudes es atómica (de manera similar, los bloqueos se pueden usar para actualizar el caché en solicitudes de lectura). El bloqueo conducirá inevitablemente a una disminución del rendimiento, por lo que el esquema de bloqueo debe tener en cuenta la pérdida de rendimiento.

- 2 -

Mecanismo de compensación

Mencionamos anteriormente que hay un problema en Cache-Aside donde se actualiza la base de datos exitosamente pero se eliminó la situación de falla de almacenamiento en caché. Si esto sucede, los datos en el caché quedarán detrás de la base de datos, lo que dará como resultado datos inconsistentes.

De hecho, este problema existe no sólo en Cache-Aside, sino también en estrategias como la doble eliminación retrasada. Existen varios mecanismos de compensación ante posibles fallos de eliminación.

1. Mecanismo de reintento de eliminación

Dado que el reintento de eliminación sincrónico afectará el rendimiento del rendimiento, generalmente puede introducir una cola de mensajes para eliminar el caché correspondiente a la eliminación fallida. la cola de mensajes y la clave cuya eliminación falló se obtienen en el consumidor correspondiente para volver a intentar la eliminación de forma asíncrona. La implementación de este método es relativamente simple, pero debido a que la lógica después de que falla la eliminación debe activarse mediante un disparador basado en el código comercial, es algo intrusivo para el código comercial.

Teniendo en cuenta la intrusión del método anterior para el código comercial, necesitamos una solución más elegante que permita que el mecanismo de compensación por falla en la eliminación de caché se ejecute detrás de escena y reduzca el acoplamiento con el código comercial tanto como sea posible. Una idea simple es usar la marca de tiempo o la versión de actualización como comparación para obtener los datos incrementales de la base de datos y actualizarlos en el caché mediante una tarea en segundo plano. Este método es muy útil en escenarios de aplicaciones de datos a pequeña escala, pero su escalabilidad y estabilidad son insuficientes.

Una solución relativamente madura es analizar y consumir registros incrementales basados ​​​​en la base de datos MySQL. La más popular es el canal de componentes de código abierto de Alibaba, que se utiliza para obtener y analizar binlog MySQL de forma incremental. Los componentes de código abierto incluyen Maxwell, Databus, etc.).

El servidor de canal simula el protocolo de interacción de la estación esclava MySQL, se disfraza de estación esclava MySQL y envía el protocolo de volcado a la estación maestra MySQL. Después de recibir la solicitud de volcado, la estación maestra comienza a. envía el binario a la estación esclava (es decir, el registro del canal de corte), el esclavo analiza el objeto de registro binario (es decir, el registro sin formato). El servidor analiza el objeto de registro binario (originalmente un flujo de bytes), que los clientes del canal pueden extraer para su consumo. El servidor del canal también admite la entrega de registros de cambios a los sistemas MQ de forma predeterminada y los envía activamente a otros sistemas para su consumo.

A través del mecanismo de confirmación, tanto push como pull pueden garantizar de manera efectiva que los datos se consuman como se espera. La versión actual del canal admite MQ como Kafka o RocketMQ. Además, Canal también depende de ZooKeeper como componente de coordinación distribuida para implementar HA, que se divide en dos partes:

Luego, la eliminación de la caché se puede completar escribiendo el código comercial relevante en el cliente o consumidor del canal.

Luego, la operación de eliminación de caché se puede escribir en el código comercial relevante en el cliente o consumidor del canal. De esta manera, el esquema de consumo de análisis incremental del registro de la base de datos se combina con el modelo Cache-Aside, que actualiza el caché cuando una solicitud de lectura no alcanza el caché (lo que a menudo implica una lógica empresarial compleja) y se actualiza cuando una solicitud de escritura no alcanza el caché. Eliminar el caché después de la base de datos y compensar posibles fallas de eliminación del caché al actualizar la base de datos según el análisis de registros incremental) puede garantizar de manera efectiva la coherencia final del caché en la mayoría de los escenarios de aplicaciones.

También es importante tener en cuenta que las transacciones deben aislarse del caché para garantizar que las eliminaciones del caché se realicen después de la entrada de la base de datos. Por ejemplo, considerando la arquitectura maestro-esclavo de la base de datos, los escenarios de sincronización maestro-esclavo y lectura-esclavo-escritura-maestro pueden hacer que el caché se actualice después de leer datos antiguos de la base de datos, lo que hace que el caché se retrase con respecto a la base de datos. Esto requiere garantizar que la operación de la base de datos elimine el caché cuando finalice. Por lo tanto, una solución de sincronización de datos basada en registros incrementales de binlog puede evitar el problema de la eliminación prematura de la caché en la sincronización maestro-esclavo al elegir analizar el binlog del nodo esclavo.

3. Servicio de transmisión de datos DTS

- 3 -

Lectura

Lectura <

Read-Through se refiere al modo de lectura directa. Su proceso es similar al Cache-Aside. La diferencia es que el modo de lectura directa tiene una capa de control de acceso adicional. capa de control. La lógica detrás del acierto o error de la caché interactúa con la fuente de datos mediante la capa de control de acceso, lo que simplifica la implementación de la capa empresarial. La implementación de la capa empresarial es más simple y la interacción entre la capa de caché y la capa de persistencia está más encapsulada, lo que facilita la portabilidad.

- 4 -

Escritura directa

Escritura directa significa modo de escritura simultánea. Con los patrones, también agrega una capa de control de acceso para proporcionar un mayor grado de encapsulación. A diferencia de Cache-Aside, Write-Through no elimina el caché después de que una solicitud de escritura actualiza la base de datos, pero actualiza el caché.

La ventaja de este método es que el proceso de solicitud de lectura es simple y no es necesario consultar la base de datos para actualizar el caché. Pero las deficiencias también son muy obvias, además de las deficiencias mencionadas anteriormente de actualizar primero la base de datos y luego actualizar el caché, esta solución también provocará actualizaciones ineficientes y la falla de cualquiera de las dos operaciones de escritura provocará inconsistencia en los datos. .

Si desea utilizar este escenario, es mejor tratar las dos operaciones como transacciones, que pueden fallar o tener éxito al mismo tiempo, admitir la reversión y evitar inconsistencias en entornos concurrentes. Además, para evitar la destrucción frecuente de la caché, también puede mitigar este problema agregando un TTL a la caché.

Desde el punto de vista de la viabilidad, tanto el modelo de escritura directa como el de caché aparte pueden idealmente garantizar la coherencia entre los datos almacenados en caché y los persistentes a través de transacciones distribuidas, pero en proyectos reales, la coherencia existe un cierto grado de libertad. en los requisitos sexuales, por lo que a menudo es necesario hacer concesiones en los escenarios de aplicación.

El modo de escritura directa por escritura es adecuado para escenarios de aplicaciones con muchas operaciones de escritura y requisitos de alta coherencia. Al aplicar el modo de escritura directa, también se necesitan algunos mecanismos de compensación para resolver sus problemas. En primer lugar, en un entorno concurrente, mencionamos anteriormente que actualizar la base de datos antes de actualizar el caché generará inconsistencias entre el caché y la base de datos, entonces, ¿qué sucede al actualizar el caché antes de actualizar la base de datos?

Este tiempo todavía resulta en la siguiente situación: el subproceso 1 actualiza el caché primero y la base de datos al final, es decir, la base de datos y el caché son inconsistentes debido a la ejecución no determinista del subproceso 1 y el subproceso 2.

Las inconsistencias de la caché causadas por la competencia de subprocesos se pueden resolver mediante bloqueos distribuidos. Los bloqueos distribuidos pueden garantizar que las operaciones en la caché y la base de datos solo puedan ser completadas por el mismo subproceso. Los subprocesos que no adquieren el bloqueo están sujetos a un tiempo de espera de bloqueo o tienen solicitudes almacenadas en la cola de mensajes para consumirse secuencialmente.

En el siguiente escenario de ejecución concurrente, la solicitud de escritura del subproceso 1 actualiza la base de datos, luego la solicitud de lectura del subproceso 2 llega al caché y luego el subproceso 1 actualiza el caché, lo que hace que la lectura del caché del subproceso 2 se retrase en el base de datos. Asimismo, actualizar la caché antes de actualizar la base de datos es un problema similar cuando las solicitudes de escritura y lectura son simultáneas. En este caso, también podemos agregar un candado para solucionar el problema.

Por otro lado, en el modo de penetración de escritura, ya sea que el caché o la base de datos se actualicen primero, no se podrá actualizar el caché o la base de datos, y los mecanismos de reintento y compensación mencionados anteriormente se desactivarán. también eficaz aquí.

- 5 -

Escritura detrás

La escritura detrás se refiere al modo de escritura asíncrona, que también tiene el mismo Las características de la capa de control de acceso de lectura/escritura tienen funciones similares, excepto que la escritura detrás solo actualiza el caché y no la base de datos cuando procesa solicitudes de escritura, y actualiza la base de datos de manera asincrónica por lotes. La escritura por lotes ocurre cuando la base de datos. la carga es baja.

En el modo de "escritura oculta", las solicitudes de escritura tienen una latencia más baja, lo que reduce la presión sobre la base de datos y proporciona un mejor rendimiento. Sin embargo, la coherencia entre la base de datos y el caché es débil. Por ejemplo, cuando los datos actualizados aún no se han escrito en la base de datos, la consulta directa de los datos desde la base de datos quedará rezagada con respecto al caché. Al mismo tiempo, la carga del caché es grande. Si el caché deja de funcionar, se perderán datos, por lo que el caché debe tener alta disponibilidad. Obviamente, el modo de escritura detrás es adecuado para una gran cantidad de escenarios de operaciones de escritura y, a menudo, se usa para deducciones de inventario en escenarios de venta flash de comercio electrónico.

- 6 -

Omitir

Si tiene algunos negocios no principales con requisitos de coherencia débiles, puede optar por Agregar almacenamiento en caché en modo de lectura de caché. El tiempo de vencimiento del caché se agrega al modo de lectura del caché. En la solicitud de escritura, solo es necesario actualizar la base de datos sin eliminar ni actualizar el caché. De esta manera, el caché solo puede caducar antes del tiempo de vencimiento. Esta solución es simple de implementar, pero los datos en el caché son menos consistentes con los datos en la base de datos, lo que a menudo conduce a una mala experiencia del usuario, por lo que debe elegirse con cuidado.

- 7 -

Resumen

En el proceso de resolución de problemas de coherencia de la caché, hay muchas formas de garantizar la coherencia de la caché. La coherencia final debe seleccionarse de acuerdo con el escenario. En escenarios con muchas lecturas y pocas escrituras, puede optar por utilizar la opción "Caché aparte combinado con el consumo de registros de la base de datos para compensar" en el caso de muchas escrituras; utilizar la opción "Escritura" combinada con bloqueo distribuido"; en el caso de muchas escrituras, puede optar por utilizar la opción "Escritura combinada con bloqueo distribuido". En un escenario de escritura penetrante, puede optar por utilizar el esquema de "escritura penetrante combinada con bloqueo distribuido", y en un escenario de escritura extremadamente penetrante, puede optar por utilizar el esquema de "escritura oculta".