前言:
什么是monkey ? 英语里面试猴子,但程序里面我们称之为 补丁. 用过python gevent的朋友应该很熟悉这个monkey补丁字眼吧。 对的,这里golang的monkey实现的功能跟gevent monkey是一样的,都是为了粗暴直接的替换标准库。
我这里的场景很简单,就是想替换一些第三方库的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.