使用Golang在《经济学人》中创建微服务:回顾

大家好! 已经在5月28日,我们将在Golang开发人员课程中启动第一个小组。 今天,我们将与您分享致力于该课程启动的第一本出版物。 走吧



关键摘录

  • 经济学家需要更大的灵活性才能将内容分发到日益多样化的数字频道。 为了实现此目标并保持高水平的性能和可靠性,该平台已从单片架构转变为微服务架构。
  • 用Go语言编写的工具是新系统的关键组成部分,使《经济学人》能够提供可扩展的高性能服务并快速创建新产品。
  • 旨在并发和API支持的Go以及其静态编译语言的构建,促进了可扩展的分布式事件处理系统的开发。 测试支持也是一个加号。
  • 总体而言,《经济学人》在Go方面的团队经验是积极的,这是有助于扩展Content Platform的决定性因素之一。
  • Go并不总是适合的工具,这很正常。 《经济学人》拥有多种语言的平台,并在有意义的地方使用不同的语言。

我以Drupal开发人员的身份加入了《经济学人》的开发团队。 但是,我真正的任务是参加一个从根本上改变《经济学人》内容交付技术的项目。 我花了几个月的时间研究Go,与外部顾问一起工作了几个月以创建MVP(最低可行产品),然后再次加入团队,监督他们对Go的了解。
随着新闻消费从印刷媒体转移,《经济学人》扩大数字受众的使命触发了技术的这种转变。 《经济学人》需要更大的灵活性才能将内容交付给越来越多的数字渠道。 为了实现此目标并保持高水平的性能和可靠性,该平台已从单片架构转变为微服务架构。 用Go语言编写的工具是新系统的关键组成部分,这使《经济学人》能够提供可扩展的高性能服务并快速创建新产品。

在《经济学人》中实施Go:

  • 允许工程师快速开发和实施新功能。
  • 经过批准的最佳做法,可通过智能错误处理快速提供服务。
  • 为分布式系统中的并发和网络操作提供了可靠的支持。
  • 它表明在内容和媒体所需的某些领域缺乏成熟度和支持。
  • 促进了一个可扩展为数字出版的平台。

为什么《经济学人》选择了Go?

为了回答这个问题,突出新平台的整体架构将很有用。 该平台称为内容平台,是一个事件处理系统。 它响应来自不同内容创作平台的事件,并启动在单独工作的微服务中执行的流程流。 这些服务执行诸如标准化数据,分析语义标签,在ElasticSearch中建立索引以及将内容发送到外部平台(例如Apple News或Facebook)之类的功能。 该平台还具有RESTful API,该API与GraphQL结合使用是前端客户端和产品的主要入口点。

在开发通用架构时,团队研究了哪种语言适合该平台。 Go已与Python,Ruby,Node,PHP和Java进行了比较。 尽管每种语言都有自己的优势,但是Go最适合平台架构。 旨在并发和API支持的Go以及其静态编译语言的构建,促进了可扩展的分布式事件处理系统的开发。 另外,相对简单的Go语法使开发人员可以轻松参与开发并开始编写工作代码,这为经历如此大规模技术转型的团队带来了直接的好处。 通常,已确定Go是最适合分布式云系统中的可用性和效率的语言。

三年后:Go符合这些雄心勃勃的目标吗?

一些平台设计元素与Go语言完全吻合。 快速失败是系统的关键部分,因为它由分布式独立服务组成。 根据十二要素应用程序(“十二要素应用程序”)的原理,该应用程序必须快速启动并迅速失败(快速失败)。 Go的设计是一种静态的,已编译的语言,因此可以缩短启动时间,并且编译器的性能正在不断提高,而对于设计或部署从来就不是问题。 此外,Go的错误处理设计使应用程序不仅可以更快地失败,而且可以更智能地失败。

错误处理

工程师在Go中很快注意到的一项功能是错误类型,而不是异常系统。 在Go中,所有错误均为值。 错误类型是预定义的,并且是一个接口。 Go中的接口本质上是方法的命名集合,并且任何其他具有相同方法的用户类型都可以满足该接口。 Error类型是一个可以将自身描述为字符串的接口。

type error interface { Error() string } 

这为工程师提供了更多的错误处理控制和功能。 通过添加在任何用户模块中返回字符串的Error方法,您可以创建自己的错误并生成它们,例如,使用下面的New函数(来自Errors包)。

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

