霸王餐用户行为埋点:Kafka Connect+ClickHouse实时OLAP分析

霸王餐用户行为埋点:Kafka Connect+ClickHouse实时OLAP分析

整体架构概述

"吃喝不愁"App需对用户点击、下单、分享等行为进行毫秒级追踪与分析,以支撑运营决策与推荐策略。系统采用三层架构:

  1. 前端/客户端:通过 SDK 上报 JSON 格式埋点事件至 Kafka;
  2. Kafka Connect:使用官方或自研 Sink Connector 将消息写入 ClickHouse;
  3. ClickHouse:提供亚秒级 OLAP 查询能力。

该方案避免了传统批处理延迟,实现端到端秒级数据可见。

埋点事件格式定义

每条事件包含公共字段与业务字段:

json 复制代码
{
  "event_id": "evt_123456",
  "user_id": "u_7890",
  "event_type": "click_claim_button",
  "timestamp": 1717020800000,
  "app_version": "2.3.1",
  "device_id": "d_abc123",
  "properties": {
    "restaurant_id": "r_456",
    "campaign_id": "camp_789"
  }
}

ClickHouse 表结构设计

使用 MergeTree 引擎,按日期分区,按用户ID和事件类型排序:

sql 复制代码
CREATE TABLE eatfree.user_events (
    event_id String,
    user_id String,
    event_type LowCardinality(String),
    timestamp DateTime64(3),
    app_version String,
    device_id String,
    restaurant_id String,
    campaign_id String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (user_id, event_type, timestamp)
SETTINGS index_granularity = 8192;

Kafka Connect Sink 配置

使用开源 clickhouse-kafka-connect 插件,配置如下:

properties 复制代码
name=eatfree-clickhouse-sink
connector.class=com.clickhouse.kafka.connect.ClickHouseSinkConnector
tasks.max=3
topics=user_events

key.converter=org.apache.kafka.connect.storage.StringConverter
value.converter=org.apache.kafka.connect.json.JsonConverter
value.converter.schemas.enable=false

clickhouse.server.url=http://clickhouse:8123
clickhouse.database=eatfree
clickhouse.table=user_events
clickhouse.username=default
clickhouse.password=

batch.size=1000
linger.ms=1000

部署后,Kafka 中 user_events 主题的消息将自动批量写入 ClickHouse。

Java 埋点上报示例(SDK 端)

java 复制代码
package juwatech.cn.tracking;

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class EventTracker {

    private final KafkaProducer<String, String> producer;
    private static final String TOPIC = "user_events";

    public EventTracker() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "kafka:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        this.producer = new KafkaProducer<>(props);
    }

    public void track(String userId, String eventType, String propertiesJson) {
        long ts = System.currentTimeMillis();
        String eventId = "evt_" + ts + "_" + userId.hashCode();
        String payload = String.format(
            "{\"event_id\":\"%s\",\"user_id\":\"%s\",\"event_type\":\"%s\",\"timestamp\":%d,\"app_version\":\"2.3.1\",\"device_id\":\"d_abc123\",%s}",
            eventId, userId, eventType, ts, propertiesJson.substring(1)
        );
        ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, userId, payload);
        producer.send(record, (metadata, exception) -> {
            if (exception != null) {
                System.err.println("Failed to send event: " + exception.getMessage());
            }
        });
    }

    public void close() {
        producer.close();
    }
}

调用示例:

java 复制代码
EventTracker tracker = new EventTracker();
tracker.track("u_7890", "click_claim_button", 
    "{\"restaurant_id\":\"r_456\",\"campaign_id\":\"camp_789\"}");

实时查询场景示例

每小时霸王餐领取次数:

sql 复制代码
SELECT
    toStartOfHour(timestamp) AS hour,
    count(*) AS claim_count
FROM eatfree.user_events
WHERE event_type = 'click_claim_button'
  AND timestamp >= now() - INTERVAL 24 HOUR
GROUP BY hour
ORDER BY hour DESC;

活跃用户地域分布(假设 device_id 关联 IP 地理库):

sql 复制代码
SELECT
    multiIf(
        startsWith(device_id, 'BJ'), 'Beijing',
        startsWith(device_id, 'SH'), 'Shanghai',
        'Other'
    ) AS region,
    uniq(user_id) AS dau
FROM eatfree.user_events
WHERE timestamp >= today()
GROUP BY region;

容错与性能保障

  • Kafka 设置副本数 ≥ 2,确保消息不丢失;
  • ClickHouse 使用 ReplicatedMergeTree 实现集群高可用;
  • Kafka Connect 启用 exactly.once 语义(需 broker 支持)避免重复写入;
  • 对高频事件(如曝光)可采样上报,降低吞吐压力。

扩展:自定义 Transformer(可选)

若需在 Connect 层清洗数据,可实现 Transformation 接口:

java 复制代码
package juwatech.cn.connect;

import org.apache.kafka.connect.connector.ConnectRecord;
import org.apache.kafka.connect.transforms.Transformation;
import java.util.Map;

public class EnrichEventTransformer<R extends ConnectRecord<R>> implements Transformation<R> {

    @Override
    public void configure(Map<String, ?> configs) {}

    @Override
    public R apply(R record) {
        // 示例:添加处理时间字段
        // 实际中可解析 JSON 并注入新字段
        return record;
    }

    @Override
    public void close() {}
}

注册后在 connector 配置中启用:

properties 复制代码
transforms=enrich
transforms.enrich.type=juwatech.cn.connect.EnrichEventTransformer

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

相关推荐
徐先生 @_@|||17 小时前
Spark DataFrame常见的Transformation和Actions详解
大数据·分布式·spark
Gofarlic_oms117 小时前
通过Kisssoft API接口实现许可证管理自动化集成
大数据·运维·人工智能·分布式·架构·自动化
what丶k18 小时前
深度解析:以Kafka为例,消息队列消费幂等性的实现方案与生产实践
java·数据结构·kafka
走遍西兰花.jpg19 小时前
spark配置
大数据·分布式·spark
hellojackjiang201119 小时前
如何保障分布式IM聊天系统的消息可靠性(即消息不丢)
分布式·网络安全·架构·信息与通信
BYSJMG20 小时前
计算机毕业设计选题推荐:基于Hadoop的城市交通数据可视化系统
大数据·vue.js·hadoop·分布式·后端·信息可视化·课程设计
liux352820 小时前
Kafka 4.1.1 部署指南:单机版与安全认证配置
安全·kafka·linq
一只大袋鼠20 小时前
分布式 ID 生成:雪花算法原理、实现与 MyBatis-Plus 实战
分布式·算法·mybatis
三水不滴21 小时前
对比一下RabbitMQ和RocketMQ
经验分享·笔记·分布式·rabbitmq·rocketmq
麦兜*21 小时前
深入解析分布式数据库TiDB核心架构:基于Raft一致性协议与HTAP混合负载实现金融级高可用与实时分析的工程实践
数据库·分布式·tidb