首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AI三连误诊后,我终于抓住了grpc protobuf序列化的‘幽灵bug’

AI三连误诊后,我终于抓住了grpc protobuf序列化的‘幽灵bug’

原创
作者头像
不做虫子
发布2025-09-15 22:33:47
发布2025-09-15 22:33:47
1320
举报

背景

有一天,我收到一个接口报错,内容如下:

代码语言:javascript
复制
ERROR: [core] [Server #13] grpc: server failed to encode response: rpc error: code = Internal desc = grpc: error while marshaling: marshaling proto.xxx.xxx: size mismatch (see https://github.com/golang/protobuf/issues/1609): calculated=176, measured=172

从报错信息来看,是 gRPC 编码时出现了问题,而且是由于长度不匹配导致的。

因为代码工程量很大,作为一个懒惰的程序员,我觉得自己一点点排查效率太低,所以打算让 AI 帮我扫描代码,看看问题出在哪里。

使用工具

Cursor

排查过程

第一回合 AI的“想当然”

我直接把报错信息贴给它,并告诉它:

服务日志中有报错,请帮我分析可能导致这个报错的原因

Cursor看起来很认真地扫描了项目,分析了错误,并给出了问题原因。

但首先要说,这两个原因其实都是错的。

第一,虽然有截断操作,但不是在协程中截断的,而是在协程发起前就已经截断了,所以不存在并发截断的问题。

第二,如果是版本兼容问题,那一定是必现问题,但实际上这是偶现的,不符合情况。

唯一有用的是这句话:

这个错误是由于protobuf序列化时大小计算不匹配导致的

这句话给了我一些思路,我大概知道是哪里出问题了。但为了再“拷打”一下AI,我决定继续提醒它。

第二回合 AI的“知错不改”

我提示它分析得不对,没有动态截断。

对于第一点,MicConnUsers 字段的动态截断,我表示疑惑,因为我明明是把长度传给了 goroutine。

它虽然承认了错误,但分析还是不对,列举的原因本质上还是必现和偶现的问题。我觉得它没有理解我的问题,所以打算提醒它这是个偶现问题。

第三回合 AI的"半对半错"

如果是两个不同的 protobuf 库引起的,这个错误应该是必现的,但实际上它是偶现的。

这次它提到了一个关键点,但还是没找到那个关键的代码位置,不过总算有点进步了。

第四回合

我直接进行了修改。其实在 AI 第一次给出可能原因时,我就大概知道问题所在了。

几轮下来,cursor 一直没能抓住这一点。

技术深析

它提到缓存的并发访问可能有问题,这只说对了一半。实际上,并不是并发访问的问题,而是缓存的过期机制有问题。

在极端情况下,服务正在打包编码时,突然缓存过期被清除,就会报错。

具体来说,我的服务逻辑如下:

  1. 从缓存读取数据(假设数据存在,大小为176);
  2. 开始进行 protobuf 序列化,计算大小(基于缓存数据,得到176);
  3. 序列化过程中,缓存突然过期被清除,实际读取到的数据不完整(大小变为172);
  4. 序列化完成后,计算值(176)与实际测量值(172)不匹配,触发报错。

问题代码如下:

代码语言:txt
复制
var cache = cache.New(15*time.Second, 15*time.Second)

func GetxxxUser(ctx context.Context, roomSession *x.RoomSession) ([]string, error) {
	// 先获取缓存
	cacheKey := fmt.Sprintf("%d_%d", roomSession.GetRoomId(), roomSession.GetLiveSessionId())
	if v, ok := cache.Get(cacheKey); ok {
		return v.([]string), nil
	}
	...
}

解决

代码语言:txt
复制
func GetxxxUser(ctx context.Context, roomSession *x.RoomSession) ([]string, error) {
	// 先获取缓存
	cacheKey := fmt.Sprintf("%d_%d", roomSession.GetRoomId(), roomSession.GetLiveSessionId())
	if v, ok := cache.Get(cacheKey); ok {
		// 复制一份
		vList := v.([]string)
		result := make([]string, 0, len(vList))
		result = append(result, vList...)
		return result, nil
	}
	...
}

这和前面 cursor 给出的原因是呼应的。

总结

AI只是助手,不是“甩手掌柜”。

这次经历让我深刻体会到,AI工具确实能帮助我们快速定位常见问题,但面对系统的“暗角”,最终还是要靠开发者的系统思维——对业务逻辑的熟悉、对技术原理的理解,以及对异常场景的敏感。与其期待AI“一键解决”,不如把它当作思路启发器,始终保持批判性使用的态度。毕竟,真正的技术成长,往往藏在AI误诊后的独立排查过程中。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 使用工具
  • 排查过程
    • 第一回合 AI的“想当然”
    • 第二回合 AI的“知错不改”
    • 第三回合 AI的"半对半错"
    • 第四回合
  • 技术深析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档