前言:
什么是monkey ? 英语里面试猴子,但程序里面我们称之为 补丁. 用过python gevent的朋友应该很熟悉这个monkey补丁字眼吧。 对的,这里golang的monkey实现的功能跟gevent monkey是一样的,都是为了粗暴直接的替换标准库。
该文章后续仍在不断更新中, 请移步到原文地址 http://xiaorui.cc/?p=5128
我这里的场景很简单,就是想替换一些第三方库的json模块,像logrus就提供了优雅的jsonFormat接口,但其他的第三方库就没这么方便了,多少有些费劲的。 如果想批量替换标准库的一些方法,不外乎几种方法,一种是直接修改vendor源代码。,另一种是通过reflect和unsafe来实现monkey的逻辑。 这里离用的是bouk写的monkey库,github地址在这里 https://github.com/bouk/monkey
下面是我写的go monkey测试代码, 代码逻辑很简单,就是尝试用滴滴陶文的json-iterator替换encoding/json库。 这里多说一嘴,json-iterator在序列化struct的时候性能要比encoding/json高, map他俩半斤八两的。 json-iterator内部大量的使用sync.pool,一定程度上减少了对象的malloc创建。 还使用了缓存reflect技术,避免每次都去valueOf, typeOf …
//xiaorui.cc package main import ( "encoding/json" "fmt" "github.com/bouk/monkey" jjson "github.com/json-iterator/go" ) type Dto struct { Name string `json:"name"` Addr string `json:"addr"` Like string `json:"like"` } func main() { monkey.Patch(json.Marshal, func(v interface{}) ([]byte, error) { fmt.Println("use jsoniter") return jjson.Marshal(v) }) monkey.Patch(json.Unmarshal, func(data []byte, v interface{}) error { fmt.Println("use jsoniter") return jjson.Unmarshal(data, v) }) dd := &Dto{ Name: "xiaorui", Addr: "rfyiamcool@163.com", Like: "Python & Golang", } resDto := &Dto{} v, err := json.Marshal(dd) fmt.Println(string(v), err) errDe := json.Unmarshal(v, resDto) fmt.Println(resDto, errDe) fmt.Println("test end") }
下面是monkey patch的实现的主要逻辑.
func Patch(target, replacement interface{}) *PatchGuard { t := reflect.ValueOf(target) r := reflect.ValueOf(replacement) patchValue(t, r) return &PatchGuard{t, r} } func patchValue(target, replacement reflect.Value) { lock.Lock() defer lock.Unlock() if target.Kind() != reflect.Func { panic("target has to be a Func") } if replacement.Kind() != reflect.Func { panic("replacement has to be a Func") } if target.Type() != replacement.Type() { panic(fmt.Sprintf("target and replacement have to have the same type %s != %s", target.Type(), replacement.Type())) } if patch, ok := patches[target]; ok { unpatch(target, patch) } bytes := replaceFunction(*(*uintptr)(getPtr(target)), uintptr(getPtr(replacement))) patches[target] = patch{bytes, &replacement} }
总结:
monkey相关的逻辑放在main方法里就可以了,整个runtime时期只需要替换一次就ok了。golang monkey的方法虽然可以替换标准库和第三方库,但不管怎么说,还是显得有些粗暴了。还有就是 打入monkey后,pprof 和trace 显得有些异常,但业务没有影响。
最后说一点,大家可能因为性能问题更换第三方库的时候,请一定要做好go test benchmark,压力测试的逻辑尽量贴合自己的业务。先前我们就因为golang nuts或者是社区的推荐,直接更换的库,但事实说明这不是很靠谱。 通过一次次的pprof分析,一次次的打脸。
END.