关键点

  • 经济学人内容分发系统需要更大的灵活性,将内容传递给日益多样化的数字渠道。为了实现这一灵活性目标并保持高水平的性能和可靠性,平台从一个单体结构过渡到微服务体系结构。

  • 用Go编写的服务是新系统的一个关键组件,它使得团队能够交付可伸缩的、高性能的服务并快速迭代新产品。

  • Go的并发性和对API的支持以及它作为静态编译语言的设计,使得分布式事件系统能够大规模执行。与此同时,Go对于测试的支持也非常出色。

  • 总的来说,团队在Go上的使用经验是积极的,这也是内容平台得以扩展的关键因素之一。

随着新闻消费从纸媒转向数字媒体,《经济学人》的使命是让更广泛的数字受众看到这种技术转变。因此需要更大的灵活性,将内容传递给日益多样化的数字渠道。为了实现这一灵活性目标并保持高水平的性能和可靠性,平台从一个单体结构走向微服务体系结构。用Go编写的服务是新系统的一个关键组件,它将使团队能够交付可伸缩的、高性能的服务并快速迭代新产品。

以下是基于Go的一些实践与问题:

  • 允许工程师快速迭代产品并开发新特性

  • 强化智能错误处理并针对服务快速失败的实践

  • 为分布式系统中的高并发和网络提供有力支持

  • 在内容和媒体所需的领域缺乏成熟度和支持

  • 通过平台实现规模化的数字内容出版发行

为什么使用Go

为了回答这个问题,先看看新平台的总体架构是很有帮助的。这个平台称为内容平台,是一个基于事件的系统。它响应来自不同内容创作平台的事件,并触发独立的微服务处理这些流程。这些服务的功能包括数据标准化、语义标签分析、ES索引,以及将内容推送到苹果新闻或Facebook等外部平台。该平台还有一个RESTful API,它与GraphQL相结合,是前端客户端和产品的主要入口。

在设计总体架构时,团队研究了哪些语言适合平台的需求。将Go与Python、Ruby、Node、PHP和Java进行比较。虽然每种语言都有其优点,但最好与平台的体系结构保持一致。Go的并发性和API支持以及它作为静态编译语言的设计将使分布式事件系统能够大规模执行。此外,Go相对简单的语法使学习和开始编写工作代码变得很容易,这对于一个经历了如此多技术转换的团队来说是一个好消息。总的来说,Go被认为是分布式云系统中可用性和效率的最佳设计语言。

Go的目标 

平台设计的几个元素与Go语言很好地结合在一起。快速失败是系统的关键部分,因为系统本身是由分布式的、独立的服务组成的。按照应用程序的12个因素原则,应用程序需要快速启动和失败。Go作为一种静态编译语言的先天在快速启动上有优势,并且随着编译器的性能不断提高,对于工程或部署来说从来都不是问题。此外,Go的错误处理从设计上不仅允许应用程序快速失败,还允许应用程序更智能地失败。

错误处理

Go与其他语言相比有一个明显的区别,它没有异常,而是用一个错误类型代替。在Go中,所有错误都是值。错误类型是预先声明的,是一个接口。Go中的接口本质上是一个命名的方法集合,如果它具有相同的方法,那么任何其他自定义类型都可以满足该接口。错误类型是一个可以用字符串描述自身的接口。

typeerror interface {

Error() string

}

Go为工程师提供了更好的控制错误处理功能。通过在定制模块中添加返回字符串的Error方法,可以创建定制错误,如下面的函数所示,该函数来自errors包。

typeerrorString struct {

s string

}

func(e *errorString) Error() string {

return e.s

}

在Go中,函数允许多个返回值,因此如果函数可能失败,它很可能返回一个错误值。这种语言鼓励开发人员显式地检查错误发生的地方(而不是抛出和捕获异常),因此代码通常会有一个“if err != nil”检查。在分布式系统中,可以通过包装错误轻松地启用重试。

网络问题总是会在系统中遇到,无论是向其他内部服务发送数据,还是向第三方工具推送数据。这个来自Net包的示例强调了如何利用错误作为一种类型来区分临时网络错误和永久网络错误。当将内容推送到外部api时,团队使用类似的错误包装来构建增量重试。

