一、前言
当前公司的大数据实时链路如下图,数据源是MySQL数据库,然后通过Binlog Query的方式消费或者直接客户端采集到Kafka,最终通过基于Spark/Flink实现的批流一体计算引擎处理,最后输出到下游对应的存储。
二、 模型特征架构的演进
2.1 第一代架构
广告业务发展初期,为了提升策略迭代效率,整理出一套通用的特征生产框架,该框架由三部分组成:特征统计、特征推送和特征获取模型训练。如下图所示:
- 客户端以及服务端数据先通过统一服务Sink到HDFS上
- 基于基HDFS数据,统计特定维度的总量、分布等统计类特征并推送到Codis中
- 从Codis中获取特征小时维度模型增量Training,读取HDFS文件进行天级别增量Training
该方案能够满足算法的迭代,但是有以下几个问题 - 由于Server端直接Put本地文件到HDFS上无法做到根据事件时间精准分区,导致数据源不同存在口径问题
- 不可控的小文件、空文件问题
- 数据格式单一,只支持json格式
- 用户使用成本较高,特征抽取需要不断的Coding
- 整个架构扩展性较差
为解决上述问题,我们对第一代架构进行了演进和改善,构建了第二代批流一体架构(另外该架构升级也是笔者在饿了么进行架构升级的演进路线)。
2.2 第二代架构
2.2.1 批流一体平台的构建
首先将数据链路改造为实时架构,将Spark Structured Streaming(下文统一简称SS)与Flink SQL语法统一,同时实现与Flink SQL语法大体上一致的批流一体架构,并且做了一些功能上的增强与优化。
为什么有了Flink还需要支持SS呢?主要有以下几点原因
- Spark生态相对更完善,当然现在Flink也做的非常好了
- 用户使用习惯问题,有些用户对从Spark迁移到Flink没有多大诉求
- SS Micro Batch引擎的抽象做批流统一更加丝滑
- 相比Flink纯内存的计算模型,在延迟不敏感的场景Spark更友好
这里举一个例子,比如批流一体引擎SS与Flink分别创建Kafka table并写入到ClickHouse,语法分别如下
Spark Structured Streaming语法如下
--Spark Structured Streaming
CREATE STREAM spark (
ad_id STRING,
ts STRING,
event_ts as to_timestamp(ts)
) WITH (
'connector' = 'kafka',
'topic' = 'xx',
'properties.bootstrap.servers'='xx',
'properties.group.id'='xx',
'startingOffsets'='earliest',
'eventTimestampField' = 'event_ts',
'watermark' = '60 seconds',
'format'='json'
);
create SINK ck(
ad_id STRING,
ts STRING,
event_ts timestamp
) WITH(
'connector'='jdbc',
'url'='jdbc:clickhouse://host:port/db',
'table-name'='table',
'username'='user',
'password'='pass',
'sink.buffer-flush.max-rows'='10',
'sink.buffer-flush.interval' = '5s',
'sink.parallelism' = '3'
'checkpointLocation'= 'checkpoint_path',
);
insert into ck select * from spark ;
Flink SQL语法如下
CREATE TABLE flink (
ad_id STRING,
ts STRING,
event_ts as to_timestamp(ts)
)
WITH (
'connector' = 'kafka',
'topic' = 'xx',
'properties.bootstrap.servers'='xx',
'properties.group.id'='xx',
'scan.topic-partition-discovery.interval'='300s',
'format' = 'json'
);
CREATE TABLE ck (
ad_id VARCHAR,
ts VARCHAR,
event_ts timestamp(3)
PRIMARY KEY