Mikhail Salosin. Golang Meetup. Usando Go en el backend de la aplicación Watch +

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



¿Alguien presente como el hockey?



Entonces esta aplicación es para ti. Es para Android e iOS, se utiliza para ver transmisiones de varios eventos deportivos en línea y en grabaciones. También en la aplicación hay varias estadísticas, transmisiones de texto, mesas para conferencias, torneos y otra información útil para los fanáticos.



Además, en la aplicación hay momentos de video, es decir, puedes ver los momentos agudos de los partidos (goles, peleas, tiroteos, etc.). Si no desea ver la transmisión completa, puede ver solo las más interesantes.

¿Qué se usó en el desarrollo?


El cuerpo principal fue escrito en Go. La API con la que se comunicaron los clientes móviles se escribió en Go. Además, se escribió un servicio en Go para enviar notificaciones push a dispositivos móviles. También tuvimos que escribir nuestro propio ORM, del que algún día podríamos hablar. Bueno, algunos servicios pequeños están escritos en Go: cambio de tamaño y carga de imágenes para los editores ...

Utilizamos Postgres (PostgreSQL) como base de datos. La interfaz para editores se escribió en Ruby on Rails utilizando la gema ActiveAdmin. La importación de estadísticas del proveedor de estadísticas también está escrita en Ruby.

Para las pruebas de API del sistema, utilizamos el Python unittest (Python). Memcached se utiliza para limitar las solicitudes de pago de la API, Chef para controlar la configuración, Zabbix para recopilar y monitorear las estadísticas internas del sistema. Graylog2: para recopilar registros, Slate es una documentación de API para clientes.



Selección de protocolo


El primer problema que encontramos: tuvimos que elegir un protocolo para la interacción del backend con clientes móviles, basado en los siguientes puntos ...

  • El requisito más importante: los datos sobre los clientes deben actualizarse en tiempo real. Es decir, todos los que actualmente están viendo la transmisión deberían recibir actualizaciones casi al instante.
  • Para simplificar, aceptamos que los datos que se sincronizan con los clientes no se eliminan, sino que se ocultan mediante indicadores especiales.
  • Las solicitudes GET ordinarias reciben todo tipo de solicitudes raras (como estadísticas, alineaciones, estadísticas de equipo).
  • Además, se suponía que el sistema resistiría tranquilamente a 100 mil usuarios al mismo tiempo.

En base a esto, teníamos dos opciones de protocolo:
  1. Websockets. Pero no necesitábamos canales del cliente al servidor. Solo necesitábamos enviar actualizaciones del servidor al cliente, por lo que un socket web es una opción redundante.
  2. ¡Los eventos enviados por el servidor (SSE) surgieron a la perfección! Es bastante simple y básicamente satisface todo lo que necesitamos.

Eventos enviados por el servidor


Algunas palabras sobre cómo funciona esto ...

Funciona encima de la 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: los



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

Ahora sobre cómo funciona la interacción en sí.
  • En primer lugar, el cliente determina cuándo se realizó la última sincronización con el servicio: mira su base de datos local y determina la fecha del último cambio registrado en él.
  • Envía una solicitud con esta fecha.
  • En respuesta, le enviamos todas las actualizaciones que se han producido en esta fecha.
  • Después de eso, se conecta al canal en vivo y no cierra hasta que necesita estas actualizaciones:



Le enviamos una lista de cambios: si alguien marcó un gol, cambiamos el puntaje del partido, nos lesionamos, también lo enviamos en tiempo real. Por lo tanto, en el flujo de eventos del partido, los clientes reciben instantáneamente datos relevantes. Periódicamente, para que el cliente comprenda que el servidor no ha muerto, que no le ha pasado nada, enviamos una marca de tiempo cada 15 segundos, para que sepa que todo está en orden y que no hay necesidad de volver a conectarse.

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


  • En primer lugar, creamos un canal en el que vendrán actualizaciones con un búfer.
  • Después de eso, suscribimos este canal para recibir actualizaciones.
  • Establezca el encabezado correcto para que el cliente sepa que todo está bien.
  • ping. timestamp .
  • , . timestamp, , .



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 teníamos 6,000 conexiones con una máquina (con un servidor API), Se crearon 6 mil temporizadores. Esto llevó al hecho de que la máquina no tenía la carga necesaria. El problema no era tan obvio para nosotros, pero nos ayudaron un poco y lo eliminamos.

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

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

