在go项目中使用redis时,难免要使用Get/Set
存取一些复杂的数据结构
直接缓存会报错
1
| redis: can't marshal xxx (implement encoding.BinaryMarshaler)
|
xxx为对应的数据结构
此时就要自定义一些方法来解决了
当前使用的redis客户端为 go-redis v6
msgpack版本为 msgpack v4(4.0.4)
需要为缓存的对象添加两个方法,go-redis
会自动应用
1 2
| MarshalBinary() ([]byte, error) UnmarshalBinary(data []byte) error
|
在其中实现序列化与反序列化的方法即可,一般使用json库
当然为了性能考虑,也可以使用其它更高效的库,如msgpack,benchmark有5-10倍提升
1 2 3 4 5
| BenchmarkStructVmihailencoMsgpack-4 200000 12814 ns/op 2128 B/op 26 allocs/op BenchmarkStructUgorjiGoMsgpack-4 100000 17678 ns/op 3616 B/op 70 allocs/op BenchmarkStructUgorjiGoCodec-4 100000 19053 ns/op 7346 B/op 23 allocs/op BenchmarkStructJSON-4 20000 69438 ns/op 7864 B/op 26 allocs/op BenchmarkStructGOB-4 10000 104331 ns/op 14664 B/op 278 allocs/op
|
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package main
import ( "fmt" "time" "github.com/go-redis/redis" "github.com/vmihailenco/msgpack" )
func main() { client := redis.NewClient(&redis.Options{ Addr: "192.168.0.1:6379", Password: "kljsdfslkdfj", DB: 0, })
pong, err := client.Ping().Result() fmt.Println(pong, err) fmt.Println(client.Set("cache1", &something{100, "101"}, time.Second*1).Result()) fmt.Println(client.Get("cache1").Result()) }
type something struct { ID int Name string }
func (s *something) MarshalBinary() ([]byte, error) { return msgpack.Marshal(s) }
func (s *something) UnmarshalBinary(data []byte) error { return msgpack.Unmarshal(data, s) }
|
新版本不可用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| var ( binaryMarshalerType = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem() binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem() )
func _getEncoder(typ reflect.Type) encoderFunc { kind := typ.Kind()
if kind == reflect.Ptr { if _, ok := typeEncMap.Load(typ.Elem()); ok { return ptrEncoderFunc(typ) } }
if typ.Implements(customEncoderType) { return encodeCustomValue } if typ.Implements(marshalerType) { return marshalValue } if typ.Implements(binaryMarshalerType) { return marshalBinaryValue } ... }
|
msgpack更新到4.3版本后(不确认具体版本),上述用法会由于循环调用而造成堆栈泄露
1 2 3 4
| runtime: goroutine stack exceeds 1000000000-byte limit runtime: sp=0xc020bc0378 stack=[0xc020bc0000, 0xc040bc0000] fatal error: stack overflow ...
|
由于上述判断,实现*encoding.BinaryMarshaler接口的类型,将调用其MarshalBinary方法,但该方法又是通过调用msgpack的Marshal方法实现的,从而导致循环
1
| NewEncoder() -> Pool Get Encocer -> Marshal() -> 数据的MarshalBinary方法 -> marshalBinaryValue() -> EncodeValue() -> Encode() -> Marshal() -> 数据的MarshalBinary方法 ...
|
解决方案
如果一定还是要用之前的方式,那么需要做如下修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import "github.com/vmihailenco/msgpack/v5"
type baseType struct { Tag string }
type newType []baseType
func (s *newType) MarshalBinary() ([]byte, error) { return msgpack.Marshal((*[]baseType)(s)) }
func (s *newType) UnmarshalBinary(data []byte) error { return msgpack.Unmarshal(data, (*[]baseType)(s)) }
|
参考链接
go-redis
go-redis issues
msgpack
原文链接