欢迎光临
我们一直在努力

Doris与Kafka/Flink实时集成:Routine Load、Stream Load与实时数仓链路

  • 引言
  • Apache Doris(原名Palo)是一个现代化的MPP分析型数据库,具备高并发、低延迟的查询性能。在现代数据架构中,实时数据集成是构建高效数据仓库的关键环节。Doris通过与Kafka和Flink的集成,能够实现实时数据的无缝接入与处理,为企业提供实时的数据分析能力。本文将详细介绍Doris通过Routine Load和Stream Load两种方式与Kafka集成的实现方法,并结合Flink构建完整的实时数仓链路。

  • Routine Load 详细介绍与实现
  • Routine Load是Doris提供的一种持续从消息队列(如Kafka)拉取数据并导入的方式,特别适合处理持续不断的数据流。

    2.1 工作原理

    Routine Load通过创建一个异步任务,定期从Kafka中读取数据并导入到Doris表中。该任务会持续运行,直到被手动停止。Doris使用一个名为"broker"的组件连接到Kafka集群,获取数据并导入。

    2.2 适用场景

    Routine Load适用于以下场景:

    • 持续不断的数据流导入
    • 数据量较大但实时性要求不是最高的场景
    • 能够容忍少量数据延迟的场景
    • 无需频繁启动/停止导入任务的情况

    2.3 实现步骤

    实现Routine Load的基本步骤如下:

  • 在Doris中创建导入任务:
  • CREATE ROUTINE LOAD example_db.example_load ON TABLE example_table
    COLUMNS(k1, k2, k3),
    PROPERTIES (
    "format" = "json",
    "strip_outer_array" = "true"
    )
    FROM KAFKA (
    "kafka_broker" = "kafka1:9092,kafka2:9092",
    "kafka_topic" = "example_topic",
    "property.group.id" = "example_group"
    );

  • 查看任务状态:
  • SHOW ROUTINE LOAD WHERE NAME = 'example_load';

  • 暂停任务:
  • PAUSE ROUTINE LOAD FOR example_db.example_load;

  • 恢复任务:
  • RESUME ROUTINE LOAD FOR example_db.example_load;

  • 停止任务:
  • STOP ROUTINE LOAD FOR example_db.example_load;

    2.4 优缺点分析

    特点描述
    优点 1. 持续运行,无需手动干预<br>2. 支持高吞吐量数据导入<br>3. 自动重试机制,保证数据可靠性<br>4. 支持数据过滤和转换
    缺点 1. 启动和停止操作较为复杂<br>2. 无法精确控制导入时间点<br>3. 任务状态管理较为繁琐<br>4. 配置参数较多,调试成本高
  • Stream Load 详细介绍与实现
  • Stream Load是Doris提供的一种通过HTTP协议导入数据的方式,适合需要手动触发或通过程序控制的数据导入场景。

    3.1 工作原理

    Stream Load通过发送HTTP请求将数据文件导入到Doris表中。客户端可以构造包含数据内容的HTTP请求,Doris收到请求后解析数据并导入。这种方式不需要额外的服务组件,直接通过Doris的HTTP服务完成导入。

    3.2 适用场景

    Stream Load适用于以下场景:

    • 需要精确控制导入时间的场景
    • 数据量相对较小但要求实时性高的场景
    • 程序化控制导入过程的场景
    • 需要快速验证数据正确性的场景

    3.3 实现步骤

    实现Stream Load的基本步骤如下:

  • 使用curl命令导入数据:
  • curl –location-trusted -u user:password -H "label:stream_load_20230601" -H "Content-Type: text/plain" -T data.txt http://doris_host:8030/api/example_db/example_table/_stream_load

    参数说明:

    • user:password:Doris认证信息
    • label:导入任务标签,用于唯一标识一次导入
    • Content-Type:数据格式,支持多种格式
    • data.txt:包含数据的文件
    • http://doris_host:8030/api/example_db/example_table/_stream_load:Stream Load API地址
  • 程序化实现(Python示例):
  • import requests
    def stream_load_to_doris(data, doris_url, user, password, table):
    headers = {
    'label': 'stream_load_' + str(int(time.time())),
    'Content-Type': 'text/plain'
    }
    response = requests.post(
    f'http://{doris_url}/api/{table}',
    auth=(user, password),
    headers=headers,
    data=data
    )
    return response.json()
    # 使用示例
    data = "1,example1,2023-06-01\\n2,example2,2023-06-01"
    result = stream_load_to_doris(data, 'doris_host:8030', 'user', 'password', 'example_db.example_table')
    print(result)

    3.4 优缺点分析

    特点描述
    优点 1. 实现简单,无需额外组件<br>2. 可精确控制导入时机<br>3. 支持各种编程语言调用<br>4. 导入状态反馈及时
    缺点 1. 需要客户端构造数据<br>2. 不适合持续不断的数据流<br>3. 大数据量导入性能较差<br>4. 需要处理网络异常情况
  • 实时数仓链路构建
  • 结合Routine Load/Stream Load与Flink,可以构建完整的实时数仓链路。下面通过mermaid流程图展示整体架构:

    #publish-mermaid-0-1790499718749{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-0-1790499718749 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-0-1790499718749 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-0-1790499718749 .error-icon{fill:#552222;}#publish-mermaid-0-1790499718749 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-0-1790499718749 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-0-1790499718749 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-0-1790499718749 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-0-1790499718749 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-0-1790499718749 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-0-1790499718749 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-0-1790499718749 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-0-1790499718749 .marker.cross{stroke:#333333;}#publish-mermaid-0-1790499718749 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-0-1790499718749 p{margin:0;}#publish-mermaid-0-1790499718749 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-0-1790499718749 .cluster-label text{fill:#333;}#publish-mermaid-0-1790499718749 .cluster-label span{color:#333;}#publish-mermaid-0-1790499718749 .cluster-label span p{background-color:transparent;}#publish-mermaid-0-1790499718749 .label text,#publish-mermaid-0-1790499718749 span{fill:#333;color:#333;}#publish-mermaid-0-1790499718749 .node rect,#publish-mermaid-0-1790499718749 .node circle,#publish-mermaid-0-1790499718749 .node ellipse,#publish-mermaid-0-1790499718749 .node polygon,#publish-mermaid-0-1790499718749 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-0-1790499718749 .rough-node .label text,#publish-mermaid-0-1790499718749 .node .label text,#publish-mermaid-0-1790499718749 .image-shape .label,#publish-mermaid-0-1790499718749 .icon-shape .label{text-anchor:middle;}#publish-mermaid-0-1790499718749 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-0-1790499718749 .rough-node .label,#publish-mermaid-0-1790499718749 .node .label,#publish-mermaid-0-1790499718749 .image-shape .label,#publish-mermaid-0-1790499718749 .icon-shape .label{text-align:center;}#publish-mermaid-0-1790499718749 .node.clickable{cursor:pointer;}#publish-mermaid-0-1790499718749 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-0-1790499718749 .arrowheadPath{fill:#333333;}#publish-mermaid-0-1790499718749 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-0-1790499718749 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-0-1790499718749 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-0-1790499718749 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-0-1790499718749 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-0-1790499718749 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-0-1790499718749 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-0-1790499718749 .cluster text{fill:#333;}#publish-mermaid-0-1790499718749 .cluster span{color:#333;}#publish-mermaid-0-1790499718749 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-0-1790499718749 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-0-1790499718749 rect.text{fill:none;stroke-width:0;}#publish-mermaid-0-1790499718749 .icon-shape,#publish-mermaid-0-1790499718749 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-0-1790499718749 .icon-shape p,#publish-mermaid-0-1790499718749 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-0-1790499718749 .icon-shape .label rect,#publish-mermaid-0-1790499718749 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-0-1790499718749 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-0-1790499718749 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-0-1790499718749 .node .neo-node{stroke:#9370DB;}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node rect,#publish-mermaid-0-1790499718749 [data-look=\”neo\”].cluster rect,#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1790499718749 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1790499718749 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}Stream Load方式Routine Load方式Flink处理

    数据接入

    实时计算

    结果输出

    数据源

    Kafka

    Flink处理

    数据清洗/转换

    Doris存储

    实时查询

    Doris Routine Load任务

    客户端程序

    Stream Load API

    构建实时数仓链路的步骤如下:

  • 数据接入层:使用Kafka作为消息队列,接收来自业务系统的实时数据
  • 实时处理层:通过Flink从Kafka读取数据,进行清洗、转换和聚合处理
  • 数据存储层:处理后的数据通过Routine Load或Stream Load导入Doris
  • 查询服务层:通过Doris提供高性能的实时查询服务
  • 一个完整的Flink处理示例:

    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
    import org.apache.flink.streaming.util.serialization.SimpleStringSchema;
    public class DorisRealtimeETL {
    public static void main(String[] args) throws Exception {
    // 创建执行环境
    final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

    // 配置Kafka消费者
    properties.setProperty("bootstrap.servers", "kafka1:9092,kafka2:9092");
    properties.setProperty("group.id", "doris_etl");

    FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer<>(
    "example_topic",
    new SimpleStringSchema(),
    properties
    );

    // 添加数据源
    DataStream<String> stream = env.addSource(kafkaSource);

    // 数据处理逻辑(示例:简单解析和转换)
    DataStream<String> processedStream = stream.map(value -> {
    // 解析JSON数据并进行转换
    // 这里只是示例,实际应根据业务需求编写处理逻辑
    return processAndTransform(value);
    });

    // 输出到Doris(通过自定义Sink或调用Stream Load API)
    processedStream.addSink(new DorisSink());

    // 执行任务
    env.execute("Doris Realtime ETL");
    }

    private static String processAndTransform(String input) {
    // 数据处理逻辑
    return transformedData;
    }
    }

  • 完整示例与注意事项
  • 5.1 最小示例

    以下是一个完整的Doris与Kafka/Flink集成的最小示例:

  • 创建Doris表:
  • CREATE TABLE example_db.user_events (
    user_id BIGINT,
    event_type VARCHAR(32),
    event_time DATETIME,
    event_data VARCHAR(1024)
    ) ENGINE=OLAP
    PRIMARY KEY(user_id, event_time)
    DISTRIBUTED BY HASH(user_id) BUCKETS 10;

  • 创建Routine Load任务:
  • CREATE ROUTINE LOAD example_db.user_events_load ON TABLE example_db.user_events
    COLUMNS(user_id, event_type, event_time, event_data),
    PROPERTIES (
    "format" = "json",
    "jsonpaths" = "$.user_id, $.event_type, $.event_time, $.event_data"
    )
    FROM KAFKA (
    "kafka_broker" = "kafka1:9092,kafka2:9092",
    "kafka_topic" = "user_events",
    "property.group.id" = "user_events_group"
    );

  • Flink处理代码(简化版):
  • import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
    import org.apache.flink.streaming.util.serialization.JSONKeyValueDeserializationSchema;
    import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.JsonNode;
    public class SimpleFlinkJob {
    public static void main(String[] args) throws Exception {
    final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

    Properties properties = new Properties();
    properties.setProperty("bootstrap.servers", "kafka1:9092,kafka2:9092");
    properties.setProperty("group.id", "user_events_group");

    FlinkKafkaConsumer<JsonNode> kafkaSource = new FlinkKafkaConsumer<>(
    "user_events",
    new JSONKeyValueDeserializationSchema(true),
    properties
    );

    env.addSource(kafkaSource)
    .addSink(new DorisSink("example_db.user_events"));

    env.execute("Simple Flink to Doris");
    }
    }

  • Doris Sink实现(简化版):
  • import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
    import org.apache.flink.streaming.connectors.kafka.KafkaSerializationSchema;
    import org.apache.kafka.clients.producer.ProducerRecord;
    public class DorisSink extends RichSinkFunction<JsonNode> {
    private final String table;
    private transient HttpPost httpPost;

    public DorisSink(String table) {
    this.table = table;
    }

    @Override
    public void open(Configuration parameters) throws Exception {
    // 初始化HTTP请求
    httpPost = new HttpPost("http://doris_host:8030/api/" + table + "/_stream_load");
    httpPost.setHeader("Content-Type", "application/json");
    httpPost.setHeader("Authorization", "Basic " + Base64.encodeBase64String("user:password".getBytes()));
    }

    @Override
    public void invoke(JsonNode value, Context context) throws Exception {
    // 发送数据到Doris
    String requestBody = value.toString();
    httpPost.setEntity(new StringEntity(requestBody));
    try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
    // 处理响应
    EntityUtils.consume(response.getEntity());
    }
    }

    @Override
    public void close() throws Exception {
    if (httpPost != null) {
    httpPost.releaseConnection();
    }
    }
    }

    5.2 注意事项

  • 性能优化:
    • Doris表设计中合理设置分桶数量
    • 针对查询模式优化列存储
    • 批量导入时调整超时参数
  • 数据一致性:
    • 使用唯一的label确保数据不重复
    • 设置合适的重试机制
    • 监控导入失败情况
  • 资源管理:
    • 合理设置Flink并行度
    • 控制Doris导入任务数量
    • 监控系统资源使用情况
  • 错误处理:
    • 实现完善的错误捕获机制
    • 设置合理的重试策略
    • 记录详细的日志信息
  • 安全考虑:
    • 使用HTTPS连接Doris
    • 定期更换认证凭据
    • 实施最小权限原则

    通过以上内容,我们了解了Doris与Kafka/Flink实时集成的实现方法,包括Routine Load和Stream Load两种数据导入方式,以及如何构建完整的实时数仓链路。结合提供的示例代码和注意事项,开发者可以根据实际业务需求快速搭建实时数据集成平台。

    赞(0)
    未经允许不得转载:171主机测评 » Doris与Kafka/Flink实时集成:Routine Load、Stream Load与实时数仓链路
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址