在Tomcat中使用Firebase Java Admin SDK遇到API阻塞问题
有人成功在Apache Tomcat中使用Firebase Java Admin SDK吗?我在Linux服务器上用Java 11(OpenJDK)、Tomcat 9.0.80和firebase-admin 9.2.0时遇到严重问题:FirebaseApp.initializeApp()看似初始化成功,但所有Firebase API调用均阻塞或超时。同一服务器上用相同Java版本、相同服务账号文件运行控制台程序时一切正常,可排除认证或网络问题。
环境信息
- Java版本:OpenJDK 11
- Tomcat版本:9.0.80
- Firebase Admin SDK版本:9.2.0
- 操作系统:Linux
初始化代码(放在ServletContextListener.contextInitialized()中)
org.apache.log4j.Logger.getLogger(CaasaAdminServletListener.class).info("[init]"); String serviceAccountKey = sce.getServletContext().getRealPath("/WEB-INF/firebase.json"); org.apache.log4j.Logger.getLogger(CaasaAdminServletListener.class).info("[init] serviceAccountKey= "+serviceAccountKey); FileInputStream serviceAccount = new FileInputStream(serviceAccountKey); FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .setDatabaseUrl("https://mydatabase.firebaseio.com/") .build(); FirebaseApp.initializeApp(options); org.apache.log4j.Logger.getLogger(CaasaAdminServletListener.class).info("[init] FirebaseApp.initializeApp OK");
阻塞的API调用代码(Servlet的processRequest方法中)
final FirebaseDatabase database = FirebaseDatabase.getInstance(); database.getReference().child(key).addListenerForSingleValueEvent(listener);
已尝试的排查操作
- 同一服务器上的控制台程序可正常读写Firebase数据库,同步异步API均可用;
- 尝试过在静态代码块、Servlet.init方法或API调用前初始化Firebase,均无效;
- 对比控制台程序(正常)与Tomcat Web应用(异常)的Firebase调试日志,发现Web应用中缺少io.netty内部消息系统的初始化日志。
日志对比
两者共有的初始日志片段
12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.logging.InternalLoggerFactory - Using Log4J as the default logging framework 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - Java version: 11 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @6c382782 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, {int,long}): unavailable 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - maxDirectMemory: 7677673472 bytes (maybe) 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: /tmp (java.io.tmpdir) 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model) 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false
仅控制台程序存在的日志片段
12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.channel.MultithreadEventLoopGroup - -Dio.netty.eventLoopThreads: 16 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.concurrent.GlobalEventExecutor - -Dio.netty.globalEventExecutor.quietPeriodSeconds: 1 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.channel.nio.NioEventLoop - -Dio.netty.noKeySetOptimization: false 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.channel.nio.NioEventLoop - -Dio.netty.selectorAutoRebuildThreshold: 512 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.internal.PlatformDependent - org.jctools-core.MpscChunkedArrayQueue: available 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.handler.ssl.OpenSsl - netty-tcnative not in the classpath; OpenSslEngine will be unavailable. 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.handler.ssl.JdkSslContext - Default protocols (JDK): [TLSv1.3, TLSv1.2] 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.handler.ssl.JdkSslContext - Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384] 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.channel.DefaultChannelId - -Dio.netty.processId: 17423 (auto-detected) 12 10 2023 10:27:41 DEBUG firebase-database-worker io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false ....
可能的解决方案
根据日志缺失和Tomcat类加载特性,问题大概率和Netty的类加载冲突或线程初始化被Tomcat上下文限制有关,可尝试以下操作:
调整Tomcat的类加载属性
在Tomcat的conf/context.xml中添加antiResourceLocking="false"和antiJARLocking="false",避免类加载时的资源锁定导致Netty初始化失败:<Context antiResourceLocking="false" antiJARLocking="false"> <!-- 其他配置 --> </Context>强制指定Netty的线程模型
在Firebase初始化前,手动设置Netty的系统属性,确保EventLoop线程正常初始化:System.setProperty("io.netty.eventLoopThreads", "16"); System.setProperty("io.netty.selectorAutoRebuildThreshold", "512"); // 然后再执行Firebase初始化代码 FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .setDatabaseUrl("https://mydatabase.firebaseio.com/") .build(); FirebaseApp.initializeApp(options);升级Firebase Admin SDK版本
9.2.0版本相对较旧,尝试升级到较新的稳定版本(比如最新的9.x或10.x版本),新版本可能修复了Web容器下的类加载和Netty初始化问题。检查Tomcat的安全管理器配置
如果Tomcat启用了安全管理器,可能限制了Netty反射调用相关的权限,需要在conf/catalina.policy中添加对应的权限:grant codeBase "file:${catalina.base}/webapps/你的应用名称/-" { permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; };
内容的提问来源于stack exchange,提问作者Andrea Murru

