阅读更多

1顶
0踩

编程语言

转载新闻 产品环境中Go语言的最佳实践

2014-09-01 10:01 by 见习编辑 u012797015 评论(0) 有6841人浏览

在SoundCloud,我们为客户构建了产品的API。或者说,我们主要的网站、手机客户端和手机应用是该API的第一批客户。该API背后是一个领域性的服务:SoundCloud基本上以面向服务体系结构的形式运作。

 

我们也是通晓多种语言的组织,因为我们使用了很多语言。并且这些服务(和基础设施支持)的许多部分是使用Golang开发的。事实上,我们都是早期Golang的使用者:目前,我们已在产品中使用Golang有两年半的时间。相关项目包括:

 

  • Bazooka,我们内部服务平台;产品思想非常类似于Keroku或Flynn。
  • 我们外围的传输层使用通用的nginx, HAProxy等等,但是它们要和Golang服务协作。
  • 我们的音频存储在AWS S3上,但是上传、转码和生成链接等需要Golang服务协调处理。
  • 搜索采用了Elasticsearch, 探测使用复杂的机器学习模型,但是它们都与由Golang开发的基础设施相集成。
  • Prometheus,一个早期阶段的遥测系统纯粹是有Golang开发。
  • 当前,流处理采用Cassandra,但是我们正打算(几乎)完全使用Golang代替。
  • 我们也正在试验用Golnag开发的HTTP流媒体直播服务。
  • 许多其他面向产品的小服务。

这些项目大概有六个团队开发,包括十多人的SoundCloud勤杂工,他们中的大部分会全职使用Golang。毕竟在这个时候,这些项目和这样混杂的工程师中,我们已经逐渐形成了在产品中使用Golang的最好实践方法。我们的这些教训将对其他开始大举投资Golang的组织提供帮助。

 

开发环境

在我们的笔记本上,我们已经设定了单一、全局的GOPATH。就个人而言,我喜欢使用$HOME,但是许多其他人使用$HOME下的一个子目录。我们克隆仓库进入GOPATH的相对路径,然后就可直接工作。即,

我们中的许多人在早期一直和约定俗成的事情做斗争,以保持我们自己特有的代码组织方法。事实上,它根本不值得如此麻烦。

 

对于编辑器,许多用户使用Vim以及各种插件。(我使用的vim-go就不错。)还有许多人,包括我自己也是,结合GoSublime使用Sublime Text。也有少数人使用Emacs,但没有人用IDE。我不确定这是不是个最佳的实践,但标出来挺有趣的。

 

库结构

我们的最佳实践是确保任何事情简单。许多服务源码半打包在main包中。

比如我们的搜索调度器,两年后仍然是这样。在确定需要前不要创建新结构。

 

也许在某些时候你需要创建一个新的支持包。在你的main库中使用子目录,并使用完整的限定名导入。如果该包只有一个文件或一个结构,那么它肯定不需要分拆出来。

 

有时一个仓库中需要包含多个二进制文件;比如这个任务需要一个服务,一个工作进程,或一个监控。在这种情况下,将每个二进制文件放在特定main包的单独的子目录中,并使用其他的子目录(或包)来实现共享的功能。

请注意,不要引入asrc目录。由于vendor子目录异常(下面介绍更多内容)不要在仓库中包含src目录,或将其添加到GOPATH。

 

格式及样式

通常来说,首先配置你的编辑器保存代码交给go fmt(或goimports),使用默认参数。这意味使用tab缩进,用空格对齐。格式不正确的代码将不能提交。

 

过去的风格指南非常广泛,但谷歌最近发布了他们的 代码审查意见 文档,这几乎就是我们应遵守的公约。因此,我们使用它。

 

实际上我们把它推进了一点:


避免命名返回参数,除非他们能明确和显着地提高透明度。

避免用 make 和 new,除非他们是必要的(new(int),或 make(Chan int)),或者我们能提前知道要分配的东西的尺寸( make(map[int]string,n),或 make([]int,0,256))。

使用 struct{} 作为标记值,而不是布尔或接口{}。例如,集合是 map[string]struct{};信道是 chan struct{}。它明确标明了信息的明确缺乏。


打断长行的参数也很好。那更象是Java的风格:

 
这样会更好:

 
当构造对象时也同样分为多行:

 
另外,当分配新的对象时,在初始化部分传递成员值(如上面)比下面这样过后设置要好。

 