在实践中这意味着什么? 在Go中,函数允许多个返回值,因此,如果您的函数可能不起作用,则很可能会返回错误值。 该语言鼓励您明确检查错误发生的位置(与引发和捕获异常相反),因此您的代码通常应包括“ if err!if check!”。 =无。” 首先,这种频繁的错误处理可能看起来很单调。 但是,将error作为值允许您使用Error简化错误处理。 例如,在分布式系统中,您可以通过包装错误来轻松实现重试查询的尝试。

无论是将数据发送到其他内部服务还是将其传输到第三方工具,网络中总是会出现网络问题。 此Net数据包示例演示如何使用错误作为一种类型,以区分临时网络错误和永久错误。 当将内容发送到外部API时,经济学人团队使用类似的错误包装程序来创建增量重试。

 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) } 

Go语言作者认为,并非所有例外都是例外。 工程师更可能从错误中智能恢复,而不是使应用程序崩溃。 此外,Go错误处理可让您更好地控制错误,从而改善调试或错误可用性等方面。 在Content Platform中,Go的这一设计功能使开发人员可以就错误做出明智的决策,从而提高了整个系统的可靠性。

资料一致性

数据一致性是Content Platform中的关键因素。 在《经济学人》杂志上,内容是业务的基础,内容平台的目标是确保内容可以一次发布并且可以在任何地方使用。 因此,重要的是,每个产品和使用者都必须与Content Platform API保持数据一致性。 产品主要将GraphQL用于API请求,这需要一个静态方案,该方案是使用者和平台之间的一种契约。 平台处理的内容必须与此方案一致。 静态语言帮助实现了这一点,并使其易于实现数据一致性。

使用Go进行测试

增强一致性的另一个功能是Go测试套件。 Go的快速编译时间以及该语言的特色是将一流的测试功能结合在一起,使团队可以将有效的测试方法整合到设计工作流程中,并在装配管道中快速失败。 Go的测试工具使其易于设置和运行。 运行“ go test”将运行当前目录中的所有测试,并且test命令具有几个有用的标志。 Cover标志提供详细的代码覆盖率报告。 基准测试运行基准测试,通过使用单词“基准”而不是“测试”来运行测试功能的名称来指示基准测试。 TestMain函数提供了用于其他测试设置的方法,例如虚拟身份验证服务器。

此外,Go能够使用匿名结构和带有接口的存根创建表格测试,从而提高了测试覆盖率。 尽管就语言功能而言,测试并不是新生事物,但是Go可以轻松创建强大的测试并将其轻松集成到工作流中。 从一开始,《经济学人》的工程师就能够在不进行特殊配置的情况下将测试作为组装流水线的一部分运行,甚至在将代码推送到Github之前添加了Git Hooks来运行测试。

但是,该项目并非没有努力实现数据一致性。 该平台的第一个主要问题是管理来自不可预测的后端的动态内容。 该平台主要通过不保证结构和数据类型的JSON端点来消费原始CMS系统中的内容。 这意味着平台无法使用标准的Go包来解释json,该包支持将JSON反序列化为结构,但是如果struct和输入数据字段的类型不匹配,则会发出警报。

为了克服此问题,需要一种特殊的方法将服务器部分映射到标准格式。 在对所选方法进行多次迭代之后,该团队介绍了自己的反序列化过程。 尽管这种方法有点像重新设计标准库包,但它使工程师可以完全控制源数据的处理。

网络支持

可扩展性是新平台的最前沿,它是由用于网络和API的标准Go库提供的。 在Go中,您无需框架即可快速实现可扩展的HTTP端点。 在下面的示例中,net / http标准库包用于配置接受请求和响应编写器的处理程序。 首次实现Content Platform API时,它使用了API框架。 最终,它被标准库所取代,因为该团队承认它可以满足他们所有的网络需求,而不会造成其他不必要的损害。 Golang HTTP处理程序可扩展,因为对处理程序的每个请求都是在Goroutine中并行执行的,而Goroutine是无需定制的轻量级线程。

 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)) } 

并发模型

