在MongoDB中已打开连接的情况下出现SocketTimeout问题

18

我有一个Java应用程序,在MongoDB上执行一些聚合操作,但有时它会挂起并抛出SocketTimeout异常。在异常之后,该应用程序将正常运行(一段时间后,它可能会再次引发异常)。

我刚刚找到了这个解释,它似乎是可能的原因,但我不确定。

我初始化MongoClient并保持与DB的连接打开。我不确定这是否可能是一个问题,我是否应该每次获取数据库,然后让数据库进行垃圾收集(并关闭连接)。

另一种方法是定期ping Mongo以保持连接池“新鲜”。

使用的客户端类似于:

public class DbClient {

    private static MongoClient mongoClient;
    private static MongoDatabase db;

    private DbClient() {}

    public static void init() throws Exception {
        mongoClient = new MongoClient();
    }

    public static MongoDatabase getDB() {
        if(mongoClient == null)
            throw new IllegalStateException("Client not initialized!");

        if(db == null) {
            db = mongoClient.getDatabase("my_db");
        }
        return db;
    }
}

这可能是SocketTimeout的原因吗?

这是抛出的异常:

09:20:45.742 [qtp605535417-46] INFO  org.mongodb.driver.connection - Closed connection [connectionId{localValue:16, serverValue:6562}] to myapp.com:27017 because there was a socket exception raised by this connection.
09:20:45.743 [qtp605535417-46] ERROR myapp.service.Api - Error processing request
com.mongodb.MongoSocketReadTimeoutException: Timeout while receiving message
    at com.mongodb.connection.InternalStreamConnection.translateReadException(InternalStreamConnection.java:474) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.InternalStreamConnection.receiveMessage(InternalStreamConnection.java:225) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.UsageTrackingInternalConnection.receiveMessage(UsageTrackingInternalConnection.java:102) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.DefaultConnectionPool$PooledConnection.receiveMessage(DefaultConnectionPool.java:435) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.CommandProtocol.execute(CommandProtocol.java:112) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:159) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:286) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.DefaultServerConnection.command(DefaultServerConnection.java:173) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:215) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:206) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:112) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.FindOperation$1.call(FindOperation.java:487) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.FindOperation$1.call(FindOperation.java:482) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:239) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:212) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.FindOperation.execute(FindOperation.java:482) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.operation.FindOperation.execute(FindOperation.java:79) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.Mongo.execute(Mongo.java:772) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.Mongo$2.execute(Mongo.java:759) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.OperationIterable.iterator(OperationIterable.java:47) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.FindIterableImpl.iterator(FindIterableImpl.java:143) ~[mongo-java-driver-3.2.2.jar!/:na]
    at myapp.common.db.service.dao.AnalysisMongoImpl.getAnalysis(AnalysisMongoImpl.java:66) ~[common-0.2.0-SNAPSHOT.jar!/:na]
    at myapp.common.db.service.AnalysisServiceImpl.getAnalysis(AnalysisServiceImpl.java:31) ~[common-0.2.0-SNAPSHOT.jar!/:na]
    at myapp.aggregator.service.Api$1.handle(Api.java:88) ~[aggregator-0.2.0-SNAPSHOT.jar!/:na]
    at spark.webserver.MatcherFilter.doFilter(MatcherFilter.java:139) [spark-core-1.1.1.jar!/:na]
    at spark.webserver.JettyHandler.doHandle(JettyHandler.java:54) [spark-core-1.1.1.jar!/:na]
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:179) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:136) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.server.Server.handle(Server.java:451) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:252) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:266) [jetty-server-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240) [jetty-io-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596) [jetty-util-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527) [jetty-util-9.0.2.v20130417.jar!/:9.0.2.v20130417]
    at java.lang.Thread.run(Thread.java:745) [na:1.7.0_95]
Caused by: java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.7.0_95]
    at java.net.SocketInputStream.read(SocketInputStream.java:152) ~[na:1.7.0_95]
    at java.net.SocketInputStream.read(SocketInputStream.java:122) ~[na:1.7.0_95]
    at com.mongodb.connection.SocketStream.read(SocketStream.java:85) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.InternalStreamConnection.receiveResponseBuffers(InternalStreamConnection.java:491) ~[mongo-java-driver-3.2.2.jar!/:na]
    at com.mongodb.connection.InternalStreamConnection.receiveMessage(InternalStreamConnection.java:221) ~[mongo-java-driver-3.2.2.jar!/:na]
    ... 34 common frames omitted

