php与go开发的一点心得,go项目的一些心得

d9bebff94797c3c3762f5a827a5dc0d3.png

spacex

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

包管理

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

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

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

8a89b7c8f9ae41f56601009f2d51061c.png

包管理

我司将所有语言第三方库都放到 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()

// 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 来标识。

1240

最终执行的代码逻辑

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

a057100aa0fb59704d3767e44f90a090.png

cancel signal

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

goroutine和panic

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

3c39602be1792bf002de3c65f750544d.png

错误示例

最开始程序如上图,原以为会捕获到 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 则会常驻。

9dd1614d2c8d6e605826c19f235a1015.png

对象池

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

内部服务注册

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

699e1bc5bdb59de69052abcff2263526.png

服务注册

实现在 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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值