Estas son algunas funciones auxiliares: enviar el encabezado, el ping y la estructura misma. Es decir, el nombre de la tabla (persona, partido, temporada) y la información sobre este registro se transmiten aquí:



Mecanismo para enviar actualizaciones


Ahora un poco sobre de dónde vienen los cambios. Tenemos varias personas, editores, que ven la transmisión en tiempo real. Crean todos los eventos: alguien fue eliminado, alguien resultó herido, algún tipo de reemplazo ...

Con la ayuda de CMS, los datos ingresan a la base de datos. Después de eso, la base de datos que utiliza el mecanismo de Escuchar / Notificar notifica a los servidores API sobre esto. Los servidores API ya están enviando esta información a los clientes. Por lo tanto, de hecho, 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:



PostgreSQL: escuchar / notificar


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



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

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

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



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. Omito la comprobación de errores para ahorrar espacio (no marque cargada).

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

¿Cómo funciona el despliegue?


En ruso, esto se traduce como un "divisor". Tenemos un objeto que registra a los suscriptores que desean recibir actualizaciones. Y tan pronto como llega una actualización de este objeto, difunde esta actualización a todos los suscriptores que tiene. Bastante simple:



cómo se implementa en Go:



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, en el momento en que escucha y recibirá actualizaciones, así como una lista de todos los canales disponibles: mapa, cuya clave es el canal y la estructura en forma de valores (de hecho, no utilizado de ninguna manera).

Dos métodos, Conectado y Desconectado, le permiten decirle a Fanout que tenemos una conexión a la base, que apareció y que la conexión a la base está desconectada. En el segundo caso, debe desconectar a todos los clientes y decirles que ya no pueden escuchar nada y que se vuelven a conectar, ya que la conexión con ellos se ha cerrado.

También hay un método de suscripción que agrega un canal a los oyentes:



hay un método de cancelación de suscripción que elimina un canal de los oyentes si el cliente se desconecta, así como un método de publicación que le permite enviar un mensaje a todos los suscriptores.

Pregunta: ¿Qué se transmite a través de este canal?

MS: - Se transmite un modelo que ha cambiado o ping (esencialmente solo un número, entero).

EM:- Puedes enviar cualquier cosa, publicar cualquier estructura, simplemente se convierte en JSON y eso es todo.

MS: - Recibimos una notificación de Postgres - contiene el nombre de la tabla y el identificador. Por el nombre de la tabla que obtenemos y el identificador obtenemos el registro que necesitamos, y esta estructura ya se envía para su publicación.

Infraestructura


¿Cómo se ve en términos de infraestructura? Tenemos 7 servidores de hierro: uno de ellos está completamente dedicado a la base, las computadoras virtuales están girando sobre los seis restantes. Hay 6 copias de la API: cada máquina virtual con la API se ejecuta en un servidor de hierro por separado, esto es por fiabilidad.



Tenemos dos interfaces en las que se instala Keepalived para mejorar la accesibilidad, de modo que en caso de que una interfaz pueda reemplazar a la otra. Otras dos copias del CMS.

También hay un importador de estadísticas. Hay un DB Slave desde el que se realizan copias de seguridad periódicamente. Está Pigeon Pusher, la aplicación que envía insultos a los clientes, así como cosas de infraestructura: Zabbix, Graylog2 y Chef.

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

Pros de Go


Después de trabajar en esta aplicación, se revelaron ventajas obvias de Go.
  • Genial biblioteca http. Al usarlo, puede crear bastante ya fuera de la caja.
  • Además, los canales que nos permitieron implementar muy fácilmente el mecanismo para enviar notificaciones a los clientes.
  • El maravilloso detector Race nos permitió eliminar varios errores críticos (infraestructura de ensayo). Todo lo que funciona en la puesta en escena se está ejecutando, compilado con la clave Race; y, en consecuencia, podemos ver qué problemas potenciales tenemos en la infraestructura de preparación.
  • Minimalismo y simplicidad del lenguaje.




Estamos buscando desarrolladores! Si alguien quiere, por favor.

Preguntas


Pregunta de la audiencia (en adelante - B): - Me parece que te perdiste un punto importante con respecto al Fan-out. Entiendo correctamente que cuando envía una respuesta a un cliente, ¿está bloqueado si el cliente no quiere leer?

