动态catalog引起PluginClassLoader冲突

问题描述

        这个问题产生的背景是:

  1. trino开启了动态catalog功能(我们自己的一个实现,非社区的实现)
  2. 当前trino已有个linkhouse的iceberg catalog,并使用SQL查询过这个catalog的某些iceberg表。
  3. 模拟动态更新trino catalog的操作,即调用coordinator  http接口更新catalog。
    curl --location --request POST '127.0.0.1:8080/trino/catalog/update' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "catalogName":"linkhouse",
        "connector.name":"iceberg",
        "hive.metastore.uri":"thrift://localhost:9083"
    }'
  4. 然后使用相同SQL再次查询,会发生SQL执行失败报错。

        报错如下:

java.lang.ClassCastException: class io.trino.plugin.iceberg.IcebergSplit cannot be cast to class io.trino.plugin.iceberg.IcebergSplit (io.trino.plugin.iceberg.IcebergSplit is in unnamed module of loader io.trino.server.PluginClassLoader @7cc45a0d; io.trino.plugin.iceberg.IcebergSplit is in unnamed module of loader io.trino.server.PluginClassLoader @730c1318)

原因分析

        首先在讲原因前,我们先理解一下trino怎么设计plugin的classLoader,理解一下原理。

PluginClassLoader机制

  1. trino对每一种connector设计成一个plugin,定制了PluginClassLoader来加载plugin的那些jar实现,这样就可以做到不同的plugin之间的类隔离,避免类冲突。
  2. 同时一个connector可以有多个catalog,每一个catalog都有会一个PluginClassLoader实例(这点不知道为什么这么设计,按理说一个connector一个PluginClassLoader实例就够了)
  3. 每一个catalog实例都会创建一个ConnectorFactory
  4. 一个PluginClassLoader实例有一个classLoader id,比如一个名字为linkhouse的catalog(connector为 iceberg)。那它的classLoader id就是 iceberg:linkhouse
  5. 这个classLoader id的作用是什么呢?用于cn与worker之间的HttpRemoteTask对象的序列化反序列化使用正确的classLoader去反序列化。
    • cn在生成IcebergSplit之后,会将IcebergSplit打包成HttpRemoteTask发生给worker去处理,这个时候在序列化IcebergSplit实例的时候会将IcebergSplit的类名io.trino.plugin.iceberg.IcebergSplit替换成iceberg:linkhouse:io.trino.plugin.iceberg.IcebergSplit。类名上携带了classLoader id信息。
      worker在反序列IcebergSplit对象时,会先基于类名iceberg:linkhouse:io.trino.plugin.iceberg.IcebergSplit提取出classLoader id,然后找到对应的PluginClassLoader实例去对io.trino.plugin.iceberg.IcebergSplit进行loadClass(HandleResolver.getHandleClass
      cn和worker就是通过基于这个classLoader id的设计来保证使用正确的PluginClassLoader实例来loadClass类实例,从而保证HttpRemoteTask对象可以正常的反序列化。(比如如果我用Hive PluginClassLoader实例去反序列化IcebergSplit的HttpRemoteTask肯定会异常,因为它没有相关jar实现)

        在序列化反序列化中,主要通过HandleResolver,相关代码:

分析

        通过在本地debug来复现这个问题并跟踪,最终发现了一个异常情况:在cn发送HttpRemoteTask前IcebergSplit的classLoader是PluginClassLoader@27764这个实例,而woker收到HttpRemoteTask后得到IcebergSplit的classLoader是PluginClassLoader@21181这个实例。如果是分布式情况,cn和worker属于不同的进程,这种情况是正常的,但是本地debug模式cn和worker是属于一个进程,所以正常的话是同一个PluginClassLoader实例才对。

        这个是cn发送HttpRemoteTask前IcebergSplit的classLoader:

下面是woker收到反序列化后的HttpRemoteTask后得到IcebergSplit的classLoader:

        进一步跟踪发现目前trino http模块使用jackson来序列化反序列化请求对象,而jackson来反序列化对象中只有在第一次碰到某一个class id,才会调用HandleResolver.getHandleClass

验证

        为了进一步验证确实是这个原因,我将PluginClassLoader进行魔改,以达到PluginClassLoader实例更新后,IcebergSplit可以得到一个不同的class id。

        通过下面的魔改之后进行测试,问题得到解决,可以确定是这个原因。

        注: 下面的魔改只适合本地local单进程模式,无法对分布式起作用,所以不能作为这个问题的最终解决方案。

总结

        通过分析和验证,我们确定是http rpc 反序列过程cache了第一次的Class对象导致了这个问题。解决这个问题大致几个方向:

  1. 禁用jackson的class cache,但每一次都需要load class势必会影响整体性能。
  2. 一个plugin一个PluginClassLoader实例。对trino的一个catalog一个PluginClassLoader实例在设计上进行了调整了,不知道有没有其他影响。
  3. 重新创建catalog之后,保证PluginClassLoader实例的classLoader id与之前的不重复。这里的困难点是分布式情况下,怎么协调cn和worker之间classLoader id的匹配。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值