Diseño de un sistema de fondo de flujo de alimentación basado en una línea de tiempo
Los productos de flujo de feeds están casi en todas partes en nuestras aplicaciones móviles. Los flujos de feeds comunes incluyen WeChat Moments, Sina Weibo, Toutiao, etc. La definición de flujo de alimentación puede entenderse simplemente como que mientras el pulgar siga deslizando el dedo hacia abajo en la pantalla de su teléfono móvil, seguirán apareciendo piezas de información. Al igual que alimentar al ganado con pienso, siempre que se coma, se añadirá más, de ahí el nombre Feed.
La mayoría de los productos de flujo de feeds contienen dos flujos de feeds, uno basado en la recomendación de un algoritmo y el otro en el seguimiento (amistad). Por ejemplo, en Weibo y Zhihu en la imagen siguiente, las tarjetas de página en la columna superior incluyen "Seguir" y "Recomendar". La tecnología detrás de las dos corrientes de alimentación será bastante diferente. Este artículo se centrará en explorar la implementación en segundo plano de la tarjeta de página "Seguir".
Fuente de la imagen: Zhihu
A diferencia de la tarjeta de página "Recomendar", que es recomendada por miles de personas, el contenido que se muestra en la tarjeta de página "Seguir" generalmente se muestra en orden. Reglas fijas, la regla más común es la clasificación según la línea de tiempo, es decir, mostrar "publicaciones publicadas por personas que sigo, ordenadas de tarde a temprano según el momento de publicación".
Introducción a la solución de implementación Feed Stream
La difusión de lectura también se denomina modo pull, que debería ser el método de implementación más intuitivo. Como se muestra a continuación:
Cada editor de contenido tiene su propia bandeja de salida ("Contenido que publiqué"). Cada vez que enviamos una nueva publicación, se almacena en su propia bandeja de salida. Cuando nuestros fanáticos vienen a leer, el sistema primero debe hacer que todos los fanáticos sigan, luego recorrer las bandejas de salida de todos los editores, sacar las publicaciones que publicaron y luego ordenarlas según el tiempo de publicación y mostrárselas a los lectores.
En este diseño, cuando un lector lee el flujo de alimentación una vez, se distribuirá en N operaciones de lectura (N es igual al número de personas que lo siguen) y una operación de agregación en segundo plano, por lo que se denomina lectura. difusión. Cada vez que lees el flujo de alimentación, equivale a ir a la bandeja de entrada del seguidor para extraer publicaciones activamente, de ahí el modo de extracción de nombres.
La ventaja de este modelo es que el almacenamiento subyacente es simple y no hay desperdicio de espacio. La desventaja es que cada operación de lectura será muy pesada y habrá muchas operaciones. Imagínese si sigo a una gran cantidad de personas, reviso a todas las personas que sigo y luego las agrego, la sobrecarga del sistema será muy grande y la demora puede alcanzar un nivel intolerable. Por lo tanto, la difusión de lectura es principalmente adecuada para escenarios en los que los lectores del sistema no siguen a tanta gente y el flujo de alimentación no se actualiza con frecuencia.
Otra desventaja importante del modo de extracción es que la paginación es inconveniente. Cuando navegamos por Weibo o Moments, debemos seguir deslizando el pulgar en la pantalla y el contenido se extrae del fondo página por página. . Si no se realiza ninguna otra optimización y solo se utiliza la agregación en tiempo real, será muy problemático desplazarse hacia abajo a un número de página posterior.
Según las estadísticas, la proporción de lectura y escritura de la mayoría de los productos de flujo de feeds es de aproximadamente 100:1, lo que significa que en la mayoría de los casos, navegas por el flujo de feeds para leer los Momentos y Weibo de otras personas, y solo en En algunos casos, puede enviar personalmente un mensaje a Moments o Weibo para que otros lo vean. Por lo tanto, la lógica de lectura intensa de la difusión de lectura no es adecuada para la mayoría de los escenarios. Preferimos hacer que el proceso de publicación sea más complicado que afectar la experiencia del usuario al leer el flujo de alimentación, por lo que modificamos ligeramente la solución anterior para crear difusión de escritura. La difusión de escritura también se denomina modo push. Este modo mejorará algunas deficiencias del modo pull. Como se muestra a continuación:
Además de la bandeja de salida, cada usuario del sistema también tiene su propia bandeja de entrada. Cuando el editor publica una publicación, además de grabarla en su propia bandeja de salida, también recorrerá a todos los fanáticos del editor y colocará una copia del mismo contenido en las bandejas de entrada de estos fanáticos. De esta manera, cuando los lectores vienen a leer el feed, pueden leerlo directamente desde su propia bandeja de entrada.
Con este diseño, cada vez que se publica una publicación, se difundirá en M operaciones de escritura (M es igual al número de fans), por lo que se convierte en difusión de escritura. Cada publicación se enviará activamente a la bandeja de entrada de todos los fanáticos, de ahí el modo de envío de nombres.
Este modelo se puede imaginar. Publicar una publicación implicará muchas operaciones de escritura. Por lo general, para la experiencia del usuario del autor, cuando la publicación publicada se escribe en su bandeja de salida, se puede devolver el éxito de la publicación. Se inicia otra tarea asincrónica en segundo plano y las publicaciones se pueden enviar a las bandejas de entrada de los fans de forma pausada. El beneficio de la difusión de escritura es que mejora la experiencia del usuario del lector a través de la redundancia de datos (una publicación se almacenará en M copias). Por lo general, la redundancia de datos adecuada no es un problema, pero cuando se trata de estrellas de Weibo, no funciona en absoluto. Por ejemplo, Xie Na y He Jiong, que actualmente tienen los dos principales seguidores de Weibo, tienen más de 100 millones de seguidores en Weibo.
Imagínese que si simplemente usa el modo push, cada vez que Xie Na y He Jiong publiquen un Weibo, habrá un terremoto en el backend de Weibo. Una publicación en Weibo genera cientos de millones de operaciones de escritura en segundo plano, lo que obviamente no es factible. Además, dado que la difusión de escritura es una operación asincrónica, escribir demasiado lento hará que la publicación se envíe durante mucho tiempo y algunos fanáticos aún no puedan verla, lo que no es una buena experiencia.
Por lo general, la difusión de escritura es adecuada para situaciones en las que el número de amigos no es grande. Se informa que WeChat Moments es exactamente el modo de difusión de escritura. El límite superior de amigos de cada usuario de WeChat es 5000, lo que significa que si publica un círculo de amigos, se extenderá a un máximo de 5000 operaciones de escritura. Si el rendimiento de la tarea asincrónica es mejor, no habrá ningún problema.
Mezclar lectura y escritura también se puede llamar combinación push-pull. Este método puede combinar las ventajas de la difusión de lectura y la difusión de escritura. Primero resumamos las ventajas y desventajas de la difusión de lectura y la difusión de escritura:
Si comparamos cuidadosamente las ventajas y desventajas de la difusión de lectura y la difusión de escritura, no es difícil encontrar que los escenarios aplicables de los dos son complementario. Por lo tanto, al diseñar el almacenamiento backend, si podemos distinguir entre escenarios, elegir la solución más adecuada en diferentes escenarios y ajustar dinámicamente la estrategia, podemos lograr un modo mixto de lectura y escritura. Como se muestra a continuación:
Cuando los usuarios activos inician sesión para explorar el flujo de noticias, pueden leer las publicaciones directamente desde su bandeja de entrada, lo que garantiza la experiencia de los usuarios activos. Cuando un usuario inactivo de repente inicia sesión para navegar por el flujo de feeds, por un lado necesitamos leer su bandeja de entrada y, por otro lado, debemos recorrer las bandejas de salida de los grandes usuarios V que sigue para extraer publicaciones y realizar una visualización agregada. . Después de la visualización, el sistema aún necesita una tarea para determinar si es necesario actualizar el usuario a un usuario activo. Debido a que existe un escenario de difusión de lectura, incluso en el modo mixto, debe haber un límite superior en la cantidad de personas que cada lector puede seguir. Por ejemplo, Sina Weibo limita cada cuenta a un máximo de 2000 personas. Si no hay un límite superior, imagine que un usuario sigue todas las cuentas de Weibo. Luego, cuando abre la lista de seguimiento, leerá todas las publicaciones en Weibo. Una vez que se produzca la difusión de lectura, el sistema inevitablemente fallará incluso si se trata de difusión de escritura. , lo hará Mi bandeja de entrada no puede acomodar tantas publicaciones de Weibo.
En el modo mixto de lectura y escritura, el sistema necesita realizar dos juicios. Una es qué usuarios son grandes vs. Podemos usar la cantidad de fanáticos como indicador de juicio. El otro es qué usuarios son fanáticos activos. Este criterio puede ser la hora del último inicio de sesión, etc. Estos dos criterios de juicio deben identificarse y ajustarse dinámicamente durante el proceso de desarrollo del sistema. No existe una fórmula fija.
Se puede observar que el modo combinado de lectura y escritura combina las ventajas de los dos modos y es la mejor solución. Sin embargo, su desventaja es que el mecanismo del sistema es muy complejo, lo que genera innumerables problemas a los programadores. Por lo general, en las primeras etapas de un proyecto, cuando solo hay uno o dos desarrolladores y la base de usuarios es muy pequeña, aún hay que tener cuidado al adoptar este modelo híbrido en un solo paso, ya que es propenso a errores. Cuando la escala del proyecto se desarrolla gradualmente al nivel de Sina Weibo y hay un gran equipo dedicado a realizar el flujo de alimentación, el modo mixto de lectura y escritura es necesario.
El artículo anterior describió soluciones de diseño comunes para flujos de alimentación basadas en líneas de tiempo, pero la práctica es mucho más problemática que la teoría. A continuación, analizaremos específicamente un punto difícil: la paginación del flujo de alimentación. Ya sea difusión de lectura o difusión de escritura, el flujo de alimentación es esencialmente una lista dinámica y el contenido de la lista seguirá cambiando con el tiempo. Los parámetros de paginación de front-end tradicionales utilizan page_size y page_num. La subtabla indica cuántos elementos hay en cada página y qué página es la página actual. Para una lista dinámica, habrá los siguientes problemas:
La primera página se lee en el momento T1 y alguien publica nuevamente el "Contenido 11" en el momento T2. Si extrae la segunda página en el momento T3, provocará una desalineación y se devolverá "Contenido 6" tanto en la primera como en la segunda página. De hecho, cualquier adición o eliminación de contenido entre dos páginas provocará problemas de desalineación.
Para resolver este problema, generalmente los parámetros de entrada de paginación del flujo de alimentación no usan page_size y page_num, sino que usan last_id para registrar la identificación del último contenido en la página anterior. Cuando el front-end lee la página siguiente, debe usar last_id como parámetro de entrada. El fondo encuentra directamente los datos correspondientes a last_id, luego compensa los datos del tamaño de la página y los devuelve al front-end, evitando así el problema de desalineación. Como se muestra a continuación:
Una condición importante para usar last_id es que los datos de last_id en sí no se puedan eliminar por completo. Imagine que en la figura anterior, se devuelven 5 datos en el momento T1, y last_id es el contenido 6; el editor elimina el contenido 6 en el momento T2, luego, cuando solicitamos la segunda página en el momento T3, no podemos encontrar los datos; correspondiente a last_id y no podemos confirmarlo. Por lo general, cuando nos encontramos con escenarios de eliminación, utilizamos la eliminación temporal, que simplemente coloca una marca en el contenido para indicar que el contenido se ha eliminado. Dado que el contenido eliminado no debe devolverse al front-end, en el modo de eliminación suave, busque last_id y compénselo en franjas de tamaño de página. Si hay datos eliminados, se obtendrán suficientes franjas de datos para el front-end. Una solución aquí es continuar buscando si no puede encontrar suficientes. Otra solución es negociar con el front-end para permitir que la cantidad de elementos devueltos sea menor que page_size es solo un valor recomendado. Incluso después de que todos estén de acuerdo, se puede omitir el parámetro page_size.
Aplicación empresarial práctica
Al final del artículo, combinada con nuestro propio negocio, presentamos una solución de diseño de flujo de alimentación muy especial que se encuentra en escenarios comerciales reales. La transmisión en vivo es una herramienta de transmisión en vivo que permite al anfitrión crear una transmisión en vivo en el futuro y comenzar a vender productos una vez finalizada la transmisión en vivo, los fanáticos del anfitrión pueden ver la repetición de la transmisión en vivo. De esta manera, cada transmisión en vivo tiene tres estados: vista previa (se crea una transmisión en vivo pero aún no ha comenzado), transmisión en vivo y repetición. Como espectador, puedo seguir a varios presentadores, por lo que, desde la perspectiva de un fan, también habrá una página de transmisión de transmisiones en vivo.
Lo más especial de este flujo de alimentación son sus reglas de clasificación de flujo de alimentación.
Reglas de clasificación de transmisiones de feeds:
1. Para todos los presentadores que sigo, las transmisiones en vivo se clasifican en primer lugar; las vistas previas se clasifican en el medio; las repeticiones se clasifican en último lugar; p >
2. Si se transmiten varios programas en vivo, ordénelos desde el horario de transmisión más tardío hasta el más temprano
3. Si hay varios programas en la vista previa, ordénelos por hora de inicio prevista desde temprano en la mañana hasta tarde
p>
4. Si se están reproduciendo varios programas, ordénelos de tarde a temprano antes de la hora de finalización de la transmisión en vivo
Análisis de problemas
El punto más complicado de este requisito es la integración del contenido del flujo de alimentación. El factor "estado", el cambio de estado provocará directamente que el orden del flujo de alimentación sea diferente. Para explicar más claramente el impacto en la clasificación, podemos utilizar la siguiente figura para explicarlo en detalle:
La figura muestra 5 transmisiones en vivo de 4 presentadores como espectador, cuando abro la página en T1. , Veo que el orden de llegada es que el programa 3 está en la parte superior, y el resto de los programas están en estado de vista previa, mostrados de la mañana a la noche según el horario de transmisión esperado. Cuando abro la página en T2, el programa 5 está en la parte superior, los tres programas restantes están en el medio y el programa 3 ha finalizado, por lo que está en la parte inferior. Por analogía, hasta que finalicen todas las transmisiones en vivo, el estado final de todas las sesiones cambiará a repetición.
Una cosa a tener en cuenta aquí es que si abro la primera página en T1, luego miro la página sin moverme hasta T4, y luego me desplazo hacia abajo hasta la segunda página, luego el último_id de la página anterior, Es decir, es probable que el desplazamiento de paginación vuele a la posición incorrecta debido al cambio del estado de la transmisión en vivo, lo que provocará graves problemas de desalineación y una visualización inconsistente del estado de la transmisión en vivo (la primera página muestra el estado de la transmisión en vivo en T1 , y la primera página muestra el estado de la transmisión en vivo en T1, la segunda página muestra el estado de la transmisión en vivo en T4).
El sistema de transmisión en vivo es una cadena de relaciones unidireccional, algo similar a Weibo. Cada espectador seguirá a una pequeña cantidad de presentadores, y cada presentador puede tener una gran cantidad de seguidores. Debido a la existencia de cambios de estado, la difusión de escritura es casi imposible de lograr. Porque si se utiliza el método de difusión de escritura, el cambio en el estado del evento causado por los tres eventos de creación de una transmisión en vivo, inicio de la transmisión en vivo y finalización de la transmisión en vivo se distribuirá en muchas operaciones de escritura. Operación complicada, pero el retraso también es imposible de aceptar. La razón por la que Weibo se puede difundir es porque después de publicar una publicación, ya no habrá cambios de estado que afecten la clasificación de la publicación. En nuestro escenario, "en vista previa" y "transmisión en vivo" son dos estados intermedios, y el estado de "reproducción" es el destino final de todas las transmisiones en vivo. Una vez que se ingresa la reproducción, no habrá más cambios de estado para esta transmisión en vivo. Por lo tanto, los estados "transmisión en vivo" y "vista previa" pueden usar difusión de lectura, y el estado "reproducción" puede usar difusión de escritura.
La solución final se muestra en la siguiente figura:
Los tres eventos que afectarán el estado de la transmisión en vivo (crear transmisión en vivo, iniciar transmisión, finalizar transmisión en vivo) son todos procesados de forma asincrónica utilizando la cola de escucha. Mantenemos una cola prioritaria de transmisión en vivo + estado de vista previa para cada presentador. Siempre que se monitorea a un presentador para crear una transmisión en vivo, la sesión de transmisión en vivo se agregará a la cola y la puntuación será la opuesta (número negativo) de la marca de tiempo cuando se inició la transmisión. Siempre que se monitorea que un presentador comienza a transmitir, la puntuación de esta transmisión en vivo en la cola se modifica a la hora de inicio (número positivo). Siempre que se monitorea a un anfitrión para finalizar la transmisión en vivo, la información de reproducción se entrega de forma asincrónica a la cola de reproducción de cada espectador.
Aquí hay un pequeño truco. Como se mencionó anteriormente, el estado en la transmisión en vivo se ordena de mayor a menor según la hora de inicio, mientras que el estado en la vista previa se ordena de pequeño a grande según la hora. Por lo tanto, si la puntuación del estado en la vista previa es el número inverso del tiempo de transmisión para todos, la clasificación también será de mayor a menor. Este tipo de conversión puede garantizar que la transmisión en vivo y la vista previa estén en la misma cola. Las puntuaciones en la vista previa son todas negativas y las puntuaciones en la transmisión en vivo son todas positivas. En la agregación final, se puede garantizar que todas las transmisiones en vivo naturalmente tengan una clasificación más alta que las de la vista previa.
Además, otro problema mencionado anteriormente es que la primera página se extrae en T1 y la segunda página se extrae en T4, lo que da como resultado un estado inconsistente de las salas de transmisión en vivo en la primera y segunda página. La solución a este problema pasa por las instantáneas. Cuando la audiencia extrae el flujo de alimentación en la primera página, creamos una instantánea de todas las transmisiones en vivo y vistas previas en función de la hora actual, utilizando un identificador session_id. Cada vez que el front-end extrae la página, la extraemos directamente de la instantánea. . Solo léelo. Si se completa la lectura de la instantánea, demuestra que se han leído los tiempos de transmisión en vivo y avance del espectador, y el resto se complementará mediante la cola de reproducción. De acuerdo con esto, en nuestro sistema de flujo de feeds, hay 4 parámetros extraídos por la paginación frontal:
Siempre que session_id y last_id están vacíos, demuestra que el usuario quiere leer En la primera página, la instantánea necesita ser reconstruido. Hay otra pregunta derivada aquí: ¿cómo obtener el valor de session_id? Si no considera la situación en la que el mismo espectador inicia sesión desde múltiples terminales, de hecho, cada espectador puede mantener una ID de instantánea, es decir, configurar directamente la ID de usuario del sistema en session_id si considera la situación de inicio de sesión de múltiples terminales; , session_id debe contener la información de ID de cada terminal para evitar la influencia mutua de instantáneas de múltiples extremos; si no se preocupa por la memoria, también puede aleatorizar una cadena como session_id cada vez y establecer un tiempo de vencimiento largo. suficiente para permitir que la instantánea caduque naturalmente.
En el diseño anterior, de hecho, el momento en el que el sistema tiene la mayor cantidad de cálculo es el costo de extraer la primera página y crear una instantánea. Según los datos actuales en línea, para los espectadores que solo siguen a menos de 10 presentadores (que también es la mayoría de los escenarios), el QPS de sacar la primera página puede llegar a 15.000. Si también se incluyen las solicitudes de la segunda página en adelante, el QPS completo del flujo de alimentación puede alcanzar un nivel superior, que es más que suficiente para soportar la escala de usuarios actual. Si solo obtenemos los primeros 10 elementos al extraer la primera página y regresar directamente, y cambiamos la operación de creación de instantáneas a asíncrona, tal vez el QPS pueda ser mayor, lo que puede ser un punto de optimización posterior.
Difusión de lectura, difusión de escritura y lectura y escritura mixtas. Casi todos los flujos de alimentación basados en líneas de tiempo y relaciones de atención no pueden escapar de estos tres patrones de diseño básicos. En los negocios reales, puede haber escenarios más complejos. Por ejemplo, la transferencia de estado mencionada en este artículo afecta la clasificación. En el escenario Weibo Moments, también habrá acceso a publicidad, atención especial, temas candentes, etc. factor de clasificación del flujo de alimentación. Estos escenarios sólo se pueden modificar según las necesidades del negocio.
Reimpreso de: /developer/article/1744756