package net

typeError interface {

error

Timeout() bool   // Is the error a timeout?

Temporary() bool // Is the error temporary?

}

ifnerr, ok := err.(net.Error); ok && nerr.Temporary() {

time.Sleep(1e9)

continue

}

iferr != nil {

log.Fatal(err)

}

Go的作者认为并非所有异常都是例外。鼓励工程师明智地从错误中恢复,而不是让应用程序失败。此外,Go错误处理允许您对错误进行更多的控制。在内容平台中,Go的这个设计特性使开发人员能够围绕错误做出深思熟虑的决策,从而增强了整个系统的可靠性。

一致性

一致性是内容平台中的一个关键因素。内容是业务的核心,而内容平台的目标是确保内容可以发布一次并可以到处阅读。因此,每个产品和消费者都必须具有内容平台API的一致性。产品主要使用GraphQL查询API,这需要一个静态模式作为消费者和平台之间的契约。平台处理的内容需要符合这一模式。静态语言有助于实现这一点,并在确保数据一致性方面能轻松取胜。

Go与测试

另一个提高一致性的特性是Go的测试包。Go的快速编译能力和易于测试的特性相结合,使团队能够将强大的测试实践嵌入到工作流和构建Pipeline中。Go的测试工具使它们易于安装和运行。运行“go test”将在当前目录中运行所有测试,测试命令有几个有用的特性标志。“Cover”标志提供关于代码覆盖率的详细报告。“bench”测试运行基准测试。TestMain函数为额外的测试提供便利的功能,例如模拟身份验证服务器。

此外,Go能够使用匿名结构创建表测试,并使用接口创建模拟,从而提高测试覆盖率。尽管就语言特性而言,测试并不是什么新鲜事,但是Go使得编写健壮的测试并将其无缝地嵌入工作流变得很容易。从一开始,工程师们就能够在构建Pipeline的过程中运行测试,而不需要进行特殊的定制。

然而,该项目在实现一致性方面并非没有困难。该平台面临的第一个主要挑战是从不可预知的后端管理动态内容。该平台主要通过JSON端点(Endpoint)使用来自CMS系统的内容,而JSON端点不能保证数据结构和类型。这意味着平台不能使用Go的标准编码json包,该包支持将json解组到结构(Struct)中,但是如果结构字段和传入的数据字段类型不匹配,就会出现异常。

为了克服这个挑战,需要一种将后端映射到标准格式的自定义方法。在对该方法进行了几次迭代之后,团队实现了一个自定义的数据编出流程。虽然这种方法感觉有点像重新构建标准的lib包,但它为工程师提供了处理源数据的细粒度控制。

网络支持

可伸缩性是新平台关注的焦点,Go的网络和api标准库支持可伸缩性。在Go中,可以在不需要框架的情况下快速实现可伸缩的HTTP端点入口。在下面的示例中,标准库net/http包用于接受用户请求并响应。当内容平台实施时,首先尝试使用了一个API框架。随着团队认识到标准库能够满足所有的网络需求而又不增加额外的负担,最终标准库取代了该框架。Golang HTTP处理程序是可伸缩的,因为处理程序上的每个请求都在一个轻量级线程Goroutine中并发运行。

package main

import(

"fmt"

"log"

"net/http"

)

funchandler(w http.ResponseWriter, r *http.Request) {

fmt.Fprintf(w, "Hello World!")

}

funcmain() {

http.HandleFunc("/", handler)

log.Fatal(http.ListenAndServe(":8080", nil))

}

并发模型

Go的并发模型提供了跨平台的性能改进。处理分布式数据意味着要与向消费者承诺的保证作斗争。根据CAP定理,不可能同时提供以下三个保证中的两个以上:一致性、可用性、分区容忍。在经济学人的平台上,最终的一致性是可以接受的,这意味着来自数据源的读取最终是一致的,所有数据源达到一致状态的适度延迟是可以容忍的。缩小这种差距的方法之一是利用Goroutines。