Go并发模型在提高整个平台的性能方面提供了多个好处。 处理分布式数据涉及对承诺给消费者的保证大惊小怪。 根据CAP定理,不能同时提供以下三个保证中的两个以上:数据一致性。 有空 耐分离。 在Economist平台中,最终采用了一致性,这意味着从数据源读取将最终保持一致,并且所有数据源达到一致状态的适度延迟都是可以接受的。 最小化这种差距的一种方法是使用Goroutines。

Goroutine是Go运行时管理的轻量级线程,以防止它们用尽线程。 Goroutine允许优化平台上的异步任务。 例如,该平台的数据存储库之一是Elasticsearch。 在系统上更新内容时,在Elasticsearch中引用此项目的内容将被更新并重新编制索引。 由于使用了Goroutines,减少了处理时间,从而确保了元素的快速一致性。 此示例显示了如何在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 } 

设计系统不仅仅是编程。 工程师需要了解在何时何地适合使用哪些工具。 尽管Go是满足《经济学人》内容平台大部分需求的强大工具,但某些限制要求其他解决方案。

依赖管理

Go刚发布时,它没有依赖项管理系统。 在社区内部,已经开发了几种工具来满足这一需求。 《经济学人》使用Git子模块,这在社区积极推广标准依赖项管理工具时才有意义。 如今,尽管社区已经非常接近一种一致的依赖管理方法,但现在还不存在。 使用子模块的《经济学人》方法并没有带来严重的问题,但是对于其他Go开发人员来说却很困难,因此在切换到Go时应考虑到这一点。

对于某些平台要求,Go功能或设计也不是最佳解决方案。 因为平台增加了对音频处理的支持,所以Go提取元数据的工具当时受到限制,因此该团队选择了Exiftool Python。 平台服务在Docker容器中工作,从而可以安装Exiftool并从Go应用程序中启动它。

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

该平台的另一种常见情况是从源CMS系统接收无效的HTML代码,分析HTML代码以确保HTML代码的正确性和有效性。 最初,Go用于此过程,但由于Go的标准HTML库需要正确的HTML,因此在处理之前需要大量自定义代码来解析HTML。 这段代码很快变得脆弱,错过了临界情况,因此在Javascript中实现了一个新的解决方案。 Javascript提供了极大的灵活性和适应性,可以控制检查和清理HTML的过程。

Javascript也是在平台中过滤和路由事件的常见选择。 使用AWS Lambda过滤事件,AWS Lambdas是仅在调用时运行的轻量级函数。 一种用例是过滤不同频段上的事件,例如快和慢。 此过滤基于事件处理程序外壳程序JSON对象中的单个元数据字段。 过滤实现使用JSON Javascript指针包来捕获JSON对象中的元素。 与完全分解Go所需的JSON相比,此方法效率更高。 尽管Go可以实现这种功能,但是使用Javascript对于工程师来说更容易,并且提供了更简单的lambda。

回顾性围棋

在实施Contact Platform并在生产中提供支持之后,如果我要回顾Go和Content Platform,我的反馈如下:

什么已经好?

  • 分布式系统的关键语言设计元素。
  • 相对容易实现的并发模型。
  • 不错的编码和有趣的社区。

有什么可以改进的?

  • 版本控制和销售标准的进一步提高。
  • 在某些地区还不够成熟。
  • 特定用户案例的详细信息。

通常,这是一种积极的体验,Go是允许扩展Content Platform的最重要元素之一。 Go并不总是适合的工具,这很正常。 《经济学人》拥有多种语言的平台,并在有意义的地方使用不同的语言。 当您需要弄乱文本对象和动态内容时,Go可能永远不会是最佳选择,因此Javascript仍在工具箱中。 但是,Go的优势是使系统能够扩展和增长的基础。
在考虑Go是否适合您时,请考虑系统设计的关键问题:

  • 您系统的任务是什么?
  • 您为消费者提供什么保证?
  • 什么体系结构和模式适合您的系统?
  • 您的系统应如何扩展?

如果您要开发一个系统来应对分布式数据,异步工作流以及高性能和可伸缩性的挑战,那么我建议您考虑使用Go及其加速系统目标的功能。

朋友,我们正在等待您的评论,并邀请所有人参加开放式网络研讨会 ,该研讨会将由Yandex的高级开发人员于16日举行,我们的老师是Dmitry Smal

Source: https://habr.com/ru/post/zh-CN451760/


All Articles