故障排查
本指南介绍使用 Fory Go 时的常见问题及解决方案。
错误类型
Fory Go 使用带具体错误种类的类型化错误:
type Error struct {
kind ErrorKind
message string
// Additional context fields
}
func (e Error) Kind() ErrorKind { return e.kind }
func (e Error) Error() string { return e.message }
错误种类
| 种类 | 值 | 说明 |
|---|---|---|
ErrKindOK | 0 | 无错误 |
ErrKindBufferOutOfBound | 1 | 读写超出缓冲区边界 |
ErrKindTypeMismatch | 2 | 类型 ID 不匹配 |
ErrKindUnknownType | 3 | 遇到未知类型 |
ErrKindSerializationFailed | 4 | 常规序列化失败 |
ErrKindDeserializationFailed | 5 | 常规反序列化失败 |
ErrKindMaxDepthExceeded | 6 | 超出递归深度限制 |
ErrKindNilPointer | 7 | 意外的 nil 指针 |
ErrKindInvalidRefId | 8 | 无效的引用 ID |
ErrKindHashMismatch | 9 | 结构体哈希不匹配 |
ErrKindInvalidTag | 10 | 无效的 Fory 结构体标签 |
常见错误及解决方案
ErrKindUnknownType
错误:unknown type encountered
原因:序列化或反序列化前未注册类型。
解决方案:
f := fory.New()
// Register type before use
f.RegisterStruct(User{}, 1)
// Now serialization works
data, _ := f.Serialize(&User{ID: 1})
ErrKindTypeMismatch
错误:type mismatch: expected X, got Y
原因:序列化数据的类型与预期不同。
解决方案:
- 使用正确的目标类型:
// Wrong: Deserializing User into Order
var order Order
f.Deserialize(userData, &order) // Error!
// Correct
var user User
f.Deserialize(userData, &user)
- 确保注册一致:
// Serializer
f1 := fory.New()
f1.RegisterStruct(User{}, 1)
// Deserializer - must use same ID
f2 := fory.New()
f2.RegisterStruct(User{}, 1) // Same ID!
ErrKindHashMismatch
错误:hash X is not consistent with Y for type Z
原因:序列化和反序列化之间结构体定义发生变化。
解决方案:
- 保持启用兼容模式:
// Remove any WithCompatible(false) override from the peers.
f := fory.New(/* existing options */)
- 确保结构体定义匹配:
// Both serializer and deserializer must have same struct
type User struct {
ID int64
Name string
}
ErrKindMaxDepthExceeded
错误:max depth exceeded
原因:数据嵌套超过最大深度限制。
可能原因:
- 深层嵌套数据结构超过默认限制(20)
- 存在意外循环引用,但未启用引用跟踪
- 恶意数据:攻击者可能构造深层嵌套载荷以耗尽资源
解决方案:
- 增大最大深度(默认值为 20):
f := fory.New(fory.WithMaxDepth(50))
- 启用引用跟踪(用于循环数据):
f := fory.New(fory.WithTrackRef(true))
-
检查数据中是否存在意外循环引用。
-
验证不可信数据:反序列化来自不可信来源的数据时,不要盲目增大最大深度。应考虑在反序列化前验证输入大小和结构。
ErrKindBufferOutOfBound
错误:buffer out of bound: offset=X, need=Y, size=Z
原因:读取超出可用数据范围。
解决方案:
- 确保数据传输完整:
// Wrong: Truncated data
data := fullData[:100]
f.Deserialize(data, &target) // Error if data was larger
// Correct: Use full data
f.Deserialize(fullData, &target)
- 检查数据损坏:验证传输期间的数据完整性。
ErrKindInvalidRefId
错误:invalid reference ID
原因:序列化数据引用了不存在或未知的对象。
解决方案:
- 确保引用跟踪设置一致:
// Serializer and deserializer must have same setting
f1 := fory.New(fory.WithTrackRef(true))
f2 := fory.New(fory.WithTrackRef(true)) // Must match!
- 检查数据是否损坏。
ErrKindInvalidTag
错误:invalid fory struct tag
原因:结构体标签配置无效。
常见原因:
- 标签 ID 无效:ID 必须为非负数
// Wrong: negative ID
type Bad struct {
Field int `fory:"id=-5"`
}
// Correct
type Good struct {
Field int `fory:"id=0"`
}
- 标签 ID 重复:结构体中的每个字段必须具有唯一 ID
// Wrong: duplicate IDs
type Bad struct {
Field1 int `fory:"id=0"`
Field2 int `fory:"id=0"` // Duplicate!
}
// Correct
type Good struct {
Field1 int `fory:"id=0"`
Field2 int `fory:"id=1"`
}
跨语言问题
字段顺序不匹配
现象:数据可以反序列化,但字段值错误。
原因:不同语言的字段顺序不同。禁用兼容模式时,字段按 snake_case 名称排序。CamelCase 字段名称(例如 FirstName)会转换为 snake_case(例如 first_name)后排序。
解决方案:
- 确保转换后的 snake_case 名称一致:各语言的字段名称必须产生相同的 snake_case 顺序:
type User struct {
FirstName string // Go: FirstName -> first_name
LastName string // Go: LastName -> last_name
// Sorted alphabetically by snake_case: first_name, last_name
}
- 使用字段 ID 保持顺序一致:字段 ID(非负整数)作为字段名称的别名,同时用于排序和反序列化期间的字段匹配:
type User struct {
FirstName string `fory:"id=0"`
LastName string `fory:"id=1"`
}
确保各语言的对应字段使用相同字段 ID。
名称注册不匹配
现象:其他语言出现 unknown type。
解决方案:使用完全相同的名称:
// Go
f.RegisterStructByName(User{}, "example.User")
// Java - must match exactly
fory.register(User.class, "example.User");
// Python
fory.register_type(User, name="example.User")
性能问题
序列化缓慢
可能原因:
-
大型对象图:减小数据大小或增量序列化。
-
引用跟踪过多:不需要时禁用:
f := fory.New(fory.WithTrackRef(false))
- 嵌套过深:尽可能扁平化数据结构。
内存用量过高
可能原因:
-
序列化数据过大:分块处理。
-
引用跟踪开销:不需要时禁用。
-
缓冲区未释放:复用缓冲区:
buf := fory.NewByteBuffer(nil)
f.SerializeTo(buf, value)
// Process data
buf.Reset() // Reuse for next serialization
线程竞争
现象:并发负载下速度下降。
解决方案:
- 热路径每个 goroutine 使用独立实例:
func worker() {
f := fory.New() // Each worker has own instance
for task := range tasks {
f.Serialize(task)
}
}
- 使用线程安全包装器时分析池使用情况。
调试技巧
启用调试输出
设置环境变量:
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
检查类型注册
// Verify type is registered
f := fory.New()
err := f.RegisterStruct(User{}, 1)
if err != nil {
fmt.Printf("Registration failed: %v\n", err)
}
比较结构体哈希
出现哈希不匹配时,请比较结构体定义:
// Print struct info for debugging
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()
f1.RegisterStruct(UserV1{}, 1)
data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"})
f2 := fory.New()
f2.RegisterStruct(UserV2{}, 1)
var result UserV2
err := f2.Deserialize(data, &result)
require.NoError(t, err)
}
获取帮助
如果遇到本文未涵盖的问题:
- 查看 GitHub Issue:github.com/apache/fory/issues
- 启用调试输出:
ENABLE_FORY_DEBUG_OUTPUT=1 - 创建最小复现:隔离问题
- 报告问题:包含 Go 版本、Fory 版本和最小代码