Goroutines是Go运行时管理的轻量级线程,用于防止线程耗尽。Goroutines支持跨平台优化异步任务。例如,该平台的数据存储之一是Elasticsearch。当内容在系统中更新时,在Elasticsearch中引用该项目的内容将被更新并重新索引。通过实现Goroutines,减少了再处理时间,确保项目的一致性更快。这个示例演示了在Goroutine中如何对每个符合再处理条件的项进行再处理。

funcreprocess(searchResult *http.Response) (int, error) {

responses := make([]response,len(searchResult.Hits))

var wg sync.WaitGroup

wg.Add(len(responses))

for i, hit := rangesearchResult.Hits {

wg.Add(1)

go func(i int,item elastic.SearchHit) {

deferwg.Done()

code,err := reprocessItem(item)

responses[i].code= code

responses[i].err= err

}(i, *hit)

}

wg.Wait

return http.StatusOK, nil

}

设计系统不仅仅是简单的编程,工程师必须了解在何时何地使用哪些工具。虽然Go对于内容平台的大多数需求来说是一个强大的工具,但某些局限性需要其他解决方案。

依赖管理

Go发布时没有依赖管理系统,社区内有一些工具来满足这种需求。经济学人使用Git子模块,整个社区与此同时也正在积极推动一个标准的依赖管理工具。虽然社区更建议采用一致的方法进行依赖关系管理,但仍有许多分歧。在经济学人内部使用Git子模块进行依赖管理并没有带来重大的挑战,但对其他Go开发者来说,它是一个需要加以考虑的因素。

还有一些平台需求是Go的功能或设计不太适合的。由于平台增加了对音频处理的支持,Go的元数据提取工具在当时是有限的,因此团队选择了Python的Exiftool。平台服务在docker容器中运行,这也允许了安装Exiftool并从Go应用程序调用它。

