设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

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

2014-9-1 14:48| 发布者: joejoe0332| 查看: 4445| 评论: 0|原作者: 漠天, 赵亮-碧海情天, AndyLam, Garfielt, gones945, yxrykds, 地狱星星, GoodLoser, 无若|来自: oschina

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


日志和遥测

  我们尝试过几个日志框架,他们提供像日志级别,调试,路由输出,自定义格式化等等功能。最终我们选定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的构建标签。定义(全局)标志,比如服务地址和连接字符串,用他们在你的测试中。

1
2
3
4
5
6
7
8
// +build integration
 
var fooAddr = flag.String(...)
 
func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

  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文件,如下所示:


1
2
3
4
5
6
7
GO ?= go
GOPATH := $(CURDIR)/_vendor:$(GOPATH)
 
all: build
 
build:
    $(GO) build

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


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


构建与部署

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


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


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


模式样例模型部署名称部署形式
无状态Request router12-FactorScalingContainers
有状态RedisNone, reallyProvisioningContainers?

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

1
2
3
4
$ git push bazooka master
$ bazooka scale -r <new> -n 4 ...
$ # validate
$ bazooka scale -r <old> -n 0 ...

结论

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



酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部