MS: - No, no estamos bloqueando. En primer lugar, lo tenemos todo 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 poner hasta cien actualizaciones allí ... Si no podemos escribir en el canal, entonces lo elimina. Si vemos que el canal está bloqueado, simplemente lo cerramos, y eso es todo: el cliente se volverá a conectar si hay algún problema. Por lo tanto, en principio, el bloqueo no ocurre aquí.

P: - ¿Podría enviar inmediatamente a Listen / Notify un registro, no una tabla de identificación?

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

P: - ¿Los clientes reciben actualizaciones sobre los partidos que no les interesan?

EM:- En general, si. Como regla, hay 2-3 partidos en paralelo, y luego muy raramente. Si el cliente está mirando algo, generalmente está viendo el partido que está sucediendo. Luego, en el cliente hay 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. De hecho, sincronizamos nuestra base de datos en el servidor con la base de datos local del cliente para que pueda funcionar sin conexión.

P: - ¿Por qué hiciste tu ORM?

Alexey (uno de los desarrolladores de "Watch +"):- En ese momento (hace un año), ORM era menor que ahora, cuando hay bastantes. De la mayoría de los ORM existentes, lo que más me disgusta es que la mayoría de ellos trabajan en interfaces vacías. Es decir, los métodos que en estos ORM están listos para asumir cualquier cosa: estructura, puntero de estructura, número, algo irrelevante ...

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

P: - ¿Cuántas personas participaron?

MS: - En la etapa inicial, participaron dos personas. En algún lugar en junio comenzamos, en agosto la parte principal estaba lista (primera versión). En septiembre hubo un lanzamiento.

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

MS: - Para ser honesto, SSE sigue siendo un protocolo html5: el estándar SSE está diseñado para comunicarse con los navegadores, según tengo entendido. Tiene características 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. Probablemente no hicimos SSE, sino algo similar a SSE. Este no es el protocolo en sí.
No había necesidad. Por lo que yo entiendo, los clientes implementaron el mecanismo de conexión desde cero. En principio, no les importaba.

P: - ¿Qué utilidades adicionales usaste?

EM:- Los más activos utilizamos govet y golint, para que el estilo se unificara, además de gofmt. No usaron nada más.

P: - ¿Con qué depuraste?

MS: - En general, la depuración se realizó mediante pruebas. Sin depurador, GOP que no usamos.

P: - ¿Puede devolver la diapositiva donde se implementa la función Publicar? Los nombres de variables de una letra no te molestan?

MS: - No. Tienen un alcance bastante "estrecho". No se usan en ningún otro lugar (excepto aquí) (excepto en el interior de esta clase), y es muy compacto: solo toma 7 líneas.

P: - De alguna manera todavía no es intuitivo ...

MS:- No, no, este es un código real! No se trata de estilo. Es una clase muy utilitaria, muy pequeña: solo hay 3 campos dentro de la clase ...



MS: - En general, todos los datos que se sincronizan con los clientes (partidos de temporada, jugadores) no cambian. En términos generales, si vamos a hacer otro tipo de deporte en el que será necesario cambiar el partido, solo consideraremos todo en la nueva versión del cliente, y las versiones antiguas del cliente serán prohibidas.

P: - ¿Hay paquetes de terceros para la gestión de dependencias?

MS: - Solíamos ir a dep.

P: - Había algo sobre el video en el tema del informe, pero no sobre el video en el informe.

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

A:- ¿Dijiste que estás transmitiendo a los clientes? ..

MS: - No hicimos streaming de video. Esto fue hecho completamente por megáfono. Sí, no dije que la aplicación sea megáfono.

MS: - Ir - para enviar todos los datos - por puntuación, por eventos del partido, estadísticas ... Ir - este es todo el back-end de la aplicación. El cliente debe averiguar en algún lugar qué enlace utilizar para el jugador para que el usuario pueda ver el partido. Tenemos enlaces a videos y transmisiones que están preparados.


Un poco de publicidad :)


Gracias por estar con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más materiales interesantes? Apóyenos haciendo un pedido o recomendando a sus amigos, VPS en la nube para desarrolladores desde $ 4.99 , un análogo único de servidores de nivel básico que inventamos para usted: toda la verdad sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10GB DDR4 480GB SSD 1Gbps desde $ 19 o cómo dividir el servidor? (las opciones están disponibles 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 tenemos 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV desde $ 199 en los Países Bajos.Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - ¡desde $ 99! Lea sobre Cómo construir un edificio de infraestructura. clase c con servidores Dell R730xd E5-2650 v4 que cuestan 9,000 euros por un centavo?

All Articles