0

我们正在从 Java (Spring Boot) 应用程序连接到 LDAP (OpenLDAP) 服务。我们遇到了 TLS 和内存使用问题。

背景

  • 我们正在使用 Apache Directory LDAP API (v2) 库进行连接。
  • 我们正在使用与 LDAP 服务器的池连接。
  • 我们使用 StartTLS 来保护 Java 服务和 LDAP 服务器之间的连接。
  • 我们实际上并没有从这里对 LDAP 服务器进行身份验证!
    • 我们的 API 网关处理身份验证(针对相同的 LDAP 服务)。
    • 我们在代码中做了两件事:
      • 获取更多关于用户的数据(当接收 API 请求时)和
      • 从使其与另一个源保持同步的服务更新 LDPA。

内存问题

我们在 Java 服务上遇到内存不足错误。堆栈跟踪如下所示:

Exception in thread "pool-2454-thread-1" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:704)
    at java.util.HashMap.putVal(HashMap.java:629)
    at java.util.HashMap.put(HashMap.java:612)
    at sun.security.util.MemoryCache.put(Cache.java:365)
    at sun.security.ssl.SSLSessionContextImpl.put(SSLSessionContextImpl.java:181)
    at sun.security.ssl.ClientHandshaker.serverFinished(ClientHandshaker.java:1293)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:379)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1082)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:1010)
    at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1032)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:913)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:783)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:626)
    at org.apache.mina.filter.ssl.SslHandler.unwrap(SslHandler.java:774)
    at org.apache.mina.filter.ssl.SslHandler.unwrapHandshake(SslHandler.java:710)
    at org.apache.mina.filter.ssl.SslHandler.handshake(SslHandler.java:596)
    at org.apache.mina.filter.ssl.SslHandler.messageReceived(SslHandler.java:355)
    at org.apache.mina.filter.ssl.SslFilter.messageReceived(SslFilter.java:517)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:650)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1300(DefaultIoFilterChain.java:49)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:1128)
    at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:122)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:650)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:643)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:539)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$1200(AbstractPollingIoProcessor.java:68)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.process(AbstractPollingIoProcessor.java:1222)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.process(AbstractPollingIoProcessor.java:1211)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:683)
    at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
2020-10-13 10:03:23.388677637+03:00 Starting: /etc/alternatives/jre/bin/java -Xms128M -Xmx256M -Dlogging.config=/services/registry.svc/log4j2.json -jar 

我的同事在 JVM 上调试了一个具有 128m 内存的简单 API,LDAP 池看起来使用了大量内存而没有做太多事情: LDAP 连接池的内存使用情况

我注意到代码在进行unbind查询后正在执行。这闻起来不对 - 我们没有绑定每个用户,我们有一个(只读)用户与 API 服务连接,这允许他们读取有关用户连接的详细信息以及同步服务的另一个(读写)用户. 据我了解,绑定就像登录和使用其他连接池,这是您每次都不做的事情。我想知道通过解除绑定但不关闭我们是否会留下僵尸连接并吃掉记忆?

SSL 问题

但是,如果我们不取消绑定,我们会在日志中大量出现以下错误,但没有任何合理的方法可以找到它的来源。没有找到太多关于它的东西:

2020-10-14 11:08:57.817 [NioProcessor-3] WARN  org.apache.directory.ldap.client.api.LdapNetworkConnection - Outbound done [MDC: {}]
javax.net.ssl.SSLException: Outbound done
    at org.apache.mina.filter.ssl.SslFilter.messageReceived(SslFilter.java:513) ~[mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:650) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1300(DefaultIoFilterChain.java:49) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:1128) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:122) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:650) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:643) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:539) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$1200(AbstractPollingIoProcessor.java:68) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.process(AbstractPollingIoProcessor.java:1222) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.process(AbstractPollingIoProcessor.java:1211) [mina-core-2.1.3.jar:?]
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:683) [mina-core-2.1.3.jar:?]
    at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64) [mina-core-2.1.3.jar:?]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [?:1.8.0_261]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [?:1.8.0_261]
    at java.lang.Thread.run(Unknown Source) [?:1.8.0_261]

可能的解决方法

我确实在网上找到了一些示例,这些示例建议了以下模式:

if (connection.isConnected() && connection.isAuthenticated()) {
    connection.bind();
try {
    // do stuff
} finally {
    connection.unBind();
}

但这感觉不对 - 或者至少是一种解决方法

问题

所以,我的问题分为两部分:

  1. 我们是否应该绑定和取消绑定每个查询(即使我们始终以同一个用户身份进行身份验证),或者我们是否会失去池的好处?
  2. javax.net.ssl.SSLException: Outbound done有没有人有关于异常的任何信息?它是否相关以及如何解决?
4

1 回答 1

1

所以,似乎我关闭连接是错误的。我假设,当我从池中获取连接时,如果我“关闭”连接,它会将其返回到池中。似乎它关闭了它,但将它保存在池中(可能是借来的,可能只是无法使用,没有调查那么远)。看来我需要将其返回到池中,并为此保留对池的引用。

我有一个处理池并返回连接的函数(实际上是一个注入服务)。我想做的是:

try (final LdapConnection connection = ldapService.getConnection()) {
   // Do stuff with connection
}

我最终做的是定义一个类,如:

/**
 * Wraps a connection from a connection pool such that close with release it back to the pool.
 *
 * <p>You need a reference to the pool in order to release it, so using a wrapper</p>
 */
public class PooledLdapConnection implements Closeable {

  private final LdapConnection connection;
  private final LdapConnectionPool pool;

  public PooledLdapConnection(final LdapConnection connection, final LdapConnectionPool pool) {
    this.connection = connection;
    this.pool = pool;
  }

  public LdapConnection getConnection() {
    return connection;
  }

  @Override
  public void close() throws IOException {
    if (pool != null) {
      try {
        pool.releaseConnection(connection);
      } catch (final LdapException e) {
        throw new IOException(e.getMessage(), e);
      }
    }
  }
}

然后我的 LDAP 服务现在返回 - 在函数中,而不是仅仅返回pool.getConnection()我返回new PooledLdapConnection(pool.getConnection(), pool)

那我可以

try (final PooledLdapConnection connection = ldapService.getConnection()) {
   // Do stuff with connection.getConnection()
}

当它完成并“关闭”时,它实际上只是返回到池中。我可以用我的 PooledLdapConnection 实现 LdapConnection 接口,并简单地将所有函数的实现代理close到我的底层连接对象之外,但这更容易,并且如果接口更新也不需要重构。

我确实觉得这是图书馆应该为我做的!池返回的实现应该是与通过获取单个连接返回的对象不同的对象,不同之处在于自动关闭的作用。但这似乎有效。

我还有一个问题。我们的开发环境中的 DNS 配置错误,因此它指向错误的服务器以尝试连接到 LDAP。在这种情况下,它仍然会吃掉连接,直到我们达到 java 文件限制。还没有进一步调查

于 2020-10-30T08:01:40.200 回答