Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Mikhail Salosin (en adelante – MS): - ¡Hola a todos! Mi nombre es Michael. Trabajo como desarrollador backend en MC2 Software y hablaré sobre el uso de Go en el backend de la aplicación móvil Look+.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

¿A alguien aquí le gusta el hockey?

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Entonces esta aplicación es para ti. Es para Android e iOS y sirve para ver retransmisiones de diversos eventos deportivos online y grabados. La aplicación también contiene diversas estadísticas, retransmisiones de texto, tablas de conferencias, torneos y otra información útil para los aficionados.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

También en la aplicación existe el concepto de momentos en vídeo, es decir, puedes ver los momentos más importantes de los partidos (goles, peleas, penales, etc.). Si no quieres ver la transmisión completa, puedes ver solo las más interesantes.

¿Qué usaste en el desarrollo?

La parte principal fue escrita en Go. La API con la que se comunicaban los clientes móviles se escribió en Go. También se escribió en Go un servicio para enviar notificaciones automáticas a teléfonos móviles. También tuvimos que escribir nuestro propio ORM, del que podríamos hablar algún día. Bueno, algunos pequeños servicios fueron escritos en Go: cambiar el tamaño y cargar imágenes para los editores...

Utilizamos PostgreSQL como base de datos. La interfaz del editor fue escrita en Ruby on Rails usando la gema ActiveAdmin. La importación de estadísticas desde un proveedor de estadísticas también está escrita en Ruby.

Para las pruebas de API del sistema, utilizamos Python unittest. Memcached se utiliza para acelerar las llamadas de pago API, "Chef" se utiliza para controlar la configuración, Zabbix se utiliza para recopilar y monitorear estadísticas internas del sistema. Graylog2 es para recopilar registros, Slate es documentación API para clientes.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Selección de protocolo

El primer problema que encontramos: necesitábamos elegir un protocolo para la interacción entre el backend y los clientes móviles, basándonos en los siguientes puntos...

  • El requisito más importante: los datos de los clientes deben actualizarse en tiempo real. Es decir, todos los que estén viendo la transmisión actualmente deberían recibir actualizaciones casi al instante.
  • Para simplificar las cosas, asumimos que los datos que se sincronizan con los clientes no se eliminan, sino que se ocultan mediante indicadores especiales.
  • Todo tipo de solicitudes raras (como estadísticas, composiciones de equipos, estadísticas de equipos) se obtienen mediante solicitudes GET ordinarias.
  • Además, el sistema tenía que admitir fácilmente 100 usuarios al mismo tiempo.

En base a esto, teníamos dos opciones de protocolo:

  1. Enchufes web. Pero no necesitábamos canales del cliente al servidor. Solo necesitábamos enviar actualizaciones desde el servidor al cliente, por lo que un websocket es una opción redundante.
  2. ¡Los eventos enviados por el servidor (SSE) surgieron perfectamente! Es bastante sencillo y básicamente satisface todo lo que necesitamos.

Eventos enviados por el servidor

Unas pocas palabras sobre cómo funciona esto...

Se ejecuta sobre una conexión http. El cliente envía una solicitud, el servidor responde con Content-Type: text/event-stream y no cierra la conexión con el cliente, pero continúa escribiendo datos en la conexión:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Los datos se pueden enviar en un formato acordado con los clientes. En nuestro caso, lo enviamos de esta forma: el nombre de la estructura modificada (persona, jugador) se envió al campo del evento y JSON con campos nuevos y modificados para el jugador se envió al campo de datos.

Ahora hablemos de cómo funciona la interacción en sí.

  • Lo primero que hace el cliente es determinar la última vez que se realizó la sincronización con el servicio: mira su base de datos local y determina la fecha del último cambio registrado por ella.
  • Envía una solicitud con esta fecha.
  • En respuesta, le enviamos todas las actualizaciones que han ocurrido desde esa fecha.
  • Después de eso, se conecta al canal en vivo y no se cierra hasta que necesita estas actualizaciones:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Le enviamos una lista de cambios: si alguien marca un gol, cambiamos el resultado del partido, si se lesiona, esto también se envía en tiempo real. Por lo tanto, los clientes reciben instantáneamente datos actualizados en el feed del evento del partido. Periódicamente, para que el cliente comprenda que el servidor no ha muerto, que no le pasó nada, enviamos una marca de tiempo cada 15 segundos, para que sepa que todo está en orden y que no es necesario volver a conectarse.

