Spring Boot 整合 Apache Doris:实现海量数据实时 OLAP 分析实战
介绍基于 Spring Boot 与 Apache Doris 构建实时 OLAP 分析系统的方案。通过 Kafka 作为消息中间件,利用 Doris Routine Load 自动导入数据,实现海量订单数据的实时写入与秒级查询。涵盖环境搭建、数据模型设计、性能优化及监控可观测性实践,提供 Mermaid 与 PlantUML 架构图辅助理解。

介绍基于 Spring Boot 与 Apache Doris 构建实时 OLAP 分析系统的方案。通过 Kafka 作为消息中间件,利用 Doris Routine Load 自动导入数据,实现海量订单数据的实时写入与秒级查询。涵盖环境搭建、数据模型设计、性能优化及监控可观测性实践,提供 Mermaid 与 PlantUML 架构图辅助理解。

在大数据时代,企业对数据分析的实时性、并发性和灵活性提出了前所未有的要求。传统关系型数据库(如 MySQL)难以应对高吞吐写入与复杂聚合查询的双重压力。为此,Apache Doris 作为一种高性能、低延迟的 MPP(Massively Parallel Processing)实时分析型数据库,正被越来越多企业用于构建现代化数仓系统。
本文将深入讲解如何通过 Spring Boot 框架整合 Apache Doris,打造一套支持海量数据写入与秒级响应 OLAP 查询的完整架构,并提供可直接运行的 流程图代码(Mermaid 格式) 和 原理图代码(PlantUML + ASCII 图形),所有图片均使用不同颜色区分模块功能,便于理解。
Apache Doris 是一个开源的分布式 SQL 数据仓库,原名 Palo,由百度研发并捐赠给 Apache 基金会。它具备以下特性:
🎯 适用场景:用户行为分析、实时报表、日志分析、BI 大屏等。
JDBC/MySQL 协议
客户端/应用
FE: Frontend
SQL 解析与计划生成
元数据管理
查询调度器
BE 节点 1
BE 节点 2
BE 节点 3
本地存储
本地存储
本地存储
🔍 图中颜色说明:橙色 (#f96):外部请求源(Spring Boot 应用)蓝色 (#6fb9e8):FE 节点 —— 控制中枢青绿色 (#4db6ac):核心处理逻辑紫色 (#8d6eaa):元数据服务黄色 (#ffca28):任务分发器浅绿 (#a5d6a7):BE 存储节点(数据分片分布)
我们设计一个典型的'生产 → 消费 → 分析'链路:
展示层
分析引擎
消息中间件
后端服务
前端系统
HTTP POST
JSON Event
Stream
SQL Query
Vue/React
【Spring Boot】
Kafka Cluster
【Apache Doris】
FE Node
BE Node
Grafana / BI Dashboard
🌈 颜色含义:红色系 (#ff7043):前端交互层蓝色系 (#29b6f6):Spring Boot 微服务橙黄 (#ffa726):Kafka 消息队列绿色系 (#66bb6a):Doris 分析引擎天蓝/深青:内部组件细化紫色 (#ab47bc):可视化展示终端
# docker-compose.yml
version: '3'
services:
fe:
image: apache/doris:fe-2.0.3
ports:
- "9030:9030" # MySQL 协议连接
- "8030:8030" # RPC
environment:
- FE_CONFIG=meta_dir=/opt/doris/fe/meta
volumes:
- ./doris/fe:/opt/doris/fe
be:
image: apache/doris:be-2.0.3
depends_on:
- fe
ports:
- "9060:9060"
environment:
- BE_CONFIG=storage_root_path=/opt/doris/be/storage
volumes:
- ./doris/be:/opt/doris/be
启动命令:
docker-compose up -d
⚠️ 注意:首次启动需在 FE 初始化数据库和用户。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
server:
port: 8080
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
datasource:
url: jdbc:mysql://localhost:9030/demo_db?useSSL=false&allowPublicKeyRetrieval=true
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
CREATE DATABASE IF NOT EXISTS demo_db;
USE demo_db;
CREATE TABLE IF NOT EXISTS order_wide (
order_id BIGINT NOT NULL COMMENT "订单 ID",
user_id BIGINT NOT NULL,
product_name VARCHAR(255) NOT NULL,
category VARCHAR(100),
price DECIMAL(10,2) NOT NULL,
quantity INT NOT NULL,
total_amount AS price * quantity STORED COMMENT "衍生字段",
province VARCHAR(50),
city VARCHAR(50),
create_time DATETIME NOT NULL
) ENGINE = OLAP DUPLICATE KEY(order_id)
PARTITION BY RANGE(create_time)(
PARTITION p202401 VALUES LESS THAN ("2024-02-01"),
PARTITION p202402 VALUES LESS THAN ("2024-03-01")
) DISTRIBUTED BY HASH(order_id) BUCKETS 10 PROPERTIES("replication_num"="1","enable_persistent_index"="true");
public class OrderEvent {
private Long orderId;
private Long userId;
private String productName;
private String category;
private BigDecimal price;
private Integer quantity;
private String province;
private String city;
// 构造函数、getter/setter 略...
}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private KafkaTemplate<String, OrderEvent> kafkaTemplate;
@PostMapping
public ResponseEntity<String> create(@RequestBody OrderEvent event) {
event.setCreateTime(LocalDateTime.now());
kafkaTemplate.send("topic_doris_orders", event);
return ResponseEntity.ok("Order sent to Kafka");
}
}
CREATE ROUTINE LOAD demo_db.order_load_job ON order_wide
COLUMNS TERMINATED BY ",",
COLUMNS(order_id, user_id, product_name, category, price, quantity, province, city, create_time),
WHERE create_time IS NOT NULL
PROPERTIES ("desired_concurrent_number"="3","max_batch_interval"="15","max_error_number"="1000","strict_mode"="false")
FROM KAFKA ("kafka_broker_list"="localhost:9092","kafka_topic"="topic_doris_orders","kafka_partitions"="0,1,2","property.kafka_default_offsets"="OFFSET_BEGINNING");
✅ 成功后可通过
SHOW ROUTINE LOAD查看状态。
@Service
public class ReportService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Map<String, Object>> getRevenueByCategory() {
String sql = """
SELECT category, SUM(total_amount) AS revenue FROM order_wide WHERE create_time >= ? GROUP BY category ORDER BY revenue DESC
""";
return jdbcTemplate.queryForList(sql, LocalDateTime.now().minusDays(7));
}
public long getTotalOrders() {
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM order_wide", Long.class);
}
}
⚠️ 生产建议:高频查询使用缓存(Redis)或物化视图加速。
@startuml
skinparam backgroundColor #FEFEFE
skinparam sequenceArrowThickness 2
skinparam sequenceMessageAlign center
actor Developer as dev
database "MySQL Source" as mysql #lightblue
rectangle "Flink CDC" as flink #orange
queue "Kafka" as kafka #yellow
cloud "Doris" as doris #green
component "BI Tool" as bi #purple
dev -> mysql : 启用 Binlog
mysql -> flink : 实时捕获变更
flink -> kafka : 输出 JSON
kafka -> doris : Routine Load
doris -> bi : 提供 API 查询
bi -> dev : 展示报表
note right of doris <<优化点>>
- 物化视图
- 分区剪枝
- 前缀索引
end note
@enduml
📌 此图为高级优化路径,适用于从 MySQL 实时同步至 Doris 的场景。
┌─────────────────────────────────────────────────────┐
│ Grafana 监控大屏 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ QPS 曲线 │ │ 导入延迟 │ │ 磁盘使用率 │ │
│ │ ████░░░ │ │ ██░░░░░ │ │ ██████░░ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ └────────────┬───────────────────────────────┬────────┘ │
│ │ ▼ ▼ ┌─────────────────┐ ┌────────────────────┐ │
│ │ Prometheus │◄─────────┤ Doris │ │
│ │ (指标采集) │ │ [FE]:9030, [BE]:* │
│ └─────────────────┘ └────────────────────┘ ▲ ▲ │
│ └──────────────┬────────────────┘ ▼ ┌────────────────────┐ │
│ │ Spring Boot │ │ /metrics 暴露端点 │ └────────────────────┘ │
└─────────────────────────────────────────────────────┘
🎨 ASCII 图颜色模拟:红色边框:监控面板蓝色块:Prometheus 数据采集绿色背景:Doris 集群黄色图表:趋势可视化(用字符模拟)
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Routine Load 显示 PAUSED | Kafka offset 超出范围 | 设置 "property.kafka_default_offsets" = "OFFSET_END" |
| 查询超时 | BE 资源不足 | 增加 BE 节点或调整 query_timeout |
| JDBC 连接失败 | 用户未授权 | 执行 GRANT SELECT ON TABLE xxx TO 'user'@'%' |
| 数据未更新 | 物化视图未生效 | 检查 rollup 状态 SHOW ALTER TABLE ROLLUP |
本文详细介绍了 Spring Boot 整合 Apache Doris 的全流程实践,涵盖:
✅ 架构设计
✅ 环境部署
✅ 数据建模
✅ 实时写入(Kafka + Routine Load)
✅ OLAP 查询
✅ 性能优化
✅ 可视化监控
并通过 Mermaid、PlantUML、ASCII 图形 提供了多张彩色原理图,帮助开发者快速掌握系统交互逻辑。
💡 建议进阶方向:结合 Flink CDC 实现 MySQL → Doris 实时入湖使用 Doris Manager 或 DevOps 脚本自动化运维引入 Kylin/Doris Bridge 支持多维分析

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online