配置

我们尝试了通过多种方式向Go程序传递配置:解析配置文件,用 os.Getenv 直接从环境中提取配置,各种增值flag解析包。最后,最合乎经济原则的就是普通的package flag,它的严格类型和简单语义对我们所需的一切都绝对够用而且够好。

 

我们主要部署12-Factor 的应用,12-Factor 应用程序通过环境传递配置。但即使这样,我们也使用一个启动脚本来把环境变量转换为flags。Flags作为程序及其运行环境之间的一个明确和全文档化的表面区域。他们对于了解和操作程序来说是非常宝贵的。


一个关于flags的不错的习惯是把他们定义到你的main函数中。这样就能防止你在代码中随意的将他们作为全局变量使用,这使你严格的遵守依赖注入从而方便测试。

 
日志和遥测

我们尝试过几个日志框架,他们提供像日志级别,调试,路由输出,自定义格式化等等功能。最终我们选定package log。因为我们只记录可操作信息。 这意味着需要人工处理的 serious, panic级别的错误,或者结构化数据会被其他机器消耗。 举个例子,搜索转发器发送每一个它使用上下文信息处理的请求,因此我们的分析工作流可以看到新西兰的人们经常搜索 Lorde, 或者随便什么。
 
我们考虑到遥测,在一个运行过程中释放出的任何其他量:请求响应时间,QPS,运行错误,队列深度等等。并且遥测基本上包括两种模式:push和pull。


push意味着释放指标到一个已知的系统。例如Graphite, Statsd, and AirBrake

pull意味着在一些已知的位置暴露指标,并允许已知的系统去擦除它们。例如,expvar和Prometheus(或许还有其他的)


当然两种方式都有自己的存在性。当你开始使用时,push是直观和简单的。但是推送指标的增长却有悖常理:你得到的越大,成本越高。我们过去发现在特定规模大小的基础设施上,pull是该尺度下的唯一模型。那也有许多值能反映一个运行的系统。所以,最好的实践是:expvar或者类似风格的。
 

测试和验证

在一年的过程中我们尝试了许多的测试库和框架,但是很快放弃了他们中的大部分,今天我们所有的测试通过数据驱动(表驱动)测试,用普通的包测试。我们没有强烈或者明确的抱怨测试/检查包,除此之外,他们根本没有提供巨大的价值。有一件事情是有帮助的:reflect.DeepEqual让你更简单的对任意值进行比较(例如expected对got)。

 

包测试是面向单元测试的,对于集成测试,就会有点麻烦。运行的外部服务依赖于你的集成环境,但是我们找到了一个好的方式集成他们。写一个integration_test.go,给它一个integration的构建标签。定义(全局)标志,比如服务地址和连接字符串,用他们在你的测试中。

 

go test 和 go build 一样建立标签,所以你可以调用 go test -tags=integration 。它也综合了 flag.Parse 包的 main,所以任何被声明和可见的 flags 将被处理和提供给你的测试。

 

通过验证,我的意思是静态代码验证。幸运的是,Go 有一些很好的工具。我发现当考虑使用哪种工具时考虑编写代码的阶段很有用。


当做这种事时          使用这个
保存                  go fmt(或 goimports)
构建                  go vet,golint, 或者 go test
部署                  go test -tags=integration


插曲
到目前为止,还没东西过于疯狂。当做调查编撰这个列表的时候,让我注意的只是如何。。。。。。结论如何的无趣。让人沉闷。我想强调这些非常轻量,纯标准库的约定能真正推广到大群体的开发人员和多元化的项目生态系统。你绝对不会仅仅因为你的代码库已经超过一定的规模,或者只是因为它可能 增长超过一定行数, 而需要你自己的查错框架,或者测试库。你真的是不会需要它的。标准的语法和用法在代码大规模时仍然功能优雅。


依赖管理
依赖管理的状态在 Go 生态系统中是一个热门的争论点,我们还没有想到完美的解决方案。但是,我们选用了一个似乎不错的妥协方案。

 

你的项目有多么重要?你的依赖管理方案是…
嗯… go get -d,然后祈祷!
很好.  VENDORING

(值得提出的是,我们有令人震惊数量的长期产品服务,依然依赖于第一个选项.然而,因为我们一般没有使用太多第三方代码,以及主要问题通常在编译阶段就被检测到,我们侥幸规避了这个问题.)

 