funcrunExif(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实现了一种新的解决方案,为管理HTML验证和清洗提供了更大的灵活性和适应性。

Javascript也是平台中事件过滤和路由的常见选择。事件使用AWS Lambdas进行过滤,AWS Lambdas是轻量级函数。一个用例是将事件过滤到不同的通道中,例如快速通道和慢通道。此筛选基于事件包装器JSON对象中的单个元数据字段完成。过滤实现利用Javascript JSON指针包抓取JSON对象中的元素。与Go所需的完整JSON解组相比,这种方法要有效得多。虽然这种类型的功能可以通过Go实现,但是对于工程师来说,使用Javascript更容易,并且提供了更简单的Lambdas。

回顾

在实现了内容平台的实施并生产上线运行之后,对于这一历程回顾如下:

好的地方?

  • 分布式系统的关键语言设计元素

  • 并发模型,相对容易实现

  • 愉快的编码和有趣的社区

不足的地方?

  • 版本控制和标准方面的需要进一步提升

  • 在某些领域缺乏成熟的解决方案

总的来说,使用Go来快速构建系统是一种积极的体验,Go是内容平台扩展项目成功的关键元素之一。经济学人是一个多语言的平台(注:此处指编程语言),在合适的地方使用不同的语言来解决特定问题。例如,在处理文本和动态内容时,Go可能永远不会是首选,所以团队将Javascript纳入工具集中。然而,Go在支持系统扩展和发展起到主要作用。

在考虑是否要使用Go时,可以考虑一下系统设计的关键问题:

  • 系统目标是什么?

  • 你为你的消费者提供了什么保证与承诺?

  • 什么样的架构体系和模式适合你的系统?

  • 系统需要如何扩展?

如果你正在设计一个旨在解决分布式数据、异步工作流、高性能和可伸缩性挑战的系统,可以考虑使用Go来加速构建并达成系统目标。

原文作者:KathrynJonas  译者:江玮

英文原文:https://www.infoq.com/articles/golang-the-economist?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=

经济学人使用Golang构建微服务历程回顾的更多相关文章

  1. [译]Spring构建微服务

    此文为译文,原文地址 介绍 本文通过一个使用Spring.Spring Boot和Spring Cloud的小例子来说明如何构建微服务系统. 我们可以通过数个微服务组合成一个大型系统. 我们可以想象下 ...

  2. 构建微服务:Spring boot

    构建微服务:Spring boot 在上篇文章构建微服务:Spring boot 提高篇中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jp ...

  3. 如何使用 Java 构建微服务?

    [编者按]微服务背后的大理念是将大型.复杂且历时长久的应用在架构上设计为内聚的服务,这些服务能够随着时间的流逝而演化.本文主要介绍了利用 Java 生态系统构建微服务的多种方法,并分析了每种方法的利弊 ...

  4. 构建微服务-使用OAuth 2.0保护API接口

    微服务操作模型 基于Spring Cloud和Netflix OSS 构建微服务-Part 1 基于Spring Cloud和Netflix OSS构建微服务,Part 2 在本文中,我们将使用OAu ...

  5. 基于Spring Cloud和Netflix OSS构建微服务,Part 2

    在上一篇文章中,我们已使用Spring Cloud和Netflix OSS中的核心组件,如Eureka.Ribbon和Zuul,部分实现了操作模型(operations model),允许单独部署的微 ...

  6. 构建微服务(Building Microservices)-PDF 文档

    闲时翻译了几篇基于Spring Cloud.Netflix OSS 构建微服务的英文文章,为方便分享交流,整理为PDF文档. PDF 文档目录: 目录 一.微服务操作模型... 3 1.     前提 ...

  7. Chris Richardson微服务翻译:构建微服务之微服务架构的进程通讯

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯(本文) 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服 ...

  8. Chris Richardson微服务翻译:构建微服务之使用API网关

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关(本文) 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服 ...

  9. Spring Cloud构建微服务架构(二)服务消费者

    Netflix Ribbon is an Inter Process Communication (IPC) cloud library. Ribbon primarily provides clie ...

随机推荐

  1. springmvc mybatis shiro ios android构建cms系统

    开发语言: java.ios.android 部署平台: linux.window jdk版本:JDK1.7以上版本 开发工具: eclipse.idea等 服务器中间件:Tomcat 6.7.Jbo ...

  2. Linux - PS1

    \[\e[1;32m\][\u ^_^ aliyun\[\e[1;35m\] \[\e[33m\]\w ]\n$\[\e[m\] set tabstop=4set expandtabset shift ...

  3. 别人的Linux私房菜(23)软件安装RPM、SRPM、YUM

    RPM(RedHat Package Manager),不同Linux发行版发布的RPM文件甚至不同版本,不通用. SRPM为Source RPM,所提供的软件内容没有经过编译,格式为xxx.src. ...

  4. C#中 property 与 attribute的区别

    说的通俗些Attribute是类,不过是一类比较特殊的类,Attribute必须写在一对方括号中,用来处理.NET中多种问题:序列化.程序的安全特征等等,在.NET中的作用非同凡响 Attribute ...

  5. 【NOIP2013/Codevs3287】货车运输-最小生成树(大)-树上倍增

    https://www.luogu.org/problemnew/show/P1967 由题可知,我们走的路的边应尽可能大,所以通过kruscal建最大生成树的图,再树上倍增,注意可能有多棵树; #i ...

  6. sql 百万级或千万级数据分页处理

    笔记来源 https://blog.csdn.net/zhenyuanjie/article/details/7778102

  7. 【转】vim 命令

    Vim命令合集 建议直接看原文:(排版有些乱) 命令历史 以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令. 启动vim 在命令行窗口中输入以下命令即可 vim 直接 ...

  8. 【洛谷4070】 [SDOI2016]生成魔咒(SAM)

    传送门 洛谷 Solution 考虑要求的是什么,前缀的本质不同的字符串个数? 如果只要求一个串那么显然答案是\(\sum_{i=1}^{tot}len[i]-len[fa[i]]\)(实际上这个并不 ...

  9. django-celery 创建多个broker队列 异步执行任务时指定队列

    一.这里不再详细述说 django 框架中如何使用celery, 重点放在如何实现创建多个队列, 并指定队列存放异步任务 笔者使用   django-celery==3.2.2 模块, 配置项及配置参 ...

  10. python 使用多线程进行并发编程/互斥锁的使用

    import threading import time """ python的thread模块是比较底层的模块,python的threading模块是对thread做了 ...