¿Cómo se mantiene la conexión en vivo?

  • En primer lugar, creamos un canal en el que se recibirán las actualizaciones almacenadas.
  • Después de eso, nos suscribimos a este canal para recibir actualizaciones.
  • Configuramos el encabezado correcto para que el cliente sepa que todo está bien.
  • Envía el primer ping. Simplemente registramos la marca de tiempo de la conexión actual.
  • Después de eso, leemos del canal en un bucle hasta que se cierra el canal de actualización. El canal recibe periódicamente la marca de tiempo actual o los cambios que ya estamos escribiendo para abrir conexiones.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

El primer problema que encontramos fue el siguiente: para cada conexión abierta con el cliente, creamos un temporizador que marcaba una vez cada 15 segundos; resulta que si tuviéramos 6 mil conexiones abiertas con una máquina (con un servidor API), 6 Se crearon mil temporizadores. Esto provocó que la máquina no aguantara la carga requerida. El problema no era tan obvio para nosotros, pero conseguimos un poco de ayuda y lo solucionamos.

Como resultado, ahora nuestro ping proviene del mismo canal del que proviene la actualización.

En consecuencia, solo hay un temporizador que funciona una vez cada 15 segundos.

Aquí hay varias funciones auxiliares: enviar el encabezado, ping y la estructura misma. Es decir, aquí se transmite el nombre de la tabla (persona, partido, temporada) y la información sobre esta entrada:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Mecanismo de envío de actualizaciones

Ahora un poco sobre de dónde vienen los cambios. Contamos con varias personas, redactores, que ven la retransmisión en tiempo real. Crean todos los eventos: alguien fue expulsado, alguien resultó herido, algún tipo de reemplazo...

Usando un CMS, los datos ingresan a la base de datos. Después de esto, la base de datos notifica a los servidores API sobre esto utilizando el mecanismo Escuchar/Notificar. Los servidores API ya envían esta información a los clientes. Por lo tanto, esencialmente solo tenemos unos pocos servidores conectados a la base de datos y no hay una carga especial en la base de datos, porque el cliente no interactúa directamente con la base de datos de ninguna manera:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

PostgreSQL: escuchar/notificar

El mecanismo Escuchar/Notificar en Postgres le permite notificar a los suscriptores de eventos que algún evento ha cambiado: se ha creado algún registro en la base de datos. Para hacer esto, escribimos un disparador y una función simples:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Al insertar o cambiar un registro, llamamos a la función notify en el canal data_updates, pasando allí el nombre de la tabla y el identificador del registro que fue cambiado o insertado.

Para todas las tablas que deben sincronizarse con el cliente, definimos un disparador que, después de cambiar/actualizar un registro, llama a la función indicada en la diapositiva a continuación.
¿Cómo se suscribe la API a estos cambios?

Se crea un mecanismo Fanout: envía mensajes al cliente. Recopila todos los canales de clientes y envía las actualizaciones que recibió a través de estos canales:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Aquí la biblioteca pq estándar, que se conecta a la base de datos y dice que quiere escuchar el canal (data_updates), verifica que la conexión esté abierta y que todo esté bien. Estoy omitiendo la verificación de errores para ahorrar espacio (no verificar es peligroso).

A continuación, configuramos Ticker de forma asincrónica, que enviará un ping cada 15 segundos y comenzaremos a escuchar el canal al que nos suscribimos. Si recibimos un ping, lo publicamos. Si recibimos algún tipo de entrada, la publicamos para todos los suscriptores de este Fanout.

¿Cómo funciona el Fan-out?

En ruso esto se traduce como "divisor". Tenemos un objeto que registra suscriptores que desean recibir algunas actualizaciones. Y tan pronto como llega una actualización a este objeto, la distribuye a todos sus suscriptores. Suficientemente simple:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Cómo se implementa en Go:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Hay una estructura, se sincroniza mediante Mutexes. Tiene un campo que guarda el estado de la conexión de Fanout a la base de datos, es decir, actualmente está escuchando y recibirá actualizaciones, así como una lista de todos los canales disponibles: un mapa, cuya clave es el canal y la estructura en forma de valores (esencialmente no se utiliza de ninguna manera).

Dos métodos, Conectado y Desconectado, nos permiten decirle a Fanout que tenemos una conexión con la base, ha aparecido y que la conexión con la base se ha roto. En el segundo caso, es necesario desconectar a todos los clientes y decirles que ya no pueden escuchar nada y que se vuelven a conectar porque la conexión con ellos se ha cerrado.

También existe un método de suscripción que agrega el canal a los “oyentes”:

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Existe un método para cancelar la suscripción, que elimina el canal de los oyentes si el cliente se desconecta, así como un método para publicar, que le permite enviar un mensaje a todos los suscriptores.

Pregunta: – ¿Qué se transmite por este canal?

EM: – Se transmite el modelo que ha cambiado o se transmite el ping (esencialmente solo un número, un número entero).

EM: – Puedes enviar cualquier cosa, enviar cualquier estructura, publicarla – simplemente se convierte en JSON y listo.

EM: – Recibimos una notificación de Postgres que contiene el nombre de la tabla y el identificador. Según el nombre de la tabla y el identificador, obtenemos el registro que necesitamos y luego enviamos esta estructura para su publicación.

Infraestructura

¿Cómo se ve esto desde una perspectiva de infraestructura? Contamos con 7 servidores de hardware: uno de ellos está completamente dedicado a la base de datos, los otros seis ejecutan máquinas virtuales. Hay 6 copias de la API: cada máquina virtual con la API se ejecuta en un servidor de hardware independiente; esto es por motivos de confiabilidad.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

Tenemos dos frontends con Keepalived instalado para mejorar la accesibilidad, de modo que si pasa algo, un frontend pueda reemplazar al otro. Además, dos copias del CMS.

También hay un importador de estadísticas. Se dispone de una DB Slave de la que se realizan copias de seguridad periódicamente. Existe Pigeon Pusher, una aplicación que envía notificaciones push a los clientes, así como elementos de infraestructura: Zabbix, Graylog2 y Chef.

De hecho, esta infraestructura es redundante, porque se pueden atender 100 mil con menos servidores. Pero había hierro, lo usamos (nos dijeron que era posible, por qué no).

Ventajas de ir

Después de trabajar en esta aplicación, surgieron ventajas tan obvias de Go.

  • Genial biblioteca http. Con él puedes crear muchas cosas desde el primer momento.
  • Además, canales que nos permitieron implementar muy fácilmente un mecanismo para enviar notificaciones a los clientes.
  • Lo maravilloso del detector de carreras nos permitió eliminar varios errores críticos (infraestructura de preparación). Se lanza todo lo que funciona en la puesta en escena, compilado con la clave Race; y, en consecuencia, podemos observar la infraestructura de preparación para ver qué problemas potenciales tenemos.
  • Minimalismo y sencillez del lenguaje.

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

¡Buscamos desarrolladores! Si alguien quiere, por favor.

preguntas

Pregunta del público (en adelante – B): – Me parece que omitiste un punto importante respecto al Fan-out. ¿Estoy en lo cierto al entender que cuando envías una respuesta a un cliente, la bloqueas si el cliente no quiere leer?

EM: - No, no estamos bloqueando. En primer lugar, tenemos todo esto detrás de nginx, es decir, no hay problemas con clientes lentos. En segundo lugar, el cliente tiene un canal con un búfer; de hecho, podemos colocar allí hasta cien actualizaciones... Si no podemos escribir en el canal, lo elimina. Si vemos que el canal está bloqueado, simplemente cerraremos el canal y listo, el cliente se volverá a conectar si surge algún problema. Por lo tanto, en principio, aquí no hay ningún bloqueo.

A: – ¿No sería posible enviar inmediatamente un registro a Escuchar/Notificar y no una tabla de identificadores?

EM: – Escuchar/Notificar tiene un límite de 8 mil bytes en la precarga que envía. En principio, sería posible enviar si tratáramos con una pequeña cantidad de datos, pero me parece que así [la forma en que lo hacemos] es simplemente más confiable. Las limitaciones están en el propio Postgres.

A: – ¿Los clientes reciben actualizaciones sobre partidos que no les interesan?

EM: - En general, sí. Como regla general, se juegan 2 o 3 partidos en paralelo, y aun así, muy raramente. Si un cliente está viendo algo, normalmente está viendo el partido que se está desarrollando. Luego, el cliente tiene una base de datos local en la que se suman todas estas actualizaciones, e incluso sin una conexión a Internet, el cliente puede ver todas las coincidencias anteriores para las que tiene actualizaciones. Básicamente, sincronizamos nuestra base de datos en el servidor con la base de datos local del cliente para que pueda trabajar sin conexión.

A: – ¿Por qué hiciste tu propio ORM?