Vendoring意味着拷贝依赖到项目代码库,然后在编译的时候使用它们.依赖于你下载的内容,这里有两个vendoring的最佳实践.


下载           Vendor目录名         过程
二进制     _vendor               加GOPATH前缀编译
库           vendor                  重写import语句

如果下载二进制,就在代码库的根目录创建一个_vendor子目录.(带上下划线,这样,go工具就会在处理时忽略它,例如go test ./...)对待它就像对待GOPATH一样; 例如,拷贝这个依赖github.com/user/dep 到 _vendor/src/github.com/user/dep. 然后,编写一个所谓的神圣的编译过程,它将_vendor加入到可能存在的GOPATH之中. (记住: GOPATH 实际是一个路径的列表,当go工具处理import时,会按顺序搜索这个列表.)例如,你可能拥有一个顶层的Makefile文件,如下所示: 

 
如果你正在下载某个类库在你的根存储库上创建一个vendor子目录。处理这件事就像在包目录上加一个前缀。举例来说,拷贝来自于github.com/user/dep的项目放到vendor/user/dep。在这之后,重写你所有的引入(import),及其相互关系。此时是很痛苦的,当剩下的内容需要go get兼容的时候,看起来最有效的方式是确保事实上可重新构建(actually-reproducible build)。值得注意的是,我们在实践中很少去下载类库,因此这个办法虽然麻烦却很有效。

 

如何在实际中拷贝一个依赖关系到你自己的存储库是另外一个热门的话题。最简单的方法是从一个克隆(clone)中手动复制文件,如果你不关心上游部门的推送,这可能是最好的答案。有些人使用git子模块,但我们发现它们非常违反直觉并难以管理(对许多 人来说也是这样,这是有记录的)。我们对于git子目录(的管理)已经很成功,他工作起来就像是子模块。还有大量的工具是用来自动处理这项工作的。现在,它看起来就像godep发展非常积极,而且还很值得研究。


构建与部署

构建与部署有其技巧性,因此它与你的操作环境耦合紧密。我要描述下我们的场景,因为我认为它是个好模型,但它可能无法直接应用到你的组织机构中。

 

就构建而言,我们通常直接使用 go build 来开发,以及一个 Makefile 用于剪裁官方构建。这主要是因为我们熟悉多种语言,并且我们的工具使用需要做到最小功能合集(最小公倍数)。并且,我们的构建系统始于一个空环境,也需要自备编译器( Makefile 文件很难看!)。

 

对部署而言,对我们最大的吸引是无状态之于有状态。

 

模式     样例 模型 部署名称 部署形式
无状态 Request router 12-Factor Scaling        Containers
有状态 Redis          None,really ProvisioningContainers?

我们主要部署无状态的服务,方式类似于 Heroku。


结论
我有意让这些成为一种来自一个大组织在生产环境相对较长地运行Go 的经验报告。虽然这些都是有根据的意见,但他们仍然只是意见,所以请持保留态度。这就是说,Go 最大优势是它结构简单。最终的最佳做法是拥抱简单,而不是试图绕过它。

  • 大小: 5.6 KB
  • 大小: 5.9 KB
  • 大小: 120.1 KB
  • 大小: 58.5 KB
  • 大小: 7.5 KB
  • 大小: 53.6 KB
  • 大小: 6.4 KB
  • 大小: 74 KB
  • 大小: 6.6 KB
  • 大小: 44.9 KB
  • 大小: 5 KB