你需要在问题中发布异常、其消息和堆栈跟踪。这些成员字段或方法都不应该是静态的。 - user207421
@EJP 嗯,为什么?我猜 getDB() 可能是问题所在,但是据我所知,应该将 MongoClient 处理为单例模式。 - Enrichman
客户端不等于游标 && 客户端不等于连接。实际上,客户端维护一个连接池。使用这些连接,在服务器上执行操作,如查询。在这种情况下,将返回一个游标。如果操作时间过长,则会触发超时,并在服务器端关闭连接。从我所看到的情况来看,就是发生了这种情况。 - Markus W Mahlberg
@MarkusWMahlberg 我不认为是这种情况。我有一个查询超时,即使是非常轻的聚合也会抛出这些异常。在长时间运行的查询中,抛出的异常是正确的。collection.aggregate(pipeline).maxTime(maxTimeout, TimeUnit.SECONDS); 从日志中可以看到,在聚合之前就已经引发了异常。 - Enrichman
@MarkusWMahlberg 我已经编辑了之前的评论。只是指出当 SocketTimeout 被触发时,聚合查询甚至没有被执行。此外,聚合直接在 _id 字段上完成,而不会对服务器造成任何负载。 - Enrichman
显示剩余4条评论
3个回答

23

经过几次尝试,我发现这是与Azure的负载均衡器有关的问题。
如果闲置60秒,它将断开任何挂起的TCP连接。

进一步查找后,我在MongoDB诊断FAQ发现了这篇文章,并将tcp keepalive设置为120秒:

sudo sysctl -w net.ipv4.tcp_keepalive_time=<value>

我还将MongoClient的socketKeepAlive设置为true:

MongoClientOptions.Builder options = MongoClientOptions.builder();
options.socketKeepAlive(true);
mongoClient = new MongoClient(mongoAddress, options.build());

经过这些修复,问题似乎已经消失了!


你在哪里运行了这个命令?sudo sysctl -w net.ipv4.tcp_keepalive_time=<value> - VaidAbhishek
直接在服务器上(Linux) - Enrichman
socketKeepAlive已被弃用 - Riccardo
1
弃用了,推荐使用什么?你的评论只有一半有用。 - Pratik Patel
1
它已被弃用,建议不要设置它。根据JavaDoc的说法,“配置keep-alive已被弃用。它现在默认为true,不建议禁用它。” - TomPlum

3
如果在客户端和服务器之间,或在没有其他合理原因的分片集群或副本集成员之间出现套接字错误,请检查 TCP keepalive 值(例如,在 Linux 系统上的 tcp_keepalive_time 值)。通常的 keepalive 周期是 7200 秒(2 小时),但不同的发行版和 macOS 可能有不同的设置。
对于 MongoDB,使用较短的 keepalive 周期效果更好,大约为 120 秒(两分钟)。
在安装 MongoDB 的地方,您只需在 Linux 上运行此命令:
sudo sysctl -w net.ipv4.tcp_keepalive_time=120
参考:TCP keepalive 时间会影响 MongoDB 部署吗?

我在主机上已经有了 net.ipv4.tcp_keepalive_time = 120,并且我相信 keep-alive 设置现在默认为 true。然而,偶尔仍会出现 org.mongodb.driver.connection : Got socket exception on connection - daniyel
@NitishDeshpande 嗯,我现在不记得了,因为我们尝试了许多不同的东西,而且我也不再在公司工作了。如果我没记错的话,我们做了一些重新尝试逻辑,当 Azure 断开连接时,我们会重新建立连接。我没有片段来演示我们是如何解决这个问题的。 - daniyel
@NitishDeshpande,你能解决这个问题吗? - Amol Kshirsagar

1
我刚遇到了同样的问题,通过增加Spring Boot配置中的spring.data.mongodb.socketTimeout=<value>来解决它。默认情况下,该值为0,表示没有超时。然而,我的同事将其设置为20000(20秒),因此只要我的aggregate()运行时间超过20秒,就会抛出MongoSocketReadTimeoutException

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接