Uso de Golang para crear microservicios en The Economist: una retrospectiva

Hola a todos! Ya el 28 de mayo, estamos lanzando el primer grupo en el curso para desarrolladores de Golang . Y hoy compartimos con ustedes la primera publicación dedicada al lanzamiento de este curso. Vamos



Extractos clave

  • The Economist necesitaba más flexibilidad para distribuir contenido a un canal digital cada vez más diverso. Para lograr este objetivo y mantener un alto nivel de rendimiento y fiabilidad, la plataforma ha pasado de una arquitectura monolítica a una de microservicio.
  • Las herramientas escritas en Go fueron un componente clave del nuevo sistema que permitió a The Economist proporcionar servicios escalables de alto rendimiento y crear rápidamente nuevos productos.
  • Go, dirigido a la concurrencia y el soporte API, junto con su construcción de un lenguaje compilado estático, facilitó el desarrollo de sistemas de procesamiento de eventos distribuidos que podrían escalar. El soporte de prueba también fue una ventaja.
  • En general, la experiencia del equipo de The Economist con Go fue positiva, y este fue uno de los factores decisivos que ayudó a escalar la Plataforma de contenido.
  • Ir no siempre será una herramienta adecuada, y esto es normal. The Economist tiene una plataforma políglota y usa diferentes idiomas donde tiene sentido.

Me uní al equipo de desarrollo de The Economist como desarrollador de Drupal. Sin embargo, mi verdadera tarea era participar en un proyecto que cambiaría fundamentalmente la tecnología de entrega de contenido de Economist. Los primeros meses que pasé estudiando Go, varios meses trabajando con un consultor externo para crear un MVP (producto mínimo viable), y luego me uní al equipo nuevamente para supervisar su inmersión en Go.
Este cambio en la tecnología fue provocado por la misión de The Economist de expandir su audiencia digital, ya que el consumo de noticias se alejó de los medios impresos. The Economist necesitaba más flexibilidad para entregar contenido a canales digitales cada vez más diversos. Para lograr este objetivo y mantener un alto nivel de rendimiento y fiabilidad, la plataforma ha pasado de una arquitectura monolítica a una de microservicio. Las herramientas escritas en Go fueron un componente clave del nuevo sistema, que permitió a The Economist proporcionar servicios escalables de alto rendimiento y crear rápidamente nuevos productos.

Implementación de Go in The Economist:

  • Permitió a los ingenieros desarrollar e implementar rápidamente nuevas funcionalidades.
  • Mejores prácticas aprobadas para servicios que fallan rápidamente con manejo inteligente de errores.
  • Proporcionó soporte confiable para concurrencia y operación de red en un sistema distribuido.
  • Mostró una falta de madurez y apoyo en algunas áreas necesarias para el contenido y los medios.
  • Facilitó una plataforma que podría escalar para la publicación digital.

¿Por qué The Economist eligió Go?

Para responder a esta pregunta, será útil resaltar la arquitectura general de la nueva plataforma. La plataforma, denominada Plataforma de contenido, es un sistema de manejo de eventos. Responde a eventos de diferentes plataformas de creación de contenido y lanza una secuencia de procesos ejecutados en microservicios que funcionan por separado. Estos servicios realizan funciones como estandarizar datos, analizar etiquetas semánticas, indexar en ElasticSearch y enviar contenido a plataformas externas como Apple News o Facebook. La plataforma también tiene una API RESTful, que en combinación con GraphQL es el principal punto de entrada para clientes y productos front-end.

Al desarrollar una arquitectura común, el equipo investigó qué idiomas serían apropiados para la plataforma. Go ha sido comparado con Python, Ruby, Node, PHP y Java. Si bien cada idioma tiene sus propias fortalezas, Go se adapta mejor a la arquitectura de la plataforma. Go, dirigido a la concurrencia y el soporte API, junto con su construcción de un lenguaje compilado estático, facilitó el desarrollo de sistemas de procesamiento de eventos distribuidos que podrían escalar. Además, la sintaxis relativamente simple de Go facilitó involucrarse en el desarrollo y comenzar a escribir código de trabajo, lo que prometió beneficios inmediatos para un equipo que experimenta una transición tecnológica tan grande. En general, se determinó que Go es el lenguaje más adecuado para la usabilidad y eficiencia en un sistema de nube distribuida.

Tres años después: ¿Go encajó con estos ambiciosos objetivos?

