跳到主要内容
版本:0.16

故障排查

本指南汇总使用 Fory Go 时的常见问题与解决方案。

错误类型

Fory Go 使用带错误种类的强类型错误:

type Error struct {
kind ErrorKind
message string
// 额外上下文字段
}

func (e Error) Kind() ErrorKind { return e.kind }
func (e Error) Error() string { return e.message }

错误种类

Kind说明
ErrKindOK0无错误
ErrKindBufferOutOfBound1读写越过缓冲区边界
ErrKindTypeMismatch2Type ID 不匹配
ErrKindUnknownType3遇到未知类型
ErrKindSerializationFailed4通用序列化失败
ErrKindDeserializationFailed5通用反序列化失败
ErrKindMaxDepthExceeded6递归深度超过限制
ErrKindNilPointer7意外的空指针
ErrKindInvalidRefId8非法引用 ID
ErrKindHashMismatch9结构体 hash 不匹配
ErrKindInvalidTag10非法 fory 结构体 tag

常见错误与解决方案

ErrKindUnknownType:未知类型

错误unknown type encountered

原因:序列化或反序列化前未注册类型。

解决方式

f := fory.New()

// 使用前先注册类型
f.RegisterStruct(User{}, 1)

// 之后即可正常序列化
data, _ := f.Serialize(&User{ID: 1})

ErrKindTypeMismatch:类型不匹配

错误type mismatch: expected X, got Y

原因:序列化数据中的类型与接收端期望的类型不一致。

解决方式

  1. 使用正确的目标类型:
// 错误:把 User 数据反序列化到 Order
var order Order
f.Deserialize(userData, &order)

// 正确
var user User
f.Deserialize(userData, &user)
  1. 保证注册信息一致:
// 序列化端
f1 := fory.New()
f1.RegisterStruct(User{}, 1)

// 反序列化端,必须使用相同 ID
f2 := fory.New()
f2.RegisterStruct(User{}, 1)

ErrKindHashMismatch:结构体 hash 不匹配

错误hash X is not consistent with Y for type Z

原因:序列化端和反序列化端使用的结构体定义不一致。

解决方式

  1. 开启兼容模式:
f := fory.New(fory.WithCompatible(true))
  1. 确保结构体定义一致:
// 两端必须使用相同结构体定义
type User struct {
ID int64
Name string
}
  1. 如果使用代码生成,重新生成产物:
go generate ./...

ErrKindMaxDepthExceeded:超过最大深度

错误max depth exceeded

原因:数据嵌套层级超过了最大深度限制。

常见成因

  • 深层嵌套数据超过默认限制(20)
  • 未开启引用跟踪时意外出现循环引用
  • 恶意数据:攻击者可能构造极深嵌套载荷,导致资源耗尽

解决方式

  1. 提高最大深度限制(默认值为 20):
f := fory.New(fory.WithMaxDepth(50))
  1. 对循环数据启用引用跟踪:
f := fory.New(fory.WithTrackRef(true))
  1. 检查数据中是否存在意外循环引用。

  2. 处理不可信输入时不要盲目调大深度限制,应先校验输入大小与结构。

ErrKindBufferOutOfBound:缓冲区越界

错误buffer out of bound: offset=X, need=Y, size=Z

原因:读取超出了现有数据长度。

解决方式

  1. 确保传输的是完整数据:
// 错误:数据被截断
data := fullData[:100]
f.Deserialize(data, &target)

// 正确:使用完整数据
f.Deserialize(fullData, &target)
  1. 检查传输过程中是否出现数据损坏。

ErrKindInvalidRefId:非法引用 ID

错误invalid reference ID

原因:序列化数据中引用了不存在或未知的对象。

解决方式

  1. 确保序列化端与反序列化端的引用跟踪配置一致:
f1 := fory.New(fory.WithTrackRef(true))
f2 := fory.New(fory.WithTrackRef(true)) // 必须一致
  1. 检查数据是否损坏。

ErrKindInvalidTag:非法 struct tag

错误invalid fory struct tag

原因:结构体 tag 配置非法。

常见成因

  1. 非法 tag ID:ID 必须大于等于 -1
// 错误:非法负数 ID(-1 以外)
type Bad struct {
Field int `fory:"id=-5"`
}

// 正确
type Good struct {
Field int `fory:"id=0"`
}
  1. 重复 tag ID:同一结构体中的字段 ID 必须唯一
