尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

告别分库分表噩梦:Apache Doris 3.0构建实时数仓实战指南

告别分库分表噩梦:Apache Doris 3.0构建实时数仓实战指南 1. 项目概述从“拆”到“合”的架构演进如果你在数据团队待过几年肯定对“分库分表”这四个字又爱又恨。爱的是在业务早期面对数据库单表数据量爆炸式增长读写性能急剧下降时分库分表几乎是唯一能快速止血、让系统继续跑起来的“救命稻草”。恨的是一旦走上这条路你就给自己套上了一副沉重的枷锁。原本简单的SELECT * FROM orders WHERE user_id 123现在你得先算清楚这个用户的数据落在哪个库、哪个表然后才能去查。联表查询想都别想那简直是开发者的噩梦性能的深渊。更别提数据聚合分析了业务方要个简单的报表你得写个复杂的脚本跨多个库表捞数据然后在应用层做聚合耗时耗力还容易出错。这就是我们过去十年在构建数据系统时面临的典型困境为了应对海量数据的写入和存储我们不得不牺牲数据的统一视图和实时分析能力。我们把数据“拆”得七零八落却很难再“合”起来。而今天随着AI应用的爆发企业对数据实时性的要求达到了前所未有的高度。一个推荐模型需要秒级更新的用户行为特征一个风控系统需要毫秒级响应的交易流水分析。传统的“分库分表离线T1数仓”的架构在AI时代显得力不从心。正是在这样的背景下Apache Doris 3.0的出现为我们提供了一种全新的解题思路。它不再是一个单纯的OLAP查询引擎而是定位为“AI时代的实时数仓基石”。这个定位非常精准它直指当前企业数据架构的核心痛点如何在一个统一的平台上同时满足海量数据的高吞吐写入、低成本存储、以及针对实时数据和历史数据的极速分析查询。Doris 3.0通过其全新的存算分离架构、数据湖分析能力以及对半结构化数据的深度支持正在尝试让我们彻底“告别分库分表的噩梦”。这不是一次简单的版本升级而是一次面向未来的架构范式转移。2. 核心痛点解析分库分表为何成为“噩梦”要理解Doris 3.0带来的价值我们必须先深刻理解“分库分表”这个传统方案到底带来了哪些问题。这些问题并非技术本身的错而是在特定历史阶段为了满足核心业务系统通常是交易系统的可用性而做出的必要妥协。但当企业进入数据驱动和AI驱动的阶段时这些妥协的代价就被无限放大了。2.1 开发复杂度急剧上升这是最直观的感受。原本面向单一数据库的CRUD操作在分库分表后变得异常复杂。1. SQL支持度大幅降低分布式事务跨库的事务几乎无法实现或性能极差。业务中涉及多个分片的数据一致性只能通过最终一致性等复杂方案来弥补。关联查询JOIN这是分库分表架构下最大的痛点之一。如果关联键不是分片键那么关联操作就需要在所有分片间进行数据拉取和合并即“广播关联”或“重分布关联”性能开销巨大通常不被允许。开发被迫进行大量的业务逻辑改造比如冗余存储、应用层关联等。聚合查询GROUP BY,SUM,COUNT DISTINCT等操作无法在数据库层面高效完成。数据需要在各个分片上执行部分聚合然后汇总到应用层进行最终合并。对于复杂聚合这个过程既慢又容易内存溢出。2. 分片逻辑侵入业务代码你需要一个强大的中间件如ShardingSphere或在业务代码中硬编码分片路由逻辑。每次新增业务字段查询都要考虑它是否能用分片键定位如果不能就要走全分片扫描这迫使数据库设计必须前置考虑所有可能的查询模式这在快速变化的业务中几乎是不可能的。实操心得我曾经维护过一个按用户ID哈希分128张表的订单系统。当产品经理提出要按“商家所在城市”分析订单趋势时我们整个团队都傻眼了。这个查询条件无法命中分片键意味着要扫描所有128张表。最终我们不得不为这个需求单独建了一个离线同步到数仓的链路T1才能出报表完全无法满足实时决策的需求。2.2 运维成本高昂1. 扩容不灵活哈希取模分片这是最常用的方式但扩容如从4库扩到8库时数据需要重新哈希分布迁移数据的过程中如何保证服务不停机、数据不丢失是一个巨大的挑战。虽然有一致性哈希等改进方案但依然复杂。范围/时间分片按时间分片如按月分表在扩容上相对简单但容易产生“热点”问题。当前月份的表读写压力巨大而历史表几乎闲置。查询跨多月数据时需要合并多个表性能线性下降。2. 数据分布不均即“数据倾斜”。即使用户ID哈希理论上能均匀分布但某些“超级用户”产生的数据量可能是普通用户的成千上万倍导致其所在分片负载远高于其他分片形成性能瓶颈。3. 全局一致性视图缺失由于数据物理上分散想要得到一个全局的、实时的一致性视图比如全平台实时GMV必须查询所有分片然后汇总。这个查询本身就会对所有分片造成压力且延迟高很难做到秒级更新。2.3 与分析系统数仓割裂形成数据孤岛这是分库分表架构在AI时代最致命的缺陷。业务数据库OLTP被设计成“分”的状态以支撑高并发交易而数据分析系统OLAP则需要“合”的状态以进行全局分析。两者之间需要一条复杂、脆弱且延迟高的数据管道。ETL链路冗长通常需要经过CDC变更数据捕获工具如Canal、Debezium将分库分表的增量数据同步到消息队列如Kafka再由计算引擎如Flink进行复杂的合并、去重、转换最后才能写入数仓如Hive、ClickHouse。这条链路上的任何一个环节出问题都会导致数据延迟或错误。数据时效性差由于链路复杂T1隔天甚至H1隔小时是常态。这对于需要实时用户画像的推荐系统、需要实时反欺诈的风控系统来说是无法接受的。存储冗余与成本同一份数据在业务库存一份分片状态在消息队列存一份在数仓可能又存一份合并后的状态。存储成本和管理复杂度成倍增加。AI时代的催化AI模型特别是大模型和实时推荐模型对特征数据的新鲜度要求极高。一个用户刚刚点击的商品如果在几分钟内就能作为特征进入模型推荐效果会有质的提升。分库分表架构下冗长的数据管道成为了实时AI的“血栓”。3. Apache Doris 3.0 的核心革新存算分离与数据湖联邦Doris 3.0之所以敢宣称成为“实时数仓基石”是因为它从架构层面针对上述痛点进行了系统性重构。其核心可以概括为两点对内实现存算分离对外实现数据湖联邦。3.1 存算分离架构解耦与弹性之源在Doris 2.x及之前的版本中采用的是类似ClickHouse的存算一体架构。每个BE后端节点既负责计算也负责存储一部分数据。这种架构简单高效在数据量和查询模式相对固定的场景下表现优异。但其缺点也很明显扩容不灵活扩容存储必须同时扩容计算资源成本高。存储成本高多副本通常3副本带来巨大的存储开销。难以支持云原生无法很好地利用云上对象存储如S3、OSS这种廉价、无限扩展的存储服务。Doris 3.0引入了真正的存算分离架构计算层Compute Node无状态只负责执行查询计算任务。可以根据查询负载快速弹性伸缩。存储层Storage/Cloud数据持久化存储在远程对象存储如S3或HDFS中。Doris自身只管理数据的元数据Data Catalog并利用本地磁盘或SSD作为缓存层Cache加速热数据的访问。元数据与协调层Frontend负责接收查询、解析SQL、生成执行计划并调度计算节点。这一变化带来的直接好处极致弹性计算资源可以像在云上启动一个容器组一样快速扩缩容完美应对波峰波谷的查询压力。存储则可以独立地、近乎无限地扩展。成本大幅降低数据只需在廉价的对象存储上存一份或结合纠删码存一份计算节点本地无需持久化存储整体TCO总拥有成本显著下降。部署运维简化计算节点无状态故障后可以快速重建系统可用性更高。注意事项存算分离并非银弹。它的性能极度依赖缓存命中率和网络带宽。如果查询总是要访问对象存储上的冷数据延迟会比存算一体高。因此合理设置缓存策略、预加载热数据、优化数据布局如分区、分桶至关重要。Doris 3.0提供了智能冷热数据分层和缓存管理功能需要根据业务访问模式仔细调优。3.2 数据湖联邦分析打破数据孤岛这是Doris 3.0另一个杀手级特性。它不再试图把所有数据都“吞进来”管理而是可以作为一个统一的查询引擎去直接分析存储在外部数据湖如Apache Hudi、Iceberg、Delta Lake乃至传统Hive表中的数据。工作原理创建外部表在Doris中创建一个CREATE EXTERNAL TABLE指向数据湖中某个表的元数据位置如HMS或AWS Glue。直接查询用户可以直接使用标准的SQL查询这个外部表就像查询Doris内部表一样。查询下推Doris的查询优化器会尽可能将过滤条件WHERE、投影SELECT列等下推到数据湖的存储层只拉取需要的数据减少网络传输和计算开销。无缝关联你甚至可以将Doris内部表存放实时热数据与数据湖外部表存放历史冷数据或维度数据进行关联查询实现数据的“热温冷”一体化分析。这一特性如何解决“分库分表”的噩梦想象这样一个场景你的订单核心交易库还是MySQL分库分表但通过CDC工具将增量数据实时写入Apache Hudi表存储在对象存储上。同时Doris 3.0通过外部表功能直接挂载这张Hudi表。对于实时查询你可以将最近几小时的热数据通过INSERT INTO SELECT同步到Doris内部表存算分离享受亚秒级的查询体验。对于历史分析或全量扫描你的查询可以直接指向Hudi外部表。虽然第一次查询冷数据可能稍慢需要从对象存储读取但Doris的缓存机制会让后续查询变快。对于混合查询你可以轻松关联Doris内部的热数据和Hudi外部的历史数据得到统一的分析视图。这样一来你完全不需要再为了分析去费力地合并那些分库分表的MySQL数据。数据在写入Hudi时就已经是合并后的、分析友好的格式列存如Parquet。Doris充当了那个统一的、强大的查询大脑。原有的分库分表MySQL只需安心处理高并发交易分析的压力全部卸载到了Doris数据湖的架构上。4. 实操指南基于Doris 3.0构建实时数仓链路理论说再多不如动手搭一遍。下面我将以一个经典的“电商实时数仓”场景为例拆解如何利用Doris 3.0构建从业务库到实时分析的完整链路。我们假设源端是一个按user_id哈希分片的MySQL订单库。4.1 环境准备与集群部署1. 资源规划FE节点3个高可用建议4C8G配置主要消耗内存存储元数据。BE/CN节点在存算分离架构下我们主要部署计算节点CN。初始建议2-4个配置8C16G起步。后期根据查询压力弹性增加。对象存储准备一个S3兼容的存储桶如阿里云OSS、腾讯云COS用于持久化数据。缓存盘每个CN节点需要挂载一块高性能的本地SSD或ESSD容量建议为热数据总量的1-2倍用作缓存。2. 部署Doris 3.0集群推荐使用官方提供的Doris Manager或基于Kubernetes的Operator进行部署这比手动部署省心得多。关键配置在于fe.conf和cn.conf中关于对象存储和缓存的设置。# 以CN节点配置为例 (cn.conf) # 指定对象存储为OSS cloud_unique_id your_cluster_id aws_s3_path oss://your-bucket/doris-data/ aws_s3_endpoint oss-cn-hangzhou-internal.aliyuncs.com aws_s3_access_key your_access_key aws_s3_secret_key your_secret_key # 配置本地缓存 file_cache_path /data/doris/cache # 本地缓存目录 file_cache_total_size 536870912000 # 缓存总大小500GB3. 创建数据源与数据库通过MySQL客户端连接Doris FE执行以下SQL-- 创建用于接收实时数据的数据库 CREATE DATABASE IF NOT EXISTS realtime_dw; USE realtime_dw; -- 创建资源用于访问外部数据湖如Hudi CREATE RESOURCE hudi_resource PROPERTIES ( type hudi, hive.metastore.uris thrift://hive-metastore:9083 );4.2 实时数据接入从MySQL分库分表到Doris这是最关键的一步目标是屏蔽源端分库分表的复杂性让数据以合并后的、分析友好的形态进入Doris。方案选择Flink CDC Doris Connector这是目前最主流和成熟的方案。Flink CDC可以直接捕获MySQL分库分表的变更数据并在Flink内部进行合并根据主键然后通过flink-doris-connector写入Doris。1. 准备Flink SQL作业-- 1. 创建MySQL分库分表的CDC源表以两个分库为例 CREATE TABLE mysql_orders_0 ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, create_time TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( connector mysql-cdc, hostname mysql-host-0, port 3306, username flink_user, password flink_pwd, database-name order_db_0, table-name orders, server-id 5400-5401 -- 确保每个源有唯一server-id范围 ); CREATE TABLE mysql_orders_1 ( ... -- 结构同mysql_orders_0 ) WITH ( connector mysql-cdc, hostname mysql-host-1, database-name order_db_1, server-id 5402-5403 ); -- 2. 创建Doris目标表采用明细模型支持实时更新 CREATE TABLE doris_orders ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, create_time TIMESTAMP(3) ) WITH ( connector doris, fenodes fe1:8030,fe2:8030,fe3:8030, table.identifier realtime_dw.orders, username doris_user, password doris_pwd, sink.properties.strip_outer_array true, sink.properties.format json ); -- 3. 将两个分库的数据合并后写入Doris INSERT INTO doris_orders SELECT * FROM mysql_orders_0 UNION ALL SELECT * FROM mysql_orders_1;2. 关键配置与优化主键与模型Doris目标表采用Unique Key或Duplicate Key模型。对于订单这种有状态变更的数据使用Unique Key模型指定order_id为主键Doris会在内部进行Upsert实现实时更新完美同步MySQL的UPDATE和DELETE操作。批量提交在Flink Connector中配置sink.batch.size和sink.batch.interval平衡写入吞吐和端到端延迟。通常设置batch.size1000,interval1s是个不错的起点。自动分区在Doris中创建表时可以按时间字段如create_time进行动态分区例如PARTITION BY RANGE(create_time)() DISTRIBUTED BY HASH(user_id) BUCKETS 10。这样数据会自动按天分区方便管理和过期删除。实操心得在Flink作业启动初期如果历史数据量很大建议先关闭CDC流用DataX或Spark等批处理工具做一次全量初始化同步到Doris然后再开启CDC同步增量。这样可以避免Flink作业因追赶Catch-up历史数据而内存溢出。另外务必监控Doris BE/CN的写盘I/O和内存使用情况避免写入过快打满缓存。4.3 数据湖联邦查询实践假设我们将超过30天的历史订单数据从Doris内部表归档到Apache Hudi数据湖这是一个常见的冷热分离策略并希望查询能无缝跨越热数据和冷数据。1. 在Hudi中创建历史订单表略通过Spark或Flink写入。2. 在Doris中创建Hudi外部表USE realtime_dw; CREATE EXTERNAL TABLE hudi_historical_orders ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, create_time TIMESTAMP ) ENGINEHUDI PROPERTIES ( resource hudi_resource, table default.historical_orders, -- Hive Metastore中的表名 database datalake );3. 执行联邦查询现在我们可以轻松地查询最近7天的热数据在Doris内部表和更早的历史数据在Hudi外部表。-- 查询某个用户的所有订单自动关联热数据和冷数据 SELECT user_id, SUM(amount) as total_amount, COUNT(*) as order_count FROM ( SELECT user_id, amount FROM orders WHERE create_time DATE_SUB(NOW(), INTERVAL 7 DAY) -- 查询Doris内部表 UNION ALL SELECT user_id, amount FROM hudi_historical_orders WHERE create_time DATE_SUB(NOW(), INTERVAL 7 DAY) -- 查询Hudi外部表 ) t WHERE user_id 123456 GROUP BY user_id; -- 更复杂的例子实时大盘GMV包含今天实时数据和昨天之前的历史数据 SELECT DATE(create_time) as dt, SUM(amount) as daily_gmv FROM ( SELECT create_time, amount FROM orders WHERE create_time CURDATE() -- 今日实时数据 UNION ALL SELECT create_time, amount FROM hudi_historical_orders WHERE create_time CURDATE() -- 历史数据 ) t GROUP BY dt ORDER BY dt DESC;Doris查询优化器会自动处理这些联邦查询它会分别从Doris内部存储和Hudi外部存储读取数据在计算节点上进行合并计算。对于Hudi表的查询过滤条件下推等功能会显著减少数据读取量。5. 面向AI场景的优化与进阶特性Doris 3.0不仅在架构上革新也加入了许多针对AI时代数据分析需求的特性。5.1 向量化引擎与半结构化数据分析AI模型的特征数据常常是复杂的半结构化数据如JSON、Array、Map等。传统数仓处理这些数据非常吃力。1. 原生的半结构化数据类型支持Doris 3.0对JSON、Array、Map等类型提供了原生支持并为其实现了向量化计算。这意味着你可以在SQL中直接解析和查询JSON字段性能远超之前用字符串存储UDF解析的方式。-- 假设订单表有一个JSON字段extra_info存储商品标签和用户特征 CREATE TABLE orders_with_features ( order_id BIGINT, user_id BIGINT, extra_info JSON, -- 直接使用JSON类型 ... ); -- 直接查询JSON内的数组和字段 SELECT order_id, extra_info-$.product_tags as tags, -- 提取商品标签数组 extra_info-$.user_feature.city as city -- 提取嵌套的用户城市 FROM orders_with_features WHERE JSON_CONTAINS(extra_info-$.product_tags, discount); -- 查询包含特定标签的订单2. 向量化计算加速对于Array类型的特征列例如一个用户最近点击的100个商品ID数组Doris的向量化引擎可以高效地进行元素级的运算这对于特征工程前的数据准备至关重要。5.2 与AI生态的集成1. 作为AI特征库实时训练和推理需要快速读取特征。Doris的高并发点查能力通过PRIMARY KEY模型或Duplicate Key模型配合Enable Unique Key Merge-On-Write使其非常适合作为在线特征库。AI训练框架如TensorFlow, PyTorch或在线推理服务可以通过JDBC/ODBC接口以极低的延迟毫秒级从Doris中拉取实时更新的用户特征向量。2. 通过UDF支持模型推理虽然Doris本身不擅长运行复杂的AI模型但它可以通过用户自定义函数UDF机制调用外部服务。例如你可以编写一个UDF将Doris中查询出的数据发送给部署在旁路的TensorFlow Serving或PyTorch Serving进行模型推理然后将结果返回给Doris进行后续处理或展示。这实现了“SQLAI”的闭环。5.3 物化视图与预聚合对于AI场景中常见的固定模式聚合查询如实时大盘指标、用户分群统计Doris的异步物化视图Async Materialized View功能非常有用。它可以自动将明细数据预聚合成所需维度查询时直接命中物化视图速度极快。-- 为实时GMV仪表盘创建物化视图 CREATE MATERIALIZED VIEW mv_daily_gmv BUILD IMMEDIATE REFRESH ASYNC AS SELECT DATE(create_time) as dt, user_id, SUM(amount) as daily_amount, COUNT(*) as order_count FROM orders GROUP BY dt, user_id; -- 后续查询每日用户GMV时会自动路由到物化视图速度提升百倍 SELECT dt, SUM(daily_amount) FROM mv_daily_gmv GROUP BY dt;6. 常见问题与性能调优实录在实际生产环境中迁移到Doris 3.0一定会遇到各种问题。下面是我总结的一些典型坑点和调优经验。6.1 写入性能瓶颈问题现象Flink写入Doris的吞吐上不去作业出现反压或者Doris BE/CN节点写盘I/O或CPU持续高位。排查与解决检查批次配置首先调整Flink Doris Connector的sink.batch.size和sink.batch.interval。不是越大越好。过大的批次会导致Doris单次写入事务过大内存占用高可能触发写入失败。建议从batch.size1000, interval1s开始逐步调大观察Doris监控中的write_bytes_per_second和write_rows_per_second。观察Doris节点负载使用Doris的Web UI或SHOW PROC /backends命令查看各个BE/CN节点的LastStreamLoadTime和LastWriteTime。如果某些节点时间戳明显滞后说明出现了写入倾斜。解决写入倾斜调整分桶数创建表时DISTRIBUTED BY HASH(key) BUCKETS X分桶数X建议是BE/CN节点数的整数倍如8-16倍。分桶数太少会导致数据分布不均太多会增加元数据开销。可以尝试增加分桶数。检查分桶键确保分桶键通常是查询常用的过滤字段如user_id本身是离散的。如果分桶键是status这种枚举值很少的字段必然导致数据倾斜。启用并行导入在Flink Connector中设置sink.parallelism让多个Flink任务并行写入可以提升整体吞吐。6.2 查询速度慢特别是联邦查询问题现象查询Hudi外部表或跨冷热数据查询响应时间很长。排查与解决确认缓存命中检查Doris监控中对应表的缓存命中率。首次查询冷数据必然慢。可以手动执行ADMIN SET REPLICA STATUS PROPERTIES(tablet_id 10001, backend_id 10001, status ok);来预热缓存或者设置更激进的缓存策略。分析查询计划使用EXPLAIN命令查看SQL的执行计划。重点关注是否有PREDICATES下推到了Hudi外部表如果没有可能是Hudi表统计信息过期需要ANALYZE TABLE更新统计信息。执行计划中是否有CROSS JOIN联邦查询中如果关联条件没写好容易产生笛卡尔积性能灾难。优化Hudi表本身确保Hudi表使用了合适的文件格式Parquet并进行了分区如按天分区。小文件过多会严重影响查询性能需要定期使用Hudi的compaction和clustering服务合并小文件。增加计算资源对于复杂的联邦分析查询临时增加CN节点数量是最直接有效的办法这正是存算分离架构的优势。6.3 内存不足错误Out of Memory问题现象执行复杂聚合或排序查询时Doris返回Memory limit exceeded错误。排查与解决设置查询内存限制在会话级别或全局设置set exec_mem_limitxxxx;为单个查询分配更多内存。优化SQL这是根本解决之道。避免SELECT *只查询需要的列。尽早过滤把WHERE条件写得尽可能精确在扫描层就过滤掉大量数据。谨慎使用DISTINCT和ORDER BY全量去重和排序非常耗内存。考虑是否可以用近似去重如APPROX_COUNT_DISTINCT或带LIMIT的排序。拆分复杂SQL将多层嵌套的子查询或巨大的UNION ALL拆分成多个中间结果落地的步骤。检查数据分布如果GROUP BY的字段基数不同值的数量非常大会导致聚合中间状态膨胀。考虑是否真的需要这么细的粒度。6.4 元数据管理问题在存算分离架构下FE节点负责元数据管理压力会增大。问题现象创建表、删除分区等DDL操作变慢或FE节点内存持续增长。解决建议控制分区数量避免创建过多过细的分区例如按小时分区。对于历史冷数据可以考虑使用ROLLUP将多个分区合并成更大的分区。定期清理过期数据使用ALTER TABLE table_name DROP PARTITION及时删除过期分区减少元数据量。监控FE JVM确保FE节点有足够的堆内存建议8GB并监控Full GC情况。从我个人的实践经验来看从分库分表架构迁移到以Doris 3.0为核心的实时数仓最大的挑战往往不是技术本身而是思维模式的转变。团队需要从“如何把数据库拆开”的思维转向“如何把数据高效地合起来并快速分析”的思维。这个过程伴随着数据链路的重构、开发习惯的变更和运维工具的更新。但一旦趟过这条河你会发现数据不再是一座座孤岛而是一片可以任你实时探索和挖掘的海洋。那些曾经为了一个跨分片查询而绞尽脑汁的深夜那些因为数据延迟而被业务方催促的焦虑都将随着一个统一、实时、高效的数据基座的建立而成为历史。Doris 3.0提供的存算分离和数据湖联邦能力正是打造这个基座的关键拼图。
返回列表