Quarkus:SSL握手时执行长IO任务如何避免阻塞事件循环?
解决Quarkus SSL握手阶段调用外部服务阻塞Vert.x事件循环的问题
你的核心问题是JDK X509TrustManager的同步API限制,导致握手阶段调用外部服务时阻塞Vert.x事件循环。以下是三种可行的解决方案,覆盖不同场景需求:
方案一:将外部服务调用移至Quarkus工作线程池
Quarkus提供了专门的工作线程池处理阻塞任务,我们可以把外部服务调用逻辑转移到工作线程中执行,避免占用Vert.x事件循环线程。
实现代码
class CustomTrustManager( private val manager: X509TrustManager, private val externalService: ExternalService, private val vertx: Vertx ) : X509TrustManager { override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { // 先执行JDK默认的证书信任链验证 manager.checkClientTrusted(chain, authType) val certificate = chain[0] // 将阻塞调用提交到工作线程池,同步等待结果 val isValid = vertx.executeBlocking<Boolean>({ promise -> try { promise.complete(externalService.verifyCertificate(certificate)) } catch (e: Exception) { promise.fail(e) } }, false) // false表示使用工作线程池而非事件循环 .await() if (!isValid) { throw CertificateException("Certificate is unknown in external service") } } // 实现X509TrustManager的其他必填方法 override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { manager.checkServerTrusted(chain, authType) } override fun getAcceptedIssuers(): Array<X509Certificate> { return manager.getAcceptedIssuers() } }
配置调整
在application.properties中调整工作线程池参数,适配并发握手需求:
quarkus.vertx.worker-pool-size=16 quarkus.vertx.max-worker-execution-time=5s
方案二:预缓存合法证书+异步刷新
如果外部服务的合法证书列表变更频率低,可以通过预缓存+后台异步刷新的方式,彻底消除握手阶段的阻塞调用。
1. 证书缓存服务
@ApplicationScoped class CertificateCache(private val externalService: ExternalService, private val vertx: Vertx) { private val validCertFingerprints = ConcurrentHashMap<String, Unit>() @PostConstruct fun initCache() { refreshCache() // 每30分钟刷新一次缓存,可根据业务调整周期 vertx.setPeriodic(30 * 60 * 1000) { refreshCache() } } private fun refreshCache() { vertx.executeBlocking<Void>({ promise -> try { val validCertificates = externalService.fetchAllValidCertificates() validCertFingerprints.clear() validCertificates.forEach { cert -> // 用SHA-256指纹作为唯一标识 val fingerprint = cert.digest("SHA-256") .joinToString(":") { "%02x".format(it) } validCertFingerprints[fingerprint] = Unit } promise.complete() } catch (e: Exception) { promise.fail(e) // 刷新失败时保留旧缓存,避免服务中断 } }, false) } fun isCertificateValid(cert: X509Certificate): Boolean { val fingerprint = cert.digest("SHA-256") .joinToString(":") { "%02x".format(it) } return validCertFingerprints.containsKey(fingerprint) } }
2. 修改后的CustomTrustManager
class CustomTrustManager( private val manager: X509TrustManager, private val certificateCache: CertificateCache ) : X509TrustManager { override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { manager.checkClientTrusted(chain, authType) val certificate = chain[0] if (!certificateCache.isCertificateValid(certificate)) { throw CertificateException("Certificate is unknown in external service") } } // 实现其他X509TrustManager方法... }
优势
- 握手阶段完全无阻塞,性能最优
- 降低对外部服务的依赖风险,避免网络波动影响服务可用性
方案三:Vert.x SSL握手拦截器(进阶)
绕过X509TrustManager的同步限制,直接通过Vert.x的SslHandler钩子插入异步验证逻辑,在握手完成前完成证书校验。
实现代码
@ApplicationScoped class VertxCustomizer( private val externalService: ExternalService, private val vertx: Vertx ) : HttpServerOptionsCustomizer { override fun customizeHttpsServer(options: HttpServerOptions) { val originalTrustOptions = options.trustOptions // 自定义SslHandler生成逻辑 options.sslHandlerProvider = { sslContext -> val sslHandler = sslContext.newHandler(vertx) // 监听握手完成事件,插入异步验证 sslHandler.handshakeFuture().onSuccess { sslSession -> val peerCerts = sslSession.peerCertificates if (peerCerts.isNotEmpty() && peerCerts[0] is X509Certificate) { val cert = peerCerts[0] as X509Certificate vertx.executeBlocking<Boolean>({ promise -> try { promise.complete(externalService.verifyCertificate(cert)) } catch (e: Exception) { promise.fail(e) } }, false) .onFailure { // 验证失败直接关闭连接 sslHandler.close() } } } sslHandler } } }
注意事项
- 需确保验证失败时及时关闭连接,防止非法客户端继续发送请求
- 要处理握手后验证的时序问题,避免客户端在验证完成前发起业务请求
内容的提问来源于stack exchange,提问作者Jan Thomä
相关产品推荐
相关产品推荐

