go项目的一些心得

董泽润 · · 1603 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。


spacex

最近小伙伴们刚完成广告系统,第二个直接服务于业务的项目。踩了一些坑,更收获了不少知识。总结出来与大家分享,没什么高大尚技术,都是周边的小技巧,加深对 go 语言的理解,适合新手,老鸟勿喷。

包管理

很多人都认为 go 的包管理不够友好,深有感觉。特别是在 github 上给别人提 patch, 我先 fork 到自已目录下面,如果原作者有引用自已路径下面的库,这就麻烦了。

另外一个是版本管理,每个人的 gopath 下面同样的库可能有不同版本,官方提供了一个 Godep 来控制版本,我看很多开源项目也在用。但是如果想管理除 go 以外的依赖呢?

我们使用相对路径的方式,将引用到的库集中放到 submodule 中,如下图:


包管理

我司将所有语言第三方库都放到 tinder 里面,包括多个组之间共用的 thrfit IDL 文件。Go 第三方库都放到 golang/lib 目录下面,共用的内部库放到 golang/src/common 下面,每次 install 编译程序时将 gopath 指定到当前项目下的相对目录。

更新20160629:现在依赖使用govender,第三方的IDL使用submodule

超时控制与请求跟踪

由于业务对时延要求高,给我们定 50ms 超时时间。大家肯定会想到用 Channel 和 timer 来做控制,但是我们还想跟踪请求在内部的一系列操作,否则 Debug 日志一大堆,无法定位。

此时想到了 golang.org/x/net/context 库,官方文档很详细,用于跨 API 调用很方便,vitess 中大量使用这个库。

// A Context carries a deadline, a cancelation signal, and other values across

// API boundaries.

// Context's methods may be called by multiple goroutines simultaneously.

type Context interface {

// Deadline returns the time when work done on behalf of this context

// should be canceled.  Deadline returns ok==false when no deadline is

// set.  Successive calls to Deadline return the same results.

Deadline() (deadline time.Time, ok bool)

// Done returns a channel that's closed when work done on behalf of this

// context should be canceled.  Done may return nil if this context can

// never be canceled.  Successive calls to Done return the same value.

// See http://blog.golang.org/pipelines for more examples of how to use

// a Done channel for cancelation.

Done() <-chan struct{}

// Value returns the value associated with this context for key, or nil

// if no value is associated with key.  Successive calls to Value with

// the same key returns the same result.

// Use context values only for request-scoped data that transits

// processes and API boundaries, not for passing optional parameters to

// functions.

// Packages that define a Context key should provide type-safe accessors

// // userKey is the key for user.User values in Contexts.  It is

// // unexported; clients use user.NewContext and user.FromContext

// // instead of using this key directly.

Value(key interface{}) interface{}

}

一个请求过来,每一次流转都要携带 context.Context, 并且首先检测是否超时,如果超时或是被取消,那么直接返回。另外 context.Context 会携带每次请求 ID,这是由业务传过来的字段,如果为空,内部会生成一个 uuid 来标识。


最终执行的代码逻辑

超时参数由业务传过来,根据 timeout 生成 context.Context,最终函数要么由 ctx.Done 超时返回,要么从 rr channel 中获取业务结果返回。 真实业务请求会开启一个匿名 goroutine, 传入的 context.Context 携带了 logid, 内部打日志都会先打印 logid.


cancel signal

在每个耗时请求(redis/mysql)的入口,都会先检测是否超时。

goroutine和panic

这块学艺不精,不像 actor 有父子关系,函数派生出来的 goroutine 如果panice 会挂掉整个程序,比如如下代码:


错误示例

最开始程序如上图,原以为会捕获到 do_something 产生的 panic, 还是太年轻啊。要将 recover 放置在 go func 入口。

缓存脏数据

我们会在 redis 缓存用户信息,过期时间 6 小时,如果没有再 fallback 到数据库,另外还有一个程序内置 lru cache. 

程序升级后,发现测试逻辑不对,uid 始终为0,fix 这个问题后,缓存这时就出现了脏数据。这时有两个办法,选择了第2个。

1. 使用 redis-port 批量清除无效缓存

2. 再次更新程序,内部修订错误数据

php thrift 超时问题

这个问题蛮头痛,网上也有人遇到过 thrift中的超时(timeout)坑。底层有三个超时时间 connect, send 和 recv,最初都设置的 100ms,线上每天大量超时报错,后来我们将 recv timeout 调到 1000ms 线上就安静了。

另外两个 connect, send 仍然是 100ms,我们更倾向于底层驱动的超时时间稍长一些,由业务层来控制超时 ( context 库)。

对象池

对象池是不同于连接池,两个概念的东西。连接池特指 redis/mysql 的长连接,常驻内存。而对象池是内部实例,使用对象池可以减少程序 GC 压力。目前常用的有两种  sync.Pool 和 channel 模拟的对象池。官方有对 sync.Pool 的详细说明,对象会在两个 GC 之间被回收释放,而 channel 则会常驻。


对象池

代码很简单也易懂,Get 时 channel 有数据就返回,没有直接 New。至于 channel 缓冲大小,要根据业务压力来定。

内部服务注册

在全局 map 注册服务,这也算是 go 程序标配了,最出名的就是官方 database 库注册 mysql driver 的代码


服务注册

实现在 driver.Driver 接口的服务,直接注册进来即可,使用时直接根据 name 找到 driver。

ServerOnRun

服务内部模块有大量初始化的需求,对于全局变量等直接扔到 init() 函数里即可,但是对于依赖外部服务 (mysql/redis/servervice),在程序启动时连接句柄都不存在,就不能扔到 init() 里。

一种做法就是在各个模块里写 init_xx()等方法,然后在 main() 启动初始化外部配置后,去调用,不过这样在 main 里维护就很麻烦。

所以要在全局定义 ServerOnRun, 每个无法由 init() 完成的初始化都在这里进行注册,最后由 main 遍历 ServerOnRun 来执行即可。

thrift字段变更问题

业务升级改动,时常会加字段,并且为了兼容现有代码,必须设为 optional。另外有时还遇到要将字段类型由 int 换成 string 的问题,比较麻烦,前期还是要设计好

json序列化

程序内部有大量的json序列化需求,官方的稍慢,采用比较流行的 ffjons 


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:董泽润

查看原文:go项目的一些心得

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1603 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传