ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Doris+Trino:构建统一SQL入口的跨源查询实战指南

Doris+Trino:构建统一SQL入口的跨源查询实战指南 前一阵子接手一个数据平台的临时查询需求遇到的情况估计很多人也碰到过实时订单在Doris里跑得飞快但报表非要关联MySQL业务库的维度表再补上Hive里的历史明细。以前团队的常规操作是先把数据导来导去导完发现两边口径对不上白忙一晚上。后来我把Trino作为统一SQL入口把Doris作为核心的存储与计算引擎接进来所有结构化查询都走同一套SQL问题才算真正解决。这篇聊的就是这个组合的架构设计、实际配置、下推调优和踩坑过程。内容偏实战适合正在选型统一大数据查询引擎、或者已经部署了Doris但被“跨源查询难”困扰的工程师。我会把从“Doris集群”到“Trino Catalog”之间的每一层细节都讲透包括大家搜索很多的“Doris与Trino集成后报missing错误”这类问题也会给出完整的排查链路。1. 为什么Doris和Trino是同一套SQL体系里的一对搭档1.1 Doris的定位面向实时查询的在线分析库Doris是一个MPP架构的OLAP数据库它的强项在于“入库快、查询也快”。数据从导入到可见通常只需要秒级单表聚合在几千万行级别能做到亚秒返回高并发点查也很稳。对于很多团队来说Doris承担的是“在线分析”的角色BI报表、用户画像标签、实时大屏后端基本都是Doris。但Doris不是万能的。最常见的问题是数据不在一个地方。你的订单明细可能在Doris用户维度表在MySQL历史归档在Hive日志又有一部分在对象存储。真要让它们做一次join传统思路是把小表导到Doris、把历史数据也灌到Doris或者反过来全导到Hive跑离线。这两种做法都有代价一种是实时性差一种是存储和运维成本高。而实际业务提需求的人根本不关心数据在哪他只关心“用一条SQL能不能查出来”。1.2 Trino的定位不存数据的联邦查询引擎Trino前身是Presto SQL是另一种思路。它本身不存储任何业务数据核心能力是“连接一切数据源”。MySQL、PostgreSQL、Hive、Iceberg、Kafka、对象存储还有Doris每种数据源对应一个CatalogTrino负责把分布式查询计划下发到各个数据源执行最后汇总结果。这种“联邦查询”模型天然适合当统一入口。你不需要把历史数据搬进Doris也不需要把Doris数据导去Hive只需要在Trino的Catalog里各挂一个连接就能用一条标准SQL同时查询多个源。业务方只认一个JDBC地址也就是Trino的地址剩下的内部路由全由Trino处理。1.3 合流以后解决了哪几类问题把Doris和Trino接到一起我总结下来解决的是这几类事统一访问入口用户只记一个Trino地址不用分别记Doris的FE地址、MySQL地址、Hive地址也不用学各种客户端。冷热分离查询热数据在Doris冷数据在Hive一条SQL里同时joinTrino负责组装结果。降低BI集成成本BI工具通常只要对接Trino的标准JDBC协议就能拿到所有数据源。减少数据搬迁临时分析任务不需要先导库直接跨源查询分析完就结束数据不动。有一点要提前说明Doris本身也有很完备的SQL能力如果你所有数据都进了Doris那完全没必要再叠一层Trino。这个组合的价值恰恰是在“数据不在同一处”的时候体现出来的。在我看来大多数中型团队的真实数据格局恰好就是“Doris存热数据、Hive存离线、MySQL存业务库”这种混合状态所以Doris加Trino的定位相当准。2. 集成架构与版本选型先把Catalog规划清楚2.1 部署拓扑Trino在上Doris在下先说架构。Trino集群的角色分为Coordinator和WorkerDoris集群分为FE和BE。Trino通过Catalog机制对接Doris每个Catalog对应一个Doris集群或者一个数据库实例。常见的部署拓扑是这样的应用层BI工具、报表服务、临时查询脚本统一连Trino的Coordinator端口通常是8080。Trino的Catalog里配置一个名为doris的Catalog连接地址指向Doris的FE节点。FE负责接收查询请求、解析SQL、生成执行计划然后把任务分发给BE执行。结果由BE计算后通过FE返回给TrinoTrino再做跨源汇总和最终计算。图中的虚线关系可以理解为Trino与Doris之间至少要保持FE的MySQL协议端口可达部分Connector实现还会通过FE跳转到BE端口拉取计算结果所以BE的访问端口也要一起规划进网络策略。你自己部署的时候不需要把Trino和Doris强绑定在同一批机器上。两者的扩容维度是独立的Doris扩容BE节点提升单库查询性能Trino扩容Worker提升跨源聚合能力。生产环境里常见做法是Trino和Doris分属两套集群网络内网互通不建议把Worker和BE混布因为两者的资源消耗模型不一样混布容易互相干扰。2.2 版本匹配Connector和Doris FE要同一代版本问题是最容易被忽视、又最容易引发诡异报错的地方。Trino官方从某几个版本开始内置了独立的Doris连接器早期没有独立连接器时大家会用MySQL连接器替代所以我建议直接用官方Doris Connector不要再用MySQL Connector去连Doris。我的经验版本组合是Doris选择LTS分支里较新的维护版本比如当前仍在维护的2.0.x或2.1.x系列Trino选择较新的稳定版保证内置Doris Connector的解析逻辑与Doris FE的元数据返回格式兼容。为什么要强调版本匹配因为Doris每个版本都在增加新功能、新字段类型。比如后来引入的VARIANT、JSONB这类类型如果Trino端连接器版本太老解析元数据时就会遇到无法识别的情况。这不是SQL写得不对而是两边协议版本没对齐。升级的时候尽量“同步升级”别一端动了另一端没动。2.3 端口规划9030、8030、8040到底怎么用Doris有三个端口在集成时最容易混淆端口默认值用途集成时是否需要query_port9030MySQL协议连接端口Trino Doris Connector主要走这里必须开放http_port8030FE的HTTP接口提供Web UI和部分元数据访问建议开放webserver_port8040BE的HTTP端口部分数据读取或导入场景会用到建议开放我之前遇到过有人把Trino连接地址写成了jdbc:mysql://FE_IP:8030结果一直报连接失败。这里说明一下8030是HTTP端口不是MySQL协议端口Trino连接器需要走9030而8040是BE的端口在Trino大规模扫描Doris数据时某些实现会经过FE重定向到BE去拉数据如果8040被防火墙挡了也会出现“查询一开始正常、数据量一大就报错”的现象。所以集成前要做的检查很简单从Trino Worker节点分别telnet一下FE的9030、8030和BE的8040确定网络策略全通再去调配置文件。很多奇怪问题其实是网络策略先拦住的跟Connector本身没关系。3. 实操配置在Trino里注册Doris数据源并跑通第一条SQL3.1 准备Connector文件首先要确认你的Trino发行版里有没有Doris Connector。最直接的方式是登录Trino客户端执行SHOW CATALOGS;如果结果里能看到类似doris的Catalog说明发行版已经内置了。如果没有就检查一下Trino安装目录下的plugin文件夹看看是否存在doris目录ls /opt/trino/plugin/正常会看到hive、mysql、postgresql、doris等目录。如果缺少Doris Connector可以去Trino官方的Maven仓库找到对应版本的trino-doris插件包下载后解压到plugin目录下然后重启Trino进程。这一步较基础但不要跳过版本核对插件包的版本号必须和Trino主版本匹配否则加载时会出现类冲突或者直接加载失败。3.2 编写catalog配置文件在Trino安装目录下的etc/catalog文件夹里新建一个doris.properties文件文件名就是Catalog名字可以按你的业务习惯命名比如doris.properties对应catalog名字dorisconnector.namedoris connection-urljdbc:mysql://DORIS_FE_HOST:9030 connection-usertrino_user connection-passwordyour_password这里有几个关键参数需要注意connector.namedoris告诉Trino加载Doris连接器而不是MySQL连接器。connection-urljdbc:mysql://DORIS_FE_HOST:9030走的是Doris的MySQL协议端口不是HTTP端口。如果你的Doris FE做了高可用URL里可以配置多个FE地址用逗号分隔。账号权限要单独处理我建议不要直接用Doris的root账号而是创建一个只读账号用于日常查询再用一个单独账号用于运维任务。有些项目里还会加这些参数doris.enable-query-diagnostictrue doris.weblogintrue具体以你用的版本文档为准核心是先把上面三行配对跑通第一条SQL后再研究高级选项。3.3 验证连通性并执行第一条SQL修改完配置后重启Trino推荐通过trino-*/bin/launcher restart这样的脚本然后进入Trino客户端SHOW CATALOGS; USE doris.demo_db; SHOW TABLES; SELECT COUNT(*) FROM demo_db.doris_test_table;如果能顺利返回表信息和聚合结果说明Doris连接器已经打通了。此时再做一个验证查询一张Doris中实际存在的、带过滤条件的表确认过滤条件下推没有报错SELECT region, COUNT(*) AS cnt FROM doris.trade.orders WHERE dt DATE 2025-06-01 GROUP BY region;这一步能跑通后面的跨源查询和优化才有意义。3.4 类型映射对照避免数据类型翻车Doris和Trino的类型体系并不完全一致直接映射时会有几种常见“翻车”。我整理了部分对照关系Doris类型Trino类型注意事项TINYINTTINYINT字段类型宽度不同join时注意类型提升SMALLINTSMALLINT同左INTINTEGER常见一般没有问题BIGINTBIGINT常见一般没有问题DECIMAL(p, s)DECIMAL(p, s)Doris最大精度38位Trino可以更大但join两侧要一致CHAR/VARCHARVARCHAR长度信息可能丢失查询时注意DATETIMETIMESTAMP时区问题见下文踩坑章节DATEDATE一般可以直接映射BITMAP / HLL不支持直接映射聚合模型使用较多查询时需要转换或改造JSONJSON / VARCHAR老版本可能不支持建议转VARCHARARRAY/MAP/STRUCTARRAY/MAP/ROW版本较新时才能稳定支持这里有一个很容易踩的坑如果你在Doris里用了BITMAP或HLL类型做精确去重Trino默认是不认识这些类型的。很多人一执行查询就报类型解析错误。我的建议是不要试图让Trino直接读BITMAP而是提前在Doris里物化成可读的数值结果比如把bitmap_count()算好的结果同步到普通列或者查询时显式CAST成VARCHAR。类似的研发同事如果习惯用MyBatis-Plus根据Java实体类自动生成建表SQL拿到Doris这里也千万别直接套用Doris建表要按自己的模型语法DUPLICATE KEY、AGGREGATE KEY、UNIQUE KEY来写否则字段语义和去重策略全对不上。4. 排查“missing”错误的完整链路一次真实踩坑记录4.1 现场一个叫人摸不着头脑的错误和大家搜索的方向一致我在Doris和Trino集成后也遇到过一个极具迷惑性的报错查询一张普通表时Trino直接抛出类似SQL execution failed, reason: missing的异常没有具体列名、没有SQL状态码甚至连Doris的哪些表名都不显示。第一次遇到的时候我以为是权限问题检查了账号授权又以为是网络问题telnet了9030、8030全都通。折腾了半小时错误依旧是那个让人无语的“missing”。后来我在Doris的FE审计日志里看到了对应的记录Doris端其实是正常执行了的但返回给Trino的元数据信息里缺少了某个字段Trino解析不出来于是把这个错误归类为“missing”。这类问题名字就叫missing但根本不是查询的数据“缺失”而是协议解析失败。4.2 从网络到协议逐层验证遇到这种模糊报错我的排查顺序是固定的也建议你照着走一遍第一步排除网络策略。我前面提过从Trino的Worker节点分别telnet三个端口9030、8030、8040。只要有一个不通就可能出现“部分查询正常、部分查询失败”的情况。第二步排除账号权限。用MySQL客户端直接连Doris的9030端口mysql -h DORIS_FE_HOST -P 9030 -u trino_user -p USE demo_db; SHOW TABLES; SELECT * FROM demo_db.test_table LIMIT 10;如果MySQL客户端能查、而Trino查不了问题就大概率不在数据、权限和SQL本身而是Trino连接器与Doris之间的协议兼容性。第三步对比两侧版本。查一下Doris的版本号SHOW FRONTENDS;再查一下Trino连接器版本具体看plugin/doris目录里的META-INF/MANIFEST.MF或者官方发布说明。我遇到的那次问题根源就是Doris的FE已经升级到了新版本返回的元数据字段格式变了而Trino内置的Doris Connector还是老逻辑没有处理新字段于是解析器抛出missing。第四步抓包确认响应差异。如果需要进一步的证据可以在Trino机器上对FE的8030端口抓HTTP响应包看返回的元数据JSON里是否有老版本Connector不认识的键。这一步在故障复盘时尤其有价值。4.3 根因与修复FE新返回格式与Connector旧解析不兼容那次问题的根因本质上是Doris FE的元数据返回中包含了新版本引入的扩展字段Trino老版本连接器在解析时找不到旧格式里的必填字段于是把整个查询判定为“missing”。修复办法也直接把Trino升级到能匹配当前Doris版本的稳定版或者把Doris回退到与Trino连接器匹配的LTS版本。我推荐前者因为Doris的升级更多往后兼容性更好。升级后再执行同样的查询missing错误就消失了。这个案例的教训是集成类系统最怕“版本时间差”。Doris与Trino同时部署时尽量不要一个常年不升级、另一个频繁升级否则很多错误你会误以为是代码问题。4.4 同族的其他坑端口、时区、类型映射错误除了missing本身同族错误还有几个很容易认错的一是时区问题。Trino默认使用UTCDoris时区一般设置成Asia/Shanghai。两边在转换TIMESTAMP时如果没做映射查出来的时间可能差8小时或者datetime字符串转换直接报错。我建议在Trino侧统一用会话属性调整SET TIME ZONE Asia/Shanghai;然后在所有需要跨源join的查询里固定使用这个时区避免“上午查和下午查结果不一样”的诡异现象。二是端口写错。前面说过用jdbc:mysql://FE_IP:9030是正解写8030或8040都会报连接失败但报错信息可能五花八门有人还会误以为是Connector坏了。三是BITMAP/HLL类型识别不了。这种报错往往带着Unsupported type字样处理方式我在类型映射里已经说过了不要在Trino侧强行解析BITMAP在Doris预聚合阶段就把它转成数值。5. 下推与调优让查询真正跑在Doris里而不是Trino内存中5.1 用EXPLAIN看透查询在哪一步执行Doris和Trino集成的性能上限很大程度上取决于“下推”做得好不好。所谓下推就是把Trino能处理的过滤、聚合、排序等操作尽量转移给Doris的BE执行而不是把几十万行明细拉回Trino内存再算。判断下推最简单的方式是用执行计划EXPLAIN (TYPE DISTRIBUTED) SELECT region, COUNT(*) AS cnt FROM doris.trade.orders WHERE dt DATE 2025-06-01 GROUP BY region;在执行计划的片段里你会看到类似TableScan下面挂着的连接器信息如果过滤条件、聚合操作被下推会出现Doris连接器执行节点的相关标注比如PushdownFilter、PushdownAggregate、PushdownLimit。如果没有这些信息说明Trino把整表数据拉回来了。我遇到过一个真实的慢查询一条SQL在Doris里直接跑只要3秒但通过Trino查花了2分钟。用EXPLAIN一看问题出在过滤条件写法上——我把过滤条件写在了join子查询的ON条件里Trino把它当成join后过滤无法下推导致Doris的BE把大量原始行传到了Trino。改成WHERE子句后2分钟直接降到4秒。5.2 下推的边界哪些能推哪些推不了不是所有操作都能下推这是很多人调优时的认知盲区。根据Doris Connector的实际表现我总结了几个边界可以下推的常见操作谓词过滤等值、范围、IN、LIKE部分版本投影只查询需要的列Doris按列存裁剪聚合COUNT、SUM、MIN、MAX、AVG等简单聚合排序加LIMITORDER BY ... LIMIT N一般可以下推不能下推的常见操作JOIN跨源JOIN或Doris表之间的复杂JOIN通常在Trino执行窗口函数ROW_NUMBER、RANK这类需要在Trino内存中做分区排序自定义函数Trino的UDF不能翻译成Doris函数复杂CASE WHEN表达式很多情况下只保留原始数据Trino侧再计算理解边界之后你的SQL写法和表结构设计都会跟着改变。比如需要精确去重时不要直接让Trino去COUNT(DISTINCT uid)一个大表那样会把大量明细拉到内存更好的方式是在Doris聚合模型里维护BITMAP并物化去重结果Trino只查一个数值列。5.3 从慢SQL到预聚合一个能参考的优化案例我举一个实际优化案例。业务方每天要统计“每个区域的UV”数据在Doris单日明细有6亿行。最初在Trino里写的SQL是SELECT region, COUNT(DISTINCT uid) AS uv FROM doris.trade.user_visit_log WHERE dt DATE 2025-06-01 GROUP BY region;这个查询在Trino里跑一次大约要5分钟主要瓶颈在6亿行的明细被拉到Trino内存然后做全局去重。改动方案分两步第一步在Doris内创建预聚合表用Aggregate Key模型按region dt uid的粒度建表配合Doris的BitMap精确去重或者直接维护UV数值CREATE TABLE doris.dim.user_visit_uv ( dt DATE, region VARCHAR(64), uv BIGINT ) AGGREGATE KEY(dt, region) DISTRIBUTED BY HASH(region) BUCKETS 16;第二步定期比如每5分钟把明细表汇总到预聚合表Trino端查询时就不再扫6亿行原表而是只查预聚合表SELECT region, SUM(uv) FROM doris.dim.user_visit_uv WHERE dt BETWEEN DATE 2025-06-01 AND DATE 2025-06-07 GROUP BY region;改造后查询时间从5分钟降到2秒左右。这类思路值得推广Trino适合做跨源、灵活的联邦查询Doris适合做吞吐大、延迟敏感的高频查询。能让Doris提前算的别等到Trino内存里再算。5.4 内存与并发参数Trino侧怎么配合除了SQL改造Trino侧参数也要跟着业务形态调。下面几个是我常用的调整点内存限制query.max-memory-per-node默认值可能不够跨源join时中间结果多需要适当调大query.max-total-memory-per-node也要同步看。并发度Trino的并发由资源组控制建议给“交互查询”和“后台作业”分不同资源组避免一个慢SQL把Coordinator的线程池打满。小表广播跨源join时如果一边是几千行的小维度表把join distribution模式设为BROADCAST让每个Worker持有一份小表副本可以避免大表的全量shuffle。结果缓存对于重复的BI报表查询可以评估Trino的结果缓存插件减少相同SQL对Doris的重复压力。调优时有一个原则先看错误日志再看执行计划最后才动参数。很多团队一慢就调内存结果问题出在过滤条件没下推纯属南辕北辙。6. 一套SQL打通多个数据源的数仓落地经验6.1 典型三层数据源MySQL存业务、Doris存热数据、Hive存历史真正落到生产环境时我的经验是把数据源问题先结构化。以一个典型的电商数仓为例MySQL存放业务系统的订单主表、用户表、商品表数据量在千万级。Doris存放需要实时分析的行为明细、订单明细、流量日志数据量在亿级到十亿级。Hive存放几年份的历史归档以及大批量离线计算的结果集。对象存储如HDFS/OSS存放日志文件通过Hive或Iceberg表挂接。用户在做临时分析时经常需要“查一下这个月所有订单的GMV按区域对比还要筛掉非真实用户”。这种分析在传统方案下涉及MySQL的订单表、Doris的行为表和Hive的用户历史标签表没有Trino时光凑齐数据就要半天。接入Trino后三条数据源各有CatalogSQL长这样SELECT u.region, COUNT(DISTINCT o.order_no) AS order_cnt, SUM(o.pay_amount) AS gmv FROM mysql_biz.orders o JOIN hive_dw.dim_user u ON o.uid u.uid JOIN doris.trade.visit_log v ON o.order_no v.order_no WHERE o.pay_time TIMESTAMP 2025-01-01 00:00:00 AND v.is_real_user 1 GROUP BY 1;一个查询就把三个引擎的数据串起来了业务方完全不用关心数据在哪个系统。这类SQL在Trino里能跑通但要注意如果两个大表之间的JOIN无法下推中间结果会到Trino内存建议控制查询时间范围必要时先把结果落到Doris的临时表再做后续加工。6.2 视图层统一口径让不同报表用同一套指标统一SQL查询引擎最容易出现的新问题是“入口统一了但每个团队对GMV、UV、转化率的定义不一样”。解决这个问题我强烈建议在Trino上建立视图层把口径规范固化在视图里。例如定义一个全局统一的GMV视图CREATE VIEW dow.insight.gmv AS SELECT dt, region, SUM(pay_amount) AS gmv, COUNT(DISTINCT order_no) AS order_cnt FROM doris.trade.orders WHERE order_status PAID GROUP BY dt, region;下游报表不管怎么做都直接查这个视图不让他们接触原始表。这样有三个好处口径可追溯、变更影响面可控、缓慢变更时不用通知所有下游。视图层要建在Trino的库里因为它本质上是跨Catalog的虚拟映射可以同时引用MySQL、Doris和Hive的表。6.3 权限控制与SQL审计统一入口必须配套统一治理入口统一之后权限治理必须跟上否则就是“所有人能查所有数据”。Trino支持基于文件的访问控制file-based access control可以配置某个用户或某个组能访问哪些Catalog、哪些Schema、哪些表。我建议至少做到普通分析人员只读权限禁止INSERT、DROP。运维和数仓管理员允许DDL禁止对线上源库执行危险操作。关键表如用户隐私表显式禁止授权。同时打开Trino的审计日志记录每个查询的提交人、SQL文本、涉及表、扫描行数。Doris侧也有审计日志两者配合起来定位一条慢SQL时基本能判断慢在Trino协调端还是慢在Doris执行端。6.4 Java应用接入JDBC与ORM需要注意的问题如果是Java服务需要接入这个统一查询引擎成本很低。Trino提供标准的JDBC驱动连接串类似jdbc:trino://TRINO_COORDINATOR_HOST:8080/dow/insight?useranalyst应用侧直接像连MySQL一样使用即可。不过有两点要提醒如果你们用了MyBatis-Plus这类ORM生成的SQL是基于MySQL方言的直接连Trino可能在某些函数上不兼容。比如分页写法、函数名的差异建议关键查询直接写原生SQL不要完全依赖ORM自动生成。“根据Java实体类生成建表SQL”这套逻辑在Trino和Doris环境中只能作为开发辅助生产环境建表必须走Doris原生的DDL否则模型关键字、分桶策略都表达不了。我个人的经验是Java应用层统一走Trino JDBC入口Doris建表走Doris原生客户端两边各管各的不要试图用一个工具统一所有语法。这样维护成本最低也少踩兼容性坑。最后分享一点运维体会Doris和Trino这套组合跑了大半年后我最大的体会是能让Doris算的绝不拖到Trino里算能让预聚合解决的绝不实时去重。统一SQL入口解决的是“能不能查”的问题而性能好不好还是取决于你对下推机制和数仓模型的理解。最后再分享一个小技巧Doris的慢查询会直接落在FE的审计日志里Trino的慢查询也会记录在Trino的事件日志里遇到任何一条“两边加起来要跑很久”的SQL先在两边日志里对一下时间差基本就能定位是Doris那边卡住了还是Trino中间结果太膨胀。这个排查习惯比加内存、调并发参数都管用。
返回列表