// 错误:ID 冲突
type Bad struct {
Field1 int `fory:"id=0"`
Field2 int `fory:"id=0"`
}

// 正确
type Good struct {
Field1 int `fory:"id=0"`
Field2 int `fory:"id=1"`
}

跨语言问题

字段顺序不一致

现象:能够反序列化,但字段值落到了错误的位置。

原因:不同语言的字段排序规则不一致。非兼容模式下,字段会按 snake_case 名称排序。比如 FirstName 会先转换为 first_name 再参与排序。

解决方式

  1. 确保转换后的 snake_case 名称一致:
type User struct {
FirstName string // Go: FirstName -> first_name
LastName string // Go: LastName -> last_name
// 最终按 snake_case 的字典序排序:first_name, last_name
}
  1. 使用字段 ID 保证顺序一致。字段 ID(非负整数)会同时参与排序和反序列化匹配:
type User struct {
FirstName string `fory:"id=0"`
LastName string `fory:"id=1"`
}

对应字段在所有语言里都应使用相同的字段 ID。

名称注册不一致

现象:其他语言反序列化时报 unknown type

解决方式:保证注册名称完全一致:

// Go
f.RegisterNamedStruct(User{}, "example.User")

// Java,必须完全一致
fory.register(User.class, "example.User");

// Python
fory.register(User, typename="example.User")

性能问题

序列化速度慢

可能原因

  1. 对象图太大:减少数据规模或改为增量序列化。
  2. 引用跟踪过多:如果不需要,可以关闭。
f := fory.New(fory.WithTrackRef(false))
  1. 嵌套过深:尽量压平数据结构。

内存占用高

可能原因

  1. 单次序列化数据过大:改为分块处理。
  2. 引用跟踪有额外开销:若不需要可关闭。
  3. Buffer 没有被复用:可手动复用缓冲区。
buf := fory.NewByteBuffer(nil)
f.SerializeTo(buf, value)
// 处理数据
buf.Reset() // 为下一次序列化复用

线程争用

现象:并发压力下出现明显变慢。

解决方式

  1. 热路径使用每 goroutine 一个实例:
func worker() {
f := fory.New() // 每个 worker 自己持有实例
for task := range tasks {
f.Serialize(task)
}
}
  1. 配合线程安全封装做性能分析,观察对象池使用情况。

调试技巧

启用调试输出

设置环境变量:

ENABLE_FORY_DEBUG_OUTPUT=1 go test ./...

检查序列化结果

data, _ := f.Serialize(value)
fmt.Printf("Serialized %d bytes\n", len(data))
fmt.Printf("Header: %x\n", data[:4]) // Magic + flags

检查类型注册

// 验证类型是否注册成功
f := fory.New()
err := f.RegisterStruct(User{}, 1)
if err != nil {
fmt.Printf("Registration failed: %v\n", err)
}

对比结构体 hash

如果遇到 hash mismatch,可以先打印结构体定义做对比:

// 打印结构体字段信息用于调试
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("Field: %s, Type: %s\n", f.Name, f.Type)
}

测试建议

往返测试

func TestRoundTrip(t *testing.T) {
f := fory.New()
f.RegisterStruct(User{}, 1)

original := &User{ID: 1, Name: "Alice"}

data, err := f.Serialize(original)
require.NoError(t, err)

var result User
err = f.Deserialize(data, &result)
require.NoError(t, err)

assert.Equal(t, original.ID, result.ID)
assert.Equal(t, original.Name, result.Name)
}

跨语言测试

cd java/fory-core
FORY_GO_JAVA_CI=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest

Schema 演进测试

func TestSchemaEvolution(t *testing.T) {
f1 := fory.New(fory.WithCompatible(true))
f1.RegisterStruct(UserV1{}, 1)

data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"})

f2 := fory.New(fory.WithCompatible(true))
f2.RegisterStruct(UserV2{}, 1)

var result UserV2
err := f2.Deserialize(data, &result)
require.NoError(t, err)
}

获取帮助

如果问题不在本文覆盖范围内:

  1. 先查看 GitHub Issues
  2. 开启调试输出:ENABLE_FORY_DEBUG_OUTPUT=1
  3. 尽量构造最小复现用例。
  4. 提交 issue 时附带 Go 版本、Fory 版本和最小复现代码。

相关主题