在外部看来,trivago似乎是提供流行的酒店搜索的单一软件产品。然而,在幕后,它拥有数十个项目和工具来支持它。trivago鼓励团队选择最能完成工作的编程语言和框架。在这些决策中,对团队的限制很少,主要追求的是长期可维护性。因此,trivago拥有一个多语言代码库,可以培养创造力和多元化思维。它使我们能够根据实际需求而不是遗留代码或过时的项目做出明智的决定。 几个月前,一个新项目的工作机会出现了。为了改善跨多个会话的用户体验,trivago启动了最近搜索项目。任务是开发一个gRPC服务处理来自前端的请求,以存储、检索和汇总登录用户的最近搜索。部分任务是在Kubernetes中运行服务并针对我们的trivago验证传入请求OAuth2认证服务器。我们的团队已经在使用Java或Kotlin等JVM语言的类似环境中进行过类似项目的丰富经验。然而,这一次,我们选择了Go。 检测器 在trivago运行面向用户的服务意味着同时处理潜在的数千个传入请求。此外,接触开放的互联网绝对需要适当的管理超时和共享资源。对于这两点,我们非常确定我们可以依赖Go对并发的出色内置支持:超时,继续管道和取消语境 由于并发请求和对共享资源的访问成为常态,因此可能会发生错误。在之前的一个类似项目中,在我们决定并行运行它们之后,我们的集成测试随机开始失败并出现不同的结果。错误模式指向可能的竞争条件,经过检查,我们很快就能找到它。当你认为这个类是一个单一请求还是多个请求之间的共享资源?publicclassService{privateStringvalue;publichandle(Requestreq){this。value:req。stringField;System。out。println(this。value);}} handle方法将单个请求的数据存储value在类实例的字段中,并在以后再次使用它。如果在此期间有另一个请求进来,数据竞争发生,并且行为未定义。幸运的是,我们在用户之前发现了这个问题,但我们不想让这个问题发生。让我们看一下Go中的相同示例:typeServicestruct{valuestring}func(sService)handle(reqRequest){s。value:req。stringFieldfmt。Println(s。value)} Go代码表现出与Java代码相同的行为,因此容易发生数据竞争。这就是2013年推出的Go的竞争检测器发挥作用的地方。一个新的普通构建标志race现在允许启用数据竞争检测:编译器使用记录访问内存的时间和方式的代码检测所有内存访问,而运行时库监视对共享变量的非同步访问。 GoRaceDetector简介 在使用该标志编译上述示例时,处理两个并发请求会导致打印以下警告,并直接将我们指向有问题的代码:WARNING:DATARACEWriteat0x00c0000901e0bygoroutine7:main。(Service)。handle()modulepathcmdtestmain。go:160x3ePreviouswriteat0x00c0000901e0bymaingoroutine:main。(Service)。handle()modulepathcmdtestmain。go:160x3emain。main()modulepathcmdtestmain。go:80xc3Goroutine7(running)createdat:main。main()modulepathcmdtestmain。go:70xa0Found1datarace(s) 静态链接的二进制文件 与Python或Java等语言相比,运行使用Go编译的二进制文件不需要匹配版本的解释器或虚拟机。通过另外禁止包调用C代码(cgo),我们可以创建没有任何运行时依赖的静态链接二进制文件。这让我们有机会将我们的Docker构建过程更进一步。ThisisaminimalexampletodemonstratetheFROMscratchusage。Itdoesnotincludestepstocreateanduseanonprivilegeduser,addrootcertificatesandtimezonedata,orperformotherchecks。FROMgolang:1。13。8asbuildWORKDIRbuildCOPY。。RUNCGOENABLED0gobuild。cmdmytoolFROMscratchCOPYfrombuildbuildmytoolentrypointENTRYPOINT〔entrypoint〕 这几乎将Docker镜像的大小(20MB)缩小到我们的应用程序的大小(18MB)。相比之下,Java的最小Docker镜像本身openjdk:8jrealpine或gcr。iodistrolessjava:8大小在85MB到125MB之间。不需要解释器或者虚拟机也意味着镜像基本没有启动时间。鉴于我们在Kubernetes中运行服务的要求,小镜像和低启动时间是非常可取的,因为它们允许我们快速部署和自动扩展。 gofmt 间距和支撑位置可以说是围绕软件工程的辩论中最具争议的两个话题。除非您使用的语言语义依赖于不可见的字符,它们本质上受制于个人风格,对代码的正确性或性能没有影响。Go附带了一个源代码格式化程序。这是一个例子:iferr!nil{returnerr}gofmtexample。goiferr!nil{returnerr} 几乎每种语言都存在自动格式化源代码的工具,那么为什么要提到它作为选择Go而不是其他任何语言的理由呢?此外,如果我更喜欢第一个版本怎么办?确定必须有一个设置来配置行为?。 关键是,您可以花费数天、数周甚至数月的时间,试图找到一种所有人都同意的编码风格,但仍然失败。将代码格式化程序集成到工具链中而不是外部工具链中,可以防止在琐碎的细节上浪费大量时间。这就是为什么我们喜欢gofmt我们的代码而是专注于功能。 结论 Go已被证明非常适合我们的微服务,但它并不是唯一的。Rust是另一种支持静态链接二进制文件的现代语言,并且数据竞争主要是通过Rust的所有权系统来防止的。这意味着它们将在编译时被捕获,而不仅仅是在运行时。然而,Go的简单性和复杂的工具让我们不仅可以扩展我们的服务,更重要的是,软件工程本身的过程。减少入职和培训人员的摩擦对公司的生产力有重大影响,在像trivago这样不断变化的环境中更是如此。