Algunos elementos de diseño de la plataforma estaban bien alineados con el lenguaje Go. Failing Fast fue una parte crítica del sistema porque consistía en servicios independientes distribuidos. De acuerdo con los principios de la aplicación Twelve-Factor ("aplicación de 12 factores"), la aplicación tuvo que iniciarse rápida y rápidamente (falla rápida). El diseño de Go como lenguaje compilado estático proporciona tiempos de inicio rápidos, y el rendimiento del compilador mejora constantemente y nunca ha sido un problema para el diseño o la implementación. Además, el diseño de manejo de errores de Go permitió que las aplicaciones fallaran no solo más rápido, sino también de manera más inteligente.

Manejo de errores

Una característica que los ingenieros notan rápidamente en Go es que es del tipo Error en lugar de un sistema de excepción. En Go, todos los errores son valores. El tipo de error está predefinido y es una interfaz. Una interfaz en Go es esencialmente una colección con nombre de métodos, y cualquier otro tipo de usuario puede satisfacer una interfaz si tiene los mismos métodos. El tipo de error es una interfaz que puede describirse como una cadena.

type error interface { Error() string } 

Esto brinda a los ingenieros más control y funcionalidad en el manejo de errores. Al agregar un método de error que devuelve una cadena en cualquier módulo de usuario, puede crear sus propios errores y generarlos, por ejemplo, utilizando la función Nueva a continuación, que proviene del paquete Errores.

 type errorString struct { s string } func (e *errorString) Error() string { return es } 

¿Qué significa esto en la práctica? En Go, las funciones permiten múltiples valores de retorno, por lo que si su función puede no funcionar, lo más probable es que devuelva un valor de error. El lenguaje lo alienta a verificar explícitamente los errores donde ocurren (en lugar de lanzar y atrapar una excepción), por lo que su código generalmente debe incluir una marca "if err! = Cero ". Al principio, este frecuente manejo de errores puede parecer monótono. Sin embargo, error como valor le permite usar Error para simplificar el manejo de errores. Por ejemplo, en un sistema distribuido, puede implementar fácilmente intentos de reintentar consultas envolviendo errores.