Alexey (uno de los desarrolladores de Look+): – En ese momento (fue hace un año) había menos ORM que ahora, cuando hay bastantes. Lo que más me gusta de la mayoría de los ORM que existen es que la mayoría se ejecutan en interfaces vacías. Es decir, los métodos en estos ORM están listos para asumir cualquier cosa: una estructura, un puntero de estructura, un número, algo completamente irrelevante...

Nuestro ORM genera estructuras basadas en el modelo de datos. Mí mismo. Y por lo tanto todos los métodos son concretos, no usan reflexión, etc. Aceptan estructuras y esperan usar aquellas estructuras que vienen.

A: – ¿Cuántas personas participaron?

EM: – En la etapa inicial participaron dos personas. Comenzamos en junio y en agosto la parte principal estaba lista (la primera versión). Hubo un lanzamiento en septiembre.

A: – Cuando describe SSE, no utiliza el tiempo de espera. ¿Porqué es eso?

EM: – Para ser honesto, SSE sigue siendo un protocolo html5: el estándar SSE está diseñado para comunicarse con los navegadores, hasta donde tengo entendido. Tiene funciones adicionales para que los navegadores puedan volver a conectarse (y así sucesivamente), pero no las necesitamos porque teníamos clientes que podían implementar cualquier lógica para conectarse y recibir información. No hicimos la ESS, sino algo similar a la ESS. Este no es el protocolo en sí.
No había necesidad. Según tengo entendido, los clientes implementaron el mecanismo de conexión casi desde cero. Realmente no les importaba.

A: – ¿Qué utilidades adicionales usaste?

EM: – Utilizamos más activamente govet y golint para unificar el estilo, así como gofmt. No se utilizó nada más.

A: – ¿Qué usaste para depurar?

EM: – La depuración se llevó a cabo en gran medida mediante pruebas. No utilizamos ningún depurador ni GOP.

A: – ¿Puedes devolver la diapositiva donde se implementa la función Publicar? ¿Te confunden los nombres de variables de una sola letra?

EM: - No. Tienen un alcance de visibilidad bastante “estrecho”. No se usan en ningún otro lugar excepto aquí (a excepción de los componentes internos de esta clase) y es muy compacto: solo ocupa 7 líneas.

A: – De alguna manera todavía no es intuitivo…

EM: - ¡No, no, este es un código real! No se trata de estilo. Es una clase tan utilitaria y muy pequeña: sólo 3 campos dentro de la clase...

Mijail Salosin. Reunión de Golang. Usando Go en el backend de la aplicación Look+

EM: – En general, todos los datos que se sincronizan con los clientes (partidos de temporada, jugadores) no cambian. En términos generales, si creamos otro deporte en el que necesitamos cambiar el partido, simplemente tendremos todo en cuenta en la nueva versión del cliente y las versiones antiguas del cliente serán prohibidas.

A: – ¿Existen paquetes de gestión de dependencias de terceros?

EM: – Solíamos ir dep.

A: – Había algo sobre vídeo en el tema del informe, pero no había nada sobre vídeo en el informe.

EM: – No, no tengo nada en el tema sobre el video. Se llama "Look+", ese es el nombre de la aplicación.

A: – ¿Dijiste que se transmite a los clientes?

EM: – No estábamos involucrados en la transmisión de video. Esto fue hecho íntegramente por Megafon. Sí, no dije que la aplicación fuera MegaFon.

EM: – Go – para enviar todos los datos – sobre el marcador, los eventos del partido, estadísticas... Go es el backend completo de la aplicación. El cliente debe saber de algún lugar qué enlace utilizar para el jugador para que el usuario pueda ver el partido. Tenemos enlaces a videos y transmisiones que se han preparado.

Algunos anuncios 🙂

Gracias por estar con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más contenido interesante? Apóyanos haciendo un pedido o recomendándonos a amigos, VPS en la nube para desarrolladores desde $4.99, un análogo único de servidores de nivel de entrada, que fue inventado por nosotros para usted: Toda la verdad sobre VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps desde $19 o como compartir servidor? (disponible con RAID1 y RAID10, hasta 24 núcleos y hasta 40GB DDR4).

Dell R730xd 2 veces más barato en el centro de datos Equinix Tier IV en Amsterdam? Solo aqui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV desde $199 ¡en los Paises Bajos! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - ¡desde $99! Leer acerca de Cómo construir infraestructura corp. clase con el uso de servidores Dell R730xd E5-2650 v4 por valor de 9000 euros por un centavo?

Fuente: habr.com

Añadir un comentario