Golang gRPC集成New Relic时Redis事务未上报显示问题
问题描述
我被该问题困扰已有数日:参照官方文档与同行建议配置后仍未解决。当前我使用Golang搭配gRPC框架,接入New Relic用于追踪事务与服务整体性能,目前已可正常声明普通事务与分段,但Redis相关数据库操作事务始终未在New Relic面板的数据库板块展示。我已尝试在执行Redis操作时将New Relic上下文传入Redis客户端,先后测试了后台上下文、当前事务上下文等方案,Redis查询本身可正常执行,但始终无对应监控数据上报。以下为我的实现代码示例:
1. 执行缓存更新事务的函数
// updateCache ... func updateCached(data *statemachinepkgv1.StateMachine, sessionID string, ctx context.Context) (*statemachinepkgv1.StateMachine, error) { //Transaction and segment logic //this does not create a brand new relic application, just gets the main instance relic, err := tools.InitRelic() if err != nil { log.Fatalf("failed to create/instace newrelic.NewApplication: %v", err) } txn := relic.StartTransaction("redis", nil, nil) defer newrelic.StartSegment(txn, "updateCached").End() defer newrelic.StartSegment(tools.CurrentTransaction, "updateCached").End() //tools.CurrentTransaction has the context of main web transaction stored with singleton design defer txn.End() dataParse, err := json.Marshal(data) if err != nil { return data, err } duration, err := time.ParseDuration(os.Getenv("GO_STATEMACHINE_REDIS_TIMELIFE")) if err != nil { return data, err } //REDIS LOGIC dbTransaction := newrelic.FromContext(ctx) newRelicContext := newrelic.NewContext(context.Background(), dbTransaction) err = masterClient.Set(newRelicContext, sessionID, string(dataParse), duration).Err() if err != nil { return data, err } return data, nil }
2. Redis单例实现
package tools import ( "context" "os" "github.com/go-redis/redis/v8" nrredis "github.com/newrelic/go-agent/v3/integrations/nrredis-v8" newrelic "github.com/newrelic/go-agent/v3/newrelic" ) var redisOpt = &redis.Options{ Addr: os.Getenv("GO_STATEMACHINE_REDIS_HOST") + ":" + os.Getenv("GO_STATEMACHINE_REDIS_PORT"), DB: 1, Password: os.Getenv("GO_STATEMACHINE_REDIS_PASSWORD"), } var Client *redis.Client = nil var Ctx context.Context = nil func getTransaction() *newrelic.Transaction { redisTransaction := newrelic.FromContext(context.Background()) return redisTransaction } func init() { Client = redis.NewClient(redisOpt) Client.AddHook(nrredis.NewHook(redisOpt)) txn := getTransaction() Ctx = newrelic.NewContext(context.Background(), txn) } func GetRedis() (*redis.Client, context.Context) { if Client == nil { Client = redis.NewClient(redisOpt) Client.AddHook(nrredis.NewHook(redisOpt)) txn := getTransaction() Ctx = newrelic.NewContext(context.Background(), txn) } return Client, Ctx }
3. New Relic单例实现
package tools import ( "context" "fmt" "log" "os" newrelic "github.com/newrelic/go-agent" ) var RelicApplication newrelic.Application = nil var CurrentTransaction newrelic.Transaction = nil func init() { fmt.Println("INIT RELIC CREATING AT RUNTIME") cfg := newrelic.NewConfig("go-vozyengine-statemachine-service", os.Getenv("NRELIC_KEY")) cfg.Logger = newrelic.NewDebugLogger(os.Stdout) cfg.BrowserMonitoring.Attributes.Enabled = true cfg.TransactionTracer.Enabled = true cfg.TransactionEvents.Enabled = true cfg.DistributedTracer.Enabled = true cfg.CustomInsightsEvents.Enabled = true cfg.ErrorCollector.Enabled = true cfg.ErrorCollector.CaptureEvents = true var err error RelicApplication, err = newrelic.NewApplication(cfg) if err != nil { log.Fatalf("failed to create newrelic.NewApplication: %v", err) } fmt.Println("INIT RELIC = RETURNING") } func SetSubTransaction(txn newrelic.Transaction) { //set new current transaction CurrentTransaction = txn } func SetSubTransactionByContext(ctx context.Context) { fmt.Println("SETTING SUB TRANSACTION VIA CONTEXT - default set") txn := newrelic.FromContext(ctx) SetSubTransaction(txn) } func InitRelic() (newrelic.Application, error) { fmt.Println("INIT RELIC REBUG") if RelicApplication == nil { fmt.Println("INIT RELIC = NOT INIT CREATING") cfg := newrelic.NewConfig("go-vozyengine-statemachine-service", "29827623658187f9e25fdde2f2fee06da906NRAL") cfg.Logger = newrelic.NewDebugLogger(os.Stdout) cfg.BrowserMonitoring.Attributes.Enabled = true // Enabling distributed tracing will disable the cross application tracing feature. Distributed tracing is // an improved version of cross-application tracing and only one can be enabled at a time. cfg.DistributedTracer.Enabled = true RelicApplication, err := newrelic.NewApplication(cfg) if err != nil { log.Fatalf("failed to create newrelic.NewApplication: %v", err) } fmt.Println("INIT RELIC = RETURNING") return RelicApplication, err } else { fmt.Println("INIT RELIC = ALREADY INIT RETURNING") return RelicApplication, nil } }
若能提供相关排查帮助,我将万分感谢。
问题根因与修复方案
Redis监控数据不上报是三个核心代码错误叠加导致的:
1. New Relic SDK版本混用
你在Redis单例中引入的是v3版本的New Relic依赖(github.com/newrelic/go-agent/v3/下的nrredis钩子、核心SDK),但New Relic单例实现用的是旧v1版本的SDK(github.com/newrelic/go-agent),两个版本的事务结构、上下文存取逻辑完全不兼容,v3版本的nrredis钩子根本读不到v1 SDK存在上下文里的事务信息。
修复方案:
- 将New Relic单例里的所有import替换为v3版本的包,全链路统一使用v3版本的API,彻底删除旧v1版本SDK的依赖。
2. 上下文传递逻辑完全错误
- Redis单例在init初始化阶段就从空的
context.Background()取事务、生成全局静态Ctx,这个上下文里没有绑定任何活跃请求的事务,本身就是无效值。 - 执行Redis Set命令时用的是
masterClient实例,不是你添加了nrredis钩子的tools.Client实例,钩子逻辑根本不会触发。 - 取到入参ctx里的事务后,又把事务绑定到全新的空
context.Background()上再传给Redis,会丢失链路关联的元数据。 - 用全局单例变量
CurrentTransaction存事务,并发场景下会被不同请求的事务值覆盖,直接导致数据串扰、丢失。
修复方案: - 删除Redis单例里提前生成、存储全局Ctx的逻辑,上下文必须随请求动态传递,不能在服务启动阶段预生成静态全局上下文。
- 所有Redis操作统一使用添加了nrredis钩子的Client实例,禁止混用未加钩子的Redis客户端。
- 执行Redis命令时直接传入绑定了当前请求事务的上下文,不要新建空Background上下文二次包装。
- gRPC接口入口处拿到请求绑定的New Relic事务后,将事务存入当前请求的context,后续所有数据库、缓存操作直接透传这个请求context即可,不要用全局变量存储事务。
3. 事务重复创建、分段绑定错误
你在updateCached函数里额外新建了一个名为"redis"的独立事务,同时给这个独立事务、全局存储的主事务都绑定了同名分段,直接导致链路断裂:Redis操作的监控数据如果绑定到gRPC主事务,你新建的独立事务无法关联到这部分数据,上报时数据归属错误,不会被统计到数据库指标板块。
修复方案:
- 不要在业务子函数里额外调用
StartTransaction新建独立事务,所有分段、数据库操作全部归属到gRPC入口启动的主事务下,操作结束后正常结束对应分段即可。
内容的提问来源于stack exchange,提问作者Andrew Alizaga
相关产品推荐
相关产品推荐