1
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Go语言最佳实践

    对Go语言并发编程探讨深入,讲解细腻,同时这本书也非常适合作为Go语言的入门教材,即便是对Go语言了解不深的人也能从中获益。书中标例非常有价值,它们贴切地展现了用Go语言进行编程的方法和技巧。

  • Go 语言最佳实践

    避免使用类似 base,common 或 util 的包名称 d) 尽早 return 而不是深度嵌套 e) 让零值更有用 f) 避免包级别状态 项目结构 a) 考虑更少,更大的包 b) 通过 import 语句将代码排列到文件中 c) 优先内部测试再到外部...

  • go语言最佳实践

    Golang最佳实践 局部变量使用:=方式进行初始化声明,全局变量使用var进行声明。 //全局变量var声明 var globalVar1, globalVar2 int var ( gV3 bool = true//同时进行初始化 gV4 string = "Hello Global" ) //...

  • 大道至简—GO语言最佳实践

    导读:2007年,受够了C++煎熬的Google首席软件工程师Rob Pike纠集Robert Griesemer和Ken Thompson两位牛人,决定创造一种新语言来...

  • 产品环境中 Go 语言的最佳实践

    http://www.oschina.net/translate/go-in-production

  • Go语言-Go interface 接口的最佳实践

    Go 中的接口允许我们暂时将不同的类型视为相同的数据类型,因为这两种类型实现相同的行为。它们是Go程序员工具箱的核心,并且经常被新的Go开发人员不正确地使用,导致代码不可读且经常有错误。

  • 读生产环境下go语言最佳实践有感

    最近看了一篇关于go产品开发最佳实践的文章,go-in-procution。作者总结了他们在用go开发过程中的很多实际经验,我们很多其实也用到了,鉴于此,这里就简单的写写读后感,后续我也争取能将这篇文章翻译出来。

  • 我们必须遵循的 12 个 Go 语言最佳实践

    本文旨在提供一个切实的指导,在Go语言中实现最佳实践和设计模式。这些编程技巧可以帮助开发者编写出较好的代码。 为了让大家对这些编程技巧有更加深刻的认识,我在讨论这些最佳实践的时候会附加一些示例代码。 ...

  • Go最佳实践总结

    title: Go最佳实践总结 date: 2021-03-21 17:10:09 tags: - Go categories: - 实践总结 State & Behavior 面向对象编程(OOP)中,将对象抽象为两个重要的属性: 状态(State) 行为( Behaivor) ...

  • 从API到服务:使用Go语言构建Web应用程序的流程和最佳实践

    在现代互联网时代,开发者不仅要处理前端界面设计、后端业务逻辑开发,还需要将其打包成一个产品供用户使用。为了实现这一目标,许多公司开发出了“云服务”,并通过接口调用的方式提供不同的服务。这其中最著名的...

  • golang工程最佳实践

    主要参考极客时间《Go 语言项目开发实战》规范设计。 首先理解工程化规范包括的两方面: 非编码类规范:开源规范,文档规范,版本规范,Git 规范,发布规范,… 编码类规范:目录规范,代码规范,接口规范,日志...

  • Go语言 闭包的最佳实践 (Golang经典编程案例)

    最佳实践:编写一个程序,具体要求如下: 编写一个函数 makeSuffix(suffix string) ,可以接收一个文件后缀名(比如.jpg),并返回一个闭包; 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如 ....

  • 请解释一下Go语言中的defer、panic和recover在并发编程中的最佳实践和注意事项。

    总之,在并发编程中,要小心使用defer、panic和recover,并结合其他的并发编程技术和最佳实践来确保代码的正确性、可靠性和性能。同时,对于并发编程的复杂性,需要有深入的理解和经验积累,以避免常见的并发问题和...

  • 『每周译Go』Google:12 条 Golang 最佳实践

    这是直接总结好的 12 条,详细的再继续往下看:先处理错误避免嵌套尽量避免重复先写最重要的代码给代码写文档注释命名尽可能简洁使用多文件包使用go get可获取你的包了解自己的需求保持包...

  • Go语言中 类型断言的理解和实现细节 以及最佳实践 (Golang经典编程案例)

    最佳实践1: package main import ( "fmt" ) //声明/定义一个接口 type Usb interface { //声明了两个没有实现的方法 Start ( ) Stop ( ) } type Phone struct { name ...

  • 【Go】Go语言最佳实践建议

    每种语言都会有基本的语言规范,本文将会介绍Go语言实战建议 Practical Go: Real world advice for writing maintainable Go programs 二. 指导原则 Go语言有以下3点基本指导原则 简单性: 简单性是Go语言的最高目标...

  • Go语言中的多线程编程:理解线程的工作原理和最佳实践

    作者:禅与计算机程序设计艺术 "Go语言中的多线程编程:理解线程的工作原理和最佳实践" 1. 引言 1.1. 背景介绍 1.2. 文章目的 1.3. 目标受众

  • 基于springboot大学生就业信息管理系统源码数据库文档.zip

    基于springboot大学生就业信息管理系统源码数据库文档.zip

  • 基于java的驾校收支管理可视化平台的开题报告.docx

    基于java的驾校收支管理可视化平台的开题报告

  • 原木5秒数据20241120.7z

    时间序列 原木 间隔5秒钟 20241120

Global site tag (gtag.js) - Google Analytics