1. 项目背景与核心挑战
在当今互联网产品运营中,用户行为数据分析已成为驱动业务决策的关键引擎。作为国内领先的年轻人文化社区,B站面临着海量用户行为数据处理的严峻挑战:每天产生数千亿条行为事件,数据增量超过10TB,传统的大数据处理架构已无法满足实时分析和复杂查询的需求。
1.1 传统架构的瓶颈
早期B站采用Spark批处理方案进行用户行为分析,这种架构存在三个致命缺陷:
- 响应延迟高:简单事件分析需要3分钟以上,复杂分析如漏斗计算耗时超过30分钟
- 资源利用率低:固定资源配置无法适应查询复杂度变化,常出现资源浪费或不足
- 并发能力弱:YARN资源调度机制导致高并发查询时产生严重排队现象
1.2 ClickHouse的破局优势
2020年技术团队引入ClickHouse列式数据库后,性能指标获得质的飞跃:
- 查询响应提升98%:90%事件分析在5秒内返回,漏斗分析30秒内完成
- 实时分析能力:支持当天行为数据的即时查询
- 周活跃用户增长300%:性能改善直接带来产品使用率的显著提升
但第一代ClickHouse方案也暴露出新问题:千亿级明细数据存储消耗过大,集群存储利用率长期低于10%,实时数据处理链路复杂且维护成本高昂。
2. 架构演进与核心技术方案
2.1 三代技术架构演进路径
2.1.1 第一代:Spark批处理架构(2019-2020)
graph LR A[用户行为日志] --> B[Spark批处理] B --> C[预聚合模型] C --> D[前端展示]痛点:模型固化、资源僵化、响应迟缓
2.1.2 第二代:ClickHouse明细方案(2020-2021)
graph LR A[Kafka日志流] --> B[Flink实时处理] B --> C[ClickHouse明细存储] C --> D[直接查询分析]突破:实时性提升但存储压力巨大
2.1.3 第三代:Iceberg+ClickHouse混合架构(2021-至今)
graph LR A[原始日志] --> B[Iceberg聚合模型] B --> C[ClickHouse加速层] C --> D[多维分析]创新:存储压缩90%,查询性能再提升60%
2.2 核心技术创新点
2.2.1 通用流量聚合模型
- 采用"公有参数+私有参数"的混合存储方案
- 私有参数使用Array+Map结构动态存储
- 数据压缩率高达10:1(千亿→百亿)
2.2.2 BulkLoad数据导入
- Spark生成ClickHouse格式的data part文件
- 通过改造的FETCH语法从HDFS拉取数据
- 最终演进为直连ClickHouse的DataReceive服务
# 数据导入伪代码示例 def bulk_load(): spark_job = generate_clickhouse_parts() upload_to_hdfs(spark_job.output) execute_sql("ALTER TABLE...FETCH PARTITION...")2.2.3 智能重平衡算法
- 平衡度计算:基于变异系数(0-1范围)
- 两种平衡策略:
- 装箱算法:追求极致均衡(Best Fit+AVL树)
- 贪心算法:最小迁移成本实现较优均衡
3. 关键模块实现细节
3.1 事件分析优化方案
3.1.1 数据模型设计
CREATE TABLE polaris_dwb_flow_ubt ( dt Date, buvid UInt64, event_id String, pub_params Map(String,String), priv_params Array(String), metrics Map(String,Float64) ) ENGINE = ReplicatedReplacingMergeTree() PARTITION BY dt ORDER BY (buvid, event_id) SAMPLE BY murmurHash3_64(buvid)3.1.2 查询加速技巧
- 利用SAMPLE BY实现高效抽样
- 自定义UDF解析Map结构比原生函数快30%
- 布隆过滤器加速Array/Map字段查询
3.2 漏斗路径分析实现
3.2.1 漏斗分析优化
-- 优化后的下推查询示例 SELECT level, countMerge(uv) AS uv FROM cluster(default, SELECT windowFunnel(3600)(dt, event_id='login', event_id='pay' ) AS level, countState() AS uv FROM local_table WHERE dt BETWEEN '2023-01-01' AND '2023-01-07' GROUP BY level ) GROUP BY level3.2.2 路径分析创新
- 采用RoaringBitmap压缩用户集合
- 桑基图生成算法优化:
- 干扰事件过滤
- 路径补位拼接
- 权重阈值裁剪
3.3 人群圈选系统
3.3.1 属性字典服务
- 三级缓存架构:
- 内存缓存(Guava Cache)
- Redis集群
- 自研RockDB存储
- 雪花算法变种实现50万QPS编码能力
3.3.2 标签计算示例
-- 最近7天观看科技区且点赞的用户 WITH tech_viewers AS ( SELECT user_id FROM behavior_events WHERE event='view' AND zone='tech' AND dt >= today() - 7 ), like_users AS ( SELECT user_id FROM behavior_events WHERE event='like' AND dt >= today() - 7 ) SELECT countDistinct(user_id) FROM tech_viewers WHERE hasAny(like_users, [user_id])4. 性能优化实战经验
4.1 查询下推的三种境界
- 基础下推:使用distributed_group_by_no_merge
SELECT count() FROM distributed_table SETTINGS distributed_group_by_no_merge=1 - 高级下推:cluster+view组合
SELECT sum(cnt) FROM cluster( default, view(SELECT count() AS cnt FROM local_table) ) - 极致下推:预计算+最终聚合
4.2 压缩算法选型对比
| 算法类型 | 压缩率 | 写入性能 | 查询性能 | 适用场景 |
|---|---|---|---|---|
| LZ4 | 1x | 100% | 100% | 默认场景 |
| LZ4HC | 1.2x | 80% | 95% | 历史数据 |
| ZSTD(1) | 1.5x | 60% | 98% | 冷数据存储 |
实践建议:热数据用LZ4,温数据用LZ4HC,冷数据用ZSTD
4.3 常见问题排查指南
4.3.1 写入瓶颈问题
- 症状:Too many parts错误
- 根因:频繁小批量写入导致merge压力大
- 解决方案:
- 增大批量写入规模
- 使用BulkLoad替代JDBC
- 调整merge策略
4.3.2 查询超时问题
- 症状:Query timeout exceeded
- 根因:复杂查询未利用索引
- 解决方案:
- 检查跳数索引使用情况
- 添加SAMPLE BY子句
- 考虑预聚合方案
5. 实践心得与未来展望
在实际落地过程中,有三点关键经验值得分享:
存储计算权衡:我们发现将千亿明细预聚合为百亿级模型后,存储节省40%的同时查询性能反而提升60%,这印证了在OLAP场景中"适当的预计算是必要的"这一原则。
资源隔离策略:通过为不同业务模块配置独立的资源队列,我们成功将高峰期的查询失败率从15%降至0.5%,特别是在大促期间效果显著。
监控体系建设:自研的ClickHouse监控系统包含200+个指标,实现了从硬件资源到查询性能的全方位监控,平均故障定位时间从小时级缩短到分钟级。
未来技术团队计划在三个方向继续突破:
- ZOrder索引研发:解决多维度查询的索引效率问题
- 服务端日志分析:扩展行为分析场景覆盖面
- 实时数仓融合:探索Hudi与ClickHouse的协同方案