考察点
考察把 OLAP 技术落到具体业务系统的能力。画像系统有三个公认的难点:身份打通(ID-Mapping)、标签的高效更新、人群圈选的高并发组合查询。面试官想看你是否知道位图方案为什么是圈人的标准答案,以及离线标签和实时标签怎么分层。追问常考位图字典编码和实时标签链路。
参考答案
先拆业务需求再谈架构
画像系统对外提供三类能力:查单个用户的画像(点查,给推荐/客服系统用)、按标签组合圈人群(交集并集,给运营投放用)、人群的统计分析(分布、对比)。三类负载完全不同:点查是高并发 KV 型、圈人是集合运算型、分析是 OLAP 扫描型。想用一个存储扛所有是新手常见错误,成熟方案都是分层组合。
地基:ID-Mapping
一个用户有手机号、设备号、user_id、微信 openid 一堆标识,不打通画像就是碎的。工程做法是基于账号体系登录日志、设备上报做图计算(连通分量),把属于同一人的标识聚成一个全局唯一 ID(常叫 oneid 或 gvid)。这一步的关键产出除了映射关系本身,还有一个常被忽略但至关重要的东西:全局连续整数编码——把 oneid 映射成 0、1、2 递增的 int。后面位图方案全靠它,编码服务要保证单调、稳定、可回放,通常离线每天生成,增量实时补充。
标签体系与存储分层
标签按更新频率分层存储,这是设计的核心决策:
离线标签(性别、年龄段、消费档位、RFM 分层):T+1 由 Spark 批量算出,写画像宽表。这类标签量大(几百上千个)、更新慢,用 Doris Unique Key 模型存,按 oneid 主键 UPSERT,每天全量或增量刷。宽表列多时注意拆分主题域,或者直接用 Map/Variant 类型存稀疏标签。
实时标签(今日活跃、最近浏览品类、实时风控特征):Flink 消费行为流实时计算,写 Redis/HBase 供点查,同时落一份到 Doris。实时标签数量要少而精,别指望实时链路维护几百个标签。
行为明细(支撑自定义分析和标签回算):存 ClickHouse,追加写,保留 90 天或更久。画像系统的分析类查询从这里出。
圈人为什么用位图
「北京地区 + 近 30 天有购买 + 高消费档位」这种组合圈选,本质是集合交并。如果把「每个标签值对应的用户集合」预先建成 RoaringBitmap(用户已编码成连续整数,bitmap 的第 n 位就是 oneid=n 的用户),那么圈人就是几个位图做 AND/OR/ANDNOT,最后 count 出人数、toArray 导出用户列表。千万级用户的组合圈选毫秒级完成,比 join 宽表过滤快几个数量级。
落地链路:离线标签产出后,Spark 任务把每个标签值的用户集合转成 RoaringBitmap(注意所有标签共用同一套全局字典编码),写入 Doris 的 bitmap 列或直接写存储。圈人服务加载位图到内存或查 Doris 的 bitmap_intersect/bitmap_union。实时标签要进位图的话,走增量更新位图的链路,延迟分钟级。
对外服务的完整拼图
最终形态是四个存储各司其职:Redis/HBase 做单用户画像点查(推荐系统毫秒级读);位图引擎(Doris bitmap 列或独立的圈人服务)做人群圈选;Doris 宽表做画像明细导出和标签管理后台查询;ClickHouse 明细做行为分析和标签回算。上层一个统一的标签管理平台管元数据:标签定义、血缘、权限、上下线。
容易踩的坑
实战经验里最值钱的几个:位图方案强依赖全局编码,编码服务一旦错乱全系统崩塌,必须有版本管理和回放能力;标签数量膨胀失控是画像系统的通病,建立标签 ROI 评估和下线机制;人群导出(百万级用户 ID 拉出来)是个重操作,要异步任务化,别在圈人接口里同步做;离线实时标签口径打架时,以离线为准做 T+1 校准。
可能的追问
- 位图圈人里用户 ID 为什么要编码成连续整数?—— RoaringBitmap 的位位置必须对应整数,稀疏的长 ID 直接编码会让位图退化成稀疏数组,失去压缩和交并效率。连续编码让密集段走 run-length 压缩,存储和运算都最优。
- 实时标签和离线标签冲突怎么办?—— 定义优先级和时效语义:实时标签只管「今天」,T+1 离线全量重算覆盖纠正;对外口径文档标注每个标签的更新频率和延迟,业务方知情使用。
- 千万级人群包导出怎么设计?—— 异步化:圈人接口只返回人群 ID 和预估人数,导出提交任务后台执行,位图 toArray 分批写文件存 OSS,完成后通知下载。同步导出会把服务拖垮。