使用IAM角色连接Mongo Server时出现NoServerAvailable错误
问题解决:Mongoid结合AWS IAM会话令牌连接Atlas时出现NoServerAvailable错误
问题背景
在Rails中使用Mongoid连接MongoDB Atlas集群,采用AWS IAM会话令牌认证(令牌1小时过期),实现了令牌过期时重新加载Mongoid配置、生成新连接URL的逻辑。初始运行正常,但一段时间后出现Mongo::Error::NoServerAvailable错误,提示集群无主可用服务器,存在死监控线程。
相关代码
def logs reload_mongoid_configuration @logs ||= Log.or(attributes_condition) .where(selector) .order_by(timestamp: :desc) end def reload_mongoid_configuration mongo_config = Mongoid::Config.clients[:default] old_connection_url = mongo_config[:uri] return unless mongo_token_expired?(old_connection_url) # Disconnect existing Mongoid clients Mongoid.disconnect_clients # Logic to get mongo URL with a new AWS session token Mongoid.load_configuration( clients: { default: { uri: MongoUriGenerator.audit_log_db_url(old_connection_url), options: { database: 'my_database' } } } ) end
错误信息
#<Mongo::Error::NoServerAvailable: No primary server is available in cluster ... #<Server address=<someaddress>.mongodb.net:27017 UNKNOWN NO-MONITORING pool=#<ConnectionPool size=0 (0-20) used=0 avail=0 pending=0 paused>> #<Server address=<someaddress>.mongodb.net:27017 SECONDARY replica_set=atlas-ihuqqp-shard-0 NO-MONITORING>]> with timeout=30, LT=0.015. The following servers have dead monitor threads
连接URL格式
mongodb+srv://<temp_access key>:<temp_secret_key>@<mongourl>.mongodb.net/<db>?authMechanism=MONGODB-AWS&authSource=$external&authMechanismProperties=AWS_SESSION_TOKEN:IQo....
问题原因分析
- 查询对象缓存导致旧连接复用:
@logs ||=会缓存查询对象,即使令牌过期并重新加载了配置,后续请求仍会使用旧的查询对象关联的无效连接。 - 旧连接资源未彻底清理:
Mongoid.disconnect_clients可能没有完全终止旧的监控线程,导致这些线程残留并标记服务器为NO-MONITORING状态。 - 重载配置后未主动初始化新连接:重新加载配置后,Mongoid可能不会主动建立新连接,直到第一次查询,此时可能因网络或认证问题导致监控线程启动失败。
解决方案
1. 移除查询对象缓存
删除@logs ||=的缓存逻辑,确保每次查询都使用最新的连接:
def logs reload_mongoid_configuration Log.or(attributes_condition) .where(selector) .order_by(timestamp: :desc) end
2. 改进配置重载逻辑,彻底清理旧资源并初始化新连接
修改reload_mongoid_configuration方法,确保旧客户端完全销毁,新客户端主动初始化:
def reload_mongoid_configuration mongo_config = Mongoid::Config.clients[:default] old_connection_url = mongo_config[:uri] return unless mongo_token_expired?(old_connection_url) # 彻底断开并销毁旧客户端 Mongoid.disconnect_clients Mongoid::Config.clients.clear # 生成新的连接URL new_uri = MongoUriGenerator.audit_log_db_url(old_connection_url) # 加载新配置,添加监控与超时参数 Mongoid.load_configuration( clients: { default: { uri: new_uri, options: { database: 'my_database', server_selection_timeout: 10, heartbeat_frequency: 5, connect_timeout: 5, retry_writes: true, retry_reads: true } } } ) # 主动触发连接初始化,启动监控线程 Mongoid::Clients.default.database_names end
3. 优化令牌过期检查逻辑
调整mongo_token_expired?方法,提前5分钟刷新令牌,避免在令牌刚过期时才执行刷新,减少连接中断的概率。
4. 改用定时刷新逻辑
将令牌刷新逻辑从请求处理方法中移出,改用后台定时任务(比如每55分钟执行一次),确保应用始终持有有效的连接配置,避免在请求过程中频繁重载配置。
内容的提问来源于stack exchange,提问作者Hassan Anwer
相关产品推荐
相关产品推荐