Los problemas de red siempre ocurrirán en el sistema, ya sea enviando datos a otros servicios internos o transfiriéndolos a herramientas de terceros. Este ejemplo de paquete de red muestra cómo puede usar el error como un tipo para distinguir los errores temporales de la red de los errores permanentes. El equipo de The Economist utilizó un contenedor de errores similar para crear reintentos incrementales al enviar contenido a API externas.

 package net type Error interface { error Timeout() bool // Is the error a timeout? Temporary() bool // Is the error temporary? } if nerr, ok := err.(net.Error); ok && nerr.Temporary() { time.Sleep(1e9) continue } if err != nil { log.Fatal(err) } 

Los autores de Go creen que no todas las excepciones son excepcionales. Es más probable que los ingenieros se recuperen de manera inteligente de los errores que bloqueen la aplicación. Además, el manejo de errores Go le permite controlar mejor los errores, lo que puede mejorar aspectos como la depuración o la usabilidad de los errores. Dentro de la Plataforma de contenido, esta característica de diseño de Go permitió a los desarrolladores tomar decisiones informadas con respecto a los errores, lo que condujo a un aumento en la confiabilidad del sistema en su conjunto.

Consistencia de datos

La consistencia de los datos es un factor crítico en la plataforma de contenido. En The Economist, el contenido es la base del negocio, y el objetivo de la Plataforma de contenido es garantizar que el contenido se pueda publicar una vez y esté disponible en todas partes. Por lo tanto, es importante que cada producto y consumidor tenga consistencia de datos con la API de plataforma de contenido. Los productos utilizan principalmente GraphQL para solicitudes de API, lo que requiere un esquema estático, que sirve como una especie de contrato entre los consumidores y la plataforma. El contenido procesado por la Plataforma debe ser coherente con este esquema. El lenguaje estático ayudó a implementar esto y facilitó la consistencia de los datos.

Prueba con Go

Otra característica que promueve la coherencia es el conjunto de pruebas Go. El rápido tiempo de compilación de Go, combinado con pruebas de primera clase como una característica del lenguaje, permitió al equipo incorporar métodos de prueba efectivos en los flujos de trabajo de diseño y fallas rápidas en las tuberías de ensamblaje. Las herramientas de prueba de Go facilitan la configuración y la ejecución. La ejecución de "ir a prueba" ejecutará todas las pruebas en el directorio actual, y el comando de prueba tiene varios indicadores útiles. La bandera de portada proporciona un informe detallado de cobertura de código. La prueba de banco ejecuta pruebas de referencia, que se indican ejecutando el nombre de la función de prueba con la palabra "Banco" en lugar de "Prueba". La función TestMain proporciona métodos para la configuración de prueba adicional, como un servidor de autenticación ficticio.

Además, Go tiene la capacidad de crear pruebas tabulares con estructuras anónimas y apéndices con interfaces, mejorando la cobertura de las pruebas. Aunque las pruebas no son nuevas en términos de características del lenguaje, Go facilita la creación de pruebas robustas y las integra fácilmente en los flujos de trabajo. Desde el principio, los ingenieros de The Economist pudieron ejecutar pruebas como parte de las tuberías de ensamblaje sin una configuración especial, e incluso agregaron Git Hooks para ejecutar pruebas antes de insertar código en Github.

Sin embargo, el proyecto no estuvo exento de esfuerzos para lograr la coherencia de los datos. El primer problema importante para la plataforma fue administrar el contenido dinámico de backends impredecibles. La plataforma consume contenido de los sistemas CMS originales principalmente a través de puntos finales JSON, donde la estructura y los tipos de datos no están garantizados. Esto significa que la plataforma no puede usar el paquete Go estándar para interpretar json, que admite la deserialización de JSON en estructuras, pero suena una alarma si los tipos de los campos de estructura y datos de entrada no coinciden.

Para superar este problema, se requería un método especial para asignar la parte del servidor al formato estándar. Después de varias iteraciones del enfoque elegido, el equipo introdujo su propio proceso de deserialización. Aunque este enfoque fue un poco como reelaborar un paquete de biblioteca estándar, les dio a los ingenieros un control completo sobre el procesamiento de los datos de origen.

Soporte de red

La escalabilidad estuvo a la vanguardia de la nueva plataforma, y ​​esto fue proporcionado por las bibliotecas estándar de Go para redes y API. En Go, puede implementar rápidamente puntos finales HTTP escalables sin la necesidad de marcos. En el siguiente ejemplo, el paquete de biblioteca estándar net / http se usa para configurar un controlador que acepte un escritor de solicitud y respuesta. Cuando la API de la plataforma de contenido se implementó por primera vez, utilizó el marco de la API. Finalmente fue reemplazado por una biblioteca estándar, ya que el equipo reconoció que satisface todas sus necesidades de red sin compromisos adicionales innecesarios. Los manejadores HTTP de Golang se escalan porque cada solicitud al manejador se ejecuta en paralelo en Goroutine, un hilo ligero sin necesidad de personalización.

 package main import ( "fmt" "log" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") } func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(":8080", nil)) } 

Modelo de concurrencia

El modelo de concurrencia Go ha proporcionado múltiples beneficios para mejorar el rendimiento en toda la plataforma. Trabajar con datos distribuidos implica quejarse con las garantías prometidas a los consumidores. Según el teorema de CAP, no es posible proporcionar más de dos de las siguientes tres garantías al mismo tiempo: Consistencia de datos. Disponibilidad Resistente a la separación. En la plataforma Economist, la coherencia se adoptó en última instancia, lo que significa que la lectura de las fuentes de datos será en última instancia coherente, y son aceptables retrasos moderados en todas las fuentes de datos que alcanzan un estado coherente. Una forma de minimizar esta brecha es usar Goroutines.

Las goroutines son subprocesos livianos administrados por el tiempo de ejecución Go para evitar que se queden sin subprocesos. Goroutines permitió optimizar tareas asincrónicas en la plataforma. Por ejemplo, uno de los almacenes de datos de la Plataforma es Elasticsearch. Cuando el contenido se actualiza en el sistema, el contenido que hace referencia a este elemento en Elasticsearch se actualiza y reindexa. Gracias a la implementación de Goroutines, se redujo el tiempo de procesamiento, lo que garantizó la rápida consistencia de los elementos. Este ejemplo muestra cómo los elementos que son adecuados para el reprocesamiento se reprocesan en Goroutine.

 func reprocess(searchResult *http.Response) (int, error) { responses := make([]response, len(searchResult.Hits)) var wg sync.WaitGroup wg.Add(len(responses)) for i, hit := range searchResult.Hits { wg.Add(1) go func(i int, item elastic.SearchHit) { defer wg.Done() code, err := reprocessItem(item) responses[i].code = code responses[i].err = err }(i, *hit) } wg.Wait return http.StatusOK, nil } 

Diseñar sistemas es más que solo programar. Los ingenieros necesitan comprender qué herramientas, dónde y cuándo son apropiadas. Si bien Go era una herramienta poderosa para la mayoría de las necesidades de la plataforma de contenido de The Economist, algunas limitaciones requerían otras soluciones.

Gestión de dependencias

Cuando se lanzó Go, no tenía un sistema de gestión de dependencias. Dentro de la comunidad, se han desarrollado varias herramientas para satisfacer esta necesidad. The Economist usó submódulos Git, lo que tenía sentido en un momento en que la comunidad promovía activamente una herramienta estándar de gestión de dependencias. Hoy, aunque la comunidad ya está mucho más cerca de un enfoque coherente para la gestión de la dependencia, no está allí. El enfoque de The Economist usando submódulos no planteó problemas serios, pero fue difícil para otros desarrolladores de Go, y esto debe tenerse en cuenta al cambiar a Go.

También había requisitos de plataforma para los cuales las características o el diseño de Go no eran la mejor solución. Debido a que la plataforma agregó soporte para el procesamiento de audio, las herramientas de Go para extraer metadatos eran limitadas en ese momento, por lo que el equipo eligió Exiftool Python. Los servicios de plataforma funcionan en contenedores acoplables, lo que permitió instalar Exiftool y ejecutarlo desde la aplicación Go.

 func runExif(args []string) ([]byte, error) { cmdOut, err := exec.Command("exiftool", args...).Output() if err != nil { return nil, err } return cmdOut, nil } 

Otro escenario común para la plataforma es la recepción de código HTML que no funciona desde los sistemas de CMS de origen, el análisis del código HTML para la corrección y la sanción del código HTML. Inicialmente, Go se usó para este proceso, pero como la biblioteca HTML estándar de Go requiere HTML correcto, requería una gran cantidad de código personalizado para analizar HTML antes de procesarlo. Este código rápidamente se volvió frágil y perdió casos límite, por lo que se implementó una nueva solución en Javascript. Javascript ha proporcionado una gran flexibilidad y adaptabilidad para controlar el proceso de verificación y desinfección de HTML.

Javascript también ha sido una opción común para filtrar y enrutar eventos en la Plataforma. Los eventos se filtran con AWS Lambdas, que son funciones livianas que solo se ejecutan cuando se llama. Un caso de uso es el filtrado de eventos en diferentes bandas, como rápido y lento. Este filtrado se basa en un único campo de metadatos en el objeto JSON del shell del controlador de eventos. La implementación de filtrado utilizó un paquete de puntero Javascript JSON para capturar un elemento en un objeto JSON. Este enfoque fue mucho más eficiente en comparación con desmontar completamente el JSON que Go necesitaría. Si bien se podía lograr una funcionalidad de este tipo con Go, usar Javascript fue más fácil para los ingenieros y proporcionó lambdas más simples.

Ir retrospectiva

Después de implementar la Plataforma de contacto y apoyarla en la producción, si tuviera que realizar una retrospectiva de Go y la Plataforma de contenido, mis comentarios serían los siguientes:

¿Qué es bueno ya?

  • Elementos de diseño de lenguaje clave para sistemas distribuidos.
  • Un modelo de concurrencia que es relativamente fácil de implementar.
  • Buena codificación y comunidad divertida.

¿Qué se puede mejorar?

  • Mayor avance en los estándares de control de versiones y venta.
  • Falta de madurez en algunas áreas.
  • Detalles para casos de usuarios específicos.

En general, fue una experiencia positiva, y Go es uno de los elementos más importantes que permitió escalar la Plataforma de contenido. Ir no siempre será una herramienta adecuada, y esto es normal. The Economist tiene una plataforma políglota y usa diferentes idiomas donde tiene sentido. Ir probablemente nunca será la mejor opción cuando necesite meterse con objetos de texto y contenido dinámico, por lo que Javascript todavía está en la caja de herramientas. Sin embargo, las fortalezas de Go son la base que permite que el sistema escale y crezca.
Al considerar si Go es adecuado para usted, tenga en cuenta las cuestiones clave del diseño del sistema:

  • ¿Cuáles son las tareas de su sistema?
  • ¿Qué garantías ofrecen a sus consumidores?
  • ¿Qué arquitectura y patrones son apropiados para su sistema?
  • ¿Cómo debería escalar su sistema?

Si está desarrollando un sistema que aborde los desafíos de los datos distribuidos, los flujos de trabajo asíncronos y el alto rendimiento y la escalabilidad, le recomiendo que considere Go y sus capacidades para acelerar los objetivos de su sistema.

Amigos, estamos esperando sus comentarios e invitamos a todos al seminario web abierto , que tendrá lugar el día 16 el desarrollador senior de Yandex y, en combinación, nuestro maestro es Dmitry Smal .

Source: https://habr.com/ru/post/451760/


All Articles