使用monkey补丁替换golang的标准库

前言:

       什么是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.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc