Google PubSub .NET SDK调用PublishAsync方法挂起无响应问题
问题原因梳理
PublisherClient属于重量级资源,官方明确要求单例复用,你每次调用LogEvent方法都新建实例,会产生大量不必要的连接开销,严重时会耗尽连接资源导致请求挂起。另外你代码中创建的PublisherServiceApiClient完全没有被使用,属于冗余代码,额外占用系统资源。- 官方
PublishAsync方法不支持直接传入自定义的DataEvent类型对象,你需要先把protobuf格式的DataEvent序列化为字节数组,再传入方法,参数类型不匹配会导致内部序列化逻辑异常卡住,不会抛出可见错误。 - 自定义的
ToTopicName方法生成的主题名带[TOPICPATH]前缀,Google PubSub主题名不允许出现[、]这类特殊字符,非法的主题名会导致请求一直静默重试无响应。 - 没有配置显式的超时和重试策略,默认的客户端静默重试逻辑会导致认证失败、网络异常等问题不会立刻抛出,而是无限重试,看起来就像直接挂起,也不会输出错误日志。
修复方案
1. 全局复用PublisherClient实例
不要每次请求都新建PublisherClient,程序启动时初始化一次单例即可,还可以同时配置超时规则避免无限等待:
// 全局单例PublisherClient private static PublisherClient _publisherClient; // 程序启动时仅执行一次初始化 public async Task InitPublisher(string projectId, string baseTopicName) { var topic = TopicName.FromProjectTopic(projectId, baseTopicName); _publisherClient = await new PublisherClientBuilder { TopicName = topic, // 显式配置发布超时时间,比如10秒 Settings = new PublisherClient.Settings { PublishTimeout = TimeSpan.FromSeconds(10) } // 认证有问题可以在这里显式指定服务账号密钥路径 // CredentialsPath = "你的服务账号json密钥文件路径" }.BuildAsync(); }
2. 修正消息序列化逻辑
先把protobuf格式的DataEvent序列化为字节数组再调用发布接口:
public async Task LogEvent(DataEvent dataEvent) { try { if (dataEvent == null) throw new ArgumentNullException(nameof(dataEvent)); // 序列化protobuf对象为字节数组 using var stream = new MemoryStream(); dataEvent.WriteTo(stream); var messageBytes = stream.ToArray(); var result = await _publisherClient.PublishAsync(messageBytes).ConfigureAwait(false); logger.LogTrace("Logged data event {dataEvent} {message}", dataEvent, result); } catch (Exception ex) { logger.LogError(ex, "Error on LogEvent {dataEvent}", dataEvent); } }
3. 修正主题名生成规则
去掉特殊字符,符合PubSub主题名命名要求:
public static class Ext { public static string ToTopicName(this string name) // 只保留允许的字符:小写字母、数字、连字符、下划线、点,非法字符替换为连字符 => System.Text.RegularExpressions.Regex.Replace($"event-{name.ToLower()}", "[^a-z0-9-_.]", "-"); }
如果需要排查认证问题,可以先在本地配置环境变量GOOGLE_APPLICATION_CREDENTIALS指向你的服务账号密钥文件,再运行程序验证是否可以正常发布。
内容的提问来源于stack exchange,提问作者Christian Findlay
相关产品推荐
